Skip to content

Commit

Permalink
Prototypes a new isolation interface
Browse files Browse the repository at this point in the history
  • Loading branch information
miguelgondu committed Oct 1, 2024
1 parent aa3462a commit 7ff4f34
Show file tree
Hide file tree
Showing 10 changed files with 116 additions and 50 deletions.
6 changes: 5 additions & 1 deletion src/poli/core/chemistry/tdc_black_box.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
https://pubs.acs.org/doi/10.1021/acs.jcim.8b00839
"""

from __future__ import annotations

from pathlib import Path
from typing import Literal

import numpy as np
Expand Down Expand Up @@ -74,6 +77,7 @@ def __init__(
parallelize: bool = False,
num_workers: int = None,
evaluation_budget: int = float("inf"),
python_executable_for_isolation: str | Path = None,
**kwargs_for_oracle,
):
if parallelize:
Expand All @@ -91,7 +95,7 @@ def __init__(

from_smiles = string_representation.upper() == "SMILES"
self.inner_function = get_inner_function(
isolated_function_name="tdc__isolated",
python_executable_for_isolation=python_executable_for_isolation,
class_name="TDCIsolatedFunction",
module_to_import="poli.core.chemistry.tdc_isolated_function",
force_isolation=force_isolation,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import subprocess
import time
from multiprocessing.connection import Client, Listener
from pathlib import Path
from uuid import uuid4


Expand Down Expand Up @@ -95,28 +94,10 @@ def __init__(self, run_script, **kwargs_for_factory):
# to the shell script. We should instead use a proper IPC library.
# TODO: if we decide to pass this information in the set-up phase (instead
# of here), we can remove this.
string_for_kwargs = ""
for key, value in kwargs_for_factory.items():
if isinstance(value, str):
string_for_kwargs += f"--{key}={str(value)} "
elif isinstance(value, Path):
string_for_kwargs += f"--{key}={str(value)} "
elif isinstance(value, bool):
string_for_kwargs += f"--{key}=bool:{str(value)} "
elif isinstance(value, int):
string_for_kwargs += f"--{key}=int:{str(value)} "
elif isinstance(value, float):
string_for_kwargs += f"--{key}=float:{str(value)} "
elif isinstance(value, list):
string_for_kwargs += (
f"--{key}=list:{','.join([str(v) for v in value])} "
)
elif value is None:
string_for_kwargs += f"--{key}=none:None "

self.run_script = run_script
self.proc = subprocess.Popen(
[run_script, str(self.port), self.password, string_for_kwargs],
[run_script, str(self.port), self.password],
stdout=None,
stderr=None,
)
Expand Down
83 changes: 62 additions & 21 deletions src/poli/core/util/isolation/instancing.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,17 @@
import configparser
import importlib
import logging
import os
import stat
import subprocess
from pathlib import Path
from uuid import uuid4

from poli.core.registry import _ISOLATED_FUNCTION_SCRIPT_LOCATION, _OBSERVER
from poli.core.registry import _OBSERVER
from poli.core.util.inter_process_communication.process_wrapper import ProcessWrapper
from poli.external_isolated_function_script import (
__file__ as external_isolated_function_script_location,
)

from .external_function import ExternalFunction

Expand Down Expand Up @@ -238,8 +244,36 @@ def register_isolated_function(name: str, quiet: bool = False):
config = load_config()


def __write_isolated_function_script(
python_executable: str,
module_to_import: str,
class_name: str,
) -> Path:
temp_id = f"{uuid4()}"[:8]
template_location = Path(__file__).parent / "new_run_script_template.sht"
with open(template_location, "r") as fp:
template_string = fp.read()
run_script = template_string % (
python_executable,
external_isolated_function_script_location,
f"{module_to_import}.{class_name}",
)

isolated_function_script_path = HOME_DIR / ".poli_objectives" / f"{temp_id}.sh"
with open(isolated_function_script_path, "w+") as fp:
fp.write(run_script)
os.chmod(
isolated_function_script_path,
os.stat(isolated_function_script_path).st_mode | stat.S_IEXEC,
) # make script file executable

return isolated_function_script_path


def __create_function_as_isolated_process(
name: str,
python_executable: str,
module_to_import: str,
class_name: str,
seed: int = None,
quiet: bool = False,
**kwargs_for_isolated_function,
Expand All @@ -262,19 +296,15 @@ def __create_function_as_isolated_process(
**kwargs_for_factory : dict, optional
Additional keyword arguments for the factory.
"""
config = load_config()
if name not in config:
raise ValueError(
f"Objective function '{name.replace('__isolated', '')}' is not registered. "
)

if not quiet:
print(
f"poli 🧪: Starting the function {name.replace('__isolated', '')} as an isolated process."
)
# Write the template for this executable name
isolated_function_script_path = __write_isolated_function_script(
python_executable=python_executable,
module_to_import=module_to_import,
class_name=class_name,
)

process_wrapper = ProcessWrapper(
config[name][_ISOLATED_FUNCTION_SCRIPT_LOCATION], **kwargs_for_isolated_function
isolated_function_script_path, **kwargs_for_isolated_function
)
# TODO: add signal listener that intercepts when proc ends
# wait for connection from objective process
Expand Down Expand Up @@ -302,16 +332,17 @@ def __create_function_as_isolated_process(


def instance_function_as_isolated_process(
name: str,
python_executable: str,
module_to_import: str,
class_name: str,
seed: int = None,
quiet: bool = False,
**kwargs_for_black_box,
) -> ExternalFunction:
# Register the problem if it hasn't been registered.
register_isolated_function(name=name, quiet=quiet)

f = __create_function_as_isolated_process(
name=name,
python_executable=python_executable,
module_to_import=module_to_import,
class_name=class_name,
seed=seed,
quiet=quiet,
**kwargs_for_black_box,
Expand All @@ -321,7 +352,7 @@ def instance_function_as_isolated_process(


def get_inner_function(
isolated_function_name: str,
python_executable_for_isolation: str,
class_name: str,
module_to_import: str,
seed: int | None = None,
Expand Down Expand Up @@ -361,10 +392,20 @@ class from the sibling isolated_function.py file of each register.py.
inner_function = InnerFunctionClass(**kwargs)
except ImportError:
inner_function = instance_function_as_isolated_process(
name=isolated_function_name, seed=seed, quiet=quiet, **kwargs
python_executable=python_executable_for_isolation,
module_to_import=module_to_import,
class_name=class_name,
seed=seed,
quiet=quiet,
**kwargs,
)
else:
inner_function = instance_function_as_isolated_process(
name=isolated_function_name, seed=seed, quiet=quiet, **kwargs
python_executable=python_executable_for_isolation,
module_to_import=module_to_import,
class_name=class_name,
seed=seed,
quiet=quiet,
**kwargs,
)
return inner_function
4 changes: 4 additions & 0 deletions src/poli/core/util/isolation/new_run_script_template.sht
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/bash
PYHON_EXECUTABLE_IN_THIS_ENV=%s # python executable
# it is CRUCIAL that the shell script is given port number and password
$PYHON_EXECUTABLE_IN_THIS_ENV %s --objective-name=%s --port="$1" --password="$2"
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
#!/bin/bash
CONDA_BASE=$(conda info --base)
source $CONDA_BASE/etc/profile.d/conda.sh
cd %s # go to working directory
conda deactivate # get rid of environment variables from the calling process
PYTHONPATH="" # clear python path
conda activate %s # environment location
PYHON_EXECUTABLE_IN_THIS_ENV=%s # python executable
PYTHONPATH="${PYTHONPATH}:%s" # additions to the python path
#BASEDIR=$(dirname "$0")
export PYTHONPATH=${PYTHONPATH} # make python path available to python call
export %s=${PYTHONPATH} # some shell magic can prevent the export of PYTHONPATH -- no clue
# it is CRUCIAL that the shell script is given port number and password
python %s --objective-name=%s --port="$1" --password="$2" "$3" # factory name, port number and password
$PYHON_EXECUTABLE_IN_THIS_ENV %s --objective-name=%s --port="$1" --password="$2" "$3" # factory name, port number and password
2 changes: 1 addition & 1 deletion src/poli/external_isolated_function_script.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def dynamically_instantiate(obj: str, **kwargs):
# TODO: possible alternative: importlib
# TODO: another possible alternative: hydra
# sys.path.append(os.getcwd())
sys.path.extend(os.environ[ADDITIONAL_IMPORT_SEARCH_PATHES_KEY].split(":"))
# sys.path.extend(os.environ[ADDITIONAL_IMPORT_SEARCH_PATHES_KEY].split(":"))
# sys.path.extend(os.environ['PYTHONPATH'].split(':'))
last_dot = obj.rfind(".")

Expand Down
2 changes: 2 additions & 0 deletions src/poli/objective_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ def __create_problem_from_repository(
evaluation_budget: int = float("inf"),
force_isolation: bool = False,
observer: AbstractObserver = None,
python_executable_for_isolation: str = None,
**kwargs_for_factory,
) -> Problem:
"""Creates the objective function from the repository.
Expand Down Expand Up @@ -102,6 +103,7 @@ def __create_problem_from_repository(
num_workers=num_workers,
evaluation_budget=evaluation_budget,
force_isolation=force_isolation,
python_executable_for_isolation=python_executable_for_isolation,
**kwargs_for_factory,
)

Expand Down
4 changes: 4 additions & 0 deletions src/poli/objective_repository/deco_hop/register.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ def __init__(
parallelize: bool = False,
num_workers: int = None,
evaluation_budget: int = float("inf"),
python_executable_for_isolation: str = None,
):
super().__init__(
oracle_name="Deco Hop",
Expand All @@ -90,6 +91,7 @@ def __init__(
parallelize=parallelize,
num_workers=num_workers,
evaluation_budget=evaluation_budget,
python_executable_for_isolation=python_executable_for_isolation,
)

def get_black_box_info(self) -> BlackBoxInformation:
Expand Down Expand Up @@ -137,6 +139,7 @@ def create(
num_workers: int = None,
evaluation_budget: int = float("inf"),
force_isolation: bool = False,
python_executable_for_isolation: str = None,
) -> Problem:
"""
Creates a Decorator Hop problem.
Expand Down Expand Up @@ -187,6 +190,7 @@ def create(
parallelize=parallelize,
num_workers=num_workers,
evaluation_budget=evaluation_budget,
python_executable_for_isolation=python_executable_for_isolation,
)

# Initial example (from the TDC docs)
Expand Down
5 changes: 4 additions & 1 deletion src/poli/objective_repository/rasp/register.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ def __init__(
num_workers: int = None,
evaluation_budget: int = float("inf"),
force_isolation: bool = False,
python_executable_for_isolation: str | Path = None,
):
"""
Initialize the RaSP Register object.
Expand Down Expand Up @@ -172,7 +173,7 @@ def __init__(
self.penalize_unfeasible_with = penalize_unfeasible_with
self.device = device
self.inner_function = get_inner_function(
isolated_function_name="rasp__isolated",
python_executable_for_isolation=python_executable_for_isolation,
class_name="RaspIsolatedLogic",
module_to_import="poli.objective_repository.rasp.isolated_function",
force_isolation=self.force_isolation,
Expand Down Expand Up @@ -249,6 +250,7 @@ def create(
num_workers: int = None,
evaluation_budget: int = float("inf"),
force_isolation: bool = False,
python_executable_for_isolation: str | Path = None,
) -> Problem:
"""
Creates a RaSP black box instance, alongside initial
Expand Down Expand Up @@ -336,6 +338,7 @@ def create(
num_workers=num_workers,
evaluation_budget=evaluation_budget,
force_isolation=force_isolation,
python_executable_for_isolation=python_executable_for_isolation,
)

# Constructing x0
Expand Down
31 changes: 31 additions & 0 deletions src/poli/tests/registry/test_force_isolation.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,34 @@ def test_force_isolation_on_tdc():
quiet=True,
)
assert (problem.black_box(problem.x0) == inner_f(problem.x0)).all()


def test_force_isolation_on_tdc_using_python_executable():
from poli.objective_factory import create

problem = create(
name="deco_hop",
python_executable_for_isolation="/Users/sjt972/anaconda3/envs/poli__tdc/bin/python",
)

print(problem.black_box(problem.x0))


def test_force_isolation_on_rasp_using_python_executable():
from pathlib import Path

from poli.objective_factory import create

problem = create(
name="rasp",
python_executable_for_isolation="/Users/sjt972/anaconda3/envs/poli__rasp/bin/python",
wildtype_pdb_path=Path(__file__).parent.parent
/ "static_files_for_tests"
/ "3ned.pdb",
)

print(problem.black_box(problem.x0))


if __name__ == "__main__":
test_force_isolation_on_rasp_using_python_executable()

0 comments on commit 7ff4f34

Please sign in to comment.