bbqsearch 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
bbqsearch-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bbqsearch
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: General configurable queue-based search algorithm for teaching AI.
|
|
5
|
+
Author-email: Brandon Bennett <B.Bennett@leeds.ac.uk>
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: search,teaching
|
|
8
|
+
Requires-Python: >=3.8
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
|
|
11
|
+
# bbqsearch
|
|
12
|
+
|
|
13
|
+
`bbqsearch` is a simple, configurable, queue-based search algorithm intended for AI teaching.
|
|
14
|
+
|
|
15
|
+
**Status:** Experimental research and teaching tool.
|
|
16
|
+
|
|
17
|
+
The package is designed primarily for use in interactive environments such as a Jupyter notebook
|
|
18
|
+
or in Google Colab, providing a simple interface for running Prover9 from Python
|
|
19
|
+
code and capturing the resulting proofs and diagnostic output.
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install bbqsearch
|
|
25
|
+
```
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# bbqsearch
|
|
2
|
+
|
|
3
|
+
`bbqsearch` is a simple, configurable, queue-based search algorithm intended for AI teaching.
|
|
4
|
+
|
|
5
|
+
**Status:** Experimental research and teaching tool.
|
|
6
|
+
|
|
7
|
+
The package is designed primarily for use in interactive environments such as a Jupyter notebook
|
|
8
|
+
or in Google Colab, providing a simple interface for running Prover9 from Python
|
|
9
|
+
code and capturing the resulting proofs and diagnostic output.
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install bbqsearch
|
|
15
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"nbformat":4,"nbformat_minor":0,"metadata":{"colab":{"provenance":[],"authorship_tag":"ABX9TyMzK0ndZg+myb/pzl53iGjZ"},"kernelspec":{"name":"python3","display_name":"Python 3"},"language_info":{"name":"python"}},"cells":[{"cell_type":"markdown","source":["# `RepoMan_bbSearch.ipynb`\n","\n","Related resources:\n","* For top level Repo index and info go to:\n","[`RepoIndex.ipynb`](https://colab.research.google.com/drive/1aSzYWUuAKR12p43tO8-CWQl4WtOE-937)\n","\n","* The repo code of `bbpylib` can be found in: [`repo.ipynb`](https://colab.research.google.com/drive/1YEkkaKQkipno6S8YxoeBhOmnkfqLiK9X)\n"],"metadata":{"id":"ksTZYWNXBFC0"}},{"cell_type":"code","execution_count":16,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"N4R7lz3jAxrO","executionInfo":{"status":"ok","timestamp":1779816883237,"user_tz":-60,"elapsed":4527,"user":{"displayName":"Brandon Bennett","userId":"09986949895077581770"}},"outputId":"594e6e3b-a173-4ce4-8997-b0076d2ad34b"},"outputs":[{"output_type":"stream","name":"stdout","text":["Requirement already satisfied: bbpylib in /usr/local/lib/python3.12/dist-packages (0.1.30)\n"]}],"source":["!pip install --upgrade bbpylib\n","from bbpylib.colab import require_gdrive\n","from bbpylib.repo import Repo\n","from bbpylib.colab.tools import save_this_cell\n","\n","import os\n","from pathlib import Path\n","require_gdrive()"]},{"cell_type":"code","source":["REPO_ROOT = Path(\"/content/drive/MyDrive/GIT-repos\")\n","\n","import subprocess\n","import requests\n","import re\n","\n","from google.colab import userdata\n","\n","def run_command(cmd, *, cwd=None, env=None, check=True):\n"," r = subprocess.run( cmd,\n"," cwd=cwd, env=env,\n"," text=True, capture_output=True )\n"," if r.stdout: print(r.stdout, end=\"\")\n"," if r.stderr: print(r.stderr, end=\"\")\n"," if check and r.returncode:\n"," raise subprocess.CalledProcessError(\n"," r.returncode, r.args, r.stdout, r.stderr)\n"," return r\n","\n","def published_pip_version(package) -> str | None:\n"," url = f\"https://pypi.org/pypi/{package}/json\"\n"," r = requests.get(url, timeout=5)\n"," if r.status_code != 200:\n"," return None # package not found or network error\n"," data = r.json()\n"," # 'info' is the metadata for the *latest* release\n"," return data.get(\"info\", {}).get(\"version\")\n","\n","def upload_pip( package, new_version, repo_root=REPO_ROOT ):\n"," path = str( Path(repo_root)/ package )\n"," print(f\"Uploading {package} new version {new_version} to PyPi ...\" )\n"," pip_api_token= userdata.get(\"PIP_API_TOKEN\")\n"," env = os.environ.copy()\n"," env[\"TWINE_USERNAME\"] = \"__token__\"\n"," env[\"TWINE_PASSWORD\"] = pip_api_token\n"," run_command( [\"pip\", \"-q\", \"install\", \"twine\"], check=True)\n"," run_command( [\"twine\", \"upload\", \"--verbose\", \"dist/*\"], cwd=path, env=env, check=True)\n","\n","#published_pip_version(\"pyprover9\")\n","\n","upload_pip(\"bbqsearch\", \"0.0.1\")\n"],"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":981},"id":"tIOj7Ab9CeNg","executionInfo":{"status":"error","timestamp":1779814234956,"user_tz":-60,"elapsed":8445,"user":{"displayName":"Brandon Bennett","userId":"09986949895077581770"}},"outputId":"6a8a837a-e04e-4c87-bc17-44c124832e83"},"execution_count":5,"outputs":[{"output_type":"stream","name":"stdout","text":["Uploading bbSearch new version 0.0.1 to PyPi ...\n"," ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 42.7/42.7 kB 1.7 MB/s eta 0:00:00\n"," ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 807.0/807.0 kB 16.7 MB/s eta 0:00:00\n","Uploading distributions to https://upload.pypi.org/legacy/\n","\u001b[34mINFO \u001b[0m dist/bbsearch-0.0.1-py3-none-any.whl (6.7 KB) \n","\u001b[34mINFO \u001b[0m dist/bbsearch-0.0.1.tar.gz (8.8 KB) \n","\u001b[34mINFO \u001b[0m username set by command options \n","\u001b[34mINFO \u001b[0m password set by command options \n","\u001b[34mINFO \u001b[0m username: __token__ \n","\u001b[34mINFO \u001b[0m password: <hidden> \n","Uploading bbsearch-0.0.1-py3-none-any.whl\n","\u001b[?25l\n","\u001b[2K\u001b[35m 0%\u001b[0m \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m0.0/9.3 kB\u001b[0m • \u001b[36m--:--\u001b[0m • \u001b[31m?\u001b[0m\n","\u001b[2K\u001b[35m100%\u001b[0m \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m9.3/9.3 kB\u001b[0m • \u001b[33m00:00\u001b[0m • \u001b[31m?\u001b[0m\n","\u001b[2K\u001b[35m100%\u001b[0m \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m9.3/9.3 kB\u001b[0m • \u001b[33m00:00\u001b[0m • \u001b[31m?\u001b[0m\n","\u001b[?25h\u001b[34mINFO \u001b[0m Response from https://upload.pypi.org/legacy/: \n"," 403 Forbidden \n","\u001b[34mINFO \u001b[0m <html> \n"," <head> \n"," <title>403 The user 'brandonb' isn't allowed to upload to project \n"," 'bbsearch'. See https://pypi.org/help/#project-name for more \n"," information.</title> \n"," </head> \n"," <body> \n"," <h1>403 The user 'brandonb' isn't allowed to upload to project \n"," 'bbsearch'. See https://pypi.org/help/#project-name for more \n"," information.</h1> \n"," Access was denied to this resource.<br/><br/> \n"," The user 'brandonb' isn't allowed to upload to project \n"," 'bbsearch'. See https://pypi.org/help/#project-name for more \n"," information. \n"," \n"," \n"," </body> \n"," </html> \n","\u001b[31mERROR \u001b[0m HTTPError: 403 Forbidden from https://upload.pypi.org/legacy/ \n"," Forbidden \n"]},{"output_type":"error","ename":"CalledProcessError","evalue":"Command '['twine', 'upload', '--verbose', 'dist/*']' returned non-zero exit status 1.","traceback":["\u001b[0;31m---------------------------------------------------------------------------\u001b[0m","\u001b[0;31mCalledProcessError\u001b[0m Traceback (most recent call last)","\u001b[0;32m/tmp/ipykernel_6935/57094460.py\u001b[0m in \u001b[0;36m<cell line: 0>\u001b[0;34m()\u001b[0m\n\u001b[1;32m 39\u001b[0m \u001b[0;31m#published_pip_version(\"pyprover9\")\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 40\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 41\u001b[0;31m \u001b[0mupload_pip\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"bbSearch\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"0.0.1\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m","\u001b[0;32m/tmp/ipykernel_6935/57094460.py\u001b[0m in \u001b[0;36mupload_pip\u001b[0;34m(package, new_version, repo_root)\u001b[0m\n\u001b[1;32m 35\u001b[0m \u001b[0menv\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"TWINE_PASSWORD\"\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mpip_api_token\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 36\u001b[0m \u001b[0mrun_command\u001b[0m\u001b[0;34m(\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0;34m\"pip\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"-q\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"install\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"twine\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mcheck\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mTrue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 37\u001b[0;31m \u001b[0mrun_command\u001b[0m\u001b[0;34m(\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0;34m\"twine\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"upload\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"--verbose\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"dist/*\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mcwd\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mpath\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0menv\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0menv\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mcheck\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mTrue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 38\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 39\u001b[0m \u001b[0;31m#published_pip_version(\"pyprover9\")\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/tmp/ipykernel_6935/57094460.py\u001b[0m in \u001b[0;36mrun_command\u001b[0;34m(cmd, cwd, env, check)\u001b[0m\n\u001b[1;32m 14\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mr\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mstderr\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mr\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mstderr\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mend\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m\"\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 15\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mcheck\u001b[0m \u001b[0;32mand\u001b[0m \u001b[0mr\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mreturncode\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 16\u001b[0;31m raise subprocess.CalledProcessError(\n\u001b[0m\u001b[1;32m 17\u001b[0m r.returncode, r.args, r.stdout, r.stderr)\n\u001b[1;32m 18\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mr\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;31mCalledProcessError\u001b[0m: Command '['twine', 'upload', '--verbose', 'dist/*']' returned non-zero exit status 1."]}]},{"cell_type":"code","source":["GIT_repos_PATH = Path(\"/content/drive/MyDrive/GIT-repos\")\n","\n","repo = Repo(\"bbqsearch\", GIT_repos_PATH, \"BrandonBennett99\")\n","repo.update_pip()"],"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":321},"id":"Ql7ElUmiEI5A","executionInfo":{"status":"error","timestamp":1779816995293,"user_tz":-60,"elapsed":149,"user":{"displayName":"Brandon Bennett","userId":"09986949895077581770"}},"outputId":"68fa0637-9d5f-4b6c-a0dd-c7bfc8dfc2e7"},"execution_count":20,"outputs":[{"output_type":"error","ename":"AttributeError","evalue":"'NoneType' object has no attribute 'split'","traceback":["\u001b[0;31m---------------------------------------------------------------------------\u001b[0m","\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)","\u001b[0;32m/tmp/ipykernel_6935/3450594270.py\u001b[0m in \u001b[0;36m<cell line: 0>\u001b[0;34m()\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0mrepo\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mRepo\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"bbqsearch\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mGIT_repos_PATH\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"BrandonBennett99\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 4\u001b[0;31m \u001b[0mrepo\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mupdate_pip\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/bbpylib/repo.py\u001b[0m in \u001b[0;36mupdate_pip\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 95\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 96\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mupdate_pip\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 97\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mincrement_pip_version\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 98\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mbuild_pip\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 99\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mupload_pip\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/bbpylib/repo.py\u001b[0m in \u001b[0;36mincrement_pip_version\u001b[0;34m(self, part)\u001b[0m\n\u001b[1;32m 64\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mincrement_pip_version\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mpart\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m\"patch\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 65\u001b[0m \u001b[0mv\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpublished_pip_version\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 66\u001b[0;31m \u001b[0mnv\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mbump_version\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mv\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mpart\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mpart\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 67\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Incrementing pip version:\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mv\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"->\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mnv\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 68\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mset_version\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnv\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/bbpylib/repo.py\u001b[0m in \u001b[0;36mbump_version\u001b[0;34m(v, part)\u001b[0m\n\u001b[1;32m 105\u001b[0m \u001b[0;31m# This is just a str->str function so not in the class\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 106\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mbump_version\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mv\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mpart\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m\"patch\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 107\u001b[0;31m \u001b[0mmajor\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mminor\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mpatch\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mmap\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mint\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mv\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msplit\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\".\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 108\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mpart\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;34m\"major\"\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 109\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0;34mf\"{major + 1}.0.0\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;31mAttributeError\u001b[0m: 'NoneType' object has no attribute 'split'"]}]},{"cell_type":"code","source":["%%shell\n","cd /content/drive/MyDrive/GIT-repos/bbqsearch\n","ls\n","#python -m twine upload dist/* --verbose\n","twine upload --repository testpypi dist/*"],"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":495},"id":"l6ZEnM7pMDmi","executionInfo":{"status":"error","timestamp":1779816960033,"user_tz":-60,"elapsed":1532,"user":{"displayName":"Brandon Bennett","userId":"09986949895077581770"}},"outputId":"1598587c-1191-4a39-8292-357db7c4d53e"},"execution_count":19,"outputs":[{"output_type":"stream","name":"stdout","text":["dist pyproject.toml README.md RepoMan_bbqsearch.ipynb src\n","Uploading distributions to https://test.pypi.org/legacy/\n","\u001b[31mERROR \u001b[0m TrustedPublishingFailure: Unable to retrieve an OIDC token from the CI \n"," platform for trusted publishing GCP: OIDC token request failed \n"," (code=404, body='') \n"]},{"output_type":"error","ename":"CalledProcessError","evalue":"Command 'cd /content/drive/MyDrive/GIT-repos/bbqsearch\nls\n#python -m twine upload dist/* --verbose\ntwine upload --repository testpypi dist/*\n' returned non-zero exit status 1.","traceback":["\u001b[0;31m---------------------------------------------------------------------------\u001b[0m","\u001b[0;31mCalledProcessError\u001b[0m Traceback (most recent call last)","\u001b[0;32m/tmp/ipykernel_6935/1524148870.py\u001b[0m in \u001b[0;36m<cell line: 0>\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mget_ipython\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrun_cell_magic\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'shell'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m''\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m'cd /content/drive/MyDrive/GIT-repos/bbqsearch\\nls\\n#python -m twine upload dist/* --verbose\\ntwine upload --repository testpypi dist/*\\n'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/google/colab/_shell.py\u001b[0m in \u001b[0;36mrun_cell_magic\u001b[0;34m(self, magic_name, line, cell)\u001b[0m\n\u001b[1;32m 221\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mline\u001b[0m \u001b[0;32mand\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0mcell\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 222\u001b[0m \u001b[0mcell\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m' '\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 223\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0msuper\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrun_cell_magic\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmagic_name\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mline\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mcell\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 224\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 225\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/IPython/core/interactiveshell.py\u001b[0m in \u001b[0;36mrun_cell_magic\u001b[0;34m(self, magic_name, line, cell)\u001b[0m\n\u001b[1;32m 2471\u001b[0m \u001b[0;32mwith\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mbuiltin_trap\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2472\u001b[0m \u001b[0margs\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mmagic_arg_s\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mcell\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 2473\u001b[0;31m \u001b[0mresult\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mfn\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 2474\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mresult\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2475\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/google/colab/_system_commands.py\u001b[0m in \u001b[0;36m_shell_cell_magic\u001b[0;34m(args, cmd)\u001b[0m\n\u001b[1;32m 110\u001b[0m \u001b[0mresult\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0m_run_command\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcmd\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mclear_streamed_output\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mFalse\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 111\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0mparsed_args\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mignore_errors\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 112\u001b[0;31m \u001b[0mresult\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcheck_returncode\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 113\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mresult\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 114\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/local/lib/python3.12/dist-packages/google/colab/_system_commands.py\u001b[0m in \u001b[0;36mcheck_returncode\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 135\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mcheck_returncode\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 136\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mreturncode\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 137\u001b[0;31m raise subprocess.CalledProcessError(\n\u001b[0m\u001b[1;32m 138\u001b[0m \u001b[0mreturncode\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mreturncode\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mcmd\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0moutput\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0moutput\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 139\u001b[0m )\n","\u001b[0;31mCalledProcessError\u001b[0m: Command 'cd /content/drive/MyDrive/GIT-repos/bbqsearch\nls\n#python -m twine upload dist/* --verbose\ntwine upload --repository testpypi dist/*\n' returned non-zero exit status 1."]}]},{"cell_type":"code","source":["%%writefile /content/drive/MyDrive/GIT-repos/bbqsearch/pyproject.toml\n","[build-system]\n","requires = [\"hatchling>=1.25\"]\n","build-backend = \"hatchling.build\"\n","\n","[project]\n","name = \"bbqsearch\"\n","version = \"0.1.0\"\n","description = \"General configurable queue-based search algorithm for teaching AI.\"\n","readme = \"README.md\"\n","requires-python = \">=3.8\"\n","license = { text = \"MIT\" }\n","keywords = [\"search\", \"teaching\"]\n","\n","authors = [\n"," { name = \"Brandon Bennett\", email=\"B.Bennett@leeds.ac.uk\" }\n","]\n","\n","#[project.urls]\n","#Homepage = \"https://github.com/...\"\n","#Repository = \"https://github.com/...\"\n","\n","[tool.hatch.build]\n","dev-mode-dirs = [\"src\"]\n","\n","[tool.hatch.build.targets.wheel]\n","#sources = [\"src\"]\n","packages = [\"src/bbqsearch\"]"],"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"2oLSMMg4FS1e","executionInfo":{"status":"ok","timestamp":1779816940386,"user_tz":-60,"elapsed":14,"user":{"displayName":"Brandon Bennett","userId":"09986949895077581770"}},"outputId":"ed92bf9e-06d3-4640-c092-909e37070473"},"execution_count":18,"outputs":[{"output_type":"stream","name":"stdout","text":["Overwriting /content/drive/MyDrive/GIT-repos/bbqsearch/pyproject.toml\n"]}]},{"cell_type":"code","source":["%%writefile /content/drive/MyDrive/GIT-repos/bbqsearch/src/bbqsearch/__init__.py\n","from .bbqsearch import *\n"],"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"semBM0KJEhMv","executionInfo":{"status":"ok","timestamp":1779816808842,"user_tz":-60,"elapsed":36,"user":{"displayName":"Brandon Bennett","userId":"09986949895077581770"}},"outputId":"b1439a03-0ef9-4a15-c9e1-6babb2ce7cd0"},"execution_count":11,"outputs":[{"output_type":"stream","name":"stdout","text":["Overwriting /content/drive/MyDrive/GIT-repos/bbqsearch/src/bbqsearch/__init__.py\n"]}]},{"cell_type":"code","source":["%%writefile /content/drive/MyDrive/GIT-repos/bbqsearch/README.md\n","# bbqsearch\n","\n","`bbqsearch` is a simple, configurable, queue-based search algorithm intended for AI teaching.\n","\n","**Status:** Experimental research and teaching tool.\n","\n","The package is designed primarily for use in interactive environments such as a Jupyter notebook\n","or in Google Colab, providing a simple interface for running Prover9 from Python\n","code and capturing the resulting proofs and diagnostic output.\n","\n","## Installation\n","\n","```bash\n","pip install bbqsearch\n","```"],"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"UTAimrKZE0-3","executionInfo":{"status":"ok","timestamp":1779816810071,"user_tz":-60,"elapsed":54,"user":{"displayName":"Brandon Bennett","userId":"09986949895077581770"}},"outputId":"05e1ca79-0c5f-41ac-d670-efa6db484f73"},"execution_count":12,"outputs":[{"output_type":"stream","name":"stdout","text":["Overwriting /content/drive/MyDrive/GIT-repos/bbqsearch/README.md\n"]}]}]}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.25"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "bbqsearch"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "General configurable queue-based search algorithm for teaching AI."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
keywords = ["search", "teaching"]
|
|
13
|
+
|
|
14
|
+
authors = [
|
|
15
|
+
{ name = "Brandon Bennett", email="B.Bennett@leeds.ac.uk" }
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
#[project.urls]
|
|
19
|
+
#Homepage = "https://github.com/..."
|
|
20
|
+
#Repository = "https://github.com/..."
|
|
21
|
+
|
|
22
|
+
[tool.hatch.build]
|
|
23
|
+
dev-mode-dirs = ["src"]
|
|
24
|
+
|
|
25
|
+
[tool.hatch.build.targets.wheel]
|
|
26
|
+
#sources = ["src"]
|
|
27
|
+
packages = ["src/bbqsearch"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .bbqsearch import *
|
|
@@ -0,0 +1,580 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# coding: utf-8
|
|
3
|
+
|
|
4
|
+
# # bbSearch
|
|
5
|
+
#
|
|
6
|
+
# This version of `bbSearch` written during January and February 2022.
|
|
7
|
+
#
|
|
8
|
+
#
|
|
9
|
+
# ### My personal notes:
|
|
10
|
+
# To install on cloud for download use shell script `publish-bbSearch`.
|
|
11
|
+
|
|
12
|
+
# In[47]:
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
#<<<
|
|
16
|
+
#<<< def __MAKE_SCRIPT__(notebook_stem):
|
|
17
|
+
#<<< get_ipython().system( f' jupyter nbconvert --to python {notebook_stem}.ipynb')
|
|
18
|
+
#<<< with open( notebook_stem + ".py", "r") as f:
|
|
19
|
+
#<<< lines = f.read().split('\n')
|
|
20
|
+
#<<< new_lines = []
|
|
21
|
+
#<<< exclude = False
|
|
22
|
+
#<<< for l in lines:
|
|
23
|
+
#<<< if l.startswith("#<<<"):
|
|
24
|
+
#<<< exclude = True
|
|
25
|
+
#<<< new_lines.append(l)
|
|
26
|
+
#<<< continue
|
|
27
|
+
#<<< if l.startswith("#>>>"):
|
|
28
|
+
#<<< exclude = False
|
|
29
|
+
#<<< new_lines.append(l)
|
|
30
|
+
#<<< continue
|
|
31
|
+
#<<< if exclude:
|
|
32
|
+
#<<< new_lines.append("#<<< " + l)
|
|
33
|
+
#<<< else:
|
|
34
|
+
#<<< new_lines.append(l)
|
|
35
|
+
#<<<
|
|
36
|
+
#<<< new_content = '\n'.join(new_lines)
|
|
37
|
+
#<<< #new_content = "### MODIFIED\n" + new_content
|
|
38
|
+
#<<< with open( notebook_stem + ".py", "w") as f:
|
|
39
|
+
#<<< f.write(new_content)
|
|
40
|
+
#<<<
|
|
41
|
+
#<<< __MAKE_SCRIPT__("bbSearch")
|
|
42
|
+
#<<< get_ipython().system('s3cmd put --acl-public bbSearch.py s3://bb-ai.net/bb-python-modules/')
|
|
43
|
+
#>>>
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# In[34]:
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
from datetime import datetime
|
|
50
|
+
time = datetime.now().strftime("%H:%M, %a %d %b")
|
|
51
|
+
print(f"Loading bbSearch Version 2.1 (at {time})")
|
|
52
|
+
print("Last module source code edit 9am Thursday 24th Feb 2022")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# In[35]:
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class SearchProblem:
|
|
59
|
+
|
|
60
|
+
def __init__( self ):
|
|
61
|
+
"""
|
|
62
|
+
The __init__ method must set the initial state for the search.
|
|
63
|
+
Arguments could be added to __init__ and used to configure the
|
|
64
|
+
initial state and/or other aspects of a problem instance.
|
|
65
|
+
"""
|
|
66
|
+
self.initial_state = None
|
|
67
|
+
raise NotImplementedError
|
|
68
|
+
|
|
69
|
+
def info(self):
|
|
70
|
+
"""
|
|
71
|
+
This function is called when the search is stared and should
|
|
72
|
+
print out useful information about the problem.
|
|
73
|
+
"""
|
|
74
|
+
print("This is the general SearchProblem parent class")
|
|
75
|
+
print("You must extend this class to encode a particular search problem.")
|
|
76
|
+
|
|
77
|
+
def possible_actions(self, state):
|
|
78
|
+
"""
|
|
79
|
+
This takes a state as argument and must return a list of actions.
|
|
80
|
+
Both states and actions can be any kinds of python data type (e.g.
|
|
81
|
+
numbers, strings, tuples or complex objects of any class).
|
|
82
|
+
"""
|
|
83
|
+
return []
|
|
84
|
+
|
|
85
|
+
def successor(self, state, action):
|
|
86
|
+
"""
|
|
87
|
+
This takes a state and an action and returns the new state that would result
|
|
88
|
+
from doing that action in that state. You can assume that the given action
|
|
89
|
+
is in the list of 'possible_actions' for that state.
|
|
90
|
+
"""
|
|
91
|
+
return state
|
|
92
|
+
|
|
93
|
+
def goal_test(self, state):
|
|
94
|
+
"""
|
|
95
|
+
This method shoudl return True or False given any state. It should return
|
|
96
|
+
True for all and only those states that are considert "goal" states.
|
|
97
|
+
"""
|
|
98
|
+
return False
|
|
99
|
+
|
|
100
|
+
def cost(self, path, state):
|
|
101
|
+
"""
|
|
102
|
+
This is an optional method that you only need to define if you are using
|
|
103
|
+
a cost based algorithm such as "uniform cost" or "A*". It should return
|
|
104
|
+
the cost of reaching a given state via a given path.
|
|
105
|
+
If this is not defined, it will is assumed that each action costs one unit
|
|
106
|
+
of effort to perform, so it returns the length of the path.
|
|
107
|
+
"""
|
|
108
|
+
return len(path)
|
|
109
|
+
|
|
110
|
+
def heuristic(self, state):
|
|
111
|
+
"""
|
|
112
|
+
This is an optional method that should return a heuristic value for any
|
|
113
|
+
state. The value should be an estimate of the remaining cost that will be
|
|
114
|
+
required to reach a goal. For an "admissible" heuristic, the value should
|
|
115
|
+
always be equal to or less than the actual cost.
|
|
116
|
+
"""
|
|
117
|
+
raise NotImplementedError
|
|
118
|
+
|
|
119
|
+
def display_action(self, action):
|
|
120
|
+
"""
|
|
121
|
+
You can set the way an action will be displayed in outputs.
|
|
122
|
+
"""
|
|
123
|
+
print(" ", action)
|
|
124
|
+
|
|
125
|
+
def display_state(self, state):
|
|
126
|
+
"""
|
|
127
|
+
You can set the way a state will be displayed in outputs.
|
|
128
|
+
"""
|
|
129
|
+
print(state)
|
|
130
|
+
|
|
131
|
+
def display_state_path( self, actions ):
|
|
132
|
+
"""
|
|
133
|
+
This defines output of a solution path when a list of actions
|
|
134
|
+
is applied to the initial state. It assumes it is a valid path
|
|
135
|
+
with all actions being possible in the preceeding state.
|
|
136
|
+
You probably don't need to override this.
|
|
137
|
+
"""
|
|
138
|
+
s = self.initial_state
|
|
139
|
+
self.display_state(s)
|
|
140
|
+
for a in actions:
|
|
141
|
+
self.display_action(a)
|
|
142
|
+
s = self.successor(s,a)
|
|
143
|
+
self.display_state(s)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
# In[36]:
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class JugPouringPuzzle( SearchProblem ):
|
|
150
|
+
|
|
151
|
+
def __init__( self, initial_state, goal_quantity ):
|
|
152
|
+
self.initial_state = initial_state
|
|
153
|
+
self.goal_quantity = goal_quantity
|
|
154
|
+
print( "Creating SearchProblem object" )
|
|
155
|
+
print( "Setting initial state to:", self.initial_state )
|
|
156
|
+
print( "Want to measure quantity:", self.goal_quantity )
|
|
157
|
+
|
|
158
|
+
self.jugs = self.initial_state.keys()
|
|
159
|
+
|
|
160
|
+
def info(self):
|
|
161
|
+
print( "\nMeasuring Jugs problem:" )
|
|
162
|
+
print( "You have a number of jugs with a certain volume and initial contents,\n"
|
|
163
|
+
"as follows:")
|
|
164
|
+
for jar in self.initial_state:
|
|
165
|
+
print(f"{jar:>10} : volume={self.initial_state[jar][0]}, "
|
|
166
|
+
f"contents={self.initial_state[jar][1]}")
|
|
167
|
+
print( f"The goal is to measure the quantity {self.goal_quantity} "
|
|
168
|
+
"by pourning liquid between the jugs.")
|
|
169
|
+
|
|
170
|
+
def possible_actions(self, state):
|
|
171
|
+
actions = []
|
|
172
|
+
for j1 in self.jugs:
|
|
173
|
+
for j2 in self.jugs:
|
|
174
|
+
if ( j1!=j2 #different jugs
|
|
175
|
+
and state[j1][1] > 0 # j1 not empty
|
|
176
|
+
and state[j2][0]>state[j2][1] # j2 not full
|
|
177
|
+
): actions.append((j1,j2))
|
|
178
|
+
return actions
|
|
179
|
+
|
|
180
|
+
def successor(self, state, action):
|
|
181
|
+
new_state = { k:state[k] for k in state }
|
|
182
|
+
j1, j2 = action[0], action[1]
|
|
183
|
+
vol_in_j1 = state[j1][1]
|
|
184
|
+
vol_in_j2 = state[j2][1]
|
|
185
|
+
space_in_j2 = state[j2][0] - state[j2][1]
|
|
186
|
+
if vol_in_j1 <= space_in_j2:
|
|
187
|
+
new_j1 = 0
|
|
188
|
+
new_j2 = vol_in_j1 + vol_in_j2
|
|
189
|
+
else:
|
|
190
|
+
new_j1 = vol_in_j1 - space_in_j2
|
|
191
|
+
new_j2 = state[j2][0]
|
|
192
|
+
new_state[j1] = (state[j1][0], new_j1)
|
|
193
|
+
new_state[j2] = (state[j2][0], new_j2)
|
|
194
|
+
return new_state
|
|
195
|
+
|
|
196
|
+
def goal_test(self, state):
|
|
197
|
+
quantities = [ state[k][1] for k in state]
|
|
198
|
+
return self.goal_quantity in quantities
|
|
199
|
+
|
|
200
|
+
def display_action( self, action ):
|
|
201
|
+
j1, j2 = action
|
|
202
|
+
print(f"Pour from {j1} to {j2}:")
|
|
203
|
+
|
|
204
|
+
def display_state( self, state ):
|
|
205
|
+
jug_quantities = [ f"{k} ({state[k][0]}): {state[k][1]}" for k in state]
|
|
206
|
+
print( ", ".join(jug_quantities))
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
# In[37]:
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
import bisect
|
|
213
|
+
|
|
214
|
+
## This is a Weighted queue optimised by using the bisect module
|
|
215
|
+
## to insert into the item list (via key indirection).
|
|
216
|
+
|
|
217
|
+
class WeightedQueue:
|
|
218
|
+
|
|
219
|
+
def __init__(self, mode='fifo', weighted=True, weightfunc=None ):
|
|
220
|
+
self.weighted = weighted
|
|
221
|
+
self.weightfunc = weightfunc
|
|
222
|
+
self.mode = mode
|
|
223
|
+
self.weights = []
|
|
224
|
+
self.items = []
|
|
225
|
+
|
|
226
|
+
if mode == 'fifo':
|
|
227
|
+
if self.weighted:
|
|
228
|
+
self.insert = self.insert_right
|
|
229
|
+
self.pop = self.pop_weighted
|
|
230
|
+
else:
|
|
231
|
+
self.insert = self.add_right
|
|
232
|
+
|
|
233
|
+
elif mode == 'lifo':
|
|
234
|
+
if self.weighted:
|
|
235
|
+
self.insert = self.insert_left
|
|
236
|
+
self.pop = self.pop_weighted
|
|
237
|
+
else:
|
|
238
|
+
self.insert = self.add_left
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
else:
|
|
242
|
+
raise ValueError("!!! WeightedQueue 'mode' keyword arg "
|
|
243
|
+
"must be 'fifo' or 'lifo'")
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def add_left(self,item, weight=None):
|
|
247
|
+
self.items.insert(0,item)
|
|
248
|
+
|
|
249
|
+
def add_right(self,item, weight=None):
|
|
250
|
+
self.items.append(item)
|
|
251
|
+
|
|
252
|
+
def insert_left(self, item, weight = None):
|
|
253
|
+
if weight == None:
|
|
254
|
+
weight = self.weightfunc(item)
|
|
255
|
+
ipoint = bisect.bisect_left(self.weights, weight)
|
|
256
|
+
bisect.insort_left(self.weights, weight)
|
|
257
|
+
self.items.insert(ipoint,item)
|
|
258
|
+
|
|
259
|
+
def insert_right(self, item, weight = None):
|
|
260
|
+
if weight == None:
|
|
261
|
+
weight = self.weightfunc(item)
|
|
262
|
+
ipoint = bisect.bisect_right(self.weights, weight)
|
|
263
|
+
bisect.insort_right(self.weights, weight)
|
|
264
|
+
self.items.insert(ipoint,item)
|
|
265
|
+
|
|
266
|
+
def initialise(self, items, weights=None):
|
|
267
|
+
self.items = items
|
|
268
|
+
if weights:
|
|
269
|
+
iwpairs = list(zip(items,weights))
|
|
270
|
+
iwpairs.sort(key=lambda x:x[1])
|
|
271
|
+
self.items = [x[0] for x in iwpairs]
|
|
272
|
+
self.weights = [x[1] for x in iwpairs]
|
|
273
|
+
elif self.weightfunc:
|
|
274
|
+
self.items.sort(key=self.weightfunc)
|
|
275
|
+
self.weights = [self.weightfunc(i) for i in self.items]
|
|
276
|
+
|
|
277
|
+
def pop(self):
|
|
278
|
+
return self.items.pop(0)
|
|
279
|
+
|
|
280
|
+
def pop_weighted(self):
|
|
281
|
+
self.weights.pop(0)
|
|
282
|
+
return self.items.pop(0)
|
|
283
|
+
|
|
284
|
+
def display(self):
|
|
285
|
+
for w, i in zip(self.weights, self.items):
|
|
286
|
+
print(f"{w}: {i}")
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
# In[44]:
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
class SearchQueue:
|
|
294
|
+
|
|
295
|
+
def __init__( self, mode='BF/FIFO', cost=None, heuristic=None ):
|
|
296
|
+
self.mod = mode
|
|
297
|
+
self.cost = cost
|
|
298
|
+
self.heuristic = heuristic
|
|
299
|
+
self.weighted = (cost!=None or heuristic!=None)
|
|
300
|
+
|
|
301
|
+
#mode parameter determines whether queue mode is filo or fifo
|
|
302
|
+
modemap = { 'BF/FIFO':'fifo', 'DF/LIFO': 'lifo',
|
|
303
|
+
'bf' : 'fifo', 'df': 'lifo',
|
|
304
|
+
'BF' : 'fifo', 'DF': 'lifo',
|
|
305
|
+
'fifo': 'fifo', 'lifo': 'lifo',
|
|
306
|
+
'FIFO': 'fifo', 'LIFO': 'lifo',
|
|
307
|
+
'breadth_first': 'fifo', 'depth_first': 'lifo',
|
|
308
|
+
}
|
|
309
|
+
if not mode in modemap.keys():
|
|
310
|
+
raise ValueError("!!! SearchQueue 'mode' argument "
|
|
311
|
+
"must be 'BF/FIFO' or 'DF/LIFO'")
|
|
312
|
+
|
|
313
|
+
self.wq = WeightedQueue(modemap[mode], weighted=self.weighted)
|
|
314
|
+
|
|
315
|
+
def empty(self):
|
|
316
|
+
return self.wq.items == []
|
|
317
|
+
|
|
318
|
+
def len(self):
|
|
319
|
+
return len(self.wq.items)
|
|
320
|
+
|
|
321
|
+
def insert(self,item, weight=None):
|
|
322
|
+
# print("Insert in SearchQueue")
|
|
323
|
+
# print("item=", item)
|
|
324
|
+
# print("weight=", weight)
|
|
325
|
+
self.wq.insert(item, weight=weight)
|
|
326
|
+
|
|
327
|
+
def initialise(self,items,weights=None):
|
|
328
|
+
return self.wq.initialise(items,weights=weights)
|
|
329
|
+
|
|
330
|
+
def pop(self):
|
|
331
|
+
return self.wq.pop()
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
#sq = SearchQueue()
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
# In[45]:
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
import time, random
|
|
343
|
+
|
|
344
|
+
def search( problem,
|
|
345
|
+
mode,
|
|
346
|
+
max_nodes,
|
|
347
|
+
loop_check = False,
|
|
348
|
+
randomise = False,
|
|
349
|
+
cost = None,
|
|
350
|
+
heuristic = None,
|
|
351
|
+
show_path = True,
|
|
352
|
+
show_state_path = False,
|
|
353
|
+
dots = True,
|
|
354
|
+
return_info = False,
|
|
355
|
+
#One could potentially define other node limits
|
|
356
|
+
#max_generated = False,
|
|
357
|
+
#max_discarded = False
|
|
358
|
+
):
|
|
359
|
+
|
|
360
|
+
problem.info()
|
|
361
|
+
|
|
362
|
+
print( "\n** Running Brandon's Search Algorithm **")
|
|
363
|
+
cost_name = (cost.__name__ if cost else None)
|
|
364
|
+
heuristic_name = (heuristic.__name__ if heuristic else None)
|
|
365
|
+
print( f"Strategy: mode={mode}, cost={cost_name}, heuristic={heuristic_name}")
|
|
366
|
+
print( f"Max search nodes: {max_nodes} (max number added to queue)" )
|
|
367
|
+
|
|
368
|
+
start_time = time.perf_counter()
|
|
369
|
+
queue = SearchQueue(mode,cost,heuristic)
|
|
370
|
+
queue.initialise( [([], problem.initial_state )], weights=[0] )
|
|
371
|
+
global weight_function
|
|
372
|
+
weight_function = node_weight_function( cost, heuristic )
|
|
373
|
+
|
|
374
|
+
states_seen = {problem.initial_state.__repr__()}
|
|
375
|
+
nodes_generated = 1 # counting initial state
|
|
376
|
+
nodes_kept = 1
|
|
377
|
+
nodes_tested = 0
|
|
378
|
+
nodes_discarded = 0
|
|
379
|
+
|
|
380
|
+
termination_condition = None
|
|
381
|
+
|
|
382
|
+
node_limit_exceeded = False
|
|
383
|
+
|
|
384
|
+
if not dots:
|
|
385
|
+
print( "Search started (progress dot output off)", flush=True)
|
|
386
|
+
|
|
387
|
+
while True:
|
|
388
|
+
if queue.empty():
|
|
389
|
+
termination_condition = "SEARCH-SPACE_EXHAUSTED" # Means there is no solution.
|
|
390
|
+
break
|
|
391
|
+
|
|
392
|
+
if dots:
|
|
393
|
+
if nodes_tested % 1000 == 0:
|
|
394
|
+
if nodes_tested==0:
|
|
395
|
+
print("Searching (will output '.' each 1000 goal_tests)", flush=True)
|
|
396
|
+
else:
|
|
397
|
+
print('.', end='')
|
|
398
|
+
if nodes_tested % 100000 == 0:
|
|
399
|
+
print( f' ({nodes_tested})', flush=True)
|
|
400
|
+
|
|
401
|
+
path, state = queue.pop()
|
|
402
|
+
nodes_tested += 1
|
|
403
|
+
if problem.goal_test(state):
|
|
404
|
+
termination_condition = "GOAL_STATE_FOUND"
|
|
405
|
+
break
|
|
406
|
+
|
|
407
|
+
if node_limit_exceeded:
|
|
408
|
+
termination_condition = "NODE_LIMIT_EXCEEDED"
|
|
409
|
+
break
|
|
410
|
+
|
|
411
|
+
#print("Considering state:", state)
|
|
412
|
+
actions = problem.possible_actions(state)
|
|
413
|
+
#print("Possible actions are:", actions)
|
|
414
|
+
|
|
415
|
+
if randomise: # randomise action choice sequance (may be useful in DFS)
|
|
416
|
+
random.shuffle(actions)
|
|
417
|
+
|
|
418
|
+
for i, a in enumerate(actions):
|
|
419
|
+
suc = problem.successor(state,a)
|
|
420
|
+
nodes_generated += 1
|
|
421
|
+
if loop_check:
|
|
422
|
+
if suc.__repr__() in states_seen:
|
|
423
|
+
nodes_discarded += 1
|
|
424
|
+
continue # skip already seen state
|
|
425
|
+
else:
|
|
426
|
+
states_seen.add(suc.__repr__())
|
|
427
|
+
nodes_kept += 1
|
|
428
|
+
if nodes_kept > max_nodes:
|
|
429
|
+
node_limit_exceeded = True
|
|
430
|
+
break
|
|
431
|
+
extended_path = path+[a]
|
|
432
|
+
#print("Making node with path:", extended_path)
|
|
433
|
+
weight=weight_function(extended_path,suc)
|
|
434
|
+
#print("weight=", weight)
|
|
435
|
+
queue.insert((extended_path,suc),
|
|
436
|
+
weight=weight)
|
|
437
|
+
|
|
438
|
+
if termination_condition == "GOAL_STATE_FOUND":
|
|
439
|
+
print( "\n:-)) *SUCCESS* ((-:\n" )
|
|
440
|
+
print( f"Path length = {len(path)}" )
|
|
441
|
+
print( "Goal state is:")
|
|
442
|
+
problem.display_state(state)
|
|
443
|
+
if cost != None:
|
|
444
|
+
print("Cost of reaching goal:", cost(path,state))
|
|
445
|
+
if show_path:
|
|
446
|
+
print( "The action path to the solution is:" )
|
|
447
|
+
for a in path:
|
|
448
|
+
problem.display_action(a)
|
|
449
|
+
print()
|
|
450
|
+
if show_state_path:
|
|
451
|
+
print( "The state/action path to the solution is:" )
|
|
452
|
+
problem.display_state_path(path)
|
|
453
|
+
goal_state = state
|
|
454
|
+
path_length = len(path)
|
|
455
|
+
|
|
456
|
+
if termination_condition == "SEARCH-SPACE_EXHAUSTED":
|
|
457
|
+
print("\n!! Search space exhausted (tried everying) !!")
|
|
458
|
+
print("): No solution found :(\n")
|
|
459
|
+
goal_state=path=path_length=None
|
|
460
|
+
|
|
461
|
+
if termination_condition == "NODE_LIMIT_EXCEEDED":
|
|
462
|
+
print( f"\n!! Search node limit ({max_nodes}) reached !!")
|
|
463
|
+
print("): No solution found :(\n")
|
|
464
|
+
goal_state=path=path_length=None
|
|
465
|
+
|
|
466
|
+
print( f"\nSEARCH SPACE STATS:")
|
|
467
|
+
print( f"Total nodes generated = {nodes_generated:>8} (includes start)")
|
|
468
|
+
|
|
469
|
+
if loop_check:
|
|
470
|
+
distinct_states_seen = len(states_seen)
|
|
471
|
+
else:
|
|
472
|
+
distinct_states_seen = "not recorded (loop_check=False)"
|
|
473
|
+
|
|
474
|
+
if loop_check:
|
|
475
|
+
print( f"Nodes discarded by loop_check = {nodes_discarded:>8}"
|
|
476
|
+
f" ({nodes_generated-nodes_discarded} distinct states added to queue)" )
|
|
477
|
+
#print( f"Distinct states seen = {distinct_states_seen:>8}" )
|
|
478
|
+
|
|
479
|
+
print( f"Nodes tested (by goal_test) = {nodes_tested:>8}",end=' ' )
|
|
480
|
+
if termination_condition == "GOAL_STATE_FOUND":
|
|
481
|
+
print(f" ({nodes_tested-1} expanded + 1 goal)")
|
|
482
|
+
else:
|
|
483
|
+
print( " (all expanded)")
|
|
484
|
+
|
|
485
|
+
print( f"Nodes left in queue = {queue.len():>8}")
|
|
486
|
+
time_taken = time.perf_counter() - start_time
|
|
487
|
+
print( f"\nTime taken = {round(time_taken, 4)} seconds\n" )
|
|
488
|
+
|
|
489
|
+
if not return_info:
|
|
490
|
+
return termination_condition
|
|
491
|
+
else:
|
|
492
|
+
return {
|
|
493
|
+
"args" : {"problem" : problem.__class__.__name__,
|
|
494
|
+
"mode" : mode,
|
|
495
|
+
"max_nodes" : max_nodes,
|
|
496
|
+
"loop_check" : loop_check,
|
|
497
|
+
"randomise" : randomise,
|
|
498
|
+
"cost" : cost_name,
|
|
499
|
+
"heuristic" : heuristic_name,
|
|
500
|
+
"dots" : dots,
|
|
501
|
+
},
|
|
502
|
+
"result": {
|
|
503
|
+
"termination_condition": termination_condition,
|
|
504
|
+
"goal_state" : goal_state,
|
|
505
|
+
"path" : path,
|
|
506
|
+
"path_length" : path_length,
|
|
507
|
+
},
|
|
508
|
+
"search_stats" : {
|
|
509
|
+
"nodes_generated" : nodes_generated,
|
|
510
|
+
"nodes_tested" : nodes_tested,
|
|
511
|
+
"nodes_discarded" : nodes_discarded,
|
|
512
|
+
"distinct_states_seen" : distinct_states_seen,
|
|
513
|
+
"nodes_left_in_queue" : queue.len(),
|
|
514
|
+
"time_taken" : time_taken,
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
def node_weight_function( cost, heuristic ):
|
|
521
|
+
if not cost and not heuristic:
|
|
522
|
+
return lambda p,s: None
|
|
523
|
+
if cost and (not heuristic):
|
|
524
|
+
return cost
|
|
525
|
+
if (not cost) and heuristic:
|
|
526
|
+
return lambda p,s: (heuristic(s))
|
|
527
|
+
if cost and heuristic:
|
|
528
|
+
return lambda p,s:(cost(p,s)+heuristic(s))
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
# In[46]:
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
def thecost(p,s):
|
|
537
|
+
return(len(p)**2)
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
def test():
|
|
541
|
+
JPP1 = JugPouringPuzzle(
|
|
542
|
+
{"small":(3,0), "medium":(5,0), "large":(8,8)}, #initial state
|
|
543
|
+
4 # goal measurement
|
|
544
|
+
)
|
|
545
|
+
|
|
546
|
+
JPP2 = JugPouringPuzzle(
|
|
547
|
+
{"small":(3,3), "medium":(5,0), "large":(8,8)}, #initial state
|
|
548
|
+
12 # goal measurement
|
|
549
|
+
)
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
|
|
553
|
+
res1 = search( JPP1, 'BF/FIFO', 10000, cost=thecost, loop_check=False, show_state_path=True )
|
|
554
|
+
print("res1 ="); display(res1)
|
|
555
|
+
res2 = search( JPP2, 'DF/LIFO', 10000,
|
|
556
|
+
#dots = False,
|
|
557
|
+
randomise=True, loop_check=False, return_info=True)
|
|
558
|
+
print("res2 ="); display(res2)
|
|
559
|
+
|
|
560
|
+
if __name__ == "__main__":
|
|
561
|
+
test()
|
|
562
|
+
|
|
563
|
+
|
|
564
|
+
# In[ ]:
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
# In[ ]:
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
# In[ ]:
|
|
577
|
+
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
|