diff --git a/.github/workflows/on-release-main.yml b/.github/workflows/on-release-main.yml index 99e80ab..bdd2c13 100644 --- a/.github/workflows/on-release-main.yml +++ b/.github/workflows/on-release-main.yml @@ -59,11 +59,16 @@ jobs: mkdir -p dist cp wheelhouse/*.whl dist/ - - name: Publish to PyPI + - name: Publish to PyPI (dry run) run: | poetry config pypi-token.pypi "${{ secrets.PYPI_TOKEN }}" poetry publish --dry-run + + - name: Publish to PyPI (release-only - push to PyPI) + run: | + poetry config pypi-token.pypi "${{ secrets.PYPI_TOKEN }}" poetry publish --skip-existing + if: github.event_name == 'release' - name: Setup tmate if: failure() diff --git a/build.py b/build.py index 6ca8df0..201222d 100644 --- a/build.py +++ b/build.py @@ -30,7 +30,9 @@ def main() -> None: run_command( "java -agentlib:native-image-agent=config-output-dir=target/recording " "-jar target/vcell-native-1.0-SNAPSHOT.jar " - "src/test/resources/TinySpacialProject_Application0.xml " + "src/test/resources/TinySpatialProject_Application0.xml " + "src/test/resources/TinySpatialProject_Application0.vcml " + "Simulation0 " "target/sbml-input", cwd=vcell_native_dir, ) diff --git a/docs/modules.md b/docs/modules.md index 758826d..950dfa7 100644 --- a/docs/modules.md +++ b/docs/modules.md @@ -1,5 +1,21 @@ # Modules -## libvcell.libvcell +## libvcell -::: libvcell.libvcell +::: libvcell + +# Modules + +## libvcell + +::: libvcell + +### Functions + +#### sbml_to_finite_volume_input + +::: libvcell.solver_utils.sbml_to_finite_volume_input + +#### vcml_to_finite_volume_input + +::: libvcell.solver_utils.vcml_to_finite_volume_input diff --git a/libvcell/__init__.py b/libvcell/__init__.py index e69de29..24a29e9 100644 --- a/libvcell/__init__.py +++ b/libvcell/__init__.py @@ -0,0 +1,3 @@ +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"] diff --git a/libvcell/libvcell.py b/libvcell/libvcell.py deleted file mode 100644 index b83c0c1..0000000 --- a/libvcell/libvcell.py +++ /dev/null @@ -1,19 +0,0 @@ -from pathlib import Path - -from libvcell._internal.native_calls import ReturnValue, VCellNativeCalls - - -class libvcell: - @staticmethod - def vcml_to_finite_volume_input(vcml_content: str, simulation_name: str, output_dir_path: Path) -> None: - native = VCellNativeCalls() - return_value: ReturnValue = native.vcml_to_finite_volume_input(vcml_content, simulation_name, output_dir_path) - if not return_value.success: - raise RuntimeError(f"Error in vcml_to_finite_volume_input: {return_value.message}") - - @staticmethod - def sbml_to_finite_volume_input(sbml_content: str, output_dir_path: Path) -> None: - native = VCellNativeCalls() - return_value: ReturnValue = native.sbml_to_finite_volume_input(sbml_content, output_dir_path) - if not return_value.success: - raise RuntimeError(f"Error in sbml_to_finite_volume_input: {return_value.message}") diff --git a/libvcell/solver_utils.py b/libvcell/solver_utils.py new file mode 100644 index 0000000..4d1d2ba --- /dev/null +++ b/libvcell/solver_utils.py @@ -0,0 +1,36 @@ +from pathlib import Path + +from libvcell._internal.native_calls import ReturnValue, VCellNativeCalls + + +def vcml_to_finite_volume_input(vcml_content: str, simulation_name: str, output_dir_path: Path) -> tuple[bool, str]: + """ + Convert VCML content to finite volume input files + + Args: + vcml_content (str): VCML content + simulation_name (str): simulation name + output_dir_path (Path): output directory path + + Returns: + tuple[bool, str]: A tuple containing the success status and a message + """ + native = VCellNativeCalls() + return_value: ReturnValue = native.vcml_to_finite_volume_input(vcml_content, simulation_name, output_dir_path) + return return_value.success, return_value.message + + +def sbml_to_finite_volume_input(sbml_content: str, output_dir_path: Path) -> tuple[bool, str]: + """ + Convert SBML content to finite volume input files + + Args: + sbml_content (str): SBML content + output_dir_path (Path): output directory path + + Returns: + tuple[bool, str]: A tuple containing the success status and a message + """ + native = VCellNativeCalls() + return_value: ReturnValue = native.sbml_to_finite_volume_input(sbml_content, output_dir_path) + return return_value.success, return_value.message diff --git a/pyproject.toml b/pyproject.toml index f2d4fa4..41422a6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] name = "libvcell" -version = "0.0.4" +version = "0.0.5" 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/scripts/build_native.sh b/scripts/local_build_native.sh similarity index 87% rename from scripts/build_native.sh rename to scripts/local_build_native.sh index 23a9eec..5c582ce 100755 --- a/scripts/build_native.sh +++ b/scripts/local_build_native.sh @@ -33,7 +33,9 @@ mvn clean install # run with native-image-agent to record configuration for native-image java -agentlib:native-image-agent=config-output-dir=target/recording \ -jar target/vcell-native-1.0-SNAPSHOT.jar \ - "$ROOT_DIR/vcell-native/src/test/resources/TinySpacialProject_Application0.xml" \ + "$ROOT_DIR/vcell-native/src/test/resources/TinySpatialProject_Application0.xml" \ + "$ROOT_DIR/vcell-native/src/test/resources/TinySpatialProject_Application0.vcml" \ + "Simulation0" \ "$ROOT_DIR/vcell-native/target/sbml-input" # build vcell-native as native shared object library diff --git a/tests/test_libvcell.py b/tests/test_libvcell.py index e86e457..2e770a4 100644 --- a/tests/test_libvcell.py +++ b/tests/test_libvcell.py @@ -1,17 +1,21 @@ from pathlib import Path -from libvcell.libvcell import libvcell +from libvcell import sbml_to_finite_volume_input, vcml_to_finite_volume_input def test_vcml_to_finite_volume_input(temp_output_dir: Path, vcml_file_path: Path, vcml_sim_name: str) -> None: vcml_content = vcml_file_path.read_text() - libvcell.vcml_to_finite_volume_input( + success, msg = vcml_to_finite_volume_input( vcml_content=vcml_content, simulation_name=vcml_sim_name, output_dir_path=temp_output_dir ) assert len(list(temp_output_dir.iterdir())) > 0 + assert success is True + assert msg == "Success" def test_sbml_to_finite_volume_input(temp_output_dir: Path, sbml_file_path: Path, vcml_sim_name: str) -> None: sbml_content = sbml_file_path.read_text() - libvcell.sbml_to_finite_volume_input(sbml_content=sbml_content, output_dir_path=temp_output_dir) + success, msg = sbml_to_finite_volume_input(sbml_content=sbml_content, output_dir_path=temp_output_dir) assert len(list(temp_output_dir.iterdir())) > 0 + assert success is True + assert msg == "Success" diff --git a/vcell-native/pom.xml b/vcell-native/pom.xml index 6d961d6..11b5244 100644 --- a/vcell-native/pom.xml +++ b/vcell-native/pom.xml @@ -23,7 +23,7 @@ ${java.specification.version} ${java.specification.version} libvcell - org.vcell.libvcell.Entrypoints + org.vcell.libvcell.MainRecorder 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 ca9c8a8..0f64d59 100644 --- a/vcell-native/src/main/java/org/vcell/libvcell/Entrypoints.java +++ b/vcell-native/src/main/java/org/vcell/libvcell/Entrypoints.java @@ -1,20 +1,5 @@ package org.vcell.libvcell; -import cbit.util.xml.VCLoggerException; -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.parser.ExpressionException; -import cbit.vcell.resource.PropertyLoader; -import cbit.vcell.solver.Simulation; -import cbit.vcell.solver.SolverException; -import cbit.vcell.solver.TimeBounds; -import cbit.vcell.solver.UniformOutputTimeSpec; -import cbit.vcell.xml.XMLSource; -import cbit.vcell.xml.XmlHelper; -import cbit.vcell.xml.XmlParseException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.graalvm.nativeimage.IsolateThread; @@ -22,17 +7,13 @@ import org.graalvm.nativeimage.c.type.CCharPointer; import org.graalvm.nativeimage.c.type.CTypeConversion; import org.json.simple.JSONValue; -import org.vcell.sbml.FiniteVolumeRunUtil; -import org.vcell.sbml.vcell.SBMLExporter; -import org.vcell.sbml.vcell.SBMLImporter; -import java.beans.PropertyVetoException; -import java.io.ByteArrayInputStream; import java.io.File; -import java.io.FileInputStream; -import java.io.InputStream; import java.util.concurrent.ConcurrentHashMap; +import static org.vcell.libvcell.SolverUtils.sbmlToFiniteVolumeInput; +import static org.vcell.libvcell.SolverUtils.vcmlToFiniteVolumeInput; + public class Entrypoints { private static final Logger logger = LogManager.getLogger(Entrypoints.class); @@ -50,7 +31,7 @@ private static CCharPointer createString(String str) { @CEntryPoint(name = "freeString", documentation = "Release memory allocated for a string") public static void freeString( - IsolateThread thread, + IsolateThread ignoredThread, CCharPointer ptr) { if (ptr.isNonNull()) { allocatedMemory.remove(ptr.rawValue()); @@ -75,7 +56,7 @@ public String toJson() { Returns a JSON string with success status and message""" ) public static CCharPointer entrypoint_vcmlToFiniteVolumeInput( - IsolateThread thread, + IsolateThread ignoredThread, CCharPointer vcml_content, CCharPointer simulation_name, CCharPointer output_dir_path) { @@ -105,7 +86,7 @@ public static CCharPointer entrypoint_vcmlToFiniteVolumeInput( Returns a JSON string with success status and message""" ) public static CCharPointer entrypoint_sbmlToFiniteVolumeInput( - IsolateThread thread, + IsolateThread ignoredThread, CCharPointer sbml_content, CCharPointer output_dir_path) { ReturnValue returnValue; @@ -123,61 +104,4 @@ public static CCharPointer entrypoint_sbmlToFiniteVolumeInput( logger.info("Returning from sbmlToFiniteVolumeInput: " + json); return createString(json); } - - - public static void vcmlToFiniteVolumeInput(String vcml_content, String simulation_name, File outputDir) throws XmlParseException, MappingException, SolverException, ExpressionException { - GeometrySpec.avoidAWTImageCreation = true; - VCMongoMessage.enabled = false; - BioModel bioModel = XmlHelper.XMLToBioModel(new XMLSource(vcml_content)); - bioModel.updateAll(false); - Simulation sim = bioModel.getSimulation(simulation_name); - FiniteVolumeRunUtil.writeInputFilesOnly(outputDir, sim); - } - - - public static void sbmlToFiniteVolumeInput(String sbml_content, File outputDir) throws MappingException, PropertyVetoException, SolverException, ExpressionException, VCLoggerException { - GeometrySpec.avoidAWTImageCreation = true; - VCMongoMessage.enabled = false; - SBMLExporter.MemoryVCLogger vcl = new SBMLExporter.MemoryVCLogger(); - boolean bValidateSBML = true; - // input stream from sbml_content String - InputStream is = new ByteArrayInputStream(sbml_content.getBytes()); - SBMLImporter importer = new SBMLImporter(is, vcl, bValidateSBML); - BioModel bioModel = importer.getBioModel(); - bioModel.updateAll(false); - - final double duration = 5.0; // endpoint arg - final double time_step = 0.1; // endpoint arg - //final ISize meshSize = new ISize(10, 10, 10); // future endpoint arg - SimulationContext simContext = bioModel.getSimulationContext(0); - Simulation sim = new Simulation(simContext.getMathDescription(), simContext); - sim.getSolverTaskDescription().setTimeBounds(new TimeBounds(0.0, duration)); - sim.getSolverTaskDescription().setOutputTimeSpec(new UniformOutputTimeSpec(time_step)); - - FiniteVolumeRunUtil.writeInputFilesOnly(outputDir, sim); - } - - - - // Input Goes as Follows: SBML Input, Output dir - // "/Users/evalencia/Documents/VCell_Repositories/vcell/vcell-rest/src/test/resources/TinySpacialProject_Application0.xml" - // "/Users/evalencia/Documents/VCell_Repositories/vcell/vcell-nativelib/target/sbml-input" - public static void main(String[] args) { - try { - logger.info("Logger logging"); - PropertyLoader.setProperty(PropertyLoader.vcellServerIDProperty, "none"); - PropertyLoader.setProperty(PropertyLoader.mongodbDatabase, "none"); - File sbml_file = new File(args[0]); - // read sbml_file and create a string object - try (FileInputStream fis = new FileInputStream(sbml_file)) { - byte[] data = fis.readAllBytes(); - logger.info("Read " + data.length + " bytes from " + sbml_file); - String sbml_str = new String(data); - sbmlToFiniteVolumeInput(sbml_str, new File(args[1])); - } - } 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/MainRecorder.java b/vcell-native/src/main/java/org/vcell/libvcell/MainRecorder.java new file mode 100644 index 0000000..02d8d71 --- /dev/null +++ b/vcell-native/src/main/java/org/vcell/libvcell/MainRecorder.java @@ -0,0 +1,47 @@ +package org.vcell.libvcell; + +import cbit.vcell.resource.PropertyLoader; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.File; +import java.io.FileInputStream; + +import static org.vcell.libvcell.SolverUtils.sbmlToFiniteVolumeInput; +import static org.vcell.libvcell.SolverUtils.vcmlToFiniteVolumeInput; + + +public class MainRecorder { + private static final Logger logger = LogManager.getLogger(MainRecorder.class); + + public static void main(String[] args) { + try { + 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]); + logger.info("Logger logging"); + PropertyLoader.setProperty(PropertyLoader.vcellServerIDProperty, "none"); + PropertyLoader.setProperty(PropertyLoader.mongodbDatabase, "none"); + + + 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); + sbmlToFiniteVolumeInput(sbml_str, output_dir); + //vcmlToFiniteVolumeInput(vcml_str, sim_name, new File(args[1])); + } + // read sbml_file and create a string object + 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); + vcmlToFiniteVolumeInput(vcml_str, vcml_sim_name, output_dir); + } + } 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/SolverUtils.java b/vcell-native/src/main/java/org/vcell/libvcell/SolverUtils.java new file mode 100644 index 0000000..d80698a --- /dev/null +++ b/vcell-native/src/main/java/org/vcell/libvcell/SolverUtils.java @@ -0,0 +1,72 @@ +package org.vcell.libvcell; + +import cbit.util.xml.VCLoggerException; +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.parser.ExpressionException; +import cbit.vcell.solver.Simulation; +import cbit.vcell.solver.SolverException; +import cbit.vcell.solver.TimeBounds; +import cbit.vcell.solver.UniformOutputTimeSpec; +import cbit.vcell.xml.XMLSource; +import cbit.vcell.xml.XmlHelper; +import cbit.vcell.xml.XmlParseException; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.vcell.sbml.FiniteVolumeRunUtil; +import org.vcell.sbml.vcell.SBMLExporter; +import org.vcell.sbml.vcell.SBMLImporter; + +import java.beans.PropertyVetoException; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.InputStream; + + +public class SolverUtils { + private static final Logger logger = LogManager.getLogger(Entrypoints.class); + + public static void vcmlToFiniteVolumeInput(String vcml_content, String simulation_name, File outputDir) throws XmlParseException, MappingException, SolverException, ExpressionException { + GeometrySpec.avoidAWTImageCreation = true; + VCMongoMessage.enabled = false; + if (vcml_content.substring(0, 300).contains(" sbmlToFiniteVolumeInput(sbmlContent, output_dir.toFile())); + assertEquals( + "class org.sbml.jsbml.xml.XMLNode cannot be cast to class org.sbml.jsbml.Annotation " + + "(org.sbml.jsbml.xml.XMLNode and org.sbml.jsbml.Annotation are in unnamed module of loader 'app')", + exc.getMessage()); + } + + @Test + public void testSbmlToFiniteVolumeInput_vcml_instead() throws IOException { + String sbmlContent = getFileContentsAsString("/TinySpatialProject_Application0.vcml"); + Path output_dir = Files.createTempDirectory("sbmlToFiniteVolumeInput"); + IllegalArgumentException exc = assertThrows(IllegalArgumentException.class, () -> sbmlToFiniteVolumeInput(sbmlContent, output_dir.toFile())); + assertEquals("expecting SBML content, not VCML", exc.getMessage()); + } + + @Test + public void testVcmlToFiniteVolumeInput() throws SolverException, ExpressionException, MappingException, IOException, XmlParseException { + String vcmlContent = getFileContentsAsString("/TinySpatialProject_Application0.vcml"); + Path output_dir = Files.createTempDirectory("vcmlToFiniteVolumeInput"); + String simulationName = "Simulation0"; + vcmlToFiniteVolumeInput(vcmlContent, simulationName, output_dir.toFile()); + } + + @Test + public void testVcmlToFiniteVolumeInput_bad_simname() throws IOException { + String vcmlContent = getFileContentsAsString("/TinySpatialProject_Application0.vcml"); + Path output_dir = Files.createTempDirectory("vcmlToFiniteVolumeInput"); + String simulationName = "wrong_sim_name"; + // expect to throw an IllegalArgumentException + IllegalArgumentException exc = assertThrows(IllegalArgumentException.class, () -> vcmlToFiniteVolumeInput(vcmlContent, simulationName, output_dir.toFile())); + assertEquals("Simulation not found: wrong_sim_name", exc.getMessage()); + } + + @Test + public void testVcmlToFiniteVolumeInput_not_well_formed() throws IOException { + String vcmlContent = getFileContentsAsString("/TinySpatialProject_Application0.vcml") + .replaceAll(" vcmlToFiniteVolumeInput(vcmlContent, simulationName, output_dir.toFile())); + assertEquals( + "source document is not well-formed\n" + + "Error on line 25: The element type \"SimpleReactionXYZ\" must be terminated by the matching end-tag \"\".", + exc.getMessage()); + } + + @Test + public void testVcmlToFiniteVolumeInput_sbml_instead() throws IOException { + String vcmlContent_actually_sbml = getFileContentsAsString("/TinySpatialProject_Application0.xml"); + Path output_dir = Files.createTempDirectory("vcmlToFiniteVolumeInput"); + String simulationName = "wrong_sim_name"; + // expect to throw an IllegalArgumentException + IllegalArgumentException exc = assertThrows(IllegalArgumentException.class, () -> vcmlToFiniteVolumeInput(vcmlContent_actually_sbml, simulationName, output_dir.toFile())); + assertEquals("expecting VCML content, not SBML", exc.getMessage()); } private static String getFileContentsAsString(String filename) throws IOException { @@ -30,9 +95,8 @@ private static String getFileContentsAsString(String filename) throws IOExceptio if (inputStream == null) { throw new FileNotFoundException("file not found! " + filename); } - String textContent = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)) + return new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)) .lines().collect(Collectors.joining("\n")); - return textContent; } } } diff --git a/vcell-native/src/test/resources/TinySpatialProject_Application0.vcml b/vcell-native/src/test/resources/TinySpatialProject_Application0.vcml new file mode 100644 index 0000000..3987717 --- /dev/null +++ b/vcell-native/src/test/resources/TinySpatialProject_Application0.vcml @@ -0,0 +1,152 @@ + + + + + + + 1.0 + 0.5 + + + s0 + + + s1 + + + + + + + + + ((Kf_r0 * s0) - (Kr_r0 * s1)) + + + + + + + + + + + + + + + + + 1.0 + + + + + + + + + + + + + (100000.0 * x) + 1.0E-9 + + + + (10.0 - (1.0 * 100000.0 * x)) + 1.0E-9 + + + + + + 96485.3321 + 9.64853321E-5 + 1.0E-9 + 6.02214179E11 + 3.141592653589793 + 8314.46261815 + 300.0 + 1000.0 + 1.0 + 0.001660538783162726 + 0.5 + 0.0 + 0.0 + 1.0E-9 + 0.0 + 0.0 + 1.0E-9 + 1.0 + + + ((Kf_r0 * s0) - (Kr_r0 * s1)) + (100000.0 * x) + (10.0 - (1.0 * 100000.0 * x)) + (VolumePerUnitVolume_c0 * vcRegionVolume('subdomain0')) + vcRegionVolume('subdomain0') + + + + + + + + + + - J_r0 + s0_diffusionRate + s0_init_umol_l_1 + + + + J_r0 + s1_diffusionRate + s1_init_umol_l_1 + + + + + + + + + + + 2 + + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vcell-native/src/test/resources/TinySpacialProject_Application0.xml b/vcell-native/src/test/resources/TinySpatialProject_Application0.xml similarity index 99% rename from vcell-native/src/test/resources/TinySpacialProject_Application0.xml rename to vcell-native/src/test/resources/TinySpatialProject_Application0.xml index c3b2447..698e2ce 100644 --- a/vcell-native/src/test/resources/TinySpacialProject_Application0.xml +++ b/vcell-native/src/test/resources/TinySpatialProject_Application0.xml @@ -5,7 +5,7 @@

Exported by VCell 7.3

- + diff --git a/vcell_submodule b/vcell_submodule index b333402..e03192e 160000 --- a/vcell_submodule +++ b/vcell_submodule @@ -1 +1 @@ -Subproject commit b3334022fa5592a1e879abb83fbddeb9c36c194e +Subproject commit e03192e882fb958f9136330c24c60ed2ce80d967