diff --git a/examples/the_basics/a_simple_objective_function_registration/environment.yml b/examples/the_basics/a_simple_objective_function_registration/environment.yml deleted file mode 100644 index 77d338fb..00000000 --- a/examples/the_basics/a_simple_objective_function_registration/environment.yml +++ /dev/null @@ -1,9 +0,0 @@ -name: poli_aloha_problem -channels: - - defaults -dependencies: - - python=3.9 - - pip - - pip: - - numpy<2 - - "git+https://github.com/MachineLearningLifeScience/poli.git@dev" diff --git a/examples/the_basics/a_simple_objective_function_registration/querying_aloha.py b/examples/the_basics/a_simple_objective_function_registration/querying_aloha.py deleted file mode 100644 index 69773f1a..00000000 --- a/examples/the_basics/a_simple_objective_function_registration/querying_aloha.py +++ /dev/null @@ -1,30 +0,0 @@ -""" -Once registered, we can create instances of the black box -function. This function is evaluated in an isolated process, -using the conda enviroment we specified at registration. -""" - -import numpy as np - -from poli import objective_factory - -if __name__ == "__main__": - # Creating an instance of the problem - f, x0, y0 = objective_factory.create( - name="our_aloha", observer_init_info=None, observer=None - ) - print(x0, y0) - - # At this point, you can call f. This will create - # a new isolated process, where the AlohaBlackBox - # will run inside the conda environment poli_aloha. - x1 = np.array(["F", "L", "E", "A", "S"]).reshape(1, -1) - y1 = f(x1) - print(x1, y1) - f.terminate() - - # Another example (using the start function) - with objective_factory.start(name="our_aloha") as f: - x = np.array(["F", "L", "E", "A", "S"]).reshape(1, -1) - y = f(x) - print(x, y) diff --git a/examples/the_basics/a_simple_objective_function_registration/registering_aloha.py b/examples/the_basics/a_simple_objective_function_registration/registering_aloha.py deleted file mode 100644 index eb2675d3..00000000 --- a/examples/the_basics/a_simple_objective_function_registration/registering_aloha.py +++ /dev/null @@ -1,75 +0,0 @@ -""" -This is the minimal example of how to register -a problem factory, which allows for creating -instances of the problem: the objective function, -the initial point, and its first evaluation. -""" - -from string import ascii_uppercase -from typing import Tuple - -import numpy as np - -from poli.core.abstract_black_box import AbstractBlackBox -from poli.core.abstract_problem_factory import AbstractProblemFactory -from poli.core.black_box_information import BlackBoxInformation -from poli.core.problem import Problem - -our_aloha_information = BlackBoxInformation( - name="our_aloha", - max_sequence_length=5, - aligned=True, - fixed_length=True, - deterministic=True, - alphabet=list(ascii_uppercase), - log_transform_recommended=False, - discrete=True, - fidelity=None, - padding_token="", -) - - -class OurAlohaBlackBox(AbstractBlackBox): - def __init__( - self, - batch_size: int = None, - parallelize: bool = False, - num_workers: int = None, - evaluation_budget: int = float("inf"), - ): - super().__init__(batch_size, parallelize, num_workers, evaluation_budget) - - # The only method you have to define - def _black_box(self, x: np.ndarray, context: dict = None) -> np.ndarray: - matches = x == np.array(["A", "L", "O", "H", "A"]) - return np.sum(matches, axis=1, keepdims=True) - - @staticmethod - def get_black_box_info() -> BlackBoxInformation: - return our_aloha_information - - -class OurAlohaProblemFactory(AbstractProblemFactory): - def get_setup_information(self) -> BlackBoxInformation: - # The alphabet: ["A", "B", "C", ...] - return our_aloha_information - - def create(self, seed: int = None, **kwargs) -> Problem: - f = OurAlohaBlackBox() - x0 = np.array([["A", "L", "O", "O", "F"]]) - - return Problem(f, x0) - - -if __name__ == "__main__": - from poli.core.registry import register_problem - - # Once we have created a simple conda enviroment - # (see the environment.yml file in this folder), - # we can register our problem s.t. it uses - # said conda environment. - aloha_problem_factory = OurAlohaProblemFactory() - register_problem( - aloha_problem_factory, - conda_environment_name="poli_aloha_problem", - ) diff --git a/src/poli/core/registry.py b/src/poli/core/registry.py index 690f9d66..e3d66b53 100644 --- a/src/poli/core/registry.py +++ b/src/poli/core/registry.py @@ -2,19 +2,16 @@ """ import configparser -import subprocess import warnings from pathlib import Path -from typing import Dict, List, Type, Union +from typing import List, Type, Union from poli.core.abstract_black_box import AbstractBlackBox from poli.core.abstract_isolated_function import AbstractIsolatedFunction -from poli.core.abstract_problem_factory import AbstractProblemFactory from poli.core.util.abstract_observer import AbstractObserver from poli.core.util.objective_management.make_run_script import ( make_isolated_function_script, make_observer_script, - make_run_script, ) # from poli.objective_repository import AVAILABLE_PROBLEM_FACTORIES, AVAILABLE_OBJECTIVES diff --git a/src/poli/core/util/isolation/external_black_box.py b/src/poli/core/util/isolation/external_black_box.py deleted file mode 100644 index c534501a..00000000 --- a/src/poli/core/util/isolation/external_black_box.py +++ /dev/null @@ -1,133 +0,0 @@ -from typing import Any - -from poli.core.abstract_black_box import AbstractBlackBox -from poli.core.black_box_information import BlackBoxInformation -from poli.core.util.inter_process_communication.process_wrapper import ProcessWrapper - - -class ExternalBlackBox(AbstractBlackBox): - """An external version of the black-box function to be instantiated in isolated processes.""" - - def __init__(self, process_wrapper: ProcessWrapper): - """ - Initialize the ExternalBlackBox object. - - Parameters - ---------- - info : ProblemSetupInformation - The information about the problem. - process_wrapper : ProcessWrapper - The process wrapper to communicate with the objective process. - """ - # We don't need to pass the kwargs to the parent class, - # because we overwrite the __getattr__ method to communicate - # with the isolated objective process. - super().__init__() - self.process_wrapper = process_wrapper - - def _black_box(self, x, context=None): - """ - Evaluates the black-box function. - - In this external black-box, the evaluation is done by sending a message - to the objective process and waiting for the response. The interprocess - communication is handled by the ProcessWrapper class, which maintains - an isolated process in which a black-box function runs with, potentially, - a completely different python executable (e.g. inside another conda - environment). - - Parameters - ---------- - x : np.ndarray - The input data points. - context : object - Additional context for the observation. - - Returns - ------- - y : np.ndarray - The output data points. - """ - self.process_wrapper.send(["QUERY", x, context]) - msg_type, *val = self.process_wrapper.recv() - if msg_type == "EXCEPTION": - e, traceback_ = val - print(traceback_) - raise e - elif msg_type == "QUERY": - y = val[0] - - return y - else: - raise ValueError( - f"Internal error: received {msg_type} when expecting QUERY or EXCEPTION" - ) - - @property - def info(self) -> BlackBoxInformation: - """The information about the black-box function.""" - self.process_wrapper.send(["ATTRIBUTE", "info"]) - msg_type, *msg = self.process_wrapper.recv() - if msg_type == "EXCEPTION": - e, traceback_ = msg - print(traceback_) - raise e - else: - assert msg_type == "ATTRIBUTE" - attribute = msg[0] - return attribute - - def terminate(self): - """Terminates the external black box.""" - # terminate objective process - if self.process_wrapper is not None: - try: - self.process_wrapper.send(["QUIT", None]) - self.process_wrapper.close() # clean up connection - except AttributeError: - # This means that the process has already been terminated - pass - self.process_wrapper = None - # terminate observer - if self.observer is not None: - try: - self.observer.finish() - self.observer = None - except: - pass - - def __getattr__(self, __name: str) -> Any: - """Gets an attribute from the underlying black-box function. - - Asks for the attribute of the underlying - black-box function by sending a message - to the process w. the msg_type "ATTRIBUTE". - - Parameters - ---------- - __name : str - The name of the attribute. - - Returns - ------- - attribute : Any - The attribute of the underlying black-box function. - """ - if __name == "process_wrapper": - return self.process_wrapper - self.process_wrapper.send(["ATTRIBUTE", __name]) - msg_type, *msg = self.process_wrapper.recv() - if msg_type == "EXCEPTION": - e, traceback_ = msg - print(traceback_) - raise e - else: - assert msg_type == "ATTRIBUTE" - attribute = msg[0] - return attribute - - def __del__(self): - self.terminate() - - def __exit__(self, exc_type, exc_val, exc_tb): - return self.terminate() diff --git a/src/poli/core/util/objective_management/make_run_script.py b/src/poli/core/util/objective_management/make_run_script.py index ad380f85..0793be69 100644 --- a/src/poli/core/util/objective_management/make_run_script.py +++ b/src/poli/core/util/objective_management/make_run_script.py @@ -9,13 +9,11 @@ from pathlib import Path from typing import List, Type, Union -from poli import external_isolated_function_script, external_problem_factory_script -from poli.core.abstract_black_box import AbstractBlackBox +from poli import external_isolated_function_script from poli.core.abstract_isolated_function import AbstractIsolatedFunction -from poli.core.abstract_problem_factory import AbstractProblemFactory from poli.core.util import observer_wrapper from poli.core.util.abstract_observer import AbstractObserver -from poli.external_problem_factory_script import ADDITIONAL_IMPORT_SEARCH_PATHES_KEY +from poli.external_isolated_function_script import ADDITIONAL_IMPORT_SEARCH_PATHES_KEY # By default, we will store the run scripts inside the # home folder of the user, on the hidden folder @@ -52,44 +50,11 @@ def make_isolated_function_script( """ command = inspect.getfile(external_isolated_function_script) - return _make_run_script( + return _make_run_script_from_template( command, isolated_function, conda_environment_name, python_paths, cwd, **kwargs ) -def make_run_script( - problem_factory: Type[AbstractProblemFactory], - conda_environment_name: Union[str, Path] = None, - python_paths: List[str] = None, - cwd=None, - **kwargs, -) -> str: - """ - Create a run script for a given problem factory. - - Parameters - ---------- - problem_factory : AbstractProblemFactory - The problem factory to create the run script for. - conda_environment_name : str or Path, optional - The conda environment to use for the run script. - Either a string containing the name, or a path to the environment. - python_paths : list of str, optional - A list of paths to append to the python path of the run script. - cwd : str or Path, optional - The working directory of the run script. - - Returns - ------- - run_script: str - The generated run script. - """ - command = inspect.getfile(external_problem_factory_script) - return _make_run_script( - command, problem_factory, conda_environment_name, python_paths, cwd, **kwargs - ) - - def make_observer_script( observer: Type[AbstractObserver], conda_environment: Union[str, Path] = None, @@ -117,27 +82,17 @@ def make_observer_script( """ command = inspect.getfile(observer_wrapper) - return _make_run_script(command, observer, conda_environment, python_paths, cwd) - - -def _make_black_box_script( - command: str, - command_for_instancing_the_black_box: str, - conda_environment_name: Union[str, Path], - python_paths: List[str], - cwd=None, -): - # TODO: implement in such a way that - ... + return _make_run_script_from_template( + command, observer, conda_environment, python_paths, cwd + ) -def _make_run_script( +def _make_run_script_from_template( command: str, non_instantiated_object, conda_environment_name: Union[str, Path], python_paths: List[str], cwd=None, - **kwargs, ): """ An internal function for creating run scripts; returns the location of the run script. diff --git a/src/poli/core/util/observer_wrapper.py b/src/poli/core/util/observer_wrapper.py index 399a4488..7de468de 100644 --- a/src/poli/core/util/observer_wrapper.py +++ b/src/poli/core/util/observer_wrapper.py @@ -7,10 +7,7 @@ from poli.core.util.abstract_observer import AbstractObserver from poli.core.util.inter_process_communication.process_wrapper import get_connection -from poli.external_problem_factory_script import ( - dynamically_instantiate, - parse_factory_kwargs, -) +from poli.external_isolated_function_script import dynamically_instantiate def start_observer_process(observer_name, port: int, password: str): diff --git a/src/poli/external_isolated_function_script.py b/src/poli/external_isolated_function_script.py index 4c2aed18..f24c3595 100644 --- a/src/poli/external_isolated_function_script.py +++ b/src/poli/external_isolated_function_script.py @@ -16,52 +16,6 @@ ADDITIONAL_IMPORT_SEARCH_PATHES_KEY = "ADDITIONAL_IMPORT_PATHS" -def parse_factory_kwargs(factory_kwargs: str) -> dict: - """Parses the factory kwargs passed to the objective function. - - Parameters - ---------- - factory_kwargs : str - The string containing the factory kwargs (see ProcessWrapper - for details about how this factory_kwargs strings is built). - - Returns - ------- - kwargs : dict - A dictionary containing the factory kwargs, parsed from the string. - """ - if factory_kwargs == "": - # Then the user didn't pass any arguments - kwargs = {} - else: - factory_kwargs = factory_kwargs.split() - kwargs = {} - for item in factory_kwargs: - item = item.strip("--") - key, value = item.split("=") - if value.startswith("list:"): - # Then we assume that the value was a list - value = value.strip("list:") - value = value.split(",") - elif value.startswith("int:"): - value = int(value.strip("int:")) - elif value.startswith("float:"): - if value == "float:inf": - value = float("inf") - elif value == "float:-inf": - value = float("-inf") - else: - value = float(value.strip("float:")) - elif value.startswith("bool:"): - value = value.strip("bool:") == "True" - elif value.startswith("none:"): - value = None - - kwargs[key] = value - - return kwargs - - def dynamically_instantiate(obj: str, **kwargs): """Dynamically instantiates an object from a string. diff --git a/src/poli/external_problem_factory_script.py b/src/poli/external_problem_factory_script.py deleted file mode 100644 index 25a69437..00000000 --- a/src/poli/external_problem_factory_script.py +++ /dev/null @@ -1,177 +0,0 @@ -"""Executable script used for isolation of objective factories and functions.""" - -import argparse -import logging -import os -import sys -import traceback - -from poli.core.abstract_problem_factory import AbstractProblemFactory -from poli.core.util.inter_process_communication.process_wrapper import get_connection - -ADDITIONAL_IMPORT_SEARCH_PATHES_KEY = "ADDITIONAL_IMPORT_PATHS" - - -def parse_factory_kwargs(factory_kwargs: str) -> dict: - """Parses the factory kwargs passed to the objective function. - - Parameters - ---------- - factory_kwargs : str - The string containing the factory kwargs (see ProcessWrapper - for details about how this factory_kwargs strings is built). - - Returns - ------- - kwargs : dict - A dictionary containing the factory kwargs, parsed from the string. - """ - if factory_kwargs == "": - # Then the user didn't pass any arguments - kwargs = {} - else: - factory_kwargs = factory_kwargs.split() - kwargs = {} - for item in factory_kwargs: - item = item.strip("--") - key, value = item.split("=") - if value.startswith("list:"): - # Then we assume that the value was a list - value = value.strip("list:") - value = value.split(",") - elif value.startswith("int:"): - value = int(value.strip("int:")) - elif value.startswith("float:"): - if value == "float:inf": - value = float("inf") - elif value == "float:-inf": - value = float("-inf") - else: - value = float(value.strip("float:")) - elif value.startswith("bool:"): - value = value.strip("bool:") == "True" - elif value.startswith("none:"): - value = None - - kwargs[key] = value - - return kwargs - - -def dynamically_instantiate(obj: str, **kwargs): - """Dynamically instantiates an object from a string. - - This function is used internally to instantiate objective - factories dynamically, inside isolated processes. It is - also used to instantiate external observers. - - Parameters - ---------- - obj : str - The string containing the name of the object to be instantiated. - **kwargs : dict - The keyword arguments to be passed to the object constructor. - """ - # FIXME: this method opens up a serious security vulnerability - # TODO: possible alternative: importlib - # TODO: another possible alternative: hydra - # sys.path.append(os.getcwd()) - if ADDITIONAL_IMPORT_SEARCH_PATHES_KEY in os.environ: - sys.path.extend(os.environ[ADDITIONAL_IMPORT_SEARCH_PATHES_KEY].split(":")) - # sys.path.extend(os.environ['PYTHONPATH'].split(':')) - last_dot = obj.rfind(".") - - command = ( - "from " - + obj[:last_dot] - + " import " - + obj[last_dot + 1 :] - + " as DynamicObject" - ) - try: - exec(command) - instantiated_object = eval("DynamicObject")(**kwargs) - except ImportError as e: - logging.fatal(f"Path: {os.environ['PATH']}") - logging.fatal(f"Python path: {sys.path}") - logging.fatal(f"Path: {os.environ[ADDITIONAL_IMPORT_SEARCH_PATHES_KEY]}") - if "PYTHONPATH" in os.environ.keys(): - logging.fatal(f"Path: {os.environ['PYTHONPATH']}") - else: - logging.fatal("PYTHONPATH is not part of the environment variables.") - raise e - return instantiated_object - - -def run(factory_kwargs: str, objective_name: str, port: int, password: str) -> None: - """Starts an objective function listener loop to wait for requests. - - Parameters - ---------- - factory_kwargs : str - The string containing the factory kwargs (see ProcessWrapper - for details about how this factory_kwargs strings is built). - objective_name : str - The name of the objective function to be instantiated. - port : int - The port number for the connection with the mother process. - password : str - The password for the connection with the mother process. - """ - kwargs = parse_factory_kwargs(factory_kwargs) - - # make connection with the mother process - conn = get_connection(port, password) - - # TODO: We could be receiving the kwargs for the factory here. - msg_type, seed = conn.recv() - kwargs["seed"] = seed - - # dynamically load objective function module - # At this point, the black box objective function - # is exactly the same as the one used in the - # registration (?). - try: - objective_factory: AbstractProblemFactory = dynamically_instantiate( - objective_name - ) - problem = objective_factory.create(**kwargs) - f, x0 = problem.black_box, problem.x0 - - # give mother process the signal that we're ready - conn.send(["SETUP", x0]) - except Exception as e: - tb = traceback.format_exc() - conn.send(["EXCEPTION", e, tb]) - raise e - - # now wait for objective function calls - while True: - msg_type, *msg = conn.recv() - # x, context = msg - if msg_type == "QUIT": - break - try: - if msg_type == "QUERY": - x, context = msg - y = f(x, context=context) - conn.send(["QUERY", y]) - elif msg_type == "ATTRIBUTE": - attribute = getattr(f, msg[0]) - conn.send(["ATTRIBUTE", attribute]) - except Exception as e: - tb = traceback.format_exc() - conn.send(["EXCEPTION", e, tb]) - - # conn.close() - # exit() # kill other threads, and close file handles - - -if __name__ == "__main__": - parser = argparse.ArgumentParser() - parser.add_argument("--objective-name", required=True) - parser.add_argument("--port", required=True, type=int) - parser.add_argument("--password", required=True, type=str) - - args, factory_kwargs = parser.parse_known_args() - run(factory_kwargs[0], args.objective_name, args.port, args.password) diff --git a/src/poli/objective_factory.py b/src/poli/objective_factory.py index 8b0e1252..a7e6867f 100644 --- a/src/poli/objective_factory.py +++ b/src/poli/objective_factory.py @@ -24,9 +24,7 @@ from poli.core.util.algorithm_observer_wrapper import AlgorithmObserverWrapper from poli.core.util.default_observer import DefaultObserver from poli.core.util.external_observer import ExternalObserver -from poli.core.util.inter_process_communication.process_wrapper import ProcessWrapper -from poli.core.util.isolation.external_black_box import ExternalBlackBox -from poli.external_problem_factory_script import dynamically_instantiate +from poli.external_isolated_function_script import dynamically_instantiate from poli.objective_repository import AVAILABLE_OBJECTIVES, AVAILABLE_PROBLEM_FACTORIES diff --git a/src/poli/tests/registry/test_force_isolation.py b/src/poli/tests/registry/test_force_isolation.py index a862ddbc..ad905135 100644 --- a/src/poli/tests/registry/test_force_isolation.py +++ b/src/poli/tests/registry/test_force_isolation.py @@ -37,10 +37,3 @@ def test_force_isolation_on_tdc(): quiet=True, ) assert (problem.black_box(problem.x0) == inner_f(problem.x0)).all() - - -def test_isolation_on_dockstring(): ... - - -if __name__ == "__main__": - test_force_isolation_on_tdc()