diff --git a/build.py b/build.py index 201222d..a2a2e21 100644 --- a/build.py +++ b/build.py @@ -33,6 +33,7 @@ def main() -> None: "src/test/resources/TinySpatialProject_Application0.xml " "src/test/resources/TinySpatialProject_Application0.vcml " "Simulation0 " + "unnamed_spatialGeom " "target/sbml-input", cwd=vcell_native_dir, ) diff --git a/libvcell/__init__.py b/libvcell/__init__.py index 24a29e9..820bdb8 100644 --- a/libvcell/__init__.py +++ b/libvcell/__init__.py @@ -1,3 +1,4 @@ +from libvcell.model_utils import sbml_to_vcml, vcml_to_sbml, vcml_to_vcml from libvcell.solver_utils import sbml_to_finite_volume_input, vcml_to_finite_volume_input -__all__ = ["vcml_to_finite_volume_input", "sbml_to_finite_volume_input"] +__all__ = ["vcml_to_finite_volume_input", "sbml_to_finite_volume_input", "sbml_to_vcml", "vcml_to_sbml", "vcml_to_vcml"] diff --git a/libvcell/_internal/native_calls.py b/libvcell/_internal/native_calls.py index 1716468..d7f81b0 100644 --- a/libvcell/_internal/native_calls.py +++ b/libvcell/_internal/native_calls.py @@ -58,3 +58,64 @@ def sbml_to_finite_volume_input(self, sbml_content: str, output_dir_path: Path) except Exception as e: logging.exception("Error in sbml_to_finite_volume_input()", exc_info=e) raise + + def vcml_to_sbml(self, vcml_content: str, application_name: str, sbml_file_path: Path) -> ReturnValue: + try: + with IsolateManager(self.lib) as isolate_thread: + json_ptr: ctypes.c_char_p = self.lib.vcmlToSbml( + isolate_thread, + ctypes.c_char_p(vcml_content.encode("utf-8")), + ctypes.c_char_p(application_name.encode("utf-8")), + ctypes.c_char_p(str(sbml_file_path).encode("utf-8")), + ) + + value: bytes | None = ctypes.cast(json_ptr, ctypes.c_char_p).value + if value is None: + logging.error("Failed to convert vcml application to sbml") + return ReturnValue(success=False, message="Failed to convert vcml to sbml") + json_str: str = value.decode("utf-8") + # self.lib.freeString(json_ptr) + return ReturnValue.model_validate_json(json_data=json_str) + except Exception as e: + logging.exception("Error in vcml_to_sbml()", exc_info=e) + raise + + def sbml_to_vcml(self, sbml_content: str, vcml_file_path: Path) -> ReturnValue: + try: + with IsolateManager(self.lib) as isolate_thread: + json_ptr: ctypes.c_char_p = self.lib.sbmlToVcml( + isolate_thread, + ctypes.c_char_p(sbml_content.encode("utf-8")), + ctypes.c_char_p(str(vcml_file_path).encode("utf-8")), + ) + + value: bytes | None = ctypes.cast(json_ptr, ctypes.c_char_p).value + if value is None: + logging.error("Failed to convert sbml to vcml") + return ReturnValue(success=False, message="Failed to convert sbml to vcml") + json_str: str = value.decode("utf-8") + # self.lib.freeString(json_ptr) + return ReturnValue.model_validate_json(json_data=json_str) + except Exception as e: + logging.exception("Error in sbml_to_vcml()", exc_info=e) + raise + + def vcml_to_vcml(self, vcml_content: str, vcml_file_path: Path) -> ReturnValue: + try: + with IsolateManager(self.lib) as isolate_thread: + json_ptr: ctypes.c_char_p = self.lib.vcmlToVcml( + isolate_thread, + ctypes.c_char_p(vcml_content.encode("utf-8")), + ctypes.c_char_p(str(vcml_file_path).encode("utf-8")), + ) + + value: bytes | None = ctypes.cast(json_ptr, ctypes.c_char_p).value + if value is None: + logging.error("Failed to regenerate vcml") + return ReturnValue(success=False, message="Failed to regenerate vcml") + json_str: str = value.decode("utf-8") + # self.lib.freeString(json_ptr) + return ReturnValue.model_validate_json(json_data=json_str) + except Exception as e: + logging.exception("Error in vcml_to_vcml()", exc_info=e) + raise diff --git a/libvcell/_internal/native_utils.py b/libvcell/_internal/native_utils.py index 537667e..bac09d4 100644 --- a/libvcell/_internal/native_utils.py +++ b/libvcell/_internal/native_utils.py @@ -38,6 +38,15 @@ def _define_entry_points(self) -> None: self.lib.sbmlToFiniteVolumeInput.restype = ctypes.c_char_p self.lib.sbmlToFiniteVolumeInput.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_char_p] + self.lib.sbmlToVcml.restype = ctypes.c_char_p + self.lib.sbmlToVcml.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_char_p] + + self.lib.vcmlToSbml.restype = ctypes.c_char_p + self.lib.vcmlToSbml.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_char_p, ctypes.c_char_p] + + self.lib.vcmlToVcml.restype = ctypes.c_char_p + self.lib.vcmlToVcml.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_char_p] + self.lib.freeString.restype = None self.lib.freeString.argtypes = [ctypes.c_char_p] diff --git a/libvcell/model_utils.py b/libvcell/model_utils.py new file mode 100644 index 0000000..65ac76f --- /dev/null +++ b/libvcell/model_utils.py @@ -0,0 +1,52 @@ +from pathlib import Path + +from libvcell._internal.native_calls import ReturnValue, VCellNativeCalls + + +def vcml_to_sbml(vcml_content: str, application_name: str, sbml_file_path: Path) -> tuple[bool, str]: + """ + Convert VCML content to SBML file + + Args: + vcml_content (str): VCML content + application_name (str): VCell Biomodel application name + sbml_file_path (Path): path to resulting SBML file + + Returns: + tuple[bool, str]: A tuple containing the success status and a message + """ + native = VCellNativeCalls() + return_value: ReturnValue = native.vcml_to_sbml(vcml_content, application_name, sbml_file_path) + return return_value.success, return_value.message + + +def sbml_to_vcml(sbml_content: str, vcml_file_path: Path) -> tuple[bool, str]: + """ + Convert SBML content to finite volume input files + + Args: + sbml_content (str): SBML content + vcml_file_path (Path): path to resulting VCML file + + Returns: + tuple[bool, str]: A tuple containing the success status and a message + """ + native = VCellNativeCalls() + return_value: ReturnValue = native.sbml_to_vcml(sbml_content, vcml_file_path) + return return_value.success, return_value.message + + +def vcml_to_vcml(vcml_content: str, vcml_file_path: Path) -> tuple[bool, str]: + """ + Process VCML content to regenerated VCML file + + Args: + vcml_content (str): VCML content + vcml_file_path (Path): path to resulting VCML file + + Returns: + tuple[bool, str]: A tuple containing the success status and a message + """ + native = VCellNativeCalls() + return_value: ReturnValue = native.vcml_to_vcml(vcml_content, vcml_file_path) + return return_value.success, return_value.message diff --git a/pyproject.toml b/pyproject.toml index 65596b1..eb8cd13 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] name = "libvcell" -version = "0.0.8" +version = "0.0.9" description = "This is a python package which wraps a subset of VCell Java code as a native python package." authors = ["Jim Schaff ", "Ezequiel Valencia "] repository = "https://github.com/virtualcell/libvcell" diff --git a/tests/conftest.py b/tests/conftest.py index 9915405..8f9e41f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1 +1,7 @@ -from tests.fixtures.data_fixtures import sbml_file_path, temp_output_dir, vcml_file_path, vcml_sim_name # noqa: F401 +from tests.fixtures.data_fixtures import ( # noqa: F401 + sbml_file_path, + temp_output_dir, + vcml_app_name, + vcml_file_path, + vcml_sim_name, +) diff --git a/tests/fixtures/data_fixtures.py b/tests/fixtures/data_fixtures.py index 4ce37a1..d46a9e6 100644 --- a/tests/fixtures/data_fixtures.py +++ b/tests/fixtures/data_fixtures.py @@ -18,6 +18,11 @@ def vcml_sim_name() -> str: return "Simulation0" +@pytest.fixture +def vcml_app_name() -> str: + return "unnamed_spatialGeom" + + @pytest.fixture def sbml_file_path() -> Path: return fixtures_dir / "TinySpatialProject_Application0.xml" diff --git a/tests/test_libvcell.py b/tests/test_libvcell.py index 2e770a4..850d329 100644 --- a/tests/test_libvcell.py +++ b/tests/test_libvcell.py @@ -1,6 +1,7 @@ +import tempfile from pathlib import Path -from libvcell import sbml_to_finite_volume_input, vcml_to_finite_volume_input +from libvcell import sbml_to_finite_volume_input, sbml_to_vcml, vcml_to_finite_volume_input, vcml_to_sbml, vcml_to_vcml 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 assert len(list(temp_output_dir.iterdir())) > 0 assert success is True assert msg == "Success" + + +def test_sbml_to_vcml(sbml_file_path: Path) -> None: + sbml_content = sbml_file_path.read_text() + with tempfile.TemporaryDirectory() as temp_dir: + temp_output_dir = Path(temp_dir) + vcml_file_path = temp_output_dir / "test.vcml" + success, msg = sbml_to_vcml(sbml_content=sbml_content, vcml_file_path=vcml_file_path) + assert vcml_file_path.exists() + assert success is True + assert msg == "Success" + + +def test_vcml_to_sbml(vcml_file_path: Path, vcml_app_name: str) -> None: + vcml_content = vcml_file_path.read_text() + with tempfile.TemporaryDirectory() as temp_dir: + temp_output_dir = Path(temp_dir) + sbml_file_path = temp_output_dir / "test.sbml" + success, msg = vcml_to_sbml( + vcml_content=vcml_content, application_name=vcml_app_name, sbml_file_path=sbml_file_path + ) + assert sbml_file_path.exists() + assert success is True + assert msg == "Success" + + +def test_vcml_to_vcml(vcml_file_path: Path) -> None: + vcml_content = vcml_file_path.read_text() + with tempfile.TemporaryDirectory() as temp_dir: + temp_output_dir = Path(temp_dir) + vcml_file_path = temp_output_dir / "test.vcml" + success, msg = vcml_to_vcml(vcml_content=vcml_content, vcml_file_path=vcml_file_path) + assert vcml_file_path.exists() + assert success is True + assert msg == "Success" diff --git a/vcell-native/src/main/java/org/vcell/libvcell/Entrypoints.java b/vcell-native/src/main/java/org/vcell/libvcell/Entrypoints.java index 35916bf..c32d971 100644 --- a/vcell-native/src/main/java/org/vcell/libvcell/Entrypoints.java +++ b/vcell-native/src/main/java/org/vcell/libvcell/Entrypoints.java @@ -9,10 +9,14 @@ import org.json.simple.JSONValue; import java.io.File; +import java.nio.file.Path; import java.util.concurrent.ConcurrentHashMap; import static org.vcell.libvcell.SolverUtils.sbmlToFiniteVolumeInput; import static org.vcell.libvcell.SolverUtils.vcmlToFiniteVolumeInput; +import static org.vcell.libvcell.ModelUtils.vcml_to_sbml; +import static org.vcell.libvcell.ModelUtils.sbml_to_vcml; +import static org.vcell.libvcell.ModelUtils.vcml_to_vcml; public class Entrypoints { @@ -52,6 +56,7 @@ public String toJson() { documentation = """ Converts VCML file into Finite Volume Input files. vcml_content: text of VCML XML document + simulation_name: name of the simulation to convert output_dir_path: path to the output directory (expected to be subdirectory of the workspace) Returns a JSON string with success status and message""" ) @@ -106,4 +111,92 @@ public static CCharPointer entrypoint_sbmlToFiniteVolumeInput( logger.info("Returning from sbmlToFiniteVolumeInput: " + json); return createString(json); } - } + + @CEntryPoint( + name = "vcmlToSbml", + documentation = """ + Converts VCML file into an SBML file. + vcml_content: text of VCML XML document + application_name: name of the application to export + sbml_file_path: path to the SBML file to write + Returns a JSON string with success status and message""" + ) + public static CCharPointer entrypoint_vcmlToSbml( + IsolateThread ignoredThread, + CCharPointer vcml_content, + CCharPointer application_name, + CCharPointer sbml_file_path) { + ReturnValue returnValue; + try { + String vcmlContentStr = CTypeConversion.toJavaString(vcml_content); + String applicationName = CTypeConversion.toJavaString(application_name); + Path sbmlFilePath = new File(CTypeConversion.toJavaString(sbml_file_path)).toPath(); + vcml_to_sbml(vcmlContentStr, applicationName, sbmlFilePath); + returnValue = new ReturnValue(true, "Success"); + }catch (Throwable t) { + logger.error("Error translating vcml application to sbml", t); + returnValue = new ReturnValue(false, t.getMessage()); + } + // return result as a json string + String json = returnValue.toJson(); + logger.info("Returning from vcellToSbml: " + json); + return createString(json); + } + + @CEntryPoint( + name = "sbmlToVcml", + documentation = """ + Converts SBML file into a VCML file. + sbml_content: text of SBML XML document + vcml_file_path: path to the VCML file to write + Returns a JSON string with success status and message""" + ) + public static CCharPointer entrypoint_sbmlToVcml( + IsolateThread ignoredThread, + CCharPointer sbml_content, + CCharPointer vcml_file_path) { + ReturnValue returnValue; + try { + String sbmlContentStr = CTypeConversion.toJavaString(sbml_content); + Path vcmlFilePath = new File(CTypeConversion.toJavaString(vcml_file_path)).toPath(); + sbml_to_vcml(sbmlContentStr, vcmlFilePath); + returnValue = new ReturnValue(true, "Success"); + }catch (Throwable t) { + logger.error("Error translating sbml to vcml", t); + returnValue = new ReturnValue(false, t.getMessage()); + } + // return result as a json string + String json = returnValue.toJson(); + logger.info("Returning from sbmlToVcell: " + json); + return createString(json); + } + + @CEntryPoint( + name = "vcmlToVcml", + documentation = """ + Updates a VCML file into a fully populated VCML file. + vcml_content: text of VCML XML document + vcml_file_path: path to the VCML file to write + Returns a JSON string with success status and message""" + ) + public static CCharPointer entrypoint_vcmlToVcml( + IsolateThread ignoredThread, + CCharPointer vcml_content, + CCharPointer vcml_file_path) { + ReturnValue returnValue; + try { + String vcmlContentStr = CTypeConversion.toJavaString(vcml_content); + Path vcmlFilePath = new File(CTypeConversion.toJavaString(vcml_file_path)).toPath(); + vcml_to_vcml(vcmlContentStr, vcmlFilePath); + returnValue = new ReturnValue(true, "Success"); + }catch (Throwable t) { + logger.error("Error refreshing vcml", t); + returnValue = new ReturnValue(false, t.getMessage()); + } + // return result as a json string + String json = returnValue.toJson(); + logger.info("Returning from vcellToVcml: " + json); + return createString(json); + } + +} diff --git a/vcell-native/src/main/java/org/vcell/libvcell/MainRecorder.java b/vcell-native/src/main/java/org/vcell/libvcell/MainRecorder.java index f3c7f92..6fdd780 100644 --- a/vcell-native/src/main/java/org/vcell/libvcell/MainRecorder.java +++ b/vcell-native/src/main/java/org/vcell/libvcell/MainRecorder.java @@ -9,17 +9,24 @@ import static org.vcell.libvcell.SolverUtils.sbmlToFiniteVolumeInput; import static org.vcell.libvcell.SolverUtils.vcmlToFiniteVolumeInput; - +import static org.vcell.libvcell.ModelUtils.sbml_to_vcml; +import static org.vcell.libvcell.ModelUtils.vcml_to_sbml; +import static org.vcell.libvcell.ModelUtils.vcml_to_vcml; public class MainRecorder { private static final Logger logger = LogManager.getLogger(MainRecorder.class); public static void main(String[] args) { try { + if (args.length != 5) { + System.out.println("Usage: java -cp org.vcell.libvcell.MainRecorder "); + return; + } File sbml_file = new File(args[0]); File vcml_file = new File(args[1]); String vcml_sim_name = args[2]; - File output_dir = new File(args[3]); + String vcml_app_name = args[3]; + File output_dir = new File(args[4]); File parent_dir = output_dir.getParentFile(); logger.info("Logger logging"); PropertyLoader.setProperty(PropertyLoader.vcellServerIDProperty, "none"); @@ -39,6 +46,59 @@ public static void main(String[] args) { vcmlToFiniteVolumeInput(vcml_str, vcml_sim_name, parent_dir, output_dir); } + // exercise the sbml_to_vcml and vcml_to_sbml methods + try (FileInputStream f_sbml = new FileInputStream(sbml_file)) { + byte[] data = f_sbml.readAllBytes(); + logger.info("Read " + data.length + " bytes from " + sbml_file.getAbsolutePath()); + String sbml_str = new String(data); + + // create a temporary file for the VCML output + File temp_vcml_file = new File(output_dir, "temp.vcml"); + sbml_to_vcml(sbml_str, temp_vcml_file.toPath()); + // remove temporary file + if (temp_vcml_file.exists()) { + boolean deleted = temp_vcml_file.delete(); + if (!deleted) { + logger.warn("Failed to delete temporary VCML file: " + temp_vcml_file.getAbsolutePath()); + } + } + } + try (FileInputStream f_vcml = new FileInputStream(vcml_file)) { + byte[] data = f_vcml.readAllBytes(); + logger.info("Read " + data.length + " bytes from " + vcml_file.getAbsolutePath()); + String vcml_str = new String(data); + + // create a temporary file for the SBML output + File temp_sbml_file = new File(output_dir, "temp.vcml"); + vcml_to_sbml(vcml_str, vcml_app_name, temp_sbml_file.toPath()); + // remove temporary file + if (temp_sbml_file.exists()) { + boolean deleted = temp_sbml_file.delete(); + if (!deleted) { + logger.warn("Failed to delete temporary SBML file: " + temp_sbml_file.getAbsolutePath()); + } + } + + } + + try (FileInputStream f_vcml = new FileInputStream(vcml_file)) { + byte[] data = f_vcml.readAllBytes(); + logger.info("Read " + data.length + " bytes from " + vcml_file.getAbsolutePath()); + String vcml_str = new String(data); + + // create a temporary file for the VCML output + File temp_vcml_file = new File(output_dir, "temp.vcml"); + vcml_to_vcml(vcml_str, temp_vcml_file.toPath()); + // remove temporary file + if (temp_vcml_file.exists()) { + boolean deleted = temp_vcml_file.delete(); + if (!deleted) { + logger.warn("Failed to delete temporary VCML file: " + temp_vcml_file.getAbsolutePath()); + } + } + + } + // use reflection to load jsbml classes and call their default constructors Class.forName("org.sbml.jsbml.AlgebraicRule").getDeclaredConstructor().newInstance(); Class.forName("org.sbml.jsbml.Annotation").getDeclaredConstructor().newInstance(); @@ -67,25 +127,25 @@ public static void main(String[] args) { Class.forName("org.sbml.jsbml.Trigger").getDeclaredConstructor().newInstance(); Class.forName("org.sbml.jsbml.Unit").getDeclaredConstructor().newInstance(); Class.forName("org.sbml.jsbml.UnitDefinition").getDeclaredConstructor().newInstance(); - Class.forName("org.sbml.jsbml.xml.parsers.ArraysParser").getDeclaredConstructor().newInstance(); - Class.forName("org.sbml.jsbml.xml.parsers.CompParser").getDeclaredConstructor().newInstance(); - Class.forName("org.sbml.jsbml.xml.parsers.DistribParser").getDeclaredConstructor().newInstance(); - Class.forName("org.sbml.jsbml.xml.parsers.DynParser").getDeclaredConstructor().newInstance(); - Class.forName("org.sbml.jsbml.xml.parsers.FBCParser").getDeclaredConstructor().newInstance(); - Class.forName("org.sbml.jsbml.xml.parsers.GroupsParser").getDeclaredConstructor().newInstance(); - Class.forName("org.sbml.jsbml.xml.parsers.L3LayoutParser").getDeclaredConstructor().newInstance(); - Class.forName("org.sbml.jsbml.xml.parsers.LayoutParser").getDeclaredConstructor().newInstance(); - Class.forName("org.sbml.jsbml.xml.parsers.MathMLStaxParser").getDeclaredConstructor().newInstance(); - Class.forName("org.sbml.jsbml.xml.parsers.MultiParser").getDeclaredConstructor().newInstance(); - Class.forName("org.sbml.jsbml.xml.parsers.QualParser").getDeclaredConstructor().newInstance(); - Class.forName("org.sbml.jsbml.xml.parsers.RenderParser").getDeclaredConstructor().newInstance(); - Class.forName("org.sbml.jsbml.xml.parsers.ReqParser").getDeclaredConstructor().newInstance(); - Class.forName("org.sbml.jsbml.xml.parsers.SBMLCoreParser").getDeclaredConstructor().newInstance(); - Class.forName("org.sbml.jsbml.xml.parsers.SBMLLevel1Rule").getDeclaredConstructor().newInstance(); - Class.forName("org.sbml.jsbml.xml.parsers.SBMLRDFAnnotationParser").getDeclaredConstructor().newInstance(); +// Class.forName("org.sbml.jsbml.xml.parsers.ArraysParser").getDeclaredConstructor().newInstance(); +// Class.forName("org.sbml.jsbml.xml.parsers.CompParser").getDeclaredConstructor().newInstance(); +// Class.forName("org.sbml.jsbml.xml.parsers.DistribParser").getDeclaredConstructor().newInstance(); +// Class.forName("org.sbml.jsbml.xml.parsers.DynParser").getDeclaredConstructor().newInstance(); +// Class.forName("org.sbml.jsbml.xml.parsers.FBCParser").getDeclaredConstructor().newInstance(); +// Class.forName("org.sbml.jsbml.xml.parsers.GroupsParser").getDeclaredConstructor().newInstance(); +// Class.forName("org.sbml.jsbml.xml.parsers.L3LayoutParser").getDeclaredConstructor().newInstance(); +// Class.forName("org.sbml.jsbml.xml.parsers.LayoutParser").getDeclaredConstructor().newInstance(); +// Class.forName("org.sbml.jsbml.xml.parsers.MathMLStaxParser").getDeclaredConstructor().newInstance(); +// Class.forName("org.sbml.jsbml.xml.parsers.MultiParser").getDeclaredConstructor().newInstance(); +// Class.forName("org.sbml.jsbml.xml.parsers.QualParser").getDeclaredConstructor().newInstance(); +// Class.forName("org.sbml.jsbml.xml.parsers.RenderParser").getDeclaredConstructor().newInstance(); +// Class.forName("org.sbml.jsbml.xml.parsers.ReqParser").getDeclaredConstructor().newInstance(); +// Class.forName("org.sbml.jsbml.xml.parsers.SBMLCoreParser").getDeclaredConstructor().newInstance(); +// Class.forName("org.sbml.jsbml.xml.parsers.SBMLLevel1Rule").getDeclaredConstructor().newInstance(); +// Class.forName("org.sbml.jsbml.xml.parsers.SBMLRDFAnnotationParser").getDeclaredConstructor().newInstance(); Class.forName("org.sbml.jsbml.xml.parsers.SpatialParser").getDeclaredConstructor().newInstance(); - Class.forName("org.sbml.jsbml.xml.parsers.UncertMLXMLNodeReader").getDeclaredConstructor().newInstance(); - Class.forName("org.sbml.jsbml.xml.parsers.XMLNodeReader").getDeclaredConstructor().newInstance(); +// Class.forName("org.sbml.jsbml.xml.parsers.UncertMLXMLNodeReader").getDeclaredConstructor().newInstance(); +// Class.forName("org.sbml.jsbml.xml.parsers.XMLNodeReader").getDeclaredConstructor().newInstance(); } catch (Exception e) { System.out.println(e.getMessage()); logger.error("Error processing spatial model", e); diff --git a/vcell-native/src/main/java/org/vcell/libvcell/ModelUtils.java b/vcell-native/src/main/java/org/vcell/libvcell/ModelUtils.java new file mode 100644 index 0000000..55d6ccc --- /dev/null +++ b/vcell-native/src/main/java/org/vcell/libvcell/ModelUtils.java @@ -0,0 +1,93 @@ +package org.vcell.libvcell; + +import cbit.util.xml.VCLogger; +import cbit.util.xml.VCLoggerException; +import cbit.util.xml.XmlUtil; +import cbit.vcell.biomodel.BioModel; +import cbit.vcell.geometry.GeometrySpec; +import cbit.vcell.mapping.MappingException; +import cbit.vcell.mapping.SimulationContext; +import cbit.vcell.mongodb.VCMongoMessage; +import cbit.vcell.xml.XMLSource; +import cbit.vcell.xml.XmlHelper; +import cbit.vcell.xml.XmlParseException; +import org.vcell.sbml.SbmlException; +import org.vcell.sbml.vcell.SBMLExporter; +import org.vcell.sbml.vcell.SBMLImporter; + +import javax.xml.stream.XMLStreamException; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Path; +import java.util.ArrayList; + +public class ModelUtils { + + public static void sbml_to_vcml(String sbml_content, Path vcmlPath) + throws VCLoggerException, XmlParseException, IOException, MappingException { + + GeometrySpec.avoidAWTImageCreation = true; + VCMongoMessage.enabled = false; + + record LoggerMessage(VCLogger.Priority priority, VCLogger.ErrorType errorType, String message) {}; + final ArrayList messages = new ArrayList<>(); + + VCLogger vclogger = new VCLogger() { + @Override public boolean hasMessages() { return false; } + @Override public void sendAllMessages() { } + @Override public void sendMessage(Priority p, ErrorType et, String message) { + messages.add(new LoggerMessage(p,et,message)); + } + }; + + // parse the SBML content + final BioModel bioModel; + try (InputStream inputStream = new ByteArrayInputStream(sbml_content.getBytes())) { + // create a SBMLImporter from the XMLSource + boolean validateSBML = true; + SBMLImporter sbmlImporter = new SBMLImporter(inputStream, vclogger, validateSBML); + bioModel = sbmlImporter.getBioModel(); + } + bioModel.updateAll(false); + + // check for errors and warnings + for (LoggerMessage message : messages) { + if (message.priority == VCLogger.Priority.HighPriority) { + throw new RuntimeException("Error: " + message.message); + } else if (message.priority == VCLogger.Priority.MediumPriority) { + System.err.println("Warning: " + message.message); + } + } + + // write the BioModel to a VCML file + String vcml_str = XmlHelper.bioModelToXML(bioModel); + XmlUtil.writeXMLStringToFile(vcml_str, vcmlPath.toFile().getAbsolutePath(), true); + } + + + public static void vcml_to_sbml(String vcml_content, String applicationName, Path sbmlPath) + throws XmlParseException, IOException, XMLStreamException, SbmlException, MappingException { + GeometrySpec.avoidAWTImageCreation = true; + VCMongoMessage.enabled = false; + + BioModel bioModel = XmlHelper.XMLToBioModel(new XMLSource(vcml_content)); + bioModel.updateAll(false); + SimulationContext simContext = bioModel.getSimulationContext(applicationName); + boolean validateSBML = true; + SBMLExporter sbmlExporter = new SBMLExporter(simContext, 3, 1, validateSBML); + String sbml_string = sbmlExporter.getSBMLString(); + XmlUtil.writeXMLStringToFile(sbml_string, sbmlPath.toFile().getAbsolutePath(), true); + } + + public static void vcml_to_vcml(String vcml_content, Path vcmlPath) throws XmlParseException, IOException, MappingException { + GeometrySpec.avoidAWTImageCreation = true; + VCMongoMessage.enabled = false; + + BioModel bioModel = XmlHelper.XMLToBioModel(new XMLSource(vcml_content)); + bioModel.updateAll(false); + // write the BioModel to a VCML file + String vcml_str = XmlHelper.bioModelToXML(bioModel); + XmlUtil.writeXMLStringToFile(vcml_str, vcmlPath.toFile().getAbsolutePath(), true); + } +} diff --git a/vcell-native/src/test/java/org/vcell/libvcell/EntrypointsTest.java b/vcell-native/src/test/java/org/vcell/libvcell/EntrypointsTest.java index 9bec3ba..f93c3c3 100644 --- a/vcell-native/src/test/java/org/vcell/libvcell/EntrypointsTest.java +++ b/vcell-native/src/test/java/org/vcell/libvcell/EntrypointsTest.java @@ -9,7 +9,9 @@ import org.apache.commons.compress.archivers.tar.TarArchiveEntry; import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; import org.junit.jupiter.api.Test; +import org.vcell.sbml.SbmlException; +import javax.xml.stream.XMLStreamException; import java.beans.PropertyVetoException; import java.io.*; import java.nio.charset.StandardCharsets; @@ -23,6 +25,9 @@ import static org.junit.jupiter.api.Assertions.*; import static org.vcell.libvcell.SolverUtils.sbmlToFiniteVolumeInput; import static org.vcell.libvcell.SolverUtils.vcmlToFiniteVolumeInput; +import static org.vcell.libvcell.ModelUtils.sbml_to_vcml; +import static org.vcell.libvcell.ModelUtils.vcml_to_sbml; +import static org.vcell.libvcell.ModelUtils.vcml_to_vcml; public class EntrypointsTest { @@ -107,6 +112,34 @@ public void testVcmlToFiniteVolumeInput() throws SolverException, ExpressionExce assertEquals(4, countFiles(output_dir)); } + @Test + public void test_sbml_to_vcml() throws MappingException, IOException, XmlParseException, VCLoggerException { + String sbmlContent = getFileContentsAsString("/TinySpatialProject_Application0.xml"); + File parent_dir = Files.createTempDirectory("sbmlToVcml").toFile(); + File vcml_temp_file = new File(parent_dir, "temp.vcml"); + sbml_to_vcml(sbmlContent, vcml_temp_file.toPath()); + assert(vcml_temp_file.exists()); + } + + @Test + public void test_vcml_to_sbml() throws MappingException, IOException, XmlParseException, XMLStreamException, SbmlException { + String vcmlContent = getFileContentsAsString("/TinySpatialProject_Application0.vcml"); + File parent_dir = Files.createTempDirectory("vcmlToSbml").toFile(); + File sbml_temp_file = new File(parent_dir, "temp.sbml"); + String applicationName = "unnamed_spatialGeom"; + vcml_to_sbml(vcmlContent, applicationName, sbml_temp_file.toPath()); + assert(sbml_temp_file.exists()); + } + + @Test + public void test_vcml_to_vcml() throws MappingException, IOException, XmlParseException, XMLStreamException, SbmlException { + String vcmlContent = getFileContentsAsString("/TinySpatialProject_Application0.vcml"); + File parent_dir = Files.createTempDirectory("vcmlToVcml").toFile(); + File vcml_temp_file = new File(parent_dir, "temp.vcml"); + vcml_to_vcml(vcmlContent, vcml_temp_file.toPath()); + assert(vcml_temp_file.exists()); + } + @Test public void testVcmlToFiniteVolumeInput_bad_simname() throws IOException { String vcmlContent = getFileContentsAsString("/TinySpatialProject_Application0.vcml");