Skip to content

Commit fd07035

Browse files
Merge pull request #14 from virtualcell/AddNumExprInfix
Exposed Entrypoints for NumExpr infix generation
2 parents cb05309 + 043085f commit fd07035

11 files changed

Lines changed: 160 additions & 19 deletions

File tree

build.py

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -80,20 +80,6 @@ def main() -> None:
8080
if shared_lib.exists():
8181
shutil.copy(shared_lib, libvcell_lib_dir / f"libvcell.{ext}")
8282

83-
# Copy the shared library to libvcell/lib
84-
copied = False
85-
for ext in ["so", "dylib", "dll"]:
86-
shared_lib = vcell_native_dir / f"target/libvcell.{ext}"
87-
if shared_lib.exists():
88-
shutil.copy(shared_lib, libvcell_lib_dir / f"libvcell.{ext}")
89-
copied = True
90-
print(f"Copied {shared_lib} to {libvcell_lib_dir}")
91-
92-
if not copied:
93-
print(f"ERROR: No shared library found in {vcell_native_dir / 'target'}", file=sys.stderr)
94-
print(f"Contents: {list((vcell_native_dir / 'target').glob('*'))}", file=sys.stderr)
95-
sys.exit(1)
96-
9783

9884
if __name__ == "__main__":
9985
main()

libvcell/__init__.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
from libvcell.model_utils import sbml_to_vcml, vcell_infix_to_python_infix, vcml_to_sbml, vcml_to_vcml
1+
from libvcell.model_utils import (
2+
sbml_to_vcml,
3+
vcell_infix_to_num_expr_infix,
4+
vcell_infix_to_python_infix,
5+
vcml_to_sbml,
6+
vcml_to_vcml,
7+
)
28
from libvcell.solver_utils import sbml_to_finite_volume_input, vcml_to_finite_volume_input
39

410
__all__ = [
@@ -8,4 +14,5 @@
814
"vcml_to_sbml",
915
"vcml_to_vcml",
1016
"vcell_infix_to_python_infix",
17+
"vcell_infix_to_num_expr_infix",
1118
]

libvcell/_internal/native_calls.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,3 +165,41 @@ def vcell_infix_to_python_infix(
165165
except Exception as e:
166166
logging.exception("Error in vcell_infix_to_python_infix()", exc_info=e)
167167
raise
168+
169+
def vcell_infix_to_num_expr_infix(
170+
self, vcell_infix: str, target_num_expr_infix: MutableString, buffer_size: int | None = None
171+
) -> ReturnValue:
172+
try:
173+
needed_buffer_size = int(1.5 * len(vcell_infix)) if buffer_size is None else buffer_size
174+
buff = ctypes.create_string_buffer(needed_buffer_size)
175+
with IsolateManager(self.lib) as isolate_thread:
176+
json_ptr = self.lib.vcellInfixToNumExprInfix(
177+
isolate_thread, ctypes.c_char_p(vcell_infix.encode("utf-8")), buff, needed_buffer_size
178+
)
179+
value: bytes | None = ctypes.cast(json_ptr, ctypes.c_char_p).value
180+
if value is None:
181+
logging.error("Failed to regenerate vcml")
182+
return ReturnValue(success=False, message="Failed to generate NumExpr infix")
183+
json_str = value.decode("utf-8")
184+
if "not enough room, need: `" in json_str:
185+
if buffer_size is not None:
186+
logging.error("Failed to identify correct buffer size reported by previous error")
187+
return ReturnValue(
188+
success=False, message="Failed to identify correct buffer size reported by previous error"
189+
)
190+
# get the size from the error
191+
index = json_str.find("not enough room, need: `") + len("not enough room, need: `")
192+
end_index = json_str.find("`", index)
193+
size_as_string: str = json_str[index:end_index]
194+
if not size_as_string.isnumeric():
195+
logging.error("Buffer size reported by previous error is not an integer!")
196+
return ReturnValue(
197+
success=False, message="Buffer size reported by previous error is not an integer!"
198+
)
199+
return self.vcell_infix_to_num_expr_infix(vcell_infix, target_num_expr_infix, int(size_as_string))
200+
# self.lib.freeString(json_ptr)
201+
target_num_expr_infix.value = buff.value.decode("utf-8")
202+
return ReturnValue.model_validate_json(json_data=json_str)
203+
except Exception as e:
204+
logging.exception("Error in vcell_infix_to_num_expr_infix()", exc_info=e)
205+
raise

libvcell/_internal/native_utils.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,14 @@ def _define_entry_points(self) -> None:
6161
ctypes.c_longlong,
6262
]
6363

64+
self.lib.vcellInfixToNumExprInfix.restype = ctypes.c_char_p
65+
self.lib.vcellInfixToNumExprInfix.argtypes = [
66+
ctypes.c_void_p,
67+
ctypes.c_char_p,
68+
ctypes.c_char_p,
69+
ctypes.c_longlong,
70+
]
71+
6472
self.lib.freeString.restype = None
6573
self.lib.freeString.argtypes = [ctypes.c_char_p]
6674

libvcell/model_utils.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,3 +73,19 @@ def vcell_infix_to_python_infix(vcell_infix: str) -> tuple[bool, str, str]:
7373
target_python_infix = MutableString("")
7474
return_value: ReturnValue = native.vcell_infix_to_python_infix(vcell_infix, target_python_infix)
7575
return return_value.success, return_value.message, target_python_infix.value
76+
77+
78+
def vcell_infix_to_num_expr_infix(vcell_infix: str) -> tuple[bool, str, str]:
79+
"""
80+
Converts an infix string version of a VCell Native Expression, and converts it to a NumExpr compatible version
81+
82+
Args:
83+
vcell_infix (str): the infix to convert
84+
85+
Returns:
86+
tuple[bool, str, str]: A tuple containing the success status, a message, and the converted infix
87+
"""
88+
native = VCellNativeCalls()
89+
target_num_expr_infix = MutableString("")
90+
return_value: ReturnValue = native.vcell_infix_to_num_expr_infix(vcell_infix, target_num_expr_infix)
91+
return return_value.success, return_value.message, target_num_expr_infix.value

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.14.4"
7+
version = "0.0.15"
88
description = "This is a python package which wraps a subset of VCell Java code as a native python package."
99
authors = ["Jim Schaff <schaff@uchc.edu>", "Ezequiel Valencia <evalencia@uchc.edu>"]
1010
repository = "https://github.com/virtualcell/libvcell"

tests/test_libvcell.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from libvcell import (
55
sbml_to_finite_volume_input,
66
sbml_to_vcml,
7+
vcell_infix_to_num_expr_infix,
78
vcell_infix_to_python_infix,
89
vcml_to_finite_volume_input,
910
vcml_to_sbml,
@@ -92,7 +93,24 @@ def test_vcell_infix_to_python_infix() -> None:
9293
assert value == expectedResult
9394

9495

95-
def test_bad_vcell_infix() -> None:
96+
def test_bad_vcell_infix_through_python_conversion() -> None:
9697
vcellInfix = "id_1 / + / /- cos(/ / /) id_2"
9798
success, msg, value = vcell_infix_to_python_infix(vcellInfix)
9899
assert success is False
100+
assert "Parse Error while parsing expression" in msg
101+
102+
103+
def test_vcell_infix_to_num_expr_infix() -> None:
104+
vcell_infix = "(id_2 || 3.2) * id_1 * csc(id_0 ^ 2.2)"
105+
success, msg, value = vcell_infix_to_num_expr_infix(vcell_infix)
106+
expectedResult = "(where(((0.0!=id_2) | (0.0!=3.2)), id_1 * (1.0/sin(((id_0)**(2.2)))), 0.0))"
107+
assert success is True
108+
assert msg == "Success"
109+
assert value == expectedResult
110+
111+
112+
def test_bad_vcell_infix_through_num_expr_conversion() -> None:
113+
vcellInfix = "id_1 / + / /- cos(/ / /) id_2"
114+
success, msg, value = vcell_infix_to_num_expr_infix(vcellInfix)
115+
assert success is False
116+
assert "Parse Error while parsing expression" in msg

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

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,6 @@ public static CCharPointer entrypoint_vcellInfixToPythonInfix(
214214
CCharPointer targetBufferForPythonInfix,
215215
long sizeOfBuffer
216216
){
217-
System.err.println("Entrypoint_vcellInfixToPythonInfix");
218217
ReturnValue returnValue;
219218
try {
220219
String vcellInfix = CTypeConversion.toJavaString(vcellInfixPtr);
@@ -240,4 +239,39 @@ public static CCharPointer entrypoint_vcellInfixToPythonInfix(
240239
return createString(json);
241240
}
242241

242+
@CEntryPoint(
243+
name = "vcellInfixToNumExprInfix",
244+
documentation = """
245+
converts a vcell infix into a NumExpr-safe version"""
246+
)
247+
public static CCharPointer entrypoint_vcellInfixToNumExprInfix(
248+
IsolateThread ignoredThread,
249+
CCharPointer vcellInfixPtr,
250+
CCharPointer targetBufferForConvertedInfix,
251+
long sizeOfBuffer
252+
){
253+
ReturnValue returnValue;
254+
try {
255+
String vcellInfix = CTypeConversion.toJavaString(vcellInfixPtr);
256+
String numExprInfix = get_numexpr_infix(vcellInfix);
257+
if (numExprInfix.length() >= sizeOfBuffer){
258+
// not enough room
259+
returnValue = new ReturnValue(false, "not enough room, need: `" + numExprInfix.length() + 1 + "`");
260+
} else {
261+
CTypeConversion.toCString(
262+
numExprInfix,
263+
targetBufferForConvertedInfix,
264+
WordFactory.unsigned(numExprInfix.length() + 1)
265+
);
266+
returnValue = new ReturnValue(true, "Success");
267+
}
268+
} catch (Throwable t) {
269+
logger.error("Error translating vcell infix to NumExpr infix", t);
270+
returnValue = new ReturnValue(false, t.getMessage());
271+
}
272+
// return result as a json string
273+
String json = returnValue.toJson();
274+
logger.info("Returning from vcellInfixToNumExprInfix: " + json);
275+
return createString(json);
276+
}
243277
}

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,4 +133,11 @@ public static String get_python_infix(String vcellInfix) throws ExpressionExcept
133133
VCMongoMessage.enabled = false;
134134
return new Expression(vcellInfix).infix_Python();
135135
}
136+
137+
public static String get_numexpr_infix(String vcellInfix) throws ExpressionException {
138+
GeometrySpec.avoidAWTImageCreation = true;
139+
XmlHelper.cloneUsingXML = true;
140+
VCMongoMessage.enabled = false;
141+
return new Expression(vcellInfix).infix_NumExpr();
142+
}
136143
}

vcell-native/src/test/java/org/vcell/libvcell/ModelEntrypointsTest.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,4 +87,31 @@ public void test_bad_python_infix_attempt(){
8787
assert(false);
8888
}
8989

90+
@Test
91+
public void test_get_num_expr_infix(){
92+
String vcellInfix = "(id_2 || 3.2) * id_1 * csc(id_0 ^ 2.2)";
93+
String convertedInfix;
94+
try {
95+
convertedInfix = get_numexpr_infix(vcellInfix);
96+
} catch (ExpressionException e) {
97+
System.err.println("get_python_infix exception: " + e.getMessage());
98+
assert(false);
99+
return;
100+
}
101+
String expected = "(where(((0.0!=id_2) | (0.0!=3.2)), id_1 * (1.0/sin(((id_0)**(2.2)))), 0.0))";
102+
assert expected.equals(convertedInfix);
103+
}
104+
105+
@Test
106+
public void test_bad_num_expr_infix_attempt(){
107+
String vcellInfix = "id_1 / + / /- cos(/ / /) id_2";
108+
String convertedInfix;
109+
try {
110+
convertedInfix = get_numexpr_infix(vcellInfix);
111+
} catch (ExpressionException e) {
112+
return; // this is what we'd expect
113+
}
114+
System.err.println("test_bad_python_infix_attempt did not throw an exception");
115+
assert(false);
116+
}
90117
}

0 commit comments

Comments
 (0)