Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prototypes a new isolation interface #266

Draft
wants to merge 7 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 0 additions & 32 deletions .github/workflows/python-tox-testing-including-conda-on-master.yml

This file was deleted.

1,224 changes: 1,224 additions & 0 deletions examples/protein_stability_and_sasa/running_foldx/101m_Repair.pdb

Large diffs are not rendered by default.

14 changes: 14 additions & 0 deletions examples/protein_stability_and_sasa/running_foldx/running_foldx.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from pathlib import Path

from poli.repository import FoldXStabilityAndSASAProblemFactory

if __name__ == "__main__":
pdb_file = Path(__file__).parent / "101m_Repair.pdb"
problem = FoldXStabilityAndSASAProblemFactory().create(
wildtype_pdb_path=pdb_file,
)
f, x0 = problem.black_box, problem.x0

print(f)
print(x0)
print(f(x0))
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
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
bioinformatics. Bioinformatics, 25, 1422-1423
"""

from __future__ import annotations

from pathlib import Path
from typing import List, Union

Expand Down Expand Up @@ -75,6 +77,7 @@ def __init__(
num_workers: int = None,
evaluation_budget: int = float("inf"),
force_isolation: bool = False,
python_executable_for_isolation: str | None = None,
):
super().__init__(
batch_size=batch_size,
Expand All @@ -88,12 +91,13 @@ def __init__(
self.eager_repair = eager_repair
self.verbose = verbose
self.force_isolation = force_isolation
self.python_executable_for_isolation = python_executable_for_isolation
if not (Path.home() / "foldx" / "foldx").exists():
raise FoldXNotFoundException(
"FoldX wasn't found in ~/foldx/foldx. Please install it."
)
inner_function = get_inner_function(
isolated_function_name="foldx_stability_and_sasa__isolated",
python_executable_for_isolation=python_executable_for_isolation,
class_name="FoldXStabilitityAndSASAIsolatedLogic",
module_to_import="poli.objective_repository.foldx_stability_and_sasa.isolated_function",
wildtype_pdb_path=wildtype_pdb_path,
Expand Down Expand Up @@ -126,7 +130,7 @@ def _black_box(self, x: np.ndarray, context: None) -> np.ndarray:
The computed stability and SASA score(s) as a numpy array.
"""
inner_function = get_inner_function(
isolated_function_name="foldx_stability_and_sasa__isolated",
python_executable_for_isolation=self.python_executable_for_isolation,
class_name="FoldXStabilitityAndSASAIsolatedLogic",
module_to_import="poli.objective_repository.foldx_stability_and_sasa.isolated_function",
quiet=True,
Expand Down Expand Up @@ -177,6 +181,7 @@ def create(
num_workers: int = None,
evaluation_budget: int = float("inf"),
force_isolation: bool = False,
python_executable_for_isolation: str | None = None,
) -> Problem:
"""
Create a FoldXSASABlackBox object and compute the initial values of wildtypes.
Expand Down Expand Up @@ -254,6 +259,7 @@ def create(
num_workers=num_workers,
evaluation_budget=evaluation_budget,
force_isolation=force_isolation,
python_executable_for_isolation=python_executable_for_isolation,
)

# We need to compute the initial values of all wildtypes
Expand Down
Loading