Skip to content

Commit 16df8c4

Browse files
committed
added utils.field_data_refs(Biomodel) to query biomodel for field data
1 parent 4a41e6c commit 16df8c4

File tree

6 files changed

+129
-3
lines changed

6 files changed

+129
-3
lines changed

poetry.lock

Lines changed: 37 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ trame-vtk = "^2.8.15"
3838
trame-vuetify = "^2.8.1"
3939
pyvista = "^0.44.2"
4040
trame-server = "^3.4.0"
41+
sympy = "^1.13.3"
4142

4243
[tool.poetry.group.dev.dependencies]
4344
pytest = "^7.2.0"

pyvcell/vcml/models.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,19 @@ class SpeciesMapping(VcmlNode):
275275
diff_coef: float | str | None = None
276276
boundary_values: list[float | str | None] = Field(default_factory=list)
277277

278+
@property
279+
def expressions(self) -> list[str]:
280+
exps: list[str] = []
281+
if isinstance(self.init_conc, str):
282+
exps.append(self.init_conc)
283+
if isinstance(self.diff_coef, str):
284+
exps.append(self.diff_coef)
285+
if self.boundary_values:
286+
for value in self.boundary_values:
287+
if isinstance(value, str):
288+
exps.append(value)
289+
return exps
290+
278291

279292
class ReactionMapping(VcmlNode):
280293
reaction_name: str

pyvcell/vcml/utils.py

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import tempfile
22
from pathlib import Path
33

4+
import sympy # type: ignore[import-untyped]
45
from libvcell import sbml_to_vcml, vcml_to_sbml, vcml_to_vcml
6+
from sympy.parsing.sympy_parser import parse_expr # type: ignore[import-untyped]
57

8+
from pyvcell._internal.simdata.simdata_models import VariableType
69
from pyvcell.sbml.sbml_spatial_model import SbmlSpatialModel
7-
from pyvcell.vcml import VCMLDocument, VcmlReader, VcmlWriter
10+
from pyvcell.vcml import Application, VCMLDocument, VcmlReader, VcmlWriter
811
from pyvcell.vcml.models import Biomodel
912

1013

@@ -91,3 +94,55 @@ def to_sbml(bio_model: Biomodel, application_name: str, round_trip_validation: b
9194
raise ValueError(f"Failed to import SBML: {error_message}")
9295
sbml_spatial_model = SbmlSpatialModel(filepath=sbml_file_path)
9396
return sbml_spatial_model
97+
98+
99+
def field_data_refs(bio_model: Biomodel, simulation_name: str) -> set[tuple[str, str, VariableType, float]]:
100+
"""
101+
Extract field data references from the VCML model and return them as a list of tuples.
102+
Each tuple contains the following elements:
103+
- field_data_name: str
104+
- field_data_varname: str
105+
- field_data_type: VariableType
106+
- field_data_time: float
107+
"""
108+
application: Application | None = None
109+
for app in bio_model.applications:
110+
for sim in app.simulations:
111+
if sim.name == simulation_name:
112+
application = app
113+
break
114+
115+
if application is None:
116+
raise ValueError(f"Simulation name '{simulation_name}' not found in VCML model")
117+
118+
# Extract field data references from the application (look in species mapping only for now)
119+
function_calls: set[sympy.Function] = set()
120+
for species_mapping in application.species_mappings:
121+
for exp_str in species_mapping.expressions:
122+
if "vcField(" in exp_str:
123+
func_calls: set[sympy.Function] = parse_expr(exp_str).atoms(sympy.Function)
124+
function_calls.update(func_calls)
125+
126+
field_data_refs: set[tuple[str, str, VariableType, float]] = set()
127+
for func_call in function_calls:
128+
# e.g. {vcField(test2_lsm_DEMO, species0_cyt, 17.0, Volume), exp(2)}
129+
if func_call.func.__name__ == "vcField":
130+
from typing import cast
131+
132+
data_name: sympy.Symbol = cast(sympy.Symbol, func_call.args[0])
133+
varname: sympy.Symbol = cast(sympy.Symbol, func_call.args[1])
134+
time: sympy.Number = cast(sympy.Number, func_call.args[2])
135+
data_type: sympy.Symbol = cast(sympy.Symbol, func_call.args[3])
136+
if not isinstance(data_name, sympy.Symbol):
137+
raise ValueError(f"Invalid field data name: {data_name}")
138+
if not isinstance(varname, sympy.Symbol):
139+
raise ValueError(f"Invalid field data varname: {varname}")
140+
if not isinstance(data_type, sympy.Symbol):
141+
raise ValueError(f"Invalid field data type: {data_type}")
142+
if not isinstance(time, sympy.Number):
143+
raise ValueError(f"Invalid field data time: {time}")
144+
if data_type.name.upper() != VariableType.VOLUME.name:
145+
raise ValueError(f"Invalid field data type: {data_type}, expected 'Volume'")
146+
field_data_refs.add((data_name.name, varname.name, VariableType.VOLUME, float(time)))
147+
148+
return field_data_refs

tests/vcml/test_fielddata.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
import tempfile
33
from pathlib import Path
44

5+
from pyvcell._internal.simdata.simdata_models import VariableType
56
from pyvcell.sim_results.result import Result
67
from pyvcell.vcml import Biomodel, ModelParameter, VcmlReader
8+
from pyvcell.vcml.utils import field_data_refs
79
from pyvcell.vcml.vcml_simulation import VcmlSpatialSimulation
810

911

@@ -44,6 +46,12 @@ def test_vcml_field_data_demo(vcml_field_data_demo_path: Path, vcml_field_data_t
4446
"species0_cyt",
4547
"species0_ec",
4648
]
49+
50+
refs = field_data_refs(bio_model=bio_model, simulation_name=sim_name)
51+
assert refs == {
52+
("test2_lsm_DEMO", "species0_cyt", VariableType.VOLUME, 0.5),
53+
("test2_lsm_DEMO", "species0_ec", VariableType.VOLUME, 0.5),
54+
}
4755
# assert np.allclose(
4856
# results_orig.concentrations[0, 0::10],
4957
# np.array(

tests/vcml/test_utils.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22

33
import pytest
44

5+
from pyvcell._internal.simdata.simdata_models import VariableType
56
from pyvcell.sbml.sbml_spatial_model import SbmlSpatialModel
67
from pyvcell.vcml import Biomodel, VcmlReader
7-
from pyvcell.vcml.utils import from_sbml, to_sbml, update_biomodel
8+
from pyvcell.vcml.utils import field_data_refs, from_sbml, to_sbml, update_biomodel
89

910

1011
def test_update(vcml_spatial_model_1d_path: Path) -> None:
@@ -85,3 +86,15 @@ def test_from_sbml(sbml_spatial_model_1d_path: Path) -> None:
8586
sbml_spatial_model = SbmlSpatialModel(sbml_spatial_model_1d_path)
8687
bio_model: Biomodel = from_sbml(sbml_spatial_model=sbml_spatial_model)
8788
assert bio_model is not None
89+
90+
91+
def test_extract_field_data_refs(vcml_field_data_demo_path: Path) -> None:
92+
assert vcml_field_data_demo_path.is_file()
93+
94+
bio_model: Biomodel = VcmlReader.biomodel_from_file(vcml_source=vcml_field_data_demo_path)
95+
assert bio_model.model is not None
96+
refs = field_data_refs(bio_model=bio_model, simulation_name="Simulation0")
97+
assert refs == {
98+
("test2_lsm_DEMO", "species0_cyt", VariableType.VOLUME, 0.5),
99+
("test2_lsm_DEMO", "species0_ec", VariableType.VOLUME, 0.5),
100+
}

0 commit comments

Comments
 (0)