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.
@@ -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 &#x27;brandonb&#x27; isn&#x27;t allowed to upload to project \n"," &#x27;bbsearch&#x27;. 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
+