From 9655105542a4c6a233279af15ad6a6a4ec9c2897 Mon Sep 17 00:00:00 2001 From: Hershraj Niranjani <60162641+talkierbox@users.noreply.github.com> Date: Tue, 4 Mar 2025 18:55:56 -0800 Subject: [PATCH 01/10] Moved RegEx stuff to spec files --- .gitignore | 7 +++++- Dockerfile | 9 +++++--- examples/dm_control_ballcup_spec.py | 5 +++++ examples/dm_control_swingup_spec.py | 4 ++++ funsearch/code_manipulation.py | 34 +++++++++++++++++++++++++++++ funsearch/evaluator.py | 30 ++++++++++++++++--------- 6 files changed, 75 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index ff5302c..b1238be 100644 --- a/.gitignore +++ b/.gitignore @@ -25,4 +25,9 @@ MANIFEST data/ .vscode/ .devcontainer/ -.github/ \ No newline at end of file +.github/ +pdm.lock +.pdm-python + +# Misc +my_cmds.txt diff --git a/Dockerfile b/Dockerfile index cc843d1..7d07c03 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,12 +13,15 @@ COPY funsearch ./funsearch RUN pip install --no-deps . RUN llm install llm-ollama -RUN pip install dm_control + +RUN pip install mujoco==3.2.4 +RUN pip install dm_control==1.0.24 + # if running the container RUN rm -r ./funsearch ./build CMD /bin/bash # if debugging -# RUN pip install debugpy -# CMD ["python", "-Xfrozen_modules=off", "-m", "debugpy", "--listen", "0.0.0.0:5678", "--wait-for-client", "funsearch", "run", "examples/inv_pendulum_spec.py", "0.6", "--sandbox_type", "ExternalProcessSandbox"] +#RUN pip install debugpy +#CMD ["python", "-Xfrozen_modules=off", "-m", "debugpy", "--listen", "0.0.0.0:5678", "--wait-for-client", "funsearch", "run", "examples/inv_pendulum_spec.py", "0.6", "--sandbox_type", "ExternalProcessSandbox"] \ No newline at end of file diff --git a/examples/dm_control_ballcup_spec.py b/examples/dm_control_ballcup_spec.py index b6cb2b5..f310448 100644 --- a/examples/dm_control_ballcup_spec.py +++ b/examples/dm_control_ballcup_spec.py @@ -6,8 +6,13 @@ import numpy as np import funsearch +import re from dm_control import suite +METHOD_MATCHER = re.compile(r"def policy_v\d\(.*?\) -> np.ndarray:(?:\s*(?:[ \t]*(?!def|#|`|').*(?:\n|$)))+") +METHOD_NAME_MATCHER = re.compile(r"policy_v\d+") +method_str = "def policy_v" + @funsearch.run def solve(num_runs) -> float: diff --git a/examples/dm_control_swingup_spec.py b/examples/dm_control_swingup_spec.py index 652588b..a660c27 100644 --- a/examples/dm_control_swingup_spec.py +++ b/examples/dm_control_swingup_spec.py @@ -6,8 +6,12 @@ import numpy as np import funsearch +import re from dm_control import suite +METHOD_MATCHER = re.compile(r"def policy_v\d\(.*?\) -> float:(?:\s*(?:[ \t]*(?!def|#|`|').*(?:\n|$)))+") +METHOD_NAME_MATCHER = re.compile(r"policy_v\d+") +method_str = "def policy_v" @funsearch.run def solve(num_runs) -> float: diff --git a/funsearch/code_manipulation.py b/funsearch/code_manipulation.py index 16a9f61..e53006c 100644 --- a/funsearch/code_manipulation.py +++ b/funsearch/code_manipulation.py @@ -251,3 +251,37 @@ def yield_decorated(code: str, module: str, name: str) -> Iterator[str]: and attribute.value.id == module and attribute.attr == name): yield node.name + +def extract_variable_value(code_str, var_name): + """ + Extracts the value assigned to a variable from a Python code string using AST. + This function supports extraction of: + - Direct string assignments (e.g., my_var = "hello") + - Values produced by a call to re.compile (e.g., my_regex = re.compile(r"...")) + + Parameters: + code_str (str): The Python code to parse. + var_name (str): The name of the variable to extract. + + Returns: + str or None: The extracted string value if found; otherwise, None. + """ + tree = ast.parse(code_str) + + for node in ast.walk(tree): + if isinstance(node, ast.Assign): + for target in node.targets: + if isinstance(target, ast.Name) and target.id == var_name: + value_node = node.value + + # Case 1: Direct string assignment + if isinstance(value_node, ast.Constant) and isinstance(value_node.value, str): + return value_node.value + + # Case 2: A call to re.compile (or similar) + elif isinstance(value_node, ast.Call): + # Ensure we are dealing with a call to a function with attribute 'compile' + if hasattr(value_node.func, 'attr') and value_node.func.attr == 'compile': + if value_node.args and isinstance(value_node.args[0], ast.Constant) and isinstance(value_node.args[0].value, str): + return value_node.args[0].value + return None diff --git a/funsearch/evaluator.py b/funsearch/evaluator.py index e2e49eb..bc4a4ee 100644 --- a/funsearch/evaluator.py +++ b/funsearch/evaluator.py @@ -18,7 +18,7 @@ import re from collections.abc import Sequence import copy -from typing import Any, Tuple +from typing import Any, Tuple, Pattern from funsearch import code_manipulation from funsearch import programs_database @@ -32,9 +32,9 @@ """ # use this for pendulum swingup -METHOD_MATCHER = re.compile(r"def policy_v\d\(.*?\) -> float:(?:\s*(?:[ \t]*(?!def|#|`|').*(?:\n|$)))+") -METHOD_NAME_MATCHER = re.compile(r"policy_v\d+") -method_str = "def policy_v" +# METHOD_MATCHER = re.compile(r"def policy_v\d\(.*?\) -> float:(?:\s*(?:[ \t]*(?!def|#|`|').*(?:\n|$)))+") +# METHOD_NAME_MATCHER = re.compile(r"policy_v\d+") +# method_str = "def policy_v" # use this for ball in cup # METHOD_MATCHER = re.compile(r"def policy_v\d\(.*?\) -> np.ndarray:(?:\s*(?:[ \t]*(?!def|#|`|').*(?:\n|$)))+") @@ -47,6 +47,7 @@ class _FunctionLineVisitor(ast.NodeVisitor): def __init__(self, target_function_name: str) -> None: self._target_function_name: str = target_function_name self._function_end_line: int | None = None + def visit_FunctionDef(self, node: Any) -> None: # pylint: disable=invalid-name """Collects the end line number of the target function.""" @@ -61,7 +62,7 @@ def function_end_line(self) -> int: return self._function_end_line -def _find_method_implementation(generated_code: str) -> Tuple[str, str]: +def _find_method_implementation(generated_code: str, METHOD_MATCHER: Pattern, METHOD_NAME_MATCHER: Pattern) -> Tuple[str, str]: """Find the last method specified in METHOD_MATCHER within generated code. Return the code and the name of the method. @@ -74,7 +75,7 @@ def _find_method_implementation(generated_code: str) -> Tuple[str, str]: return last_match, name -def _trim_function_body(generated_code: str) -> str: +def _trim_function_body(generated_code: str, method_str: str, method_matcher: Pattern, method_name_matcher: Pattern) -> str: """Extracts the body of the generated function, trimming anything after it.""" if not generated_code: return '' @@ -84,7 +85,7 @@ def _trim_function_body(generated_code: str) -> str: method_name = "fake_function_header" # Check is the response only a continuation for our prompt or full method implementation with header if method_str in generated_code: - code, method_name = _find_method_implementation(generated_code) + code, method_name = _find_method_implementation(generated_code, method_matcher, method_name_matcher) else: code = f'def {method_name}():\n{generated_code}' @@ -111,9 +112,12 @@ def _sample_to_program( version_generated: int | None, template: code_manipulation.Program, function_to_evolve: str, + method_str: str, + method_matcher: Pattern, + method_name_matcher: Pattern ) -> tuple[code_manipulation.Function, str]: """Returns the compiled generated function and the full runnable program.""" - body = _trim_function_body(generated_code) + body = _trim_function_body(generated_code, method_str, method_matcher, method_name_matcher) if version_generated is not None: body = code_manipulation.rename_function_calls( body, @@ -150,7 +154,10 @@ def __init__( function_to_evolve: str, function_to_run: str, inputs: Sequence[Any], - timeout_seconds: int = 30, + method_str: str, + method_matcher: Pattern, + method_name_matcher: Pattern, + timeout_seconds: int = 30 ): self._database = database self._template = template @@ -159,6 +166,9 @@ def __init__( self._inputs = inputs self._timeout_seconds = timeout_seconds self._sandbox = sbox + self._method_matcher = method_matcher + self._method_str = method_str + self._method_name_matcher = method_name_matcher def analyse( self, @@ -168,7 +178,7 @@ def analyse( ) -> None: """Compiles the sample into a program and executes it on test inputs.""" new_function, program = _sample_to_program( - sample, version_generated, self._template, self._function_to_evolve) + sample, version_generated, self._template, self._function_to_evolve, self._method_str, self._method_matcher, self._method_name_matcher) scores_per_test = {} for current_input in self._inputs: # runs the function on all inputs provided in the launch command From d7518358a752893bec8f92f9bcc312b677e66911 Mon Sep 17 00:00:00 2001 From: Hershraj Niranjani <60162641+talkierbox@users.noreply.github.com> Date: Tue, 4 Mar 2025 19:29:03 -0800 Subject: [PATCH 02/10] Bug fixes on the regex extraction --- .gitignore | 1 + funsearch/__main__.py | 10 ++++++++-- funsearch/code_manipulation.py | 17 +++++++---------- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index b1238be..4a9a17f 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,4 @@ pdm.lock # Misc my_cmds.txt +tests/ \ No newline at end of file diff --git a/funsearch/__main__.py b/funsearch/__main__.py index 796dfcd..49866e1 100644 --- a/funsearch/__main__.py +++ b/funsearch/__main__.py @@ -24,7 +24,6 @@ LOGLEVEL = os.environ.get('LOGLEVEL', 'INFO').upper() logging.basicConfig(level=LOGLEVEL) - def get_all_subclasses(cls): all_subclasses = [] @@ -111,7 +110,11 @@ def run(spec_file, inputs, model_name, output_path, load_backup, iterations, sam # model.key = model.get_key() lm = sampler.LLM(2, model, log_path) - specification = spec_file.read() + specification = spec_file.read() + method_str = code_manipulation.extract_variable_value(specification, "method_str") + method_matcher = code_manipulation.extract_variable_value(specification, "METHOD_MATCHER") + method_name_matcher = code_manipulation.extract_variable_value(specification, "METHOD_NAME_MATCHER") + function_to_evolve, function_to_run = core._extract_function_names(specification) template = code_manipulation.text_to_program(specification) @@ -131,6 +134,9 @@ def run(spec_file, inputs, model_name, output_path, load_backup, iterations, sam function_to_evolve, function_to_run, inputs, + method_str, + method_matcher, + method_name_matcher ) for _ in range(conf.num_evaluators)] # We send the initial implementation to be analysed by one of the evaluators. diff --git a/funsearch/code_manipulation.py b/funsearch/code_manipulation.py index e53006c..5bc2229 100644 --- a/funsearch/code_manipulation.py +++ b/funsearch/code_manipulation.py @@ -26,6 +26,7 @@ import dataclasses import io import tokenize +import re from absl import logging @@ -259,12 +260,8 @@ def extract_variable_value(code_str, var_name): - Direct string assignments (e.g., my_var = "hello") - Values produced by a call to re.compile (e.g., my_regex = re.compile(r"...")) - Parameters: - code_str (str): The Python code to parse. - var_name (str): The name of the variable to extract. - - Returns: - str or None: The extracted string value if found; otherwise, None. + If the variable is assigned via re.compile, this function returns the compiled regex. + Otherwise, it returns the string literal. """ tree = ast.parse(code_str) @@ -274,14 +271,14 @@ def extract_variable_value(code_str, var_name): if isinstance(target, ast.Name) and target.id == var_name: value_node = node.value - # Case 1: Direct string assignment + # Direct string assignment: if isinstance(value_node, ast.Constant) and isinstance(value_node.value, str): return value_node.value - # Case 2: A call to re.compile (or similar) + # Call to re.compile: elif isinstance(value_node, ast.Call): - # Ensure we are dealing with a call to a function with attribute 'compile' if hasattr(value_node.func, 'attr') and value_node.func.attr == 'compile': if value_node.args and isinstance(value_node.args[0], ast.Constant) and isinstance(value_node.args[0].value, str): - return value_node.args[0].value + pattern_str = value_node.args[0].value + return re.compile(pattern_str) return None From e8fe59ecc8dba8de20ccff8f6514e5cbf3da1832 Mon Sep 17 00:00:00 2001 From: Hershraj Niranjani <60162641+talkierbox@users.noreply.github.com> Date: Sun, 9 Mar 2025 14:01:57 -0700 Subject: [PATCH 03/10] RoboGrammar Search Spec WIP --- .gitignore | 3 +- pdm.lock | 22 ++++++++++-- robot_search/robogrammar_search_spec.py | 45 +++++++++++++++++++++++++ 3 files changed, 66 insertions(+), 4 deletions(-) create mode 100644 robot_search/robogrammar_search_spec.py diff --git a/.gitignore b/.gitignore index 4a9a17f..f7842a2 100644 --- a/.gitignore +++ b/.gitignore @@ -31,4 +31,5 @@ pdm.lock # Misc my_cmds.txt -tests/ \ No newline at end of file +tests/ +pdm.lock diff --git a/pdm.lock b/pdm.lock index 003c323..056e19f 100644 --- a/pdm.lock +++ b/pdm.lock @@ -3,9 +3,12 @@ [metadata] groups = ["default"] -strategy = ["cross_platform", "inherit_metadata"] -lock_version = "4.4.1" -content_hash = "sha256:386e9f8dc3f1c89a2e635b9f5eebbb5d3a8106ac42e98ff47780957e26f25c8c" +strategy = ["inherit_metadata"] +lock_version = "4.5.0" +content_hash = "sha256:2176753c00ed5391d22c98eb87ff425c9d1b9ca45176eb49f02f7a4f85fd2550" + +[[metadata.targets]] +requires_python = ">=3.9" [[package]] name = "absl-py" @@ -116,6 +119,9 @@ version = "0.6.0" requires_python = ">=3.8" summary = "Reusable constraint types to use with typing.Annotated" groups = ["default"] +dependencies = [ + "typing-extensions>=4.0.0; python_version < \"3.9\"", +] files = [ {file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"}, {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, @@ -128,6 +134,9 @@ requires_python = ">=3.7" summary = "Timeout context manager for asyncio programs" groups = ["default"] marker = "python_version < \"3.11\"" +dependencies = [ + "typing-extensions>=3.6.5; python_version < \"3.8\"", +] files = [ {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, @@ -139,6 +148,9 @@ version = "23.2.0" requires_python = ">=3.7" summary = "Classes Without Boilerplate" groups = ["default"] +dependencies = [ + "importlib-metadata; python_version < \"3.8\"", +] files = [ {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, @@ -234,6 +246,7 @@ summary = "Composable command line interface toolkit" groups = ["default"] dependencies = [ "colorama; platform_system == \"Windows\"", + "importlib-metadata; python_version < \"3.8\"", ] files = [ {file = "click-8.1.0-py3-none-any.whl", hash = "sha256:19a4baa64da924c5e0cd889aba8e947f280309f1a2ce0947a3e3a7bcb7cc72d6"}, @@ -493,6 +506,7 @@ dependencies = [ "aiohttp", "requests>=2.20", "tqdm", + "typing-extensions; python_version < \"3.8\"", ] files = [ {file = "openai-0.28.1-py3-none-any.whl", hash = "sha256:d18690f9e3d31eedb66b57b88c2165d760b24ea0a01f150dd3f068155088ce68"}, @@ -529,6 +543,7 @@ summary = "Data validation using Python type hints" groups = ["default"] dependencies = [ "annotated-types>=0.4.0", + "importlib-metadata; python_version == \"3.7\"", "pydantic-core==2.14.6", "typing-extensions>=4.6.1", ] @@ -880,6 +895,7 @@ groups = ["default"] dependencies = [ "idna>=2.0", "multidict>=4.0", + "typing-extensions>=3.7.4; python_version < \"3.8\"", ] files = [ {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a8c1df72eb746f4136fe9a2e72b0c9dc1da1cbd23b5372f94b5820ff8ae30e0e"}, diff --git a/robot_search/robogrammar_search_spec.py b/robot_search/robogrammar_search_spec.py new file mode 100644 index 0000000..6ab49de --- /dev/null +++ b/robot_search/robogrammar_search_spec.py @@ -0,0 +1,45 @@ +"""Finds a policy for designing a robot for the given task. + +On every iteration, improve robot_v1 over the robot_vX from previous iterations. +Make only small changes. Try to make the code short. + +The robot is formed through a list of numbers, and are automatically designed symmetrically. +""" + +import numpy as np +import funsearch +import re +from typing import List +import requests + + +METHOD_MATCHER = re.compile(r"def robot_v\d\(.*?\) -> float:(?:\s*(?:[ \t]*(?!def|#|`|').*(?:\n|$)))+") +METHOD_NAME_MATCHER = re.compile(r"robot_v\d+") +method_str = "def robot_v" + +@funsearch.run +def evaluate_robot(design) -> float: + """Returns the best reward managed by the robot design. Done via calling the robogrammar API + """ + url = "http://127.0.0.1:5555/simulate" + payload = { + "task": "RidgedTerrainTask", + "grammar_file": "data/designs/grammar_apr30.dot", + "rule_sequence": design, + "jobs": 8, + "optim": True, + "episodes": 1, # using multiple episodes causes FCValueEstimator to crash apparently --- keep at 1 + "episode_len": 128 + } + response = requests.post(url, json=payload) + data = response.json() + optimization_result = data["optimization_result"] + return optimization_result + +@funsearch.evolve +def robot() -> List[int]: + """Returns a list of numbers corresponding to a robot + """ + design = [0] + + return design \ No newline at end of file From bd5cf30998c7b0685878daf8b5f0805619406c8c Mon Sep 17 00:00:00 2001 From: Hershraj Niranjani <60162641+talkierbox@users.noreply.github.com> Date: Sun, 9 Mar 2025 14:03:26 -0700 Subject: [PATCH 04/10] Update robogrammar_search_spec.py --- robot_search/robogrammar_search_spec.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/robot_search/robogrammar_search_spec.py b/robot_search/robogrammar_search_spec.py index 6ab49de..1080aef 100644 --- a/robot_search/robogrammar_search_spec.py +++ b/robot_search/robogrammar_search_spec.py @@ -1,4 +1,4 @@ -"""Finds a policy for designing a robot for the given task. +"""Designs a robot for the given task. On every iteration, improve robot_v1 over the robot_vX from previous iterations. Make only small changes. Try to make the code short. @@ -42,4 +42,4 @@ def robot() -> List[int]: """ design = [0] - return design \ No newline at end of file + return design From 2d69b675d6cc1c9fe419c3bf5bd159232a95fbdb Mon Sep 17 00:00:00 2001 From: Hershraj Niranjani <60162641+talkierbox@users.noreply.github.com> Date: Sun, 9 Mar 2025 14:06:09 -0700 Subject: [PATCH 05/10] Update robogrammar_search_spec.py --- robot_search/robogrammar_search_spec.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/robot_search/robogrammar_search_spec.py b/robot_search/robogrammar_search_spec.py index 1080aef..9cb8ca8 100644 --- a/robot_search/robogrammar_search_spec.py +++ b/robot_search/robogrammar_search_spec.py @@ -13,7 +13,7 @@ import requests -METHOD_MATCHER = re.compile(r"def robot_v\d\(.*?\) -> float:(?:\s*(?:[ \t]*(?!def|#|`|').*(?:\n|$)))+") +METHOD_MATCHER = re.compile(r"def robot_v\d\(.*?\) -> List\[int\]:(?:\s*(?:[ \t]*(?!def|#|`|').*(?:\n|$)))+") METHOD_NAME_MATCHER = re.compile(r"robot_v\d+") method_str = "def robot_v" From 82d91a6a57891b209f50a6dcdd515241e7f01f29 Mon Sep 17 00:00:00 2001 From: Hershraj Niranjani <60162641+talkierbox@users.noreply.github.com> Date: Sun, 9 Mar 2025 17:23:58 -0700 Subject: [PATCH 06/10] Update robogrammar_search_spec.py --- robot_search/robogrammar_search_spec.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/robot_search/robogrammar_search_spec.py b/robot_search/robogrammar_search_spec.py index 9cb8ca8..7e58193 100644 --- a/robot_search/robogrammar_search_spec.py +++ b/robot_search/robogrammar_search_spec.py @@ -5,27 +5,24 @@ The robot is formed through a list of numbers, and are automatically designed symmetrically. """ - -import numpy as np import funsearch import re from typing import List import requests - METHOD_MATCHER = re.compile(r"def robot_v\d\(.*?\) -> List\[int\]:(?:\s*(?:[ \t]*(?!def|#|`|').*(?:\n|$)))+") METHOD_NAME_MATCHER = re.compile(r"robot_v\d+") method_str = "def robot_v" @funsearch.run -def evaluate_robot(design) -> float: +def evaluate_robot(task="RidgedTerrainTask") -> float: """Returns the best reward managed by the robot design. Done via calling the robogrammar API """ url = "http://127.0.0.1:5555/simulate" payload = { - "task": "RidgedTerrainTask", + "task": task, "grammar_file": "data/designs/grammar_apr30.dot", - "rule_sequence": design, + "rule_sequence": robot(), "jobs": 8, "optim": True, "episodes": 1, # using multiple episodes causes FCValueEstimator to crash apparently --- keep at 1 From 1352eb11f1f96c713aa20f28f8024501c41135fe Mon Sep 17 00:00:00 2001 From: Hershraj Niranjani <60162641+talkierbox@users.noreply.github.com> Date: Sun, 9 Mar 2025 22:49:56 -0700 Subject: [PATCH 07/10] Update Dockerfile --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 7d07c03..9dfd33e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,6 +9,7 @@ COPY pyproject.toml README.md pdm.lock ./ ENV PATH="/workspace/.venv/bin:$PATH" RUN pdm install --no-self COPY examples ./examples +COPY robot_search ./robot_search COPY funsearch ./funsearch RUN pip install --no-deps . @@ -17,7 +18,6 @@ RUN llm install llm-ollama RUN pip install mujoco==3.2.4 RUN pip install dm_control==1.0.24 - # if running the container RUN rm -r ./funsearch ./build CMD /bin/bash From 88d226facb0bb188e7e95ea26871e3f7573b1d5c Mon Sep 17 00:00:00 2001 From: Hershraj Niranjani <60162641+talkierbox@users.noreply.github.com> Date: Tue, 29 Apr 2025 19:50:25 -0700 Subject: [PATCH 08/10] program-based search --- Dockerfile | 4 +- models/Modelfile-7B | 16 +++++ Modelfile => models/Modelfile-Carlo | 0 ...pec.py => robogrammar_list_search_spec.py} | 26 +++++--- .../robogrammar_program_search_spec.py | 64 +++++++++++++++++++ 5 files changed, 99 insertions(+), 11 deletions(-) create mode 100644 models/Modelfile-7B rename Modelfile => models/Modelfile-Carlo (100%) rename robot_search/{robogrammar_search_spec.py => robogrammar_list_search_spec.py} (51%) create mode 100644 robot_search/robogrammar_program_search_spec.py diff --git a/Dockerfile b/Dockerfile index 9dfd33e..d4bb1f4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,5 +23,5 @@ RUN rm -r ./funsearch ./build CMD /bin/bash # if debugging -#RUN pip install debugpy -#CMD ["python", "-Xfrozen_modules=off", "-m", "debugpy", "--listen", "0.0.0.0:5678", "--wait-for-client", "funsearch", "run", "examples/inv_pendulum_spec.py", "0.6", "--sandbox_type", "ExternalProcessSandbox"] \ No newline at end of file +# RUN pip install debugpy +# CMD ["python", "-Xfrozen_modules=off", "-m", "debugpy", "--listen", "0.0.0.0:5678", "--wait-for-client", "funsearch", "run", "examples/inv_pendulum_spec.py", "0.6", "--sandbox_type", "ExternalProcessSandbox"] \ No newline at end of file diff --git a/models/Modelfile-7B b/models/Modelfile-7B new file mode 100644 index 0000000..aff950b --- /dev/null +++ b/models/Modelfile-7B @@ -0,0 +1,16 @@ +FROM hf.co/QuantFactory/starcoder2-7b-instruct-GGUF:Q8_0 +SYSTEM """You are an intelligent programming assistant. Most of your requests will be to complete the given function. Try not to use any additional formatting. Any explanations should be written as comments in the completed code.""" + +TEMPLATE """ +{{ if .System }}{{ .System }} + +{{ end }}{{ if .Prompt }}### Instruction +{{ .Prompt }} + + +{{ end }}### Response +{{ .Response }}<|endoftext|> +""" +PARAMETER temperature 1 +PARAMETER top_p 0.95 +PARAMETER repeat_last_n 15 \ No newline at end of file diff --git a/Modelfile b/models/Modelfile-Carlo similarity index 100% rename from Modelfile rename to models/Modelfile-Carlo diff --git a/robot_search/robogrammar_search_spec.py b/robot_search/robogrammar_list_search_spec.py similarity index 51% rename from robot_search/robogrammar_search_spec.py rename to robot_search/robogrammar_list_search_spec.py index 7e58193..f2fe782 100644 --- a/robot_search/robogrammar_search_spec.py +++ b/robot_search/robogrammar_list_search_spec.py @@ -1,15 +1,17 @@ -"""Designs a robot for the given task. - -On every iteration, improve robot_v1 over the robot_vX from previous iterations. +"""On every iteration, improve robot_v1 over the robot_vX from previous iterations. Make only small changes. Try to make the code short. -The robot is formed through a list of numbers, and are automatically designed symmetrically. +Each function should return a list of integers. The integers should be between 0 and 19 (inclusive). Duplicates are allowed. Return only one completed function with no additional text, explanation, or formatting. + +An example of a good design is: [0, 7, 1, 13, 1, 2, 16, 12, 13, 6, 4, 19, 4, 17, 5, 3, 2, 16, 4, 5, 18, 9, 8, 9, 9, 8] +An example of a bad design is: [0, 1, 2, 3] """ import funsearch import re from typing import List import requests +# Ignore these 3 variables METHOD_MATCHER = re.compile(r"def robot_v\d\(.*?\) -> List\[int\]:(?:\s*(?:[ \t]*(?!def|#|`|').*(?:\n|$)))+") METHOD_NAME_MATCHER = re.compile(r"robot_v\d+") method_str = "def robot_v" @@ -18,25 +20,31 @@ def evaluate_robot(task="RidgedTerrainTask") -> float: """Returns the best reward managed by the robot design. Done via calling the robogrammar API """ + # Type checking + robo_design = robot() + + if not isinstance(robo_design, list) or not all([isinstance(x, int) for x in robo_design]): + return 0 + url = "http://127.0.0.1:5555/simulate" payload = { "task": task, "grammar_file": "data/designs/grammar_apr30.dot", - "rule_sequence": robot(), + "rule_sequence": robo_design, "jobs": 8, "optim": True, "episodes": 1, # using multiple episodes causes FCValueEstimator to crash apparently --- keep at 1 - "episode_len": 128 + "episode_len": 30 } response = requests.post(url, json=payload) data = response.json() - optimization_result = data["optimization_result"] + optimization_result = data["distance_travelled"] return optimization_result @funsearch.evolve def robot() -> List[int]: - """Returns a list of numbers corresponding to a robot + """Returns a list of numbers between 1 and 19 (inclusive) that represent the robot design. """ design = [0] - return design + return design \ No newline at end of file diff --git a/robot_search/robogrammar_program_search_spec.py b/robot_search/robogrammar_program_search_spec.py new file mode 100644 index 0000000..c74e8c5 --- /dev/null +++ b/robot_search/robogrammar_program_search_spec.py @@ -0,0 +1,64 @@ +""" +On every iteration, improve robot_v1 over the previous robot_vX from previous iterations. + +Make only small changes. Try to make the code short. + +The robot function takes in a sequence of integers and returns a new integer from 0 to 20. +The function should return -1 in order to indicate that you want to end the sequence. + +You may use duplicate integers and can use any method you like to come up with the new integer. +""" + +import funsearch +import re +from typing import List +import requests +import math +import numpy as np + +# Regex Patterns --- Ignore these 3 variables +METHOD_MATCHER = re.compile(r"def robot_v\d\(input_list: List\[int\]\) -> int:(?:\s*(?:[ \t]*(?!def|#|`|').*(?:\n|$)))+") +METHOD_NAME_MATCHER = re.compile(r"robot_v\d+") +method_str = "def robot_v" + +@funsearch.run +def evaluate_robot(task="RidgedTerrainTask") -> float: + """Returns the best reward managed by the robot design. Done via calling the robogrammar API + """ + rule_seq = [0] # Starting with 0 + filter_after_each_entry = False + rule_gen_max_depth = 25 + + i = 0 + while i < rule_gen_max_depth and rule_seq[-1] != -1: + rule_seq.append(robot(rule_seq[:])) + if filter_after_each_entry and rule_seq[-1] != -1: + rule_seq = filter(rule_seq) + i += 1 + + def filter(input_list: List[int]) -> List[int]: + return [k for k in input_list if k in range(0, 20)] + + final_rule_seq = filter(rule_seq) # Filter the rule sequence + + url = "http://127.0.0.1:5555/simulate" + payload = { + "task": task, + "grammar_file": "data/designs/grammar_apr30.dot", + "rule_sequence": final_rule_seq, + "jobs": 8, + "optim": True, + "episodes": 1, # using multiple episodes causes FCValueEstimator to crash apparently --- keep at 1 + "episode_len": 30 + } + response = requests.post(url, json=payload) + data = response.json() + optimization_result = data["distance_travelled"] + return optimization_result + +@funsearch.evolve +def robot(input_list: List[int]) -> int: + """Returns the next number that should be appended to this sequence. Return -1 to stop the sequence. + Feel free to use any logic or functions you like to come up with the next number. + """ + return 0 \ No newline at end of file From 249217de181ff98a359931c416d61902158c8c50 Mon Sep 17 00:00:00 2001 From: Hershraj Niranjani <60162641+talkierbox@users.noreply.github.com> Date: Wed, 30 Apr 2025 03:23:58 -0700 Subject: [PATCH 09/10] Update robogrammar_program_search_spec.py --- robot_search/robogrammar_program_search_spec.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/robot_search/robogrammar_program_search_spec.py b/robot_search/robogrammar_program_search_spec.py index c74e8c5..a6f081f 100644 --- a/robot_search/robogrammar_program_search_spec.py +++ b/robot_search/robogrammar_program_search_spec.py @@ -7,6 +7,8 @@ The function should return -1 in order to indicate that you want to end the sequence. You may use duplicate integers and can use any method you like to come up with the new integer. + +Make sure to specify the return type and input typings in the function header. """ import funsearch @@ -15,6 +17,7 @@ import requests import math import numpy as np +import random # Regex Patterns --- Ignore these 3 variables METHOD_MATCHER = re.compile(r"def robot_v\d\(input_list: List\[int\]\) -> int:(?:\s*(?:[ \t]*(?!def|#|`|').*(?:\n|$)))+") @@ -58,7 +61,6 @@ def filter(input_list: List[int]) -> List[int]: @funsearch.evolve def robot(input_list: List[int]) -> int: - """Returns the next number that should be appended to this sequence. Return -1 to stop the sequence. - Feel free to use any logic or functions you like to come up with the next number. - """ + """Returns the next number that should be appended to this sequence. Return -1 to stop the sequence. The sequence always starts as just [0]""" + return 0 \ No newline at end of file From f3fa0240cafba66babcbf74f50a97d6acc741311 Mon Sep 17 00:00:00 2001 From: Hershraj Niranjani <60162641+talkierbox@users.noreply.github.com> Date: Tue, 13 May 2025 23:18:25 -0700 Subject: [PATCH 10/10] Update some things --- .gitignore | 1 + funsearch/__main__.py | 2 +- models/Modelfile-7B | 3 ++- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index f7842a2..2029c41 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,4 @@ pdm.lock my_cmds.txt tests/ pdm.lock +launch.json \ No newline at end of file diff --git a/funsearch/__main__.py b/funsearch/__main__.py index 49866e1..9e72585 100644 --- a/funsearch/__main__.py +++ b/funsearch/__main__.py @@ -68,7 +68,7 @@ def main(ctx): @click.argument('inputs') # @click.option('--model_name', default="gpt-3.5-turbo-instruct", help='LLM model') # @click.option('--model_name', default="deepseek-coder", help='LLM model') -@click.option('--model_name', default="starcoder2-control", help='LLM model') +@click.option('--model_name', default="starcoder-7b:latest", help='LLM model') # start with 7b @click.option('--output_path', default="./data/", type=click.Path(file_okay=False), help='path for logs and data') @click.option('--load_backup', default=None, type=click.File("rb"), help='Use existing program database') @click.option('--iterations', default=-1, type=click.INT, help='Max iterations per sampler') diff --git a/models/Modelfile-7B b/models/Modelfile-7B index aff950b..085ca12 100644 --- a/models/Modelfile-7B +++ b/models/Modelfile-7B @@ -13,4 +13,5 @@ TEMPLATE """ """ PARAMETER temperature 1 PARAMETER top_p 0.95 -PARAMETER repeat_last_n 15 \ No newline at end of file +PARAMETER repeat_last_n 15 +PARAMETER num_predict 200 \ No newline at end of file