Skip to content

Commit 61a6b13

Browse files
committed
update to workflow
1 parent 7538eeb commit 61a6b13

File tree

9 files changed

+2548
-123
lines changed

9 files changed

+2548
-123
lines changed

examples/EDITED_combined_fvsolver_data.ipynb

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,60 @@
1313
],
1414
"id": "29a6cffd5df3cb63"
1515
},
16+
{
17+
"metadata": {
18+
"jupyter": {
19+
"is_executing": true
20+
},
21+
"ExecuteTime": {
22+
"end_time": "2025-01-31T19:58:13.698322Z",
23+
"start_time": "2025-01-31T19:57:44.757366Z"
24+
}
25+
},
26+
"cell_type": "code",
27+
"source": [
28+
"from pathlib import Path\n",
29+
"import tempfile\n",
30+
"import shutil\n",
31+
"import os\n",
32+
"\n",
33+
"from pyvcell.data_model.simulation import SpatialSimulation\n",
34+
"from pyvcell.data_model.spatial_model import SpatialModel\n",
35+
"\n",
36+
"\n",
37+
"def prepare_input_dir(solver_input_dir: Path, temp_dir_prefix: str = None) -> Path:\n",
38+
" # prepare input dir\n",
39+
" temp_solver_dir = Path(tempfile.mkdtemp(prefix=temp_dir_prefix or 'pyvcell_test_data_'))\n",
40+
" for file in solver_input_dir.iterdir():\n",
41+
" shutil.copy(file, temp_solver_dir)\n",
42+
"\n",
43+
" return temp_solver_dir\n",
44+
"\n",
45+
"\n",
46+
"# define input dir, output_dir and spatial model\n",
47+
"solver_input_dir = Path(os.getcwd()) / \"solver_input\"\n",
48+
"solver_output_dir = Path(os.getcwd()) / \"test_output\"\n",
49+
"model_fp = os.path.join(solver_input_dir, \"all.xml\")\n",
50+
"\n",
51+
"# define editable spatial model and simulation instances\n",
52+
"model = SpatialModel(filepath=model_fp)\n",
53+
"simulation = SpatialSimulation(model=model, input_dir_path=solver_input_dir, output_dir_path=solver_output_dir)\n",
54+
"\n",
55+
"# TODO: replace below with simulation.get_input_files()\n",
56+
"temp_solver_dir = prepare_input_dir(solver_input_dir)\n",
57+
"functions_file = temp_solver_dir / \"SimID_946368938_0_.functions\"\n",
58+
"fv_input_file = temp_solver_dir / \"SimID_946368938_0_.fvinput\"\n",
59+
"vcg_file = temp_solver_dir / \"SimID_946368938_0_.vcg\"\n",
60+
"\n",
61+
"job_id = 0\n",
62+
"sim_id = 946368938\n",
63+
"result = simulation.run(fv_input_file=fv_input_file, vcg_input_file=vcg_file, solver_output_dir=solver_output_dir, sim_id=sim_id, job_id=job_id)\n",
64+
"\n"
65+
],
66+
"id": "4dd5c4de89c0402c",
67+
"outputs": [],
68+
"execution_count": null
69+
},
1670
{
1771
"metadata": {
1872
"ExecuteTime": {
@@ -40,13 +94,7 @@
4094
"\n",
4195
"# -- move all files from solver_input directory to a temporary directory for solving --\n",
4296
"\n",
43-
"def prepare_input_dir(solver_input_dir: Path, temp_dir_prefix: str = None) -> Path:\n",
44-
" # prepare input dir\n",
45-
" temp_solver_dir = Path(tempfile.mkdtemp(prefix=temp_dir_prefix or 'pyvcell_test_data_'))\n",
46-
" for file in solver_input_dir.iterdir():\n",
47-
" shutil.copy(file, temp_solver_dir)\n",
4897
"\n",
49-
" return temp_solver_dir\n",
5098
"\n",
5199
"\n",
52100
"def prepare_output_dir(solver_output_dir: Path, functions_file: Path) -> None:\n",

examples/solver_input/all.xml

Lines changed: 1078 additions & 0 deletions
Large diffs are not rendered by default.

examples/solver_input/all_edited.xml

Lines changed: 1078 additions & 0 deletions
Large diffs are not rendered by default.

poetry.lock

Lines changed: 139 additions & 115 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,7 @@ typing-extensions = "^4.12.2"
2828
urllib3 = "^2.3.0"
2929
requests = "^2.32.3"
3030
python-dateutil = "^2.9.0.post0"
31-
ipykernel = "^6.29.5"
32-
jupyterlab = "^4.3.4"
31+
notebook = "^7.3.2"
3332

3433
[tool.poetry.group.dev.dependencies]
3534
pytest = "^7.2.0"

pyvcell/data_model/__init__.py

Whitespace-only changes.

pyvcell/data_model/result.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import os
2+
import shutil
3+
import tempfile
4+
from pathlib import Path
5+
from typing import Dict
6+
7+
import matplotlib.pyplot as plt
8+
import numpy as np
9+
import zarr
10+
11+
from pyvcell.simdata.mesh import CartesianMesh
12+
from pyvcell.simdata.postprocessing import PostProcessing
13+
from pyvcell.simdata.simdata_models import PdeDataSet, DataFunctions
14+
from pyvcell.simdata.zarr_writer import write_zarr
15+
from pyvcell.solvers.fvsolver import solve as fvsolve
16+
17+
18+
class Result(object):
19+
def __init__(self, solver_output_dir: Path, sim_id: int, job_id: int):
20+
self.solver_output_dir = solver_output_dir
21+
self.zarr_dir = self.solver_output_dir / "zarr"
22+
self.sim_id = sim_id
23+
self.job_id = job_id
24+
25+
self.dataset = self.get_dataset()
26+
self.metadata = self.get_metadata()
27+
28+
def get_dataset(self, zarr_dir=None) -> zarr.Array | zarr.Group:
29+
return zarr.open(zarr_dir or self.zarr_dir, mode='r')
30+
31+
def get_metadata(self, dataset=None):
32+
ds = dataset or self.dataset
33+
return ds.attrs.asdict()['metadata']
34+
35+
def slice_dataset(self, dataset: zarr.Array | zarr.Group, time_index: int, channel_index: int, z_index: int):
36+
return dataset[time_index, channel_index, z_index, :, :]
37+
38+
def plot_slice(self, dataset: zarr.Array | zarr.Group, time_index: int, channel_index: int, z_index: int):
39+
data_slice = self.slice_dataset(dataset, time_index, channel_index, z_index)
40+
41+
metadata = self.get_metadata(dataset)
42+
t = metadata['times'][time_index]
43+
channel_label = metadata['channels'][channel_index]['label']
44+
channel_domain = metadata['channels'][channel_index]['domain_name']
45+
title = f"{channel_label} (in {channel_domain}) at t={t}, slice z={z_index}"
46+
47+
# Display the slice as an image
48+
plt.imshow(data_slice)
49+
plt.title(title)
50+
return plt.show()
51+
52+
def get_post_processing(self) -> PostProcessing:
53+
post_processing = PostProcessing(postprocessing_hdf5_path=self.solver_output_dir / f"SimID_{self.sim_id}_{self.job_id}_.hdf5")
54+
post_processing.read()
55+
return post_processing
56+
57+
def _to_zarr(self) -> None:
58+
pde_dataset = self._get_pde_dataset()
59+
data_functions = self._get_data_functions()
60+
mesh = self._get_mesh()
61+
return write_zarr(pde_dataset=pde_dataset, data_functions=data_functions, mesh=mesh, zarr_dir=self.zarr_dir)
62+
63+
def _get_pde_dataset(self) -> PdeDataSet:
64+
pde_dataset = PdeDataSet(base_dir=self.solver_output_dir, log_filename=f"SimID_{self.sim_id}_{self.job_id}_.log")
65+
pde_dataset.read()
66+
return pde_dataset
67+
68+
def _get_data_functions(self) -> DataFunctions:
69+
data_functions = DataFunctions(function_file=self.solver_output_dir / f"SimID_{self.sim_id}_{self.job_id}_.functions")
70+
data_functions.read()
71+
return data_functions
72+
73+
def _get_mesh(self) -> CartesianMesh:
74+
mesh = CartesianMesh(mesh_file=self.solver_output_dir / f"SimID_{self.sim_id}_{self.job_id}_.mesh")
75+
mesh.read()
76+
return mesh
77+
78+
79+
sim_id = 946368938
80+
job_id = 0

pyvcell/data_model/simulation.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import abc
2+
import os
3+
import shutil
4+
import tempfile
5+
from pathlib import Path
6+
from uuid import uuid4
7+
8+
import matplotlib.pyplot as plt
9+
import numpy as np
10+
import zarr
11+
12+
from pyvcell.data_model.result import Result
13+
from pyvcell.data_model.spatial_model import SpatialModel
14+
from pyvcell.simdata.mesh import CartesianMesh
15+
from pyvcell.simdata.postprocessing import PostProcessing
16+
from pyvcell.simdata.simdata_models import PdeDataSet, DataFunctions
17+
from pyvcell.simdata.zarr_writer import write_zarr
18+
from pyvcell.solvers.fvsolver import solve as fvsolve
19+
20+
21+
class Simulation(abc.ABC):
22+
@abc.abstractmethod
23+
def run_simulation(self):
24+
pass
25+
26+
27+
class SpatialSimulation(object):
28+
def __init__(self, model: SpatialModel, input_dir_path: Path, output_dir_path: Path):
29+
self.model = model
30+
self.input_dir_path = input_dir_path
31+
self.output_dir_path = output_dir_path
32+
33+
def get_input_files(self) -> tuple:
34+
model_path = self.model.filepath or Path("model.xml")
35+
sbml_fp = self.model.export(
36+
os.path.join(self.input_dir_path, model_path)
37+
)
38+
39+
# TODO: call request here and return input files
40+
# return functions_file, fv_input_file, vcg_file
41+
42+
def run(self, fv_input_file: Path, vcg_input_file: Path, solver_output_dir: Path, sim_id: int, job_id: int) -> Result:
43+
# prepare output dir/files
44+
# self._prepare_output_dir(solver_output_dir, functions_file)
45+
# run simulation
46+
try:
47+
ret_code = self._run_solver(fv_input_file, vcg_input_file, solver_output_dir)
48+
assert ret_code == 0
49+
50+
return Result(solver_output_dir=solver_output_dir, sim_id=sim_id, job_id=job_id)
51+
except AssertionError as e:
52+
raise e("The simulation did not finish successfully")
53+
54+
def _prepare_input_dir(self, solver_input_dir: Path, temp_dir_prefix: str = None) -> Path:
55+
# prepare input dir
56+
temp_solver_dir = Path(tempfile.mkdtemp(prefix=temp_dir_prefix or 'pyvcell_test_data_'))
57+
for file in solver_input_dir.iterdir():
58+
shutil.copy(file, temp_solver_dir)
59+
60+
return temp_solver_dir
61+
62+
def _prepare_output_dir(self, solver_output_dir: Path, functions_file: Path) -> None:
63+
# prepare output dir: clear contents of solver_output_dir
64+
for file in solver_output_dir.iterdir():
65+
if file.is_file() and not file.name.startswith('.'):
66+
file.unlink()
67+
68+
# copy functions file to solver_output_dir
69+
shutil.copy(functions_file, solver_output_dir)
70+
71+
def _run_solver(self, fv_input_file: Path, vcg_input_file: Path, solver_output_dir: Path) -> int:
72+
return fvsolve(input_file=fv_input_file, vcg_file=vcg_input_file, output_dir=solver_output_dir)
73+
74+
75+
76+
77+
78+
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
from pathlib import Path
2+
import os
3+
import libsbml
4+
5+
6+
class SpatialModel(object):
7+
"""
8+
Spatial extension of `libsbml.Model`. All class methods are inherited from `libsbml.Model`: see libsbml documentation for more details.
9+
This class is constructed with one of 3 entrypoints: either the filepath to a valid SBMLSpatial model, OR level, version, model_id, OR model_id
10+
"""
11+
def __init__(self, filepath: Path = None, level: int = None, version: int = 3, model_id: str = "model_1"):
12+
self.filepath = filepath
13+
14+
if self.filepath is not None:
15+
reader = libsbml.SBMLReader()
16+
self.document = reader.readSBML(str(self.filepath))
17+
self.model = self.document.getModel()
18+
else:
19+
self.document = libsbml.SBMLDocument(level, version)
20+
self.model = self.document.createModel()
21+
self.model.setId(model_id)
22+
23+
def get(self, attribute: str):
24+
"""Retrieves a method from the wrapped `libsbml.Model` object if it starts with 'get'."""
25+
methods = [attr for attr in dir(self.model) if attr.startswith('get')]
26+
method = f'getListOf{attribute[0].upper() + attribute[1:]}'
27+
if method in methods:
28+
return getattr(self.model, method)
29+
else:
30+
raise AttributeError(f"Method '{attribute}' not found in libsbml.Model.")
31+
32+
def export(self, filename: os.PathLike[str]):
33+
writer = libsbml.SBMLWriter()
34+
return writer.writeSBML(self.document, filename)
35+
36+
def __getattr__(self, name):
37+
"""Delegates attribute access to the underlying libsbml.Model instance."""
38+
if "export" not in name:
39+
return getattr(self.model, name)
40+

0 commit comments

Comments
 (0)