|
1 | 1 | import tempfile |
2 | 2 | from pathlib import Path |
3 | 3 |
|
| 4 | +import sympy # type: ignore[import-untyped] |
4 | 5 | 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] |
5 | 7 |
|
| 8 | +from pyvcell._internal.simdata.simdata_models import VariableType |
6 | 9 | 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 |
8 | 11 | from pyvcell.vcml.models import Biomodel |
9 | 12 |
|
10 | 13 |
|
@@ -91,3 +94,55 @@ def to_sbml(bio_model: Biomodel, application_name: str, round_trip_validation: b |
91 | 94 | raise ValueError(f"Failed to import SBML: {error_message}") |
92 | 95 | sbml_spatial_model = SbmlSpatialModel(filepath=sbml_file_path) |
93 | 96 | 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 |
0 commit comments