Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 0 additions & 14 deletions build.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
9 changes: 8 additions & 1 deletion libvcell/__init__.py
Original file line number Diff line number Diff line change
@@ -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__ = [
Expand All @@ -8,4 +14,5 @@
"vcml_to_sbml",
"vcml_to_vcml",
"vcell_infix_to_python_infix",
"vcell_infix_to_num_expr_infix",
]
38 changes: 38 additions & 0 deletions libvcell/_internal/native_calls.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
8 changes: 8 additions & 0 deletions libvcell/_internal/native_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand Down
16 changes: 16 additions & 0 deletions libvcell/model_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 <schaff@uchc.edu>", "Ezequiel Valencia <evalencia@uchc.edu>"]
repository = "https://github.com/virtualcell/libvcell"
Expand Down
20 changes: 19 additions & 1 deletion tests/test_libvcell.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
36 changes: 35 additions & 1 deletion vcell-native/src/main/java/org/vcell/libvcell/Entrypoints.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
}
}
7 changes: 7 additions & 0 deletions vcell-native/src/main/java/org/vcell/libvcell/ModelUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Loading