Skip to content
This repository has been archived by the owner on Mar 6, 2024. It is now read-only.

Commit

Permalink
updated process types and added biosimulator_builder instance
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexPatrie committed Mar 1, 2024
1 parent a3508dd commit fbdba63
Show file tree
Hide file tree
Showing 13 changed files with 2,006 additions and 150 deletions.
27 changes: 27 additions & 0 deletions biosimulator_processes/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from builder import ProcessTypes


# Define a list of processes to attempt to import and register
PROCESSES_TO_REGISTER = [
('cobra', 'cobra_process.CobraProcess'),
('copasi', 'copasi_process.CopasiProcess'),
('smoldyn', 'smoldyn_process.SmoldynProcess'),
('tellurium', 'tellurium_process.TelluriumProcess'),
]

CORE = ProcessTypes()

for process_name, process_path in PROCESSES_TO_REGISTER:
module_name, class_name = process_path.rsplit('.', 1)
try:
# Dynamically import the module
process_module = __import__(
f'biosimulator_processes.processes.{module_name}', fromlist=[class_name])
# Get the class from the module
process_class = getattr(process_module, class_name)

# Register the process
CORE.process_registry.register(class_name, process_class)
print(f"{class_name} registered successfully.")
except ImportError as e:
print(f"{class_name} not available. Error: {e}")
9 changes: 9 additions & 0 deletions biosimulator_processes/biosimulator_builder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from typing import *
from builder import Builder
from biosimulator_processes import CORE


class BiosimulatorBuilder(Builder):
def __init__(self, schema: Dict, tree: Dict, filepath: str):
super().__init__(schema=schema, tree=tree, file_path=filepath, core=CORE)

74 changes: 74 additions & 0 deletions biosimulator_processes/process_types.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,77 @@
from pydantic import BaseModel
from typing import *
from abc import ABC, abstractmethod


class ModelChange(BaseModel):
config: Union[Dict[str, Dict[str, Dict[str, Union[float, str]]]], Dict[str, Dict[str, Union[Dict[str, float], str]]]]


class ModelChanges(BaseModel):
species_changes: ModelChange = None
global_parameter_changes: ModelChange = None
reaction_changes: ModelChange = None


class ModelSource(ABC):
value: str

def __init__(self):
super().__init__()

@abstractmethod
def check_value(self):
pass


class BiomodelId(ModelSource, BaseModel):
value: str

def __init__(self):
super().__init__()
self.check_value()

def check_value(self):
assert '/' not in self.value


class ModelFilepath(BaseModel):
value: str

def __init__(self):
super().__init__()
self.check_value()

def check_value(self):
assert '/' in self.value


class SedModel(BaseModel):
model_id: Optional[str] = None
model_source: str
model_language: str = 'sbml'
model_name: str = 'composite_process_model'
model_changes: ModelChanges


changes = {
'species_changes': {
'A': {
'initial_concent': 24.2,
'b': 'sbml'
}
}
}

r = {
'reaction_name': {
'parameters': {
'reaction_parameter_name': 23.2 # (new reaction_parameter_name value) <-- this is done with set_reaction_parameters(name="(REACTION_NAME).REACTION_NAME_PARAM", value=VALUE)
},
'reaction_scheme': 'maybe[string]' # <-- this is done like set_reaction(name = 'R1', scheme = 'S + E + F = ES')
}
}

CHANGES_SCHEMA = """The following types have been derived from both SEDML L1v4 and basico itself.
BASICO_MODEL_CHANGES_TYPE = {
Expand Down
27 changes: 0 additions & 27 deletions biosimulator_processes/processes/__init__.py
Original file line number Diff line number Diff line change
@@ -1,28 +1 @@
from builder import ProcessTypes, Builder


# Define a list of processes to attempt to import and register
PROCESSES_TO_REGISTER = [
('cobra', 'cobra_process.CobraProcess'),
('copasi', 'copasi_process.CopasiProcess'),
('smoldyn', 'smoldyn_process.SmoldynProcess'),
('tellurium', 'tellurium_process.TelluriumProcess'),
]

CORE = ProcessTypes()
BIOSIMULATOR_PROCESS_BUILDER = Builder(core=CORE)

for process_name, process_path in PROCESSES_TO_REGISTER:
module_name, class_name = process_path.rsplit('.', 1)
try:
# Dynamically import the module
process_module = __import__(
f'biosimulator_processes.processes.{module_name}', fromlist=[class_name])
# Get the class from the module
process_class = getattr(process_module, class_name)

# Register the process
BIOSIMULATOR_PROCESS_BUILDER.register_process(class_name, process_class)
print(f"{class_name} registered successfully.")
except ImportError as e:
print(f"{class_name} not available. Error: {e}")
6 changes: 3 additions & 3 deletions biosimulator_processes/processes/cobra_process.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import cobra.io
from cobra.io import read_sbml_model
from process_bigraph import Process, Composite, pf, pp
from biosimulator_processes.processes import BIOSIMULATOR_PROCESS_BUILDER
from biosimulator_processes import CORE


def check_sbml(state, schema, core):
Expand All @@ -33,8 +33,8 @@ def check_sbml(state, schema, core):
}

# register new types
BIOSIMULATOR_PROCESS_BUILDER.register_type('bounds', bounds_type)
BIOSIMULATOR_PROCESS_BUILDER.register_type('sbml', sbml_type)
CORE.type_registry.register('bounds', bounds_type)
CORE.type_registry.register('sbml', sbml_type)


class CobraProcess(Process):
Expand Down
Empty file added containers/__init__.py
Empty file.
124 changes: 124 additions & 0 deletions containers/exec.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import toml
import subprocess
from typing import *
from docker import DockerClient
from biosimulator_processes.containers.io import write_dockerfile


CLIENT = DockerClient(base_url='unix:///var/run/docker.sock')


def get_simulators(sims: List[str]):
"""Get specified simulator installation information including required dependencies
and format as a dictionary. This dictionary is used as configuration for the dynamic
creation of containers.
"""
with open('biosimulator_processes/poetry.lock') as file:
lock_data = toml.load(file)

simulators = []
for sim in sims:
for package in lock_data.get('package', []):
if package['name'] == sim:
simulators.append({
"name": package['name'],
"version": package['version'],
"deps": package.get('dependencies', {}),
"extras": package.get('extras', {})
})
break

return {"simulators": simulators}


def generate_dockerfile_contents(config: dict) -> str:
"""Generate contents to be written out to a Dockerfile derived from the base Dockerfile.
Args:
config:`dict`: a dictionary specifying simulator versions and their dependencies. The schema for
this dictionary should be (for example):
{
"simulators": [
{
"name": "tellurium",
"version": "2.2.10",
"deps": {
"antimony": ">=2.12.0",
"appdirs": ">=1.4.3",
"ipykernel": ">=4.6.1",
"ipython": "*",
"jinja2": ">=3.0.0",
"jupyter-client": ">=5.1.0",
"jupyter-core": ">=4.3.0",
"libroadrunner": ">=2.1",
"matplotlib": ">=2.0.2",
"numpy": ">=1.23",
"pandas": ">=0.20.2",
"phrasedml": {
"version": ">=1.0.9",
"markers": "platform_machine != \"arm64\""
},
"plotly": ">=2.0.12",
"pytest": "*",
"python-libcombine": ">=0.2.2",
"python-libnuml": ">=1.0.0",
"python-libsbml": ">=5.18.0",
"python-libsedml": ">=2.0.17",
"requests": "*",
"rrplugins": {
"version": ">=2.1",
"markers": "platform_system == \"Windows\""
},
"scipy": ">=1.5.1"
}
},
"""

base_path = 'biosimulator_processes/Dockerfile-base'
# TODO: automate mapping simulators to poetry.lock: ie: simulators arg that searches the lock file
with open(base_path, 'r') as fp:
dockerfile_contents = fp.read()
for simulator in config['simulators']:
# copy the appropriate process files
name = simulator['name']
deps = simulator.get('deps', {})
for dep, version in deps.items():
if not version == "*":
complete_version = f'{dep}{version}'
else:
complete_version = f'{dep}'
# dockerfile_contents += f"RUN poetry add {complete_version}\n"
dockerfile_contents += f"RUN pip install {complete_version}\n"
if name == 'copasi-basico':
name = 'copasi'
dockerfile_contents += f"COPY ./biosimulator_processes/{simulator['name']}_process.py /app/{simulator['name']}_process.py\n"
# common entrypoint used by all processes
dockerfile_contents += 'ENTRYPOINT ["jupyter", "lab", "--ip=0.0.0.0", "--port=8888", "--no-browser", "--allow-root"]'
return dockerfile_contents


def build_image(name: str, p: str = '.'):
return CLIENT.images.build(
path=p,
tag=name)


def execute_container(img_name: str = 'composition'):
img = build_image(img_name)
CLIENT.containers.run(img.id)


def run(simulators: List[str]):
config = get_simulators(simulators)
dockerfile_contents = generate_dockerfile_contents(config)
dockerfile_path = 'Dockerfile'
write_dockerfile(dockerfile_contents, out_path=dockerfile_path)
# return execute_container()


def exec_container(name: str):
build_command = f"docker buildx build --platform linux/amd64 {name}_env ."
subprocess.run(build_command.split())
run_command = f"docker run --platform linux/amd64 -it -p 8888:8888 {name}_env"
subprocess.run(run_command.split())
24 changes: 24 additions & 0 deletions containers/io.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import json


def write_dockerfile(dockerfile_contents: str, out_path: str):
"""
Args:
dockerfile_contents:`str`: content to write to Dockerfile
out_path:`str`: path to save the Dockerfile
"""
with open(out_path, 'w') as file:
file.write(dockerfile_contents)


def write_config():
config = {}
with open('biosimulator_processes/simulators.json', 'w') as fp:
json.dump(config, fp, indent=4)


def load_config():
# Load simulator configuration
with open('biosimulator_processes/simulators.json', 'r') as file:
return json.load(file)
Loading

0 comments on commit fbdba63

Please sign in to comment.