Skip to content

Commit

Permalink
enforce predetermined BiosimulatorVersion use throughout and fix tests
Browse files Browse the repository at this point in the history
  • Loading branch information
jcschaff committed Jan 31, 2025
1 parent b7602b2 commit 0d2cff5
Show file tree
Hide file tree
Showing 22 changed files with 249 additions and 200 deletions.
23 changes: 18 additions & 5 deletions biosim_server/api/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,19 +142,32 @@ async def start_verify_omex(
omex_file: OmexFile = await get_cached_omex_file_from_upload(uploaded_file=uploaded_file)

# ---- create workflow input ---- #
simulator_specs: list[BiosimSimulatorSpec] = []
simulator_versions: list[BiosimulatorVersion] = []
biosim_service = get_biosim_service()
assert biosim_service is not None
all_simulator_versions = await biosim_service.get_simulator_versions()
for simulator in simulators:
simulator_version: Optional[BiosimulatorVersion] = None
if ":" in simulator:
name, version = simulator.split(":")
simulator_specs.append(BiosimSimulatorSpec(simulator=name, version=version))
for sv in all_simulator_versions:
if sv.id == name and sv.version == version:
simulator_version = sv
break
else:
simulator_specs.append(BiosimSimulatorSpec(simulator=simulator, version=None))
omex_file = OmexFile(omex_gcs_path=omex_file.omex_gcs_path, uploaded_filename="BIOMD0000000010_tellurium_Negative_feedback_and_ultrasen.omex", file_hash_md5="hash", file_size=100, bucket_name="bucket")
for sv in all_simulator_versions:
if sv.id == simulator:
simulator_version = sv # don't break, we want the last one in the list
if simulator_version is not None:
simulator_versions.append(simulator_version)
else:
raise HTTPException(status_code=400, detail=f"Simulator {simulator} not found.")

workflow_id = f"{workflow_id_prefix}{uuid.uuid4()}"
omex_verify_workflow_input = OmexVerifyWorkflowInput(
omex_file=omex_file,
user_description=user_description,
requested_simulators=simulator_specs,
requested_simulators=simulator_versions,
include_outputs=include_outputs,
rel_tol=rel_tol,
abs_tol_min=abs_tol_min,
Expand Down
2 changes: 1 addition & 1 deletion biosim_server/common/biosim1_client/biosim_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ async def get_sim_run(self, simulation_run_id: str) -> BiosimSimulationRun:
pass

@abstractmethod
async def run_biosim_sim(self, local_omex_path: str, omex_name: str, simulator_spec: BiosimSimulatorSpec) -> BiosimSimulationRun:
async def run_biosim_sim(self, local_omex_path: str, omex_name: str, simulator_version: BiosimulatorVersion) -> BiosimSimulationRun:
pass

@abstractmethod
Expand Down
47 changes: 30 additions & 17 deletions biosim_server/common/biosim1_client/biosim_service_rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,48 +34,61 @@ async def get_sim_run(self, simulation_run_id: str) -> BiosimSimulationRun:
resp.raise_for_status()
res = await resp.json()

sim_run = BiosimSimulationRun(id=res["id"], name=res["name"], simulator=res['simulator'],
simulatorVersion=res['simulatorVersion'], simulatorDigest=res['simulatorDigest'],
status=BiosimSimulationRunStatus(res['status']))

assert res["id"] == simulation_run_id

sim_id: str = res['simulator']
sim_ver: str = res['simulatorVersion']
sim_digest: str = res['simulatorDigest']
sim_status = BiosimSimulationRunStatus(res['status'])
simulator_version = await self._get_simulator_version(sim_id=sim_id, sim_ver=sim_ver, sim_digest=sim_digest)
sim_run = BiosimSimulationRun(id=res["id"], name=res["name"], simulator_version=simulator_version, status=sim_status)
return sim_run


@override
async def run_biosim_sim(self, local_omex_path: str, omex_name: str,
simulator_spec: BiosimSimulatorSpec) -> BiosimSimulationRun:
simulator_version: BiosimulatorVersion) -> BiosimSimulationRun:
logger.info(f"Submitting simulation for {omex_name} with local path {local_omex_path} with simulator {simulator_version.id}")
This function runs the project on biosimulations.
"""
api_base_url = get_settings().biosimulations_api_base_url

simulation_run_request = BiosimSimulationRunApiRequest(name=omex_name, simulator=simulator_spec.simulator,
simulatorVersion=simulator_spec.version or "latest",
maxTime=600, )
simulation_run_request = BiosimSimulationRunApiRequest(name=omex_name, simulator=simulator_version.id,
simulatorVersion=simulator_version.version, maxTime=600)

print(local_omex_path)
async with aiohttp.ClientSession() as session:
with Path(local_omex_path).open('rb') as f:
data = FormData()
data.add_field(name='file', value=f, filename='omex.omex', content_type='multipart/form-data')
data.add_field(name='simulationRun', value=simulation_run_request.model_dump_json(),
content_type='multipart/form-data')

api_base_url = get_settings().biosimulations_api_base_url
async with session.post(url=api_base_url + '/runs', data=data) as resp:
resp.raise_for_status()
res = await resp.json()

if simulator_spec.version is None:
simulator_spec.version = res['simulatorVersion']
sim_id: str = res['simulator']
sim_ver: str = res['simulatorVersion']
sim_digest: str = res['simulatorDigest']
assert simulator_version.version == sim_ver
assert simulator_version.id == sim_id
assert simulator_version.image.digest == sim_digest

sim_run = BiosimSimulationRun(id=res["id"], name=res["name"], simulator=res['simulator'],
simulatorVersion=res['simulatorVersion'], simulatorDigest=res['simulatorDigest'],
status=BiosimSimulationRunStatus(res['status']))
sim_status = BiosimSimulationRunStatus(res['status'])
simulator_version = await self._get_simulator_version(sim_id=sim_id, sim_ver=sim_ver, sim_digest=sim_digest)
sim_run = BiosimSimulationRun(id=res["id"], name=res["name"], simulator_version=simulator_version, status=sim_status)

# logger.info("Submitted " + omex_name + " on biosimulations with simulation id: " + sim_run.id)
# logger.info("View:", api_base_url + "/runs/" + sim_run.id)
return sim_run


async def _get_simulator_version(self, sim_id: str, sim_ver: str, sim_digest: str) -> BiosimulatorVersion:
simulator_version: BiosimulatorVersion
for simulator_version in await self.get_simulator_versions():
if simulator_version.id == sim_id and simulator_version.version == sim_ver and simulator_version.image.digest == sim_digest:
return simulator_version
raise Exception(f"Simulator version not found for simulator id: {sim_id}, version: {sim_ver}, digest: {sim_digest}")


@override
async def get_hdf5_metadata(self, simulation_run_id: str) -> HDF5File:
api_base_url = get_settings().simdata_api_base_url
Expand Down
9 changes: 4 additions & 5 deletions biosim_server/workflows/simulate/biosim_activities.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,15 @@ async def get_sim_run(get_sim_run_input: GetSimRunInput) -> BiosimSimulationRun:
return BiosimSimulationRun(
id=get_sim_run_input.biosim_run_id,
name="",
simulator="",
simulatorVersion="",
status=BiosimSimulationRunStatus.RUN_ID_NOT_FOUND
simulator_version=BiosimulatorVersion(id="", name="", version="", image=DockerContainerInfo(url="", digest="")),
status=BiosimSimulationRunStatus.RUN_ID_NOT_FOUND,
)
raise e


class SubmitBiosimSimInput(BaseModel):
omex_file: OmexFile
simulator_spec: BiosimSimulatorSpec
simulator_version: BiosimulatorVersion


@activity.defn
Expand All @@ -57,7 +56,7 @@ async def submit_biosim_sim(input: SubmitBiosimSimInput) -> BiosimSimulationRun:
(_, local_omex_path) = await file_service.download_file(gcs_path=input.omex_file.omex_gcs_path)
activity.logger.info(f"Downloaded OMEX file from gcs_path {input.omex_file.omex_gcs_path} to local path {local_omex_path}")
simulation_run = await biosim_service.run_biosim_sim(local_omex_path=local_omex_path, omex_name=input.omex_file.uploaded_filename,
simulator_spec=input.simulator_spec)
simulator_version=input.simulator_version)
os.remove(local_omex_path)
activity.logger.info(f"Deleted local OMEX file at {local_omex_path}")
return simulation_run
Expand Down
12 changes: 6 additions & 6 deletions biosim_server/workflows/simulate/omex_sim_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

class OmexSimWorkflowInput(BaseModel):
omex_file: OmexFile
simulator_spec: BiosimSimulatorSpec
simulator_version: BiosimulatorVersion


class OmexSimWorkflowStatus(StrEnum):
Expand Down Expand Up @@ -54,11 +54,11 @@ def get_omex_sim_workflow_run(self) -> OmexSimWorkflowOutput:
async def run(self, sim_input: OmexSimWorkflowInput) -> OmexSimWorkflowOutput:
self.sim_output.workflow_id = workflow.info().workflow_id
workflow.logger.setLevel(level=logging.DEBUG)
workflow.logger.info(f"Child workflow started for {sim_input.simulator_spec.simulator}.")
workflow.logger.info(f"Child workflow started for {sim_input.simulator_version.id}.")

workflow.logger.info(f"submitting job for simulator {sim_input.simulator_spec.simulator}.")
workflow.logger.info(f"submitting job for simulator {sim_input.simulator_version.id}.")
submit_biosim_input = SubmitBiosimSimInput(omex_file=sim_input.omex_file,
simulator_spec=sim_input.simulator_spec)
simulator_version=sim_input.simulator_version)
self.sim_output.biosim_run = await workflow.execute_activity(
submit_biosim_sim,
args=[submit_biosim_input],
Expand All @@ -67,7 +67,7 @@ async def run(self, sim_input: OmexSimWorkflowInput) -> OmexSimWorkflowOutput:
)

workflow.logger.info(
f"Job {self.sim_output.biosim_run.id} for {sim_input.simulator_spec.simulator}, "
f"Job {self.sim_output.biosim_run.id} for {sim_input.simulator_version.id}, "
f"status is {self.sim_output.biosim_run.status}.")

while self.sim_output.biosim_run is not None and self.sim_output.biosim_run.status not in [
Expand All @@ -82,7 +82,7 @@ async def run(self, sim_input: OmexSimWorkflowInput) -> OmexSimWorkflowOutput:
)

workflow.logger.info(
f"Job {self.sim_output.biosim_run.id} for {sim_input.simulator_spec.simulator}, "
f"Job {self.sim_output.biosim_run.id} for {sim_input.simulator_version.id}, "
f"status is {self.sim_output.biosim_run.status}.")

if self.sim_output.biosim_run.status == BiosimSimulationRunStatus.FAILED:
Expand Down
12 changes: 10 additions & 2 deletions biosim_server/workflows/simulate/trigger_sim_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,23 @@
from biosim_server.common.biosim1_client import BiosimService
from biosim_server.common.database.data_models import OmexFile
from biosim_server.common.temporal import pydantic_data_converter
from biosim_server.dependencies import get_biosim_service, init_standalone, shutdown_standalone
from biosim_server.workflows.simulate import OmexSimWorkflow, OmexSimWorkflowInput, OmexSimWorkflowOutput


async def start_workflow() -> None:
await init_standalone()

client = await Client.connect("localhost:7233", data_converter=pydantic_data_converter)
omex_file = OmexFile(file_hash_md5="hash", uploaded_filename="BIOMD0000000010_tellurium_Negative_feedback_and_ultrasen.omex", file_size=100, omex_gcs_path="path/to/hash.omex", bucket_name="bucket")
omex_sim_workflow_input = OmexSimWorkflowInput(omex_file=omex_file, simulator_spec=BiosimSimulatorSpec(simulator="vcell"))
biosim_service: BiosimService | None = get_biosim_service()
assert biosim_service is not None
simulator_version = (await biosim_service.get_simulator_versions())[0]
omex_sim_workflow_input = OmexSimWorkflowInput(omex_file=omex_file, simulator_version=simulator_version)
handle = await client.start_workflow(
OmexSimWorkflow.run,
args=[OmexSimWorkflowInput(omex_file=omex_sim_workflow_input.omex_file,
simulator_spec=omex_sim_workflow_input.simulator_spec)],
simulator_version=omex_sim_workflow_input.simulator_version)],
task_queue="verification_tasks",
id=uuid.uuid4().hex,
)
Expand All @@ -25,6 +31,8 @@ async def start_workflow() -> None:
query_result: OmexSimWorkflowOutput = await handle.query(OmexSimWorkflow.get_omex_sim_workflow_run, args=[])
print(f"Workflow status: {query_result.workflow_status}")

await shutdown_standalone()



if __name__ == "__main__":
Expand Down
4 changes: 2 additions & 2 deletions biosim_server/workflows/verify/activities.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ async def generate_statistics(gen_stats_input: GenerateStatisticsInput) -> Gener
labels_i = sim_run_info_i.hdf5_file.datasets[dataset_name].sedml_labels
data_i: Hdf5DataValues = results_i[dataset_name]
array_i: NDArray[np.float64] = np.array(data_i.values, dtype=np.float64).reshape(data_i.shape)
simulation_version_i = f"{sim_run_info_i.biosim_sim_run.simulator}:{sim_run_info_i.biosim_sim_run.simulatorVersion}"
simulation_version_i = f"{sim_run_info_i.biosim_sim_run.simulator_version.id}:{sim_run_info_i.biosim_sim_run.simulator_version.version}"

ds_comparison_i: list[ComparisonStatistics] = [] # holds comparisons [i,:] for this dataset

Expand All @@ -93,7 +93,7 @@ async def generate_statistics(gen_stats_input: GenerateStatisticsInput) -> Gener
run_id_j = sim_run_info_j.biosim_sim_run.id
results_j: dict[str, Hdf5DataValues] = datasets[run_id_j]
labels_j = sim_run_info_j.hdf5_file.datasets[dataset_name].sedml_labels
simulation_version_j = f"{sim_run_info_j.biosim_sim_run.simulator}:{sim_run_info_j.biosim_sim_run.simulatorVersion}"
simulation_version_j = f"{sim_run_info_j.biosim_sim_run.simulator_version.id}:{sim_run_info_j.biosim_sim_run.simulator_version.version}"

# create a comparison statistics object with default values, add data or error message if needed
stats_i_j = ComparisonStatistics(simulator_version_i=simulation_version_i,
Expand Down
10 changes: 5 additions & 5 deletions biosim_server/workflows/verify/omex_verify_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def __str__(self) -> str:
class OmexVerifyWorkflowInput(BaseModel):
omex_file: OmexFile
user_description: str
requested_simulators: list[BiosimSimulatorSpec]
requested_simulators: list[BiosimulatorVersion]
include_outputs: bool
rel_tol: float
abs_tol_min: float
Expand All @@ -41,7 +41,7 @@ class OmexVerifyWorkflowOutput(BaseModel):
workflow_input: OmexVerifyWorkflowInput
workflow_status: OmexVerifyWorkflowStatus
timestamp: str
actual_simulators: Optional[list[BiosimSimulatorSpec]] = None
actual_simulators: Optional[list[BiosimulatorVersion]] = None
workflow_run_id: Optional[str] = None
workflow_results: Optional[GenerateStatisticsOutput] = None

Expand Down Expand Up @@ -77,9 +77,9 @@ async def run(self, verify_input: OmexVerifyWorkflowInput) -> OmexVerifyWorkflow
for simulator_spec in verify_input.requested_simulators:
child_workflows.append(
workflow.start_child_workflow(OmexSimWorkflow.run, # type: ignore
args=[OmexSimWorkflowInput(omex_file=verify_input.omex_file, simulator_spec=simulator_spec)],
result_type=OmexSimWorkflowOutput,
task_queue="verification_tasks", execution_timeout=timedelta(minutes=10), ))
args=[OmexSimWorkflowInput(omex_file=verify_input.omex_file, simulator_version=simulator_spec)],
result_type=OmexSimWorkflowOutput,
task_queue="verification_tasks", execution_timeout=timedelta(minutes=10), ))

workflow.logger.info(f"waiting for {len(child_workflows)} child simulation workflows.")
# Wait for all child workflows to complete
Expand Down
2 changes: 1 addition & 1 deletion biosim_server/workflows/verify/runs_verify_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class RunsVerifyWorkflowOutput(BaseModel):
workflow_status: RunsVerifyWorkflowStatus
timestamp: str
workflow_error: Optional[str] = None
actual_simulators: Optional[list[BiosimSimulatorSpec]] = None
actual_simulators: Optional[list[BiosimulatorVersion]] = None
workflow_run_id: Optional[str] = None
workflow_results: Optional[GenerateStatisticsOutput] = None

Expand Down
14 changes: 11 additions & 3 deletions biosim_server/workflows/verify/trigger_verify_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,22 @@


async def start_workflow() -> None:
client = await Client.connect("localhost:7233", data_converter=pydantic_data_converter)
await init_standalone()

client = get_temporal_client()
assert client is not None
omex_file = OmexFile(omex_gcs_path="path/to/model.omex", uploaded_filename="BIOMD0000000010_tellurium_Negative_feedback_and_ultrasen.omex", file_hash_md5="hash", file_size=100, bucket_name="bucket")

biosim_service = get_biosim_service()
assert biosim_service is not None
simulator_versions: list[BiosimulatorVersion] = await biosim_service.get_simulator_versions()
workflow_id = uuid.uuid4().hex
handle = await client.start_workflow(
OmexVerifyWorkflow.run,
args=[OmexVerifyWorkflowInput(
omex_file=omex_file,
user_description="description",
requested_simulators=[BiosimSimulatorSpec(simulator="vcell", version="latest"),
BiosimSimulatorSpec(simulator="copasi", version="latest")],
requested_simulators=[simulator_versions[0], simulator_versions[1]],
include_outputs=True,
rel_tol=1e-4,
abs_tol_min=1e-3,
Expand All @@ -28,6 +34,8 @@ async def start_workflow() -> None:
)
print(f"Started workflow with ID: {handle.id}")

await shutdown_standalone()


if __name__ == "__main__":
asyncio.run(start_workflow())
4 changes: 2 additions & 2 deletions tests/api/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ async def test_omex_verify_and_get_output_mockGCS(omex_verify_workflow_input: Om
assert omex_verify_workflow_input.observables is not None
query_params: dict[str, float | str | list[str]] = {
"workflow_id_prefix": "verification-",
"simulators": [sim.simulator for sim in omex_verify_workflow_input.requested_simulators],
"simulators": [f"{sim.id}:{sim.version}" for sim in omex_verify_workflow_input.requested_simulators],
"include_outputs": omex_verify_workflow_input.include_outputs,
"user_description": omex_verify_workflow_input.user_description,
"observables": omex_verify_workflow_input.observables,
Expand Down Expand Up @@ -91,7 +91,7 @@ async def test_omex_verify_and_get_output_GCS(omex_verify_workflow_input: OmexVe
assert omex_verify_workflow_input.observables is not None
query_params: dict[str, float | str | list[str]] = {
"workflow_id_prefix": "verification-",
"simulators": [sim.simulator for sim in omex_verify_workflow_input.requested_simulators],
"simulators": [f"{sim.id}:{sim.version}" for sim in omex_verify_workflow_input.requested_simulators],
"include_outputs": omex_verify_workflow_input.include_outputs,
"user_description": omex_verify_workflow_input.user_description,
"observables": omex_verify_workflow_input.observables,
Expand Down
Loading

0 comments on commit 0d2cff5

Please sign in to comment.