diff --git a/build.py b/build.py index f615286..afe1bfa 100644 --- a/build.py +++ b/build.py @@ -80,20 +80,6 @@ def main() -> None: if shared_lib.exists(): shutil.copy(shared_lib, libvcell_lib_dir / f"libvcell.{ext}") - # Copy the shared library to libvcell/lib - copied = False - for ext in ["so", "dylib", "dll"]: - shared_lib = vcell_native_dir / f"target/libvcell.{ext}" - if shared_lib.exists(): - shutil.copy(shared_lib, libvcell_lib_dir / f"libvcell.{ext}") - copied = True - print(f"Copied {shared_lib} to {libvcell_lib_dir}") - - if not copied: - print(f"ERROR: No shared library found in {vcell_native_dir / 'target'}", file=sys.stderr) - print(f"Contents: {list((vcell_native_dir / 'target').glob('*'))}", file=sys.stderr) - sys.exit(1) - if __name__ == "__main__": main() diff --git a/libvcell/__init__.py b/libvcell/__init__.py index 819f52f..d5a7bbd 100644 --- a/libvcell/__init__.py +++ b/libvcell/__init__.py @@ -1,4 +1,10 @@ -from libvcell.model_utils import sbml_to_vcml, vcell_infix_to_python_infix, vcml_to_sbml, vcml_to_vcml +from libvcell.model_utils import ( + sbml_to_vcml, + vcell_infix_to_num_expr_infix, + vcell_infix_to_python_infix, + vcml_to_sbml, + vcml_to_vcml, +) from libvcell.solver_utils import sbml_to_finite_volume_input, vcml_to_finite_volume_input __all__ = [ @@ -8,4 +14,5 @@ "vcml_to_sbml", "vcml_to_vcml", "vcell_infix_to_python_infix", + "vcell_infix_to_num_expr_infix", ] diff --git a/libvcell/_internal/native_calls.py b/libvcell/_internal/native_calls.py index 1007364..7338ffe 100644 --- a/libvcell/_internal/native_calls.py +++ b/libvcell/_internal/native_calls.py @@ -165,3 +165,41 @@ def vcell_infix_to_python_infix( except Exception as e: logging.exception("Error in vcell_infix_to_python_infix()", exc_info=e) raise + + def vcell_infix_to_num_expr_infix( + self, vcell_infix: str, target_num_expr_infix: MutableString, buffer_size: int | None = None + ) -> ReturnValue: + try: + needed_buffer_size = int(1.5 * len(vcell_infix)) if buffer_size is None else buffer_size + buff = ctypes.create_string_buffer(needed_buffer_size) + with IsolateManager(self.lib) as isolate_thread: + json_ptr = self.lib.vcellInfixToNumExprInfix( + isolate_thread, ctypes.c_char_p(vcell_infix.encode("utf-8")), buff, needed_buffer_size + ) + 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 generate NumExpr infix") + json_str = value.decode("utf-8") + if "not enough room, need: `" in json_str: + if buffer_size is not None: + logging.error("Failed to identify correct buffer size reported by previous error") + return ReturnValue( + success=False, message="Failed to identify correct buffer size reported by previous error" + ) + # get the size from the error + index = json_str.find("not enough room, need: `") + len("not enough room, need: `") + end_index = json_str.find("`", index) + size_as_string: str = json_str[index:end_index] + if not size_as_string.isnumeric(): + logging.error("Buffer size reported by previous error is not an integer!") + return ReturnValue( + success=False, message="Buffer size reported by previous error is not an integer!" + ) + return self.vcell_infix_to_num_expr_infix(vcell_infix, target_num_expr_infix, int(size_as_string)) + # self.lib.freeString(json_ptr) + target_num_expr_infix.value = buff.value.decode("utf-8") + return ReturnValue.model_validate_json(json_data=json_str) + except Exception as e: + logging.exception("Error in vcell_infix_to_num_expr_infix()", exc_info=e) + raise diff --git a/libvcell/_internal/native_utils.py b/libvcell/_internal/native_utils.py index 3208b66..1b9bdaa 100644 --- a/libvcell/_internal/native_utils.py +++ b/libvcell/_internal/native_utils.py @@ -61,6 +61,14 @@ def _define_entry_points(self) -> None: ctypes.c_longlong, ] + self.lib.vcellInfixToNumExprInfix.restype = ctypes.c_char_p + self.lib.vcellInfixToNumExprInfix.argtypes = [ + ctypes.c_void_p, + ctypes.c_char_p, + ctypes.c_char_p, + ctypes.c_longlong, + ] + 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 index ef61e7e..69dc2a5 100644 --- a/libvcell/model_utils.py +++ b/libvcell/model_utils.py @@ -73,3 +73,19 @@ def vcell_infix_to_python_infix(vcell_infix: str) -> tuple[bool, str, str]: target_python_infix = MutableString("") return_value: ReturnValue = native.vcell_infix_to_python_infix(vcell_infix, target_python_infix) return return_value.success, return_value.message, target_python_infix.value + + +def vcell_infix_to_num_expr_infix(vcell_infix: str) -> tuple[bool, str, str]: + """ + Converts an infix string version of a VCell Native Expression, and converts it to a NumExpr compatible version + + Args: + vcell_infix (str): the infix to convert + + Returns: + tuple[bool, str, str]: A tuple containing the success status, a message, and the converted infix + """ + native = VCellNativeCalls() + target_num_expr_infix = MutableString("") + return_value: ReturnValue = native.vcell_infix_to_num_expr_infix(vcell_infix, target_num_expr_infix) + return return_value.success, return_value.message, target_num_expr_infix.value diff --git a/pyproject.toml b/pyproject.toml index 7b3af3d..e78e835 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] name = "libvcell" -version = "0.0.14.4" +version = "0.0.15" 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/test_libvcell.py b/tests/test_libvcell.py index cd381bd..fb876da 100644 --- a/tests/test_libvcell.py +++ b/tests/test_libvcell.py @@ -4,6 +4,7 @@ from libvcell import ( sbml_to_finite_volume_input, sbml_to_vcml, + vcell_infix_to_num_expr_infix, vcell_infix_to_python_infix, vcml_to_finite_volume_input, vcml_to_sbml, @@ -92,7 +93,24 @@ def test_vcell_infix_to_python_infix() -> None: assert value == expectedResult -def test_bad_vcell_infix() -> None: +def test_bad_vcell_infix_through_python_conversion() -> None: vcellInfix = "id_1 / + / /- cos(/ / /) id_2" success, msg, value = vcell_infix_to_python_infix(vcellInfix) assert success is False + assert "Parse Error while parsing expression" in msg + + +def test_vcell_infix_to_num_expr_infix() -> None: + vcell_infix = "(id_2 || 3.2) * id_1 * csc(id_0 ^ 2.2)" + success, msg, value = vcell_infix_to_num_expr_infix(vcell_infix) + expectedResult = "(where(((0.0!=id_2) | (0.0!=3.2)), id_1 * (1.0/sin(((id_0)**(2.2)))), 0.0))" + assert success is True + assert msg == "Success" + assert value == expectedResult + + +def test_bad_vcell_infix_through_num_expr_conversion() -> None: + vcellInfix = "id_1 / + / /- cos(/ / /) id_2" + success, msg, value = vcell_infix_to_num_expr_infix(vcellInfix) + assert success is False + assert "Parse Error while parsing expression" in msg 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 eeb11a0..978ccc7 100644 --- a/vcell-native/src/main/java/org/vcell/libvcell/Entrypoints.java +++ b/vcell-native/src/main/java/org/vcell/libvcell/Entrypoints.java @@ -214,7 +214,6 @@ public static CCharPointer entrypoint_vcellInfixToPythonInfix( CCharPointer targetBufferForPythonInfix, long sizeOfBuffer ){ - System.err.println("Entrypoint_vcellInfixToPythonInfix"); ReturnValue returnValue; try { String vcellInfix = CTypeConversion.toJavaString(vcellInfixPtr); @@ -240,4 +239,39 @@ public static CCharPointer entrypoint_vcellInfixToPythonInfix( return createString(json); } + @CEntryPoint( + name = "vcellInfixToNumExprInfix", + documentation = """ + converts a vcell infix into a NumExpr-safe version""" + ) + public static CCharPointer entrypoint_vcellInfixToNumExprInfix( + IsolateThread ignoredThread, + CCharPointer vcellInfixPtr, + CCharPointer targetBufferForConvertedInfix, + long sizeOfBuffer + ){ + ReturnValue returnValue; + try { + String vcellInfix = CTypeConversion.toJavaString(vcellInfixPtr); + String numExprInfix = get_numexpr_infix(vcellInfix); + if (numExprInfix.length() >= sizeOfBuffer){ + // not enough room + returnValue = new ReturnValue(false, "not enough room, need: `" + numExprInfix.length() + 1 + "`"); + } else { + CTypeConversion.toCString( + numExprInfix, + targetBufferForConvertedInfix, + WordFactory.unsigned(numExprInfix.length() + 1) + ); + returnValue = new ReturnValue(true, "Success"); + } + } catch (Throwable t) { + logger.error("Error translating vcell infix to NumExpr infix", t); + returnValue = new ReturnValue(false, t.getMessage()); + } + // return result as a json string + String json = returnValue.toJson(); + logger.info("Returning from vcellInfixToNumExprInfix: " + json); + return createString(json); + } } diff --git a/vcell-native/src/main/java/org/vcell/libvcell/ModelUtils.java b/vcell-native/src/main/java/org/vcell/libvcell/ModelUtils.java index a1a960b..d200bc5 100644 --- a/vcell-native/src/main/java/org/vcell/libvcell/ModelUtils.java +++ b/vcell-native/src/main/java/org/vcell/libvcell/ModelUtils.java @@ -133,4 +133,11 @@ public static String get_python_infix(String vcellInfix) throws ExpressionExcept VCMongoMessage.enabled = false; return new Expression(vcellInfix).infix_Python(); } + + public static String get_numexpr_infix(String vcellInfix) throws ExpressionException { + GeometrySpec.avoidAWTImageCreation = true; + XmlHelper.cloneUsingXML = true; + VCMongoMessage.enabled = false; + return new Expression(vcellInfix).infix_NumExpr(); + } } diff --git a/vcell-native/src/test/java/org/vcell/libvcell/ModelEntrypointsTest.java b/vcell-native/src/test/java/org/vcell/libvcell/ModelEntrypointsTest.java index 2cc0614..220a87f 100644 --- a/vcell-native/src/test/java/org/vcell/libvcell/ModelEntrypointsTest.java +++ b/vcell-native/src/test/java/org/vcell/libvcell/ModelEntrypointsTest.java @@ -87,4 +87,31 @@ public void test_bad_python_infix_attempt(){ assert(false); } + @Test + public void test_get_num_expr_infix(){ + String vcellInfix = "(id_2 || 3.2) * id_1 * csc(id_0 ^ 2.2)"; + String convertedInfix; + try { + convertedInfix = get_numexpr_infix(vcellInfix); + } catch (ExpressionException e) { + System.err.println("get_python_infix exception: " + e.getMessage()); + assert(false); + return; + } + String expected = "(where(((0.0!=id_2) | (0.0!=3.2)), id_1 * (1.0/sin(((id_0)**(2.2)))), 0.0))"; + assert expected.equals(convertedInfix); + } + + @Test + public void test_bad_num_expr_infix_attempt(){ + String vcellInfix = "id_1 / + / /- cos(/ / /) id_2"; + String convertedInfix; + try { + convertedInfix = get_numexpr_infix(vcellInfix); + } catch (ExpressionException e) { + return; // this is what we'd expect + } + System.err.println("test_bad_python_infix_attempt did not throw an exception"); + assert(false); + } } diff --git a/vcell_submodule b/vcell_submodule index 204cea5..c831b9e 160000 --- a/vcell_submodule +++ b/vcell_submodule @@ -1 +1 @@ -Subproject commit 204cea53a470135534e6530033027b98ec2753a1 +Subproject commit c831b9eb8d1f8cc98997f8eab86847db8e3bc6e7