Skip to content

Commit c831b9e

Browse files
Merge pull request #1649 from virtualcell/AddNumExprInfixGeneration
Added NumExprCapability to VCell Expressions
2 parents 17f9cf3 + 534a40c commit c831b9e

File tree

11 files changed

+232
-61
lines changed

11 files changed

+232
-61
lines changed

vcell-math/src/main/java/cbit/vcell/parser/ASTAndNode.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,14 @@ public String infixString(int lang)
178178
buffer.append(jjtGetChild(i).infixString(lang));
179179
}
180180
buffer.append("))");
181+
}else if (lang == LANGUAGE_NUM_EXPR) {
182+
int numChildren = jjtGetNumChildren();
183+
for (int i=0;i<numChildren;i++){
184+
if (i>0) buffer.append(" & ");
185+
buffer.append("(0.0!=");
186+
buffer.append(jjtGetChild(i).infixString(lang));
187+
buffer.append(")");
188+
}
181189
}else{
182190
for (int i=0;i<jjtGetNumChildren();i++){
183191
if (i>0) {

vcell-math/src/main/java/cbit/vcell/parser/ASTFloatNode.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,11 @@ public String infixString(int lang)
146146
if (value == Double.POSITIVE_INFINITY) return "float('inf')";
147147
if (value == Double.NEGATIVE_INFINITY) return "float('-inf')";
148148
return value.toString();
149-
} else {
149+
} else if (lang == LANGUAGE_NUM_EXPR) {
150+
if (value == Double.POSITIVE_INFINITY) return "inf";
151+
if (value == Double.NEGATIVE_INFINITY) return "-inf";
152+
return value.toString();
153+
} else {
150154
return value.toString();
151155
}
152156
}

vcell-math/src/main/java/cbit/vcell/parser/ASTFuncNode.java

Lines changed: 100 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,29 @@ public final String getPythonTranslation() {
134134
default -> "math." + getName();
135135
};
136136
}
137+
138+
public final String getNumExprTranslation() {
139+
return switch (this){
140+
case USERDEFINED -> throw new IllegalArgumentException("User defined functions have nothing to do with standard python names.");
141+
case ABS -> "abs";
142+
case MAX -> "maximum";
143+
case MIN -> "minimum";
144+
case CSC -> "1.0/" + SIN.getNumExprTranslation();
145+
case COT -> "1.0/" + TAN.getNumExprTranslation();
146+
case SEC -> "1.0/" + COS.getNumExprTranslation();
147+
case ASIN -> "arcsin";
148+
case ACOS -> "arccos";
149+
case ATAN -> "arctan";
150+
case ATAN2 -> "arctan2";
151+
case CSCH -> "1.0/" + SINH.getNumExprTranslation();
152+
case COTH -> "1.0/" + TANH.getNumExprTranslation();
153+
case SECH -> "1.0/" + COSH.getNumExprTranslation();
154+
case LOGBASE -> "1.0/" + LOG.getNumExprTranslation();
155+
case ACSC, ACOT, ASEC, ACSCH, ACOTH, ASECH, FACTORIAL -> throw new UnsupportedOperationException("Python has no equivalent name.");
156+
default -> getName();
157+
};
158+
}
159+
137160
public final String getHtmlDescription() {
138161
return htmlDescription;
139162
}
@@ -2389,12 +2412,28 @@ String getMathMLName() {
23892412
}
23902413

23912414
String getPythonName(){
2392-
if (funcType == FunctionType.USERDEFINED){
2415+
return getPythonName(funcType);
2416+
}
2417+
2418+
// This function is used when we need to "override" `funcType` to get the correct answer
2419+
private String getPythonName(ASTFuncNode.FunctionType functionType){
2420+
if (functionType == FunctionType.USERDEFINED){
23932421
return funcName;
23942422
}
2395-
return funcType.getPythonTranslation();
2423+
return functionType.getPythonTranslation();
23962424
}
23972425

2426+
String getNumExprName(){
2427+
return getNumExprName(funcType);
2428+
}
2429+
2430+
// This function is used when we need to "override" `funcType` to get the correct answer
2431+
private String getNumExprName(ASTFuncNode.FunctionType functionType){
2432+
if (functionType == FunctionType.USERDEFINED){
2433+
return funcName;
2434+
}
2435+
return functionType.getNumExprTranslation();
2436+
}
23982437

23992438
/**
24002439
* Insert the method's description here.
@@ -2455,7 +2494,7 @@ public String infixString(int lang) {
24552494
buffer.append(",");
24562495
buffer.append("((double)(" + jjtGetChild(1).infixString(lang) + "))");
24572496
buffer.append(")");
2458-
} else if (lang == LANGUAGE_PYTHON) {
2497+
} else if (lang == LANGUAGE_PYTHON || lang == LANGUAGE_NUM_EXPR) {
24592498
buffer.append("((");
24602499
buffer.append(jjtGetChild(0).infixString(lang));
24612500
buffer.append(")**(");
@@ -2485,48 +2524,89 @@ public String infixString(int lang) {
24852524
buffer.append(jjtGetChild(0).infixString(lang));
24862525
buffer.append("))");
24872526
break;
2527+
} else if (lang == LANGUAGE_NUM_EXPR){
2528+
// Need parenthesis to encapsulate the "1.0/x" operation
2529+
buffer.append("(");
2530+
buffer.append(getNumExprName());
2531+
buffer.append("(");
2532+
buffer.append(jjtGetChild(0).infixString(lang));
2533+
buffer.append("))");
2534+
break;
24882535
}
24892536
// DO NOT PUT A "BREAK" HERE! We want to "fall through" to default!
24902537
}
24912538
case ACSC:
2492-
case ACOT:
24932539
case ASEC:
24942540
case ACSCH:
24952541
case ACOTH:
24962542
case ASECH:{
2497-
if (lang == LANGUAGE_PYTHON){ // Need parenthesis to encapsulate the "1.0/x" operation
2543+
if (lang == LANGUAGE_PYTHON){
2544+
// Need parenthesis to encapsulate the "1.0/x" operation
24982545
Map<FunctionType, FunctionType> inversionMap = Map.of(
24992546
FunctionType.ACSC, FunctionType.ASIN,
2500-
FunctionType.ACOT, FunctionType.ATAN,
25012547
FunctionType.ASEC, FunctionType.ACOS,
25022548
FunctionType.ACSCH, FunctionType.ASINH,
25032549
FunctionType.ACOTH, FunctionType.ATANH,
25042550
FunctionType.ASECH, FunctionType.ACOSH
25052551
);
25062552
FunctionType inversionType = inversionMap.get(funcType);
25072553
buffer.append("(");
2508-
buffer.append(getPythonName());
2554+
buffer.append(getPythonName(inversionType));
2555+
buffer.append("(1.0/(");
2556+
buffer.append(jjtGetChild(0).infixString(lang));
2557+
buffer.append(")))");
2558+
break;
2559+
} else if (lang == LANGUAGE_NUM_EXPR){
2560+
// Need parenthesis to encapsulate the "1.0/x" operation
2561+
Map<FunctionType, FunctionType> inversionMap = Map.of(
2562+
FunctionType.ACSC, FunctionType.ASIN,
2563+
FunctionType.ASEC, FunctionType.ACOS,
2564+
FunctionType.ACSCH, FunctionType.ASINH,
2565+
FunctionType.ACOTH, FunctionType.ATANH,
2566+
FunctionType.ASECH, FunctionType.ACOSH
2567+
);
2568+
FunctionType inversionType = inversionMap.get(funcType);
2569+
buffer.append("(");
2570+
buffer.append(getNumExprName(inversionType));
25092571
buffer.append("(1.0/(");
25102572
buffer.append(jjtGetChild(0).infixString(lang));
25112573
buffer.append(")))");
25122574
break;
25132575
}
25142576
// DO NOT PUT A "BREAK" HERE! We want to "fall through" to default!
25152577
}
2578+
case ACOT:{
2579+
if (lang == LANGUAGE_PYTHON){
2580+
buffer.append("(math.pi/2.0 - ");
2581+
buffer.append(getPythonName(FunctionType.ATAN));
2582+
buffer.append("(");
2583+
buffer.append(jjtGetChild(0).infixString(lang));
2584+
buffer.append("))");
2585+
break;
2586+
} else if (lang == LANGUAGE_NUM_EXPR){
2587+
buffer.append(String.format("((%s/2.0) - ", Math.PI));
2588+
buffer.append(getNumExprName(FunctionType.ATAN));
2589+
buffer.append("(");
2590+
buffer.append(jjtGetChild(0).infixString(lang));
2591+
buffer.append("))");
2592+
break;
2593+
}
2594+
}
25162595
case FACTORIAL:
25172596
if (lang == LANGUAGE_PYTHON){
25182597
String name = getPythonName();
2519-
// Since VCell now only accepts integer-values for factorial, integer casting should be safe
2520-
buffer.append(name).append("(int(");
2598+
// Since VCell now only accepts integer-values for factorial,
2599+
// Before we used casting, but there's a chance after math python can generate a different round off error.
2600+
// Since we know VCell only accepts integer, we're safe using `round` instead of just `int`
2601+
buffer.append(name).append("(round(");
25212602
if (1 < this.jjtGetNumChildren()) throw new UnsupportedOperationException("Cannot take factorial of multiple children");
25222603
buffer.append(jjtGetChild(0).infixString(lang));
25232604
buffer.append("))");
25242605
break;
25252606
}
25262607
// DO NOT PUT A "BREAK" HERE! We want to "fall through" to default!
25272608
default:{
2528-
boolean needPythonicVersionOfName = lang == LANGUAGE_PYTHON && !funcType.equals(FunctionType.USERDEFINED);
2529-
String name = needPythonicVersionOfName ? getPythonName(): getName();
2609+
String name = getAppropriateName(lang);
25302610
buffer.append(name + "(");
25312611
for (int i=0;i<jjtGetNumChildren();i++){
25322612
if (i>0) buffer.append(", ");
@@ -2545,6 +2625,15 @@ public String infixString(int lang) {
25452625

25462626
}
25472627

2628+
private String getAppropriateName(int language){
2629+
if (FunctionType.USERDEFINED.equals(funcType)) return getName();
2630+
return switch (language){
2631+
case LANGUAGE_PYTHON -> getPythonName();
2632+
case LANGUAGE_NUM_EXPR -> getNumExprName();
2633+
default -> getName();
2634+
};
2635+
}
2636+
25482637
/**
25492638
* Insert the method's description here.
25502639
* Creation date: (6/20/01 11:04:41 AM)

vcell-math/src/main/java/cbit/vcell/parser/ASTMultNode.java

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,7 @@ public String infixString(int lang){
416416

417417
buffer.append("(");
418418

419-
if (bAllBoolean || bNoBoolean || (lang != SimpleNode.LANGUAGE_C && lang != SimpleNode.LANGUAGE_VISIT && lang != SimpleNode.LANGUAGE_PYTHON)) { // old way
419+
if (bAllBoolean || bNoBoolean || (lang != SimpleNode.LANGUAGE_C && lang != SimpleNode.LANGUAGE_VISIT && lang != SimpleNode.LANGUAGE_PYTHON&& lang != SimpleNode.LANGUAGE_NUM_EXPR)) { // old way
420420
for (int i=0;i<jjtGetNumChildren();i++){
421421
if (jjtGetChild(i) instanceof ASTInvertTermNode){
422422
// bAllBoolean must be false here!
@@ -430,9 +430,15 @@ public String infixString(int lang){
430430
if (i>0){
431431
if (lang == SimpleNode.LANGUAGE_MATLAB){
432432
buffer.append(" .* ");
433-
} else if(lang == SimpleNode.LANGUAGE_PYTHON && bAllBoolean){
434-
buffer.append(" and ");
435-
} else {
433+
} else if (bAllBoolean){
434+
if (lang == SimpleNode.LANGUAGE_PYTHON){
435+
buffer.append(" and ");
436+
} else if (lang == SimpleNode.LANGUAGE_NUM_EXPR) {
437+
buffer.append(" & ");
438+
} else {
439+
buffer.append(" * ");
440+
}
441+
} else { // mixed or no-boolean, and not matlab
436442
buffer.append(" * ");
437443
}
438444
}
@@ -447,6 +453,8 @@ public String infixString(int lang){
447453
if (conditionBuffer.length() > 0) {
448454
if (lang == SimpleNode.LANGUAGE_PYTHON){
449455
conditionBuffer.append(" and ");
456+
} else if (lang == SimpleNode.LANGUAGE_NUM_EXPR) {
457+
conditionBuffer.append(" & ");
450458
} else {
451459
conditionBuffer.append(" && ");
452460
}
@@ -476,6 +484,8 @@ public String infixString(int lang){
476484
}
477485
}else if (lang == SimpleNode.LANGUAGE_PYTHON) {
478486
buffer.append("(").append(valueBuffer).append(") if (").append(conditionBuffer).append(") else 0.0");
487+
}else if (lang == SimpleNode.LANGUAGE_NUM_EXPR) {
488+
buffer.append("where(").append(conditionBuffer).append(", ").append(valueBuffer).append(", 0.0)");
479489
}else{
480490
buffer.append("((" + conditionBuffer + ") ? (" + valueBuffer + ") : 0.0)");
481491
}

vcell-math/src/main/java/cbit/vcell/parser/ASTNotNode.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,19 +136,23 @@ public Node flatten(boolean substituteConstants) throws ExpressionException {
136136
public String infixString(int language) {
137137
StringBuffer buffer = new StringBuffer();
138138
boolean parentIsLogical = parent != null && parent.isLogical();
139+
boolean childIsLogical = jjtGetChild(0).isLogical();
139140
if (language == LANGUAGE_VISIT){
140141
buffer.append("not(");
141142
}else if(language == LANGUAGE_PYTHON){
142143
if (parentIsLogical) buffer.append("not(");
143144
else buffer.append("float(not(");
145+
}else if(language == LANGUAGE_NUM_EXPR){
146+
if (childIsLogical) buffer.append("~(");
147+
else buffer.append("(0.0==(");
144148
}else if (language == LANGUAGE_ECLiPSe){
145149
buffer.append("neg(");
146150
}else{
147151
buffer.append("!(");
148152
}
149153
buffer.append(jjtGetChild(0).infixString(language));
150154
buffer.append(")");
151-
if (language == LANGUAGE_PYTHON && !parentIsLogical) buffer.append(")");
155+
if ((language == LANGUAGE_PYTHON && !parentIsLogical) || (language == LANGUAGE_NUM_EXPR && !childIsLogical)) buffer.append(")");
152156

153157
return buffer.toString();
154158
}

vcell-math/src/main/java/cbit/vcell/parser/ASTOrNode.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,14 @@ public String infixString(int lang)
180180
buffer.append(jjtGetChild(i).infixString(lang));
181181
}
182182
buffer.append("))");
183+
}else if (lang == LANGUAGE_NUM_EXPR) {
184+
int numChildren = jjtGetNumChildren();
185+
for (int i=0;i<numChildren;i++){
186+
if (i>0) buffer.append(" | ");
187+
buffer.append("(0.0!=");
188+
buffer.append(jjtGetChild(i).infixString(lang));
189+
buffer.append(")");
190+
}
183191
}else{
184192
for (int i=0;i<jjtGetNumChildren();i++){
185193
if (i>0) {

vcell-math/src/main/java/cbit/vcell/parser/ASTPowerNode.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ public String infixString(int lang){
260260
buffer.append(" ^ ");
261261
buffer.append(jjtGetChild(1).infixString(lang));
262262
buffer.append(")");
263-
} else if (lang == LANGUAGE_PYTHON) {
263+
} else if (lang == LANGUAGE_PYTHON || lang == LANGUAGE_NUM_EXPR) {
264264
buffer.append("((");
265265
buffer.append(jjtGetChild(0).infixString(lang));
266266
buffer.append(")**(");

vcell-math/src/main/java/cbit/vcell/parser/Expression.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -707,6 +707,10 @@ public String infix_Matlab()
707707
public String infix_Python() {
708708
return rootNode == null ? null : rootNode.infixString(SimpleNode.LANGUAGE_PYTHON);
709709
}
710+
711+
public String infix_NumExpr() {
712+
return rootNode == null ? null : rootNode.infixString(SimpleNode.LANGUAGE_NUM_EXPR);
713+
}
710714
/**
711715
* This method was created by a SmartGuide.
712716
* @return cbit.vcell.model.Expression

vcell-math/src/main/java/cbit/vcell/parser/SimpleNode.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ public abstract class SimpleNode implements Node, java.io.Serializable {
3232
final static int LANGUAGE_UNITS = 6;
3333
final static int LANGUAGE_BNGL = 7;
3434
final static int LANGUAGE_PYTHON = 8;
35+
final static int LANGUAGE_NUM_EXPR = 9;
3536

3637
public SimpleNode(int i) {
3738
id = i;

vcell-math/src/test/java/cbit/vcell/parser/ExpressionTest.java

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import cbit.vcell.parser.SymbolTableFunctionEntry.FunctionArgType;
55
import cbit.vcell.units.VCUnitSystem;
66
import net.sourceforge.interval.ia_math.RealInterval;
7+
import org.apache.commons.math4.core.jdkmath.AccurateMath;
78
import org.junit.jupiter.api.Assertions;
89
import org.junit.jupiter.api.BeforeEach;
910
import org.junit.jupiter.api.BeforeAll;
@@ -29,7 +30,7 @@
2930

3031
@Tag("Fast")
3132
public class ExpressionTest {
32-
private static final boolean FAIL_ON_FIRST = true;
33+
private static final boolean FAIL_ON_FIRST = false;
3334
private static final boolean ENSURE_SAME_SEED = true; // change me
3435
private static final long SAME_SEED = 1772035787937L;
3536
private static File tempDir;
@@ -47,26 +48,29 @@ public class ExpressionTest {
4748
// }
4849

4950
private static Set<String> skipInfixList = new HashSet<>(List.of(
51+
"acoth(min(cot( - id_0), floor((0.8708964264563003 * 0.29826500971825554 * 0.6457243719944876))))",
5052
"floor((pow(asinh(0.10118387985713251),cos(id_0)) * !(cot(id_0)) * min(csch(0.9837650351250335), coth(id_6))))",
5153
" - (min(cot(id_0), csch(0.36737356253208997)) * atan2((0.7233117778683602 >= 0.9759958353333693), (0.1299551891086368 * id_7 * 0.7165699878023953)) / abs(id_1))",
5254
"sech( - (coth(0.028177547796530367) ^ cot(id_1)))",
5355
"coth(((sinh(0.6449666197606081) ^ (id_4 * id_8 * id_9)) + cot((id_3 < 0.2261011754280382)) + sech(abs(0.8120086721536708))))",
5456
"(((cosh(id_3) * log(0.5598410965557573) * asin(0.015550241603964676)) * sin(floor(id_0)) * cosh(max(id_1, 0.585221823072032))) >= cot(floor((id_9 * id_2 * 0.30343841532874716))))",
57+
"(cot(!( - id_6)) || ((tan(id_8) * atan2(id_5, id_7) * atan2(id_5, id_2)) * (pow(0.9897201897706811,id_1) * acosh(id_0) * (id_1 * 0.01088401412622797 * 0.8882413207692971)) * acot((0.3322552640871813 + id_9 + id_2))))",
5558
"coth(floor(cot(abs(id_0))))",
5659
" - factorial(( - id_2 / id_1 * ceil(id_5)))",
5760
"csch(exp(csc((id_2 * id_2 * id_9))))",
5861
"atan((tan(ceil(0.0)) || (cot(id_0) + asinh(0.5597772404451554) + sec(id_4))))",
5962
"cot(factorial( - (0.20783348997951046 && id_5)))",
63+
"(sqrt((atan2(0.7218065889622693, id_1) + asech(0.5381259927491784) + atanh(0.78855529690269))) >= asinh((atan2(id_0, id_9) <= cot(id_0))))",
6064
" - cos((cos(id_1) ^ cot(id_0)))",
65+
"pow(sinh(exp(acot(id_5))),tan(min(cot(id_0), (id_3 ^ id_8))))",
6166
"atan(abs(cot(floor(id_5))))",
6267
"coth(floor(cot((id_0 * 0.10280284481700341 * 0.3190467590050531))))",
6368
" - (( - id_1 * acos(0.9408318720601397) * cot(id_0)) && asinh(min(id_6, 0.130136440506475)))",
6469
"coth(factorial(floor(log(id_4))))",
6570
"(cot(!(sech(id_3))) ^ min( - atan2(0.7870190039110462, id_2), ((id_7 * id_0 * 0.10143937822636417) * min(id_4, id_2) * sech(0.5569515469432404))))",
6671
"cot(exp(coth((0.01722292952879423 ^ 0.7576553654648215))))",
67-
"cosh(min(cot((id_7 * id_0 * 0.9871116728260001)), - !(id_6)))",
68-
" - acos(((0.01949787088472099 * id_1 * id_9) ^ cot(id_0)))",
69-
"factorial(min(tanh((id_9 ^ id_1)), floor( - 0.5153523376029631)))"
72+
"(sec((asinh(0.0) && min(id_5, 0.648784608185188))) * (cot(floor(id_7)) <= (exp(id_7) * - 0.012384014519015274 * sqrt(0.7079441990714145))) * ceil(acot((0.8546428622959772 + id_0 + id_6))))",
73+
"acsch(max(cot((id_0 * id_3 * 0.59541956426409)), (atan2(0.681661337447432, id_6) ^ atan2(0.2719307064055214, id_8))))"
7074
));
7175

7276

@@ -413,6 +417,21 @@ public void testEval() {
413417
}
414418
}
415419

420+
@Test
421+
public void testNumExprGeneration() throws ExpressionException {
422+
Expression expr1 = new Expression("(2.0 && id_2) * 3.3 / acsc(1 + id_1) * pow(2, 3.2 ^ 2)");
423+
String numExpr1 = expr1.infix_NumExpr();
424+
Assertions.assertEquals("(where(((0.0!=2.0) & (0.0!=id_2)), 3.3 / (arcsin(1.0/((1.0 + id_1)))) * ((2.0)**(((3.2)**(2.0)))), 0.0))", numExpr1);
425+
426+
Expression expr2 = new Expression("(1.2 * 2.2) * logbase(3.3) * 2.0");
427+
String numExpr2 = expr2.infix_NumExpr();
428+
Assertions.assertEquals("((1.2 * 2.2) * (1.0/log(3.3)) * 2.0)", numExpr2);
429+
430+
Expression expr3 = new Expression("(1.2 || 2.2) * (3.3 && !(2.0 && 0.0))");
431+
String numExpr3 = expr3.infix_NumExpr();
432+
Assertions.assertEquals("(((0.0!=1.2) | (0.0!=2.2)) & ((0.0!=3.3) & (0.0!=~(((0.0!=2.0) & (0.0!=0.0))))))", numExpr3);
433+
}
434+
416435

417436
/**
418437
* Insert the method's description here.

0 commit comments

Comments
 (0)