Skip to content

Commit 3ae5e81

Browse files
authored
Merge pull request #6 from virtualcell/sbml-import-export
sbml and vcml file import/export/validation
2 parents 71303eb + 0bdbca8 commit 3ae5e81

File tree

13 files changed

+475
-25
lines changed

13 files changed

+475
-25
lines changed

build.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ def main() -> None:
3333
"src/test/resources/TinySpatialProject_Application0.xml "
3434
"src/test/resources/TinySpatialProject_Application0.vcml "
3535
"Simulation0 "
36+
"unnamed_spatialGeom "
3637
"target/sbml-input",
3738
cwd=vcell_native_dir,
3839
)

libvcell/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from libvcell.model_utils import sbml_to_vcml, vcml_to_sbml, vcml_to_vcml
12
from libvcell.solver_utils import sbml_to_finite_volume_input, vcml_to_finite_volume_input
23

3-
__all__ = ["vcml_to_finite_volume_input", "sbml_to_finite_volume_input"]
4+
__all__ = ["vcml_to_finite_volume_input", "sbml_to_finite_volume_input", "sbml_to_vcml", "vcml_to_sbml", "vcml_to_vcml"]

libvcell/_internal/native_calls.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,64 @@ def sbml_to_finite_volume_input(self, sbml_content: str, output_dir_path: Path)
5858
except Exception as e:
5959
logging.exception("Error in sbml_to_finite_volume_input()", exc_info=e)
6060
raise
61+
62+
def vcml_to_sbml(self, vcml_content: str, application_name: str, sbml_file_path: Path) -> ReturnValue:
63+
try:
64+
with IsolateManager(self.lib) as isolate_thread:
65+
json_ptr: ctypes.c_char_p = self.lib.vcmlToSbml(
66+
isolate_thread,
67+
ctypes.c_char_p(vcml_content.encode("utf-8")),
68+
ctypes.c_char_p(application_name.encode("utf-8")),
69+
ctypes.c_char_p(str(sbml_file_path).encode("utf-8")),
70+
)
71+
72+
value: bytes | None = ctypes.cast(json_ptr, ctypes.c_char_p).value
73+
if value is None:
74+
logging.error("Failed to convert vcml application to sbml")
75+
return ReturnValue(success=False, message="Failed to convert vcml to sbml")
76+
json_str: str = value.decode("utf-8")
77+
# self.lib.freeString(json_ptr)
78+
return ReturnValue.model_validate_json(json_data=json_str)
79+
except Exception as e:
80+
logging.exception("Error in vcml_to_sbml()", exc_info=e)
81+
raise
82+
83+
def sbml_to_vcml(self, sbml_content: str, vcml_file_path: Path) -> ReturnValue:
84+
try:
85+
with IsolateManager(self.lib) as isolate_thread:
86+
json_ptr: ctypes.c_char_p = self.lib.sbmlToVcml(
87+
isolate_thread,
88+
ctypes.c_char_p(sbml_content.encode("utf-8")),
89+
ctypes.c_char_p(str(vcml_file_path).encode("utf-8")),
90+
)
91+
92+
value: bytes | None = ctypes.cast(json_ptr, ctypes.c_char_p).value
93+
if value is None:
94+
logging.error("Failed to convert sbml to vcml")
95+
return ReturnValue(success=False, message="Failed to convert sbml to vcml")
96+
json_str: str = value.decode("utf-8")
97+
# self.lib.freeString(json_ptr)
98+
return ReturnValue.model_validate_json(json_data=json_str)
99+
except Exception as e:
100+
logging.exception("Error in sbml_to_vcml()", exc_info=e)
101+
raise
102+
103+
def vcml_to_vcml(self, vcml_content: str, vcml_file_path: Path) -> ReturnValue:
104+
try:
105+
with IsolateManager(self.lib) as isolate_thread:
106+
json_ptr: ctypes.c_char_p = self.lib.vcmlToVcml(
107+
isolate_thread,
108+
ctypes.c_char_p(vcml_content.encode("utf-8")),
109+
ctypes.c_char_p(str(vcml_file_path).encode("utf-8")),
110+
)
111+
112+
value: bytes | None = ctypes.cast(json_ptr, ctypes.c_char_p).value
113+
if value is None:
114+
logging.error("Failed to regenerate vcml")
115+
return ReturnValue(success=False, message="Failed to regenerate vcml")
116+
json_str: str = value.decode("utf-8")
117+
# self.lib.freeString(json_ptr)
118+
return ReturnValue.model_validate_json(json_data=json_str)
119+
except Exception as e:
120+
logging.exception("Error in vcml_to_vcml()", exc_info=e)
121+
raise

libvcell/_internal/native_utils.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,15 @@ def _define_entry_points(self) -> None:
3838
self.lib.sbmlToFiniteVolumeInput.restype = ctypes.c_char_p
3939
self.lib.sbmlToFiniteVolumeInput.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_char_p]
4040

41+
self.lib.sbmlToVcml.restype = ctypes.c_char_p
42+
self.lib.sbmlToVcml.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_char_p]
43+
44+
self.lib.vcmlToSbml.restype = ctypes.c_char_p
45+
self.lib.vcmlToSbml.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_char_p, ctypes.c_char_p]
46+
47+
self.lib.vcmlToVcml.restype = ctypes.c_char_p
48+
self.lib.vcmlToVcml.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_char_p]
49+
4150
self.lib.freeString.restype = None
4251
self.lib.freeString.argtypes = [ctypes.c_char_p]
4352

libvcell/model_utils.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
from pathlib import Path
2+
3+
from libvcell._internal.native_calls import ReturnValue, VCellNativeCalls
4+
5+
6+
def vcml_to_sbml(vcml_content: str, application_name: str, sbml_file_path: Path) -> tuple[bool, str]:
7+
"""
8+
Convert VCML content to SBML file
9+
10+
Args:
11+
vcml_content (str): VCML content
12+
application_name (str): VCell Biomodel application name
13+
sbml_file_path (Path): path to resulting SBML file
14+
15+
Returns:
16+
tuple[bool, str]: A tuple containing the success status and a message
17+
"""
18+
native = VCellNativeCalls()
19+
return_value: ReturnValue = native.vcml_to_sbml(vcml_content, application_name, sbml_file_path)
20+
return return_value.success, return_value.message
21+
22+
23+
def sbml_to_vcml(sbml_content: str, vcml_file_path: Path) -> tuple[bool, str]:
24+
"""
25+
Convert SBML content to finite volume input files
26+
27+
Args:
28+
sbml_content (str): SBML content
29+
vcml_file_path (Path): path to resulting VCML file
30+
31+
Returns:
32+
tuple[bool, str]: A tuple containing the success status and a message
33+
"""
34+
native = VCellNativeCalls()
35+
return_value: ReturnValue = native.sbml_to_vcml(sbml_content, vcml_file_path)
36+
return return_value.success, return_value.message
37+
38+
39+
def vcml_to_vcml(vcml_content: str, vcml_file_path: Path) -> tuple[bool, str]:
40+
"""
41+
Process VCML content to regenerated VCML file
42+
43+
Args:
44+
vcml_content (str): VCML content
45+
vcml_file_path (Path): path to resulting VCML file
46+
47+
Returns:
48+
tuple[bool, str]: A tuple containing the success status and a message
49+
"""
50+
native = VCellNativeCalls()
51+
return_value: ReturnValue = native.vcml_to_vcml(vcml_content, vcml_file_path)
52+
return return_value.success, return_value.message

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
44

55
[tool.poetry]
66
name = "libvcell"
7-
version = "0.0.8"
7+
version = "0.0.9"
88
description = "This is a python package which wraps a subset of VCell Java code as a native python package."
99
authors = ["Jim Schaff <[email protected]>", "Ezequiel Valencia <[email protected]>"]
1010
repository = "https://github.com/virtualcell/libvcell"

tests/conftest.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
1-
from tests.fixtures.data_fixtures import sbml_file_path, temp_output_dir, vcml_file_path, vcml_sim_name # noqa: F401
1+
from tests.fixtures.data_fixtures import ( # noqa: F401
2+
sbml_file_path,
3+
temp_output_dir,
4+
vcml_app_name,
5+
vcml_file_path,
6+
vcml_sim_name,
7+
)

tests/fixtures/data_fixtures.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ def vcml_sim_name() -> str:
1818
return "Simulation0"
1919

2020

21+
@pytest.fixture
22+
def vcml_app_name() -> str:
23+
return "unnamed_spatialGeom"
24+
25+
2126
@pytest.fixture
2227
def sbml_file_path() -> Path:
2328
return fixtures_dir / "TinySpatialProject_Application0.xml"

tests/test_libvcell.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
import tempfile
12
from pathlib import Path
23

3-
from libvcell import sbml_to_finite_volume_input, vcml_to_finite_volume_input
4+
from libvcell import sbml_to_finite_volume_input, sbml_to_vcml, vcml_to_finite_volume_input, vcml_to_sbml, vcml_to_vcml
45

56

67
def test_vcml_to_finite_volume_input(temp_output_dir: Path, vcml_file_path: Path, vcml_sim_name: str) -> None:
@@ -19,3 +20,38 @@ def test_sbml_to_finite_volume_input(temp_output_dir: Path, sbml_file_path: Path
1920
assert len(list(temp_output_dir.iterdir())) > 0
2021
assert success is True
2122
assert msg == "Success"
23+
24+
25+
def test_sbml_to_vcml(sbml_file_path: Path) -> None:
26+
sbml_content = sbml_file_path.read_text()
27+
with tempfile.TemporaryDirectory() as temp_dir:
28+
temp_output_dir = Path(temp_dir)
29+
vcml_file_path = temp_output_dir / "test.vcml"
30+
success, msg = sbml_to_vcml(sbml_content=sbml_content, vcml_file_path=vcml_file_path)
31+
assert vcml_file_path.exists()
32+
assert success is True
33+
assert msg == "Success"
34+
35+
36+
def test_vcml_to_sbml(vcml_file_path: Path, vcml_app_name: str) -> None:
37+
vcml_content = vcml_file_path.read_text()
38+
with tempfile.TemporaryDirectory() as temp_dir:
39+
temp_output_dir = Path(temp_dir)
40+
sbml_file_path = temp_output_dir / "test.sbml"
41+
success, msg = vcml_to_sbml(
42+
vcml_content=vcml_content, application_name=vcml_app_name, sbml_file_path=sbml_file_path
43+
)
44+
assert sbml_file_path.exists()
45+
assert success is True
46+
assert msg == "Success"
47+
48+
49+
def test_vcml_to_vcml(vcml_file_path: Path) -> None:
50+
vcml_content = vcml_file_path.read_text()
51+
with tempfile.TemporaryDirectory() as temp_dir:
52+
temp_output_dir = Path(temp_dir)
53+
vcml_file_path = temp_output_dir / "test.vcml"
54+
success, msg = vcml_to_vcml(vcml_content=vcml_content, vcml_file_path=vcml_file_path)
55+
assert vcml_file_path.exists()
56+
assert success is True
57+
assert msg == "Success"

vcell-native/src/main/java/org/vcell/libvcell/Entrypoints.java

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,14 @@
99
import org.json.simple.JSONValue;
1010

1111
import java.io.File;
12+
import java.nio.file.Path;
1213
import java.util.concurrent.ConcurrentHashMap;
1314

1415
import static org.vcell.libvcell.SolverUtils.sbmlToFiniteVolumeInput;
1516
import static org.vcell.libvcell.SolverUtils.vcmlToFiniteVolumeInput;
17+
import static org.vcell.libvcell.ModelUtils.vcml_to_sbml;
18+
import static org.vcell.libvcell.ModelUtils.sbml_to_vcml;
19+
import static org.vcell.libvcell.ModelUtils.vcml_to_vcml;
1620

1721

1822
public class Entrypoints {
@@ -52,6 +56,7 @@ public String toJson() {
5256
documentation = """
5357
Converts VCML file into Finite Volume Input files.
5458
vcml_content: text of VCML XML document
59+
simulation_name: name of the simulation to convert
5560
output_dir_path: path to the output directory (expected to be subdirectory of the workspace)
5661
Returns a JSON string with success status and message"""
5762
)
@@ -106,4 +111,92 @@ public static CCharPointer entrypoint_sbmlToFiniteVolumeInput(
106111
logger.info("Returning from sbmlToFiniteVolumeInput: " + json);
107112
return createString(json);
108113
}
109-
}
114+
115+
@CEntryPoint(
116+
name = "vcmlToSbml",
117+
documentation = """
118+
Converts VCML file into an SBML file.
119+
vcml_content: text of VCML XML document
120+
application_name: name of the application to export
121+
sbml_file_path: path to the SBML file to write
122+
Returns a JSON string with success status and message"""
123+
)
124+
public static CCharPointer entrypoint_vcmlToSbml(
125+
IsolateThread ignoredThread,
126+
CCharPointer vcml_content,
127+
CCharPointer application_name,
128+
CCharPointer sbml_file_path) {
129+
ReturnValue returnValue;
130+
try {
131+
String vcmlContentStr = CTypeConversion.toJavaString(vcml_content);
132+
String applicationName = CTypeConversion.toJavaString(application_name);
133+
Path sbmlFilePath = new File(CTypeConversion.toJavaString(sbml_file_path)).toPath();
134+
vcml_to_sbml(vcmlContentStr, applicationName, sbmlFilePath);
135+
returnValue = new ReturnValue(true, "Success");
136+
}catch (Throwable t) {
137+
logger.error("Error translating vcml application to sbml", t);
138+
returnValue = new ReturnValue(false, t.getMessage());
139+
}
140+
// return result as a json string
141+
String json = returnValue.toJson();
142+
logger.info("Returning from vcellToSbml: " + json);
143+
return createString(json);
144+
}
145+
146+
@CEntryPoint(
147+
name = "sbmlToVcml",
148+
documentation = """
149+
Converts SBML file into a VCML file.
150+
sbml_content: text of SBML XML document
151+
vcml_file_path: path to the VCML file to write
152+
Returns a JSON string with success status and message"""
153+
)
154+
public static CCharPointer entrypoint_sbmlToVcml(
155+
IsolateThread ignoredThread,
156+
CCharPointer sbml_content,
157+
CCharPointer vcml_file_path) {
158+
ReturnValue returnValue;
159+
try {
160+
String sbmlContentStr = CTypeConversion.toJavaString(sbml_content);
161+
Path vcmlFilePath = new File(CTypeConversion.toJavaString(vcml_file_path)).toPath();
162+
sbml_to_vcml(sbmlContentStr, vcmlFilePath);
163+
returnValue = new ReturnValue(true, "Success");
164+
}catch (Throwable t) {
165+
logger.error("Error translating sbml to vcml", t);
166+
returnValue = new ReturnValue(false, t.getMessage());
167+
}
168+
// return result as a json string
169+
String json = returnValue.toJson();
170+
logger.info("Returning from sbmlToVcell: " + json);
171+
return createString(json);
172+
}
173+
174+
@CEntryPoint(
175+
name = "vcmlToVcml",
176+
documentation = """
177+
Updates a VCML file into a fully populated VCML file.
178+
vcml_content: text of VCML XML document
179+
vcml_file_path: path to the VCML file to write
180+
Returns a JSON string with success status and message"""
181+
)
182+
public static CCharPointer entrypoint_vcmlToVcml(
183+
IsolateThread ignoredThread,
184+
CCharPointer vcml_content,
185+
CCharPointer vcml_file_path) {
186+
ReturnValue returnValue;
187+
try {
188+
String vcmlContentStr = CTypeConversion.toJavaString(vcml_content);
189+
Path vcmlFilePath = new File(CTypeConversion.toJavaString(vcml_file_path)).toPath();
190+
vcml_to_vcml(vcmlContentStr, vcmlFilePath);
191+
returnValue = new ReturnValue(true, "Success");
192+
}catch (Throwable t) {
193+
logger.error("Error refreshing vcml", t);
194+
returnValue = new ReturnValue(false, t.getMessage());
195+
}
196+
// return result as a json string
197+
String json = returnValue.toJson();
198+
logger.info("Returning from vcellToVcml: " + json);
199+
return createString(json);
200+
}
201+
202+
}

0 commit comments

Comments
 (0)