Disabling model reduction creates multiple math compare failures
Disabling model reduction via mass conservation for legacy math validation testing (see #736) generated a large number of unexpected comparison failures during full testing.

This could be due to one or more of the following:
- incorrect model reduction algorithm
- incorrect implementation of no-model-reduction math generation
- incorrect/incomplete implementation of variable expansion algorithm used for comparison
- incomplete comparison routines

Further testing and troubleshooting is needed for the effects of [1b29ca7](https://github.com/virtualcell/vcell/pull/739/commits/1b29ca760f45592e872f0748c4fda9e6f722ef8e)
diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml
index 58340bb3b..326a3c620 100644
--- a/.github/workflows/ci_cd.yml
+++ b/.github/workflows/ci_cd.yml
@@ -74,6 +74,7 @@ jobs:
     strategy:
       matrix:
         test-group: [Fast, MathGen_IT, SBML_IT, SEDML_SBML_IT, SEDML_VCML_IT]
+      fail-fast: false
     if: ${{ github.event_name == 'push' }}
     name: CI-Test-group-${{ matrix.test-group }}
     steps:
diff --git a/vcell-cli/src/main/java/org/vcell/cli/vcml/VcmlValidator.java b/vcell-cli/src/main/java/org/vcell/cli/vcml/VcmlValidator.java
index d24479845..f9b4bf146 100644
--- a/vcell-cli/src/main/java/org/vcell/cli/vcml/VcmlValidator.java
+++ b/vcell-cli/src/main/java/org/vcell/cli/vcml/VcmlValidator.java
@@ -64,11 +64,9 @@ public class VcmlValidator {
             int number_succeeded = 0;
             for (SimulationContext orig_simContext : orig_biomodel.getSimulationContexts()) {
                 MathDescription originalMath = orig_simContext.getMathDescription();
-                MathDescription origMathClone = new MathDescription(originalMath); // test round trip to/from MathDescription.readFromDatabase()
                 SimulationContext new_simContext = transformed_biomodel.getSimulationContexts(orig_simContext.getName());
-                new_simContext.updateAll(false);
+				new_simContext.updateAll(false);
                 MathDescription newMath = new_simContext.getMathDescription();
-                MathDescription newMathClone = new MathDescription(newMath); // test round trip to/from MathDescription.readFromDatabase()
                 MathCompareResults results = null;
                 if (bTransformVariables) {
                     results = MathDescription.testEquivalencyWithRename(SimulationSymbolTable.createMathSymbolTableFactory(), originalMath, newMath);
@@ -79,7 +77,18 @@ public class VcmlValidator {
                     writeFileEntry(outputDirectory.getAbsolutePath(), inputFile.getName() + "," + orig_simContext.getName() + ",SUCCEEDED," + i, jobLogFile, bForceLogFiles);
                     number_succeeded++;
                 } else {
-                    writeFileEntry(outputDirectory.getAbsolutePath(), inputFile.getName() + "," + orig_simContext.getName() + ",FAILED," + 1 + "," + results.toDatabaseStatus(), jobLogFile, bForceLogFiles);
+                	// try again using non-reduced math
+   					new_simContext.setUsingMassConservationModelReduction(false);
+   					new_simContext.updateAll(false);
+   	                newMath = new_simContext.getMathDescription();
+   	                MathCompareResults results2 = MathDescription.testEquivalency(SimulationSymbolTable.createMathSymbolTableFactory(), originalMath, newMath);
+   	                if (results2.isEquivalent()) {
+   	                    writeFileEntry(outputDirectory.getAbsolutePath(), inputFile.getName() + "," + orig_simContext.getName() + ",SUCCEEDED," + i, jobLogFile, bForceLogFiles);
+   	                    number_succeeded++;
+   	                } else {
+   	                	writeFileEntry(outputDirectory.getAbsolutePath(), inputFile.getName() + "," + orig_simContext.getName() + ",FAILED," + 111 + "," + results.toDatabaseStatus(), jobLogFile, bForceLogFiles);
+   	                	writeFileEntry(outputDirectory.getAbsolutePath(), inputFile.getName() + "," + orig_simContext.getName() + ",FAILED," + 222 + "," + results2.toDatabaseStatus(), jobLogFile, bForceLogFiles);
+   	                }
                 }
                 i++;
             }
diff --git a/vcell-client/src/main/java/cbit/vcell/math/gui/MathDebuggerPanel.java b/vcell-client/src/main/java/cbit/vcell/math/gui/MathDebuggerPanel.java
index 8f50d975e..362f4b825 100644
--- a/vcell-client/src/main/java/cbit/vcell/math/gui/MathDebuggerPanel.java
+++ b/vcell-client/src/main/java/cbit/vcell/math/gui/MathDebuggerPanel.java
@@ -398,7 +398,7 @@ public class MathDebuggerPanel extends JPanel {
 		
 		setStatus("Canonical Math for both mathDescriptions: \n");
 
-		MathDescription[] canonicalMathDescs = MathUtilities.getCanonicalMathDescriptions(SimulationSymbolTable.createMathSymbolTableFactory(),getMathModel1().getMathDescription(), getMathModel2().getMathDescription());
+		MathDescription[] canonicalMathDescs = MathUtilities.getCanonicalMathDescriptions(getMathModel1().getMathDescription(), getMathModel2().getMathDescription());
 		
 		MathModel newMathModel = new MathModel(null);
 		newMathModel.setName("Math1");
@@ -533,14 +533,14 @@ public class MathDebuggerPanel extends JPanel {
 	
 	private void flatten(MathSymbolTableFactory mathSymbolTableFactory) throws PropertyVetoException, MathException, ExpressionException, MappingException, XmlParseException{
 		MathDescription math1 = getMathModel1().getMathDescription();
-		MathDescription newMath1 = MathDescription.createCanonicalMathDescription(mathSymbolTableFactory,math1);
+		MathDescription newMath1 = MathUtilities.createCanonicalMathDescription(mathSymbolTableFactory,math1);
 		MathModel newMathModel1 = new MathModel(null);
 		newMathModel1.setName("Math1");
 		newMathModel1.setMathDescription(newMath1);
 		setMathModel1(newMathModel1);
 		
 		MathDescription math2 = getMathModel2().getMathDescription();
-		MathDescription newMath2 = MathDescription.createCanonicalMathDescription(mathSymbolTableFactory,math2);
+		MathDescription newMath2 = MathUtilities.createCanonicalMathDescription(mathSymbolTableFactory,math2);
 		MathModel newMathModel2 = new MathModel(null);
 		newMathModel2.setName("Math2");
 		newMathModel2.setMathDescription(newMath2);
diff --git a/vcell-core/src/main/java/cbit/vcell/mapping/AbstractMathMapping.java b/vcell-core/src/main/java/cbit/vcell/mapping/AbstractMathMapping.java
index 788db25c1..7f4a72e89 100644
--- a/vcell-core/src/main/java/cbit/vcell/mapping/AbstractMathMapping.java
+++ b/vcell-core/src/main/java/cbit/vcell/mapping/AbstractMathMapping.java
@@ -547,29 +547,29 @@ public class RateRuleRateParameter extends MathMappingParameter {
 
 public static final int PARAMETER_ROLE_KFLUX = 2;
 public static final String PARAMETER_K_UNITFACTOR_PREFIX = "UnitFactor_";
-private static final String PARAMETER_BOUNDARY_XM_SUFFIX = "_boundaryXm";
-private static final String PARAMETER_BOUNDARY_XP_SUFFIX = "_boundaryXp";
-private static final String PARAMETER_BOUNDARY_YM_SUFFIX = "_boundaryYm";
-private static final String PARAMETER_BOUNDARY_YP_SUFFIX = "_boundaryYp";
-private static final String PARAMETER_BOUNDARY_ZM_SUFFIX = "_boundaryZm";
-private static final String PARAMETER_BOUNDARY_ZP_SUFFIX = "_boundaryZp";
+public static final String PARAMETER_BOUNDARY_XM_SUFFIX = "_boundaryXm";
+public static final String PARAMETER_BOUNDARY_XP_SUFFIX = "_boundaryXp";
+public static final String PARAMETER_BOUNDARY_YM_SUFFIX = "_boundaryYm";
+public static final String PARAMETER_BOUNDARY_YP_SUFFIX = "_boundaryYp";
+public static final String PARAMETER_BOUNDARY_ZM_SUFFIX = "_boundaryZm";
+public static final String PARAMETER_BOUNDARY_ZP_SUFFIX = "_boundaryZp";
 public static final String MATH_FUNC_SUFFIX_SPECIES_INIT_CONC_UNIT_PREFIX = "_init_";
-static final String MATH_FUNC_SUFFIX_SPECIES_CONCENTRATION = "";
+public static final String MATH_FUNC_SUFFIX_SPECIES_CONCENTRATION = "";
 public static final String MATH_VAR_SUFFIX_SPECIES_COUNT = "_Count";
 public static final String MATH_FUNC_SUFFIX_SPECIES_INIT_COUNT = "_initCount";
-	public static final String MATH_FUNC_SUFFIX_SPECIES_INIT_COUNT_template_replace = "_Count_initCount";
-static final String PARAMETER_PROBABLIITY_RATE_SUFFIX = "_probabilityRate";
-static final String PARAMETER_VELOCITY_X_SUFFIX = "_velocityX";
-static final String PARAMETER_VELOCITY_Y_SUFFIX = "_velocityY";
-static final String PARAMETER_VELOCITY_Z_SUFFIX = "_velocityZ";
-static final String PARAMETER_DIFFUSION_RATE_SUFFIX = "_diffusionRate";
-static final String PARAMETER_TRANSMEMBRANE_CURRENT_PREFIX = "F_";
-static final String PARAMETER_VOLTAGE_PREFIX = "V_";
-static final String PARAMETER_TOTAL_CURRENT_PREFIX = "I_";
-static final String PARAMETER_SPECIFIC_CAPACITANCE_PREFIX = "C_";
+public static final String MATH_FUNC_SUFFIX_SPECIES_INIT_COUNT_template_replace = "_Count_initCount";
+public static final String PARAMETER_PROBABLIITY_RATE_SUFFIX = "_probabilityRate";
+public static final String PARAMETER_VELOCITY_X_SUFFIX = "_velocityX";
+public static final String PARAMETER_VELOCITY_Y_SUFFIX = "_velocityY";
+public static final String PARAMETER_VELOCITY_Z_SUFFIX = "_velocityZ";
+public static final String PARAMETER_DIFFUSION_RATE_SUFFIX = "_diffusionRate";
+public static final String PARAMETER_TRANSMEMBRANE_CURRENT_PREFIX = "F_";
+public static final String PARAMETER_VOLTAGE_PREFIX = "V_";
+public static final String PARAMETER_TOTAL_CURRENT_PREFIX = "I_";
+public static final String PARAMETER_SPECIFIC_CAPACITANCE_PREFIX = "C_";
 public static final String PARAMETER_SIZE_FUNCTION_PREFIX = "Size_";
-static final String BIO_PARAM_SUFFIX_SPECIES_COUNT = "_temp_Count";
-static final String BIO_PARAM_SUFFIX_SPECIES_CONCENTRATION = "_temp_Conc";
+public static final String BIO_PARAM_SUFFIX_SPECIES_COUNT = "_temp_Count";
+public static final String BIO_PARAM_SUFFIX_SPECIES_CONCENTRATION = "_temp_Conc";
 protected static final Logger lg = LogManager.getLogger(AbstractMathMapping.class);
 public static final String PARAMETER_K_FLUX_PREFIX = "KFlux_";
 public static final int PARAMETER_ROLE_P = 3;
@@ -581,9 +581,9 @@ public static final int PARAMETER_ROLE_P_reverse = 4;
 public static final String PARAMETER_MASS_CONSERVATION_PREFIX = "K_";
 public static final String PARAMETER_MASS_CONSERVATION_SUFFIX = "_total";
 public static final String MATH_FUNC_SUFFIX_SPECIES_INIT_CONCENTRATION_old = "_init";
-	public static final String MATH_FUNC_SUFFIX_SPECIES_INIT_CONCENTRATION_uM = "_init_uM";
-	public static final String MATH_FUNC_SUFFIX_SPECIES_INIT_CONCENTRATION_umol_l_1 = "_init_umol_l_1";
-	public static final String MATH_FUNC_SUFFIX_SPECIES_INIT_CONCENTRATION_umol_dm_2 = "_init_umol_dm_2";
+public static final String MATH_FUNC_SUFFIX_SPECIES_INIT_CONCENTRATION_uM = "_init_uM";
+public static final String MATH_FUNC_SUFFIX_SPECIES_INIT_CONCENTRATION_umol_l_1 = "_init_umol_l_1";
+public static final String MATH_FUNC_SUFFIX_SPECIES_INIT_CONCENTRATION_umol_dm_2 = "_init_umol_dm_2";
 public static final String MATH_FUNC_SUFFIX_SPECIES_INIT_CONCENTRATION_old_molecules_per_um2 = "_init_molecules_per_um2";
 public static final String MATH_FUNC_SUFFIX_SPECIES_INIT_CONCENTRATION_molecules_um_2 = "_init_molecules_um_2";
 public static final int PARAMETER_ROLE_UNITFACTOR = 9;
diff --git a/vcell-core/src/main/java/cbit/vcell/math/MathDescription.java b/vcell-core/src/main/java/cbit/vcell/math/MathDescription.java
index 393296818..435d32558 100644
--- a/vcell-core/src/main/java/cbit/vcell/math/MathDescription.java
+++ b/vcell-core/src/main/java/cbit/vcell/math/MathDescription.java
@@ -365,6 +365,9 @@ private MathCompareResults compareEquivalentCanonicalMath(MathDescription newMat
 			}
 			Variable oldVars[] = (Variable[])BeanUtils.getArray(oldMathDesc.getVariables(),Variable.class);
 			Variable newVars[] = (Variable[])BeanUtils.getArray(newMathDesc.getVariables(),Variable.class);
+			Arrays.sort(oldVars,(v1,v2) -> v1.getName().compareTo(v2.getName()));
+			Arrays.sort(newVars,(v1,v2) -> v1.getName().compareTo(v2.getName()));
+			
 			if (oldVars.length != newVars.length){
 				//
 				// number of state variables are not equal (canonical maths only have state variables)
@@ -534,12 +537,20 @@ private MathCompareResults compareEquivalentCanonicalMath(MathDescription newMat
 							if (!oldExps.get(k).compareEqual(newExps.get(k))){
 								bFoundDifference = true;
 								if (!ExpressionUtils.functionallyEquivalent(oldExps.get(k),newExps.get(k))){
-									//
-									// difference couldn't be reconciled
-									//
-									String msg = "expressions are different: '"+oldExps.get(k).infix()+"' vs '"+newExps.get(k).infix()+"'";
-									logMathTexts(this, newMathDesc, Decision.MathDifferent_DIFFERENT_EXPRESSION, msg);
-									return new MathCompareResults(Decision.MathDifferent_DIFFERENT_EXPRESSION, msg);
+									Expression oe = new Expression(oldExps.get(k));
+									Expression ne = new Expression(newExps.get(k));
+									MathUtilities.substituteCommonDiscontinuitiesInPlace(oe, ne, "BOOLEAN_");
+									if (!ExpressionUtils.functionallyEquivalent(oe,ne,false)) {
+										//
+										// difference couldn't be reconciled
+										//
+										String msg = "expressions are different: '" + oldExps.get(k).infix() + "' vs '"
+												+ newExps.get(k).infix() + "'";
+										logMathTexts(this, newMathDesc, Decision.MathDifferent_DIFFERENT_EXPRESSION,
+												msg);
+										return new MathCompareResults(Decision.MathDifferent_DIFFERENT_EXPRESSION,
+												msg);
+									}
 								}else{
 									//if (!bSilent) System.out.println("expressions are equivalent Old: '"+oldExps[k]+"'\n"+
 													   //"expressions are equivalent New: '"+newExps[k]+"'");
@@ -630,12 +641,20 @@ private MathCompareResults compareEquivalentCanonicalMath(MathDescription newMat
 								if (!oldExps[k].compareEqual(newExps[k])){
 									bFoundDifference = true;
 									if (!ExpressionUtils.functionallyEquivalent(oldExps[k],newExps[k])){
-										//
-										// difference couldn't be reconciled
-										//
-										String msg = "expressions are different: '"+oldExps[k].infix()+"' vs '"+newExps[k].infix()+"'";
-										logMathTexts(this, newMathDesc, Decision.MathDifferent_DIFFERENT_EXPRESSION, msg);
-										return new MathCompareResults(Decision.MathDifferent_DIFFERENT_EXPRESSION, msg);
+										Expression oe = new Expression(oldExps[k]);
+										Expression ne = new Expression(newExps[k]);
+										MathUtilities.substituteCommonDiscontinuitiesInPlace(oe, ne, "BOOLEAN_");
+										if (!ExpressionUtils.functionallyEquivalent(oe,ne,false)) {
+											//
+											// difference couldn't be reconciled
+											//
+											String msg = "expressions are different: '" + oldExps[k].infix() + "' vs '"
+													+ newExps[k].infix() + "'";
+											logMathTexts(this, newMathDesc, Decision.MathDifferent_DIFFERENT_EXPRESSION,
+													msg);
+											return new MathCompareResults(Decision.MathDifferent_DIFFERENT_EXPRESSION,
+													msg);
+										}
 									}else{
 										//if (!bSilent) System.out.println("expressions are equivalent Old: '"+oldExps[k]+"'\n"+
 														   //"expressions are equivalent New: '"+newExps[k]+"'");
@@ -703,50 +722,39 @@ private MathCompareResults compareEquivalentCanonicalMath(MathDescription newMat
 						logMathTexts(this, newMathDesc, Decision.MathDifferent_DIFFERENT_NUMBER_OF_EXPRESSIONS,msg);
 						return new MathCompareResults(Decision.MathDifferent_DIFFERENT_NUMBER_OF_EXPRESSIONS,msg);
 					}
-					for (int k = 0; k < oldFastInvariantExps.length; k++){
-						if (!oldFastInvariantExps[k].compareEqual(newFastInvariantExps[k])){
+					for (int kk = 0; kk < oldFastInvariantExps.length; kk++){
+						// check same index first using simple compare
+						if (oldFastInvariantExps[kk].compareEqual(newFastInvariantExps[kk])) {
+							continue;
+						}else {
 							bFoundDifference = true;
-							if (!ExpressionUtils.functionallyEquivalent(oldFastInvariantExps[k],newFastInvariantExps[k])){
-								//
-								// difference couldn't be reconciled
-								// ... for fast invariants, it is ok if the two equations are different by a scale factor (e.g. if X+Y is conserved, so is 2*X+2*Y, or 2*(X+Y))
-								//
-								// estimate proportionality factor (average of 5 evaluations)
-								// 
-								// ratio = old/new
-								//
-								// then compare old with ratio*new.
-								//
-								Expression ratioExp = Expression.mult(oldFastInvariantExps[k], Expression.invert(newFastInvariantExps[k]));
-								String[] symbols = ratioExp.getSymbols();
-								SimpleSymbolTable symbolTable = new SimpleSymbolTable(symbols);
-								double[] values = new double[symbols.length];
-								ratioExp.bindExpression(symbolTable);
-								Random random = new Random(0);
-								double ratioAccum = 0.0;
-								final int NUM_TRIALS = 5;
-								for (int m = 0; m < NUM_TRIALS; m++){
-									for (int j = 0; j < values.length; j++) {
-										values[j] = random.nextDouble()+1.0;
+						}
+						//
+						// check all fast invariant expressions because they may be out of order
+						//
+						boolean bFoundMatch = false;
+						for (int jj = 0; jj < newFastInvariantExps.length; jj++) {
+							if (oldFastInvariantExps[kk].compareEqual(newFastInvariantExps[jj])) {
+								bFoundMatch = true;
+								break;
+							}else{
+								bFoundDifference = true;
+								if (ExpressionUtils.functionallyEquivalent(oldFastInvariantExps[kk], newFastInvariantExps[jj])) {
+									bFoundMatch = true;
+									break;
+								}else{
+									if (MathUtilities.compareEquivalent(new FastInvariant(oldFastInvariantExps[kk]),new FastInvariant(newFastInvariantExps[jj]))){
+										bFoundMatch = true;
+										break;
 									}
-									ratioAccum += ratioExp.evaluateVector(values);
-								}								
-								double estimatedRatio = ratioAccum/NUM_TRIALS;
-								Expression scaled_newFastInvariantExp = Expression.mult(new Expression(estimatedRatio),newFastInvariantExps[k]);
-								System.out.println("MathDescription.compareEquivalent(): comparing "+oldFastInvariantExps[k].infix()+" with "+scaled_newFastInvariantExp.infix());
-								if (!ExpressionUtils.functionallyEquivalent(oldFastInvariantExps[k], scaled_newFastInvariantExp)){
-									String msg = "fast invariant expressions are different Old: '"+oldFastInvariantExps[k]+"'\n"+
-											"fast invariant expressions are different New: '"+newFastInvariantExps[k]+"'";
-									logMathTexts(this, newMathDesc, Decision.MathDifferent_DIFFERENT_FASTINV_EXPRESSION, msg);
-									return new MathCompareResults(Decision.MathDifferent_DIFFERENT_FASTINV_EXPRESSION,msg);
 								}
-								//if (!bSilent) System.out.println("fast invariant expressions are proportional Old: '"+oldFastInvariantExps[k]+"'\n"+
-								//   "fast invariant expressions are proportional New: '"+newFastInvariantExps[k]+"'");
-							}else{
-								//if (!bSilent) System.out.println("expressions are equivalent Old: '"+oldExps[k]+"'\n"+
-												   //"expressions are equivalent New: '"+newExps[k]+"'");
 							}
 						}
+						if (!bFoundMatch){
+							String msg = "could not find a match for fast invariant expression'"+oldFastInvariantExps[kk]+"'";
+							logMathTexts(this, newMathDesc, Decision.MathDifferent_DIFFERENT_FASTINV_EXPRESSION, msg);
+							return new MathCompareResults(Decision.MathDifferent_DIFFERENT_FASTINV_EXPRESSION,msg);
+						}
 					}
 					Enumeration<Expression> oldFastRateExpEnum = oldFastSystem.getFastRateExpressions();
 					Enumeration<Expression> newFastRateExpEnum = newFastSystem.getFastRateExpressions();
@@ -1015,205 +1023,7 @@ private MathCompareResults compareInvariantAttributes(MathDescription newMathDes
 }
 
 
-/**
- * Insert the method's description here.
- * Creation date: (10/9/2002 10:54:06 PM)
- * @return cbit.vcell.math.MathDescription
- */
-public static MathDescription createCanonicalMathDescription(MathSymbolTableFactory mathSymbolTableFactory, MathDescription originalMathDescription) throws MathException, ExpressionException {
-	//
-	// clone current mathdescription
-	//
-	MathDescription newMath = new MathDescription(originalMathDescription);
-
-	newMath.makeCanonical(mathSymbolTableFactory);
-
-	return newMath;
-}
-
-
-/**
- * Insert the method's description here.
- * Creation date: (10/9/2002 10:54:06 PM)
- * @return cbit.vcell.math.MathDescription
- */
-public static MathDescription createMathWithExpandedEquations(MathDescription originalMathDescription, HashSet<String> varNamesToKeep) throws MathException, ExpressionException {
-	//
-	// clone current mathdescription
-	//
-	MathDescription newMath = new MathDescription(originalMathDescription);
-
-	//
-	// for any dependent variables in the "varNamesToKeep" list, create appropriate Variable/Equation
-	//
-	// this assumes that the dependent 'Function' is a linear combination of existing state variables
-	//  e.g. Function depVar = K0 + K1*indepVar1 + K2*indepVar2 + ... + Kn*indepVarN
-	// if it doesn't fit this form, then math's are not equivalent.
-	//
-	HashSet<String> stateVarSet = newMath.getStateVariableNames();
-	// Build the list of variables to be added
-	ArrayList<Function> varsToAdd = new ArrayList<Function>();
-	for (Variable var : newMath.variableList){
-		if (varNamesToKeep.contains(var.getName()) && var instanceof Function){
-			varsToAdd.add((Function)var);
-		}
-	}
-System.out.println(varsToAdd);
-	for (Function function : varsToAdd){
-			//
-			// get list of symbols that are state variables
-			//
-			ArrayList<Variable> indepVarList = new ArrayList<Variable>();         // holds the "indepVar's"
-			ArrayList<Expression> coefficientList = new ArrayList<Expression>();      // holds the "K's"
-			Expression exp = function.getExpression();
-			exp.bindExpression(null);
-			Expression K0 = new Expression(exp);
-			K0.bindExpression(null);
-			String symbols[] = exp.getSymbols();
-			for (int j = 0; j < symbols.length; j++){
-				if (stateVarSet.contains(symbols[j])){
-					//
-					// store the independent variable (indepVar_i)
-					//
-					indepVarList.add(newMath.getVariable(symbols[j]));
-					Expression differential = exp.differentiate(symbols[j]);
-					differential = differential.flatten();
-					//
-					// store the coefficient (K_i)
-					//
-					coefficientList.add(differential);
-					//
-					// remove this term from the "constant" term (K0)
-					// (e.g. for expression "K0 + K1*V1 + ... + Ki*Vi + ... + Kn*Vn", Vi set to 0.0 and flattened)
-					// after each term is removed, only K0 is left
-					//
-					K0.substituteInPlace(new Expression(symbols[j]),new Expression(0.0));
-					K0 = K0.flatten();
-				}
-			}
-			//
-			// either all independent vars should be Volume, all should be Membrane, or all should be Filament 
-			//
-			int countVolumeVars = 0;
-			int countMembraneVars = 0;
-			int countFilamentVars = 0;
-			int countPointVars = 0;
-			for (int j = 0; j < indepVarList.size(); j++){
-				if (indepVarList.get(j) instanceof VolVariable){
-					countVolumeVars++;
-				}else if (indepVarList.get(j) instanceof MemVariable){
-					countMembraneVars++;
-				}else if (indepVarList.get(j) instanceof FilamentVariable){
-					countFilamentVars++;
-				}else if (indepVarList.get(j) instanceof PointVariable){
-					countPointVars++;
-				}else{
-					throw new RuntimeException("create canonicalMath cannot handle dependent vars of type '"+indepVarList.get(j).getClass().getName()+"'");
-				}
-			}
-			//
-			// case: Volume Variable
-			// create VolVariable
-			// for each CompartmentSubDomain, create OdeEquation
-			// remove Function
-			//
-			if (countVolumeVars == indepVarList.size()){
-				VolVariable volVariable = new VolVariable(function.getName(),function.getDomain());
-				newMath.variableList.remove(function);
-				newMath.variableList.add(volVariable);
-				newMath.variableHashTable.remove(function.getName());
-				newMath.variableHashTable.put(volVariable.getName(), volVariable);
-				for (int j = 0; j < newMath.subDomainList.size(); j++){
-					SubDomain subDomain = newMath.subDomainList.get(j);
-					if (subDomain instanceof CompartmentSubDomain){
-						//
-						// add an ODE where 
-						//    initial value = K0 + Sum(coefficient_i*Var_i.init)
-						//    rate value = Sum(coefficient_i*Var_i.rate)
-						//
-						Expression initExp = new Expression(K0);
-						Expression rateExp = new Expression(0.0);
-						for (int k = 0; k < indepVarList.size(); k++){
-							Variable indepVar = indepVarList.get(k);
-							Equation indepVarEqu = subDomain.getEquation(indepVar);
-							Expression coefficient = coefficientList.get(k);
-							initExp = Expression.add(initExp,Expression.mult(new Expression(coefficient),new Expression(indepVarEqu.getInitialExpression())));
-							rateExp = Expression.add(rateExp,Expression.mult(new Expression(coefficient),new Expression(indepVarEqu.getRateExpression())));
-						}
-						Variable initVariable = null;
-						for (int k=0;k<newMath.variableList.size();k++){
-							if (newMath.variableList.get(k).getName().startsWith(function.getName()+MATH_FUNC_INIT_SUFFIX_PREFIX)){
-								initVariable = newMath.variableList.get(k);
-								initExp = new Expression(initVariable,null);
-								break;
-							}
-						}
-						OdeEquation odeEquation = new OdeEquation(volVariable, initExp.flatten(), rateExp.flatten());
-						subDomain.addEquation(odeEquation);
-					}
-				}
-			//
-			// case: Membrane Variable
-			//
-			}else if (countMembraneVars == indepVarList.size()){
-				MemVariable memVariable = new MemVariable(function.getName(),function.getDomain());
-				newMath.variableList.remove(function);
-				newMath.variableList.add(memVariable);
-				newMath.variableHashTable.remove(function.getName());
-				newMath.variableHashTable.put(memVariable.getName(), memVariable);				
-				for (int j = 0; j < newMath.subDomainList.size(); j++){
-					SubDomain subDomain = newMath.subDomainList.get(j);
-					if (subDomain instanceof MembraneSubDomain){
-						//
-						// add an ODE where 
-						//    initial value = K0 + Sum(coefficient_i*Var_i.init)
-						//    rate value = Sum(coefficient_i*Var_i.rate)
-						//
-						Expression initExp = new Expression(K0);
-						Expression rateExp = new Expression(0.0);
-						for (int k = 0; k < indepVarList.size(); k++){
-							Variable indepVar = indepVarList.get(k);
-							Equation indepVarEqu = subDomain.getEquation(indepVar);
-							Expression coefficient = coefficientList.get(k);
-							initExp = Expression.add(initExp,Expression.mult(new Expression(coefficient),new Expression(indepVarEqu.getInitialExpression())));
-							rateExp = Expression.add(rateExp,Expression.mult(new Expression(coefficient),new Expression(indepVarEqu.getRateExpression())));
-						}
-						Variable initVariable = null;
-						for (int k=0;k<newMath.variableList.size();k++){
-							if (newMath.variableList.get(k).getName().startsWith(function.getName()+MATH_FUNC_INIT_SUFFIX_PREFIX)){
-								initVariable = newMath.variableList.get(k);
-								initExp = new Expression(initVariable,null);
-								break;
-							}
-						}
-						OdeEquation odeEquation = new OdeEquation(memVariable, initExp.flatten(), rateExp.flatten());
-						subDomain.addEquation(odeEquation);
-					}
-				}
-			//
-			// case: Filament Variable
-			//
-			}else if (countFilamentVars == indepVarList.size()){
-				throw new RuntimeException("create canonicalMath cannot handle function of variables of type 'FilamentVariable'");
-				
-			//
-			// case: Point Variable
-			//
-			}else if (countPointVars == indepVarList.size()){
-				throw new RuntimeException("create canonicalMath cannot handle function of variables of type 'PointVariable'");
-
-			//
-			//
-			//
-			}else{
-				throw new RuntimeException("create canonicalMath cannot handle mixture of dependent vars types");
-			}
-	}
-	
-	return newMath;
-}
-
-/**
+	/**
  * The firePropertyChange method was generated to support the propertyChange field.
  */
 public void firePropertyChange(java.lang.String propertyName, java.lang.Object oldValue, java.lang.Object newValue) {
@@ -1536,18 +1346,26 @@ protected java.beans.PropertyChangeSupport getPropertyChange() {
 	return propertyChange;
 }
 
-/**
- * Insert the method's description here.
- * Creation date: (11/24/2004 7:20:17 AM)
- * @return cbit.vcell.math.Variable[]
- */
-public HashSet<String> getStateVariableNames() {
-	HashSet<String> stateVarNameSet = new HashSet<String>();
+public List<Variable> getStateVariables() {
+	List<Variable> stateVars = new ArrayList<>();
 	for (int i = 0; i < variableList.size(); i++){
 		Variable var = variableList.get(i);
 		if (var instanceof VolVariable || var instanceof MemVariable || var instanceof FilamentVariable ||
-			var instanceof VolumeRegionVariable || var instanceof MembraneRegionVariable || var instanceof FilamentRegionVariable ||
-			var instanceof ParticleVariable || var instanceof StochVolVariable || var instanceof PointVariable){
+				var instanceof VolumeRegionVariable || var instanceof MembraneRegionVariable || var instanceof FilamentRegionVariable ||
+				var instanceof ParticleVariable || var instanceof StochVolVariable || var instanceof PointVariable){
+			stateVars.add(var);
+		}
+	}
+	return stateVars;
+}
+
+public HashSet<String> getStateVariableNames() {
+	List<Variable> stateVars = getStateVariables();
+	HashSet<String> stateVarNameSet = new HashSet<String>();
+	for (Variable var : stateVars){
+		if (var instanceof VolVariable || var instanceof MemVariable || var instanceof FilamentVariable ||
+				var instanceof VolumeRegionVariable || var instanceof MembraneRegionVariable || var instanceof FilamentRegionVariable ||
+				var instanceof ParticleVariable || var instanceof StochVolVariable || var instanceof PointVariable){
 			stateVarNameSet.add(var.getName());
 		}
 	}
@@ -1607,6 +1425,13 @@ public Enumeration<Variable> getVariables() {
 	return Collections.enumeration(variableList);
 }
 
+	public List<Variable> getVariableList() {
+		return variableList;
+	}
+	public Map<String, Variable> getVariableMap() {
+		return variableHashTable;
+	}
+
 public Iterator<Event> getEvents() {
 	return eventList.iterator();
 }
@@ -3507,11 +3332,11 @@ public static MathCompareResults testEquivalencyWithRename(MathSymbolTableFactor
 	return testEquivalency(mathSymbolTableFactory, mathDescription1, mathDescription2);
 }
 
-public static MathCompareResults testEquivalency(MathSymbolTableFactory mathSymbolTableFactory, MathDescription mathDescription1, MathDescription mathDescription2) {
+public static MathCompareResults testEquivalency(MathSymbolTableFactory mathSymbolTableFactory, MathDescription oldMath, MathDescription newMath) {
 
 	try {
 	
-		MathCompareResults invariantResults = mathDescription2.compareInvariantAttributes(mathDescription1,false);
+		MathCompareResults invariantResults = newMath.compareInvariantAttributes(oldMath,false);
 		if (!invariantResults.isEquivalent()){
 			if (invariantResults.decision.equals(Decision.MathDifferent_VARIABLE_NOT_FOUND_AS_FUNCTION)
 					|| invariantResults.decision.equals(Decision.MathDifferent_DIFFERENT_NUMBER_OF_VARIABLES)) {
@@ -3519,95 +3344,49 @@ public static MathCompareResults testEquivalency(MathSymbolTableFactory mathSymb
 				// variable naming identical across structures
 				// no subdomains associated with variables
 				// equations are present for state variables in multiple subdomains
-				MathDescription copyMath1 = new MathDescription(mathDescription1); //clone before surgery
-				boolean bRenameSuccess = tryLegacyVarNameDomainExtensive(copyMath1, mathDescription2);
+				MathDescription copyMath1 = new MathDescription(oldMath); //clone before surgery
+				boolean bRenameSuccess = tryLegacyVarNameDomainExtensive(copyMath1, newMath);
 				if (bRenameSuccess) {
 					//try again to see whether invariants are equivalent after rename
-					MathCompareResults invariantResults2 = mathDescription2.compareInvariantAttributes(copyMath1, false);
+					MathCompareResults invariantResults2 = newMath.compareInvariantAttributes(copyMath1, false);
 					if (!invariantResults2.isEquivalent()) {
-						// can't be equivalent or equal
 						logger.error("Could not fix invariants by renaming");
 						logger.error("Initial invariantResults: "+invariantResults);
 						logger.error("After rename: "+invariantResults2);
-						return invariantResults2;
+						if (!invariantResults2.decision.equals(Decision.MathDifferent_DIFFERENT_NUMBER_OF_VARIABLES)) {
+							// can't be equivalent or equal
+							return invariantResults2;
+						}
 					}
-					// it worked for the quick test, now proceed with the patched copy
-					mathDescription1 = copyMath1;
+					// either now it works for the quick test or fails again with different number of vars
+					// number of vars can be also due to mass conservation choice differences which is not fixed by renaming, but tested later with expansion  
+					// now proceed with the patched copy
+					oldMath = copyMath1;
 				} else {
-					// can't be equivalent or equal
+					// could not rename variables, may not have legacy naming issues
 					logger.error("Attempt to rename legacy variables failed");
-					return invariantResults;					
+					if (!invariantResults.decision.equals(Decision.MathDifferent_DIFFERENT_NUMBER_OF_VARIABLES)) {
+						// can't be equivalent or equal
+						return invariantResults;
+					}
+					// number of vars can be also due to mass conservation choice differences which is not fixed by renaming, but tested later with expansion  
+					// we proceed with the original math
 				}
-			} else {
+			} else if (!invariantResults.decision.equals(Decision.MathDifferent_DIFFERENT_NUMBER_OF_VARIABLES)){
 				// can't be equivalent or equal
 				return invariantResults;				
 			}
 		}
 
 		// invariants are equivalent, now do a full compare
-		if (mathDescription2.compareEqual(mathDescription1)){
+		if (newMath.compareEqual(oldMath)){
 			return new MathCompareResults(Decision.MathEquivalent_NATIVE);
 		}else{
-			//
-			// must test for equivalence
-			//
-			HashSet<String> indepVars1 = mathDescription1.getStateVariableNames();
-			HashSet<String> indepVars2 = mathDescription2.getStateVariableNames();
-			HashSet<String> union = new HashSet<String>(indepVars1);
-			union.addAll(indepVars2);
-			
-//			MathDescription canonicalMath1 = MathDescription.createCanonicalMathDescription(mathSymbolTableFactory,createMathWithExpandedEquations(mathDescription1,union));
-//			MathDescription canonicalMath2 = MathDescription.createCanonicalMathDescription(mathSymbolTableFactory,createMathWithExpandedEquations(mathDescription2,union));
-
-			MathDescription canonicalMath1 = MathDescription.createMathWithExpandedEquations(mathDescription1,union);
-			MathDescription canonicalMath2 = MathDescription.createMathWithExpandedEquations(mathDescription2,union);
-			
-			
-			HashSet<String> canonIndepVars1 = canonicalMath1.getStateVariableNames();
-			HashSet<String> canonIndepVars2 = canonicalMath2.getStateVariableNames();
-			
-			for (String varName : union) {
-				if (!canonIndepVars1.contains(varName)) {
-					logger.error("========= indepVars1 size: "+indepVars1.size());
-					logger.error("========= union size: "+union.size());
-					logger.error("========= Canonical Math 1 is missing variable: "+varName);
-				}
-			}
-			for (String varName : union) {
-				if (!canonIndepVars2.contains(varName)) {
-					logger.error("========= indepVars2 size: "+indepVars2.size());
-					logger.error("========= union size: "+union.size());
-					logger.error("========= Canonical Math 2 is missing variable: "+varName);
-				}
-			}
-
-//			HashSet<String> depVarsToSubstitute1 = new HashSet<String>();
-//			for (Variable var : Collections.list(canonicalMath1.getVariables())) {
-//				depVarsToSubstitute1.add(var.getName());
-//			}
-//			HashSet<String> depVarsToSubstitute2 = new HashSet<String>();
-//			for (Variable var : Collections.list(canonicalMath1.getVariables())) {
-//				depVarsToSubstitute2.add(var.getName());
-//			}
-//			depVarsToSubstitute1.removeAll(union);
-//			depVarsToSubstitute2.removeAll(union);
-//			if (depVarsToSubstitute1.size()>0){
-//				System.out.println(depVarsToSubstitute1);
-//				String depVarNames[] = (String[])depVarsToSubstitute1.toArray(new String[depVarsToSubstitute1.size()]);
-//				Function functionsToSubstitute[] = MathDescription.getFlattenedFunctions(mathSymbolTableFactory,mathDescription1,depVarNames);
-//				System.out.println(functionsToSubstitute);
-//				canonicalMath1.substituteInPlace(mathSymbolTableFactory,functionsToSubstitute);
-//			}
-//			if (depVarsToSubstitute2.size()>0){
-//				String depVarNames[] = (String[])depVarsToSubstitute2.toArray(new String[depVarsToSubstitute2.size()]);
-//				Function functionsToSubstitute[] = MathDescription.getFlattenedFunctions(mathSymbolTableFactory,mathDescription2,depVarNames);
-//				canonicalMath1.substituteInPlace(mathSymbolTableFactory,functionsToSubstitute);
-//			}
-//			// flatten again
-			canonicalMath1.makeCanonical(mathSymbolTableFactory);
-			canonicalMath2.makeCanonical(mathSymbolTableFactory);
+			MathDescription[] canonicalMaths = MathUtilities.getCanonicalMathDescriptions(oldMath, newMath);
+			MathDescription canonicalMath1 = canonicalMaths[0];
+			MathDescription canonicalMath2 = canonicalMaths[1];
 			// now compare
-			return canonicalMath2.compareEquivalentCanonicalMath(canonicalMath1);
+			return canonicalMath1.compareEquivalentCanonicalMath(canonicalMath2);
 		}
 	}catch (Exception e){
 		String msg = "failure while testing for math equivalency: "+e.getMessage();
@@ -3821,7 +3600,12 @@ private static boolean tryLegacyVarNameDomainExtensive(MathDescription mathDescr
 			boolean bMatch = false;
 			if (v2.get(j).contains("_init")
 					|| v2.get(j).endsWith(InsideVariable.INSIDE_VARIABLE_SUFFIX)
-					|| v2.get(j).endsWith(OutsideVariable.OUTSIDE_VARIABLE_SUFFIX)) 
+					|| v2.get(j).endsWith(OutsideVariable.OUTSIDE_VARIABLE_SUFFIX)
+					|| v2.get(j).endsWith(AbstractMathMapping.PARAMETER_DIFFUSION_RATE_SUFFIX)
+					|| v2.get(j).endsWith(AbstractMathMapping.PARAMETER_VELOCITY_X_SUFFIX)
+					|| v2.get(j).endsWith(AbstractMathMapping.PARAMETER_VELOCITY_Y_SUFFIX)
+					|| v2.get(j).endsWith(AbstractMathMapping.PARAMETER_VELOCITY_Z_SUFFIX)
+							) 
 				continue;
 			if (v2.get(j).equals(oldName)) bMatch = true;
 			int underscorePos = v2.get(j).lastIndexOf("_");
@@ -3971,7 +3755,14 @@ private static boolean tryLegacyVarNameDomainExtensive(MathDescription mathDescr
 				mathDescription1.variableHashTable.remove(oldNameOUT);
 			}
 			// create new Variables of same type
+			HashSet<String> existingVars = new HashSet<String>(); // can happen, need to avoid naming conflicts
 			for (String newName : newNames) {
+				if (mathDescription1.getVariable(newName) != null) {
+					existingVars.add(newName);
+					SubDomain sd = mathDescription1.getSubDomain(mathDescription2.getVariable(newName).getDomain().getName());
+					mathDescription1.getVariable(newName).setDomain(new Domain(sd));
+					continue;
+				}
 				try {
 					Variable clonedVar = (Variable)BeanUtils.cloneSerializable(oldVar);
 					clonedVar.rename(newName);
@@ -3990,7 +3781,7 @@ private static boolean tryLegacyVarNameDomainExtensive(MathDescription mathDescr
 				String newNameSTOCHINIT = newName+AbstractMathMapping.MATH_FUNC_SUFFIX_SPECIES_INIT_COUNT;
 				Variable newVar = mathDescription1.getVariable(newName);
 				SubDomain sd = mathDescription1.getSubDomain(newVar.getDomain().getName());
-				if (!sd.getEquationCollection().isEmpty()) {
+				if (!sd.getEquationCollection().isEmpty() && !existingVars.contains(newName)) {
 					sd.getEquation(oldVar).setVar(newVar);
 				}
 				if (!sd.getJumpProcesses().isEmpty()) {
diff --git a/vcell-core/src/main/java/cbit/vcell/math/MathUtilities.java b/vcell-core/src/main/java/cbit/vcell/math/MathUtilities.java
index 83345f44b..7abcc09ed 100644
--- a/vcell-core/src/main/java/cbit/vcell/math/MathUtilities.java
+++ b/vcell-core/src/main/java/cbit/vcell/math/MathUtilities.java
@@ -10,22 +10,15 @@
 
 package cbit.vcell.math;
 
-import java.beans.PropertyVetoException;
-import java.util.Arrays;
-import java.util.Enumeration;
-import java.util.HashSet;
-import java.util.Vector;
+import java.util.*;
+import java.util.stream.Collectors;
 
+import cbit.vcell.parser.*;
+import cbit.vcell.solver.SimulationSymbolTable;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
+import org.vcell.test.Fast;
 
-import cbit.vcell.math.MathCompareResults.Decision;
-import cbit.vcell.parser.Expression;
-import cbit.vcell.parser.ExpressionBindingException;
-import cbit.vcell.parser.ExpressionException;
-import cbit.vcell.parser.SymbolTable;
-import cbit.vcell.parser.SymbolTableEntry;
-import cbit.vcell.parser.SymbolTableFunctionEntry;
 /**
  * Insert the type's description here.
  * Creation date: (1/29/2002 3:22:16 PM)
@@ -235,24 +228,40 @@ public static Expression substituteModelParameters(Expression exp, SymbolTable s
 	return exp2;
 }
 
-public static MathDescription[] getCanonicalMathDescriptions(MathSymbolTableFactory mathSymbolTableFactory, MathDescription referenceMathDesc, MathDescription testMathDesc) throws PropertyVetoException, MathException, ExpressionException {
-	HashSet<String> indepVars1 = referenceMathDesc.getStateVariableNames();
-	HashSet<String> indepVars2 = testMathDesc.getStateVariableNames();
-	HashSet<String> union = new HashSet<String>(indepVars1);
-	union.addAll(indepVars2);
-
-//	setStatus("union of state variables: ");
-//	for (String varName : union){
-//		addStatus(varName+" ");
-//	}
-//	addStatus("\n");
+/**
+ * Using information from both math descriptions
+ * 1. identify the union state variables from both math descriptions to get a consensus set for expansion of both maths
+ * 2. expand one or both math descriptions to rehydrate the same set of differential equations in both maths
+ * 3. substitute all functions in both maths.
+ */
+public static MathDescription[] getCanonicalMathDescriptions(MathDescription referenceMathDesc, MathDescription testMathDesc) throws MathException, ExpressionException {
+	MathSymbolTableFactory mathSymbolTableFactory = SimulationSymbolTable.createMathSymbolTableFactory();
+	HashSet<String> indepVarNames1 = referenceMathDesc.getStateVariableNames();
+	HashSet<String> indepVarNames2 = testMathDesc.getStateVariableNames();
+	HashSet<String> unionOfVarNames = new HashSet<>(indepVarNames1);
+	unionOfVarNames.addAll(indepVarNames2);
+	Map<String, Variable.Domain> indepVars1 = referenceMathDesc.getStateVariables().stream().filter(v -> v.getDomain() != null).collect(Collectors.toMap(v -> v.getName(), v -> v.getDomain()));
+	Map<String, Variable.Domain> indepVars2 = testMathDesc.getStateVariables().stream().filter(v -> v.getDomain() != null).collect(Collectors.toMap(v -> v.getName(), v -> v.getDomain()));
+	Map<String, Variable.Domain> unionMap = new HashMap<>();
+	for (String varName : unionOfVarNames){
+		Variable.Domain domain = null;
+		if (indepVars1.get(varName) != null){
+			domain = indepVars1.get(varName);
+		}
+		if (indepVars2.get(varName) != null){
+			domain = indepVars2.get(varName);
+		}
+		if (domain != null){
+			unionMap.put(varName,domain);
+		}
+	}
 	
-	HashSet<String> depVarsToSubstitute = new HashSet<String>(union);
-	depVarsToSubstitute.removeAll(indepVars1);
+	HashSet<String> depVarsToSubstitute = new HashSet<String>(unionOfVarNames);
+	depVarsToSubstitute.removeAll(indepVarNames1);
 	
-	MathDescription canonicalMath1 = new MathDescription(MathDescription.createMathWithExpandedEquations(referenceMathDesc,union));
+	MathDescription canonicalMath1 = new MathDescription(createMathWithExpandedEquations(referenceMathDesc,unionOfVarNames,unionMap));
 	canonicalMath1.makeCanonical(mathSymbolTableFactory);
-	MathDescription canonicalMath2 = new MathDescription(MathDescription.createMathWithExpandedEquations(testMathDesc,union));
+	MathDescription canonicalMath2 = new MathDescription(createMathWithExpandedEquations(testMathDesc,unionOfVarNames,unionMap));
 	canonicalMath2.makeCanonical(mathSymbolTableFactory);
 
 	if (depVarsToSubstitute.size()>0){
@@ -261,257 +270,441 @@ public static MathDescription[] getCanonicalMathDescriptions(MathSymbolTableFact
 		canonicalMath1.substituteInPlace(mathSymbolTableFactory, functionsToSubstitute);
 		canonicalMath2.substituteInPlace(mathSymbolTableFactory, functionsToSubstitute);
 	}
-	// flatten again
+
+	// flatten the maths by substitution of all remaining functions and constants - leaving expressions with only state variables and numeric literals.
 	canonicalMath1.makeCanonical(mathSymbolTableFactory);
 	canonicalMath2.makeCanonical(mathSymbolTableFactory);
 	
 	MathDescription[] canonicalMathDescs = {canonicalMath1, canonicalMath2};
 	return canonicalMathDescs;
-
 }
 
-/**
- * Insert the method's description here.
- * Creation date: (10/10/2002 11:32:46 PM)
- * @param oldMath cbit.vcell.math.MathDescription
- * @param newMath cbit.vcell.math.MathDescription
- * @deprecated
- */
-public static MathCompareResults testIfSame(MathSymbolTableFactory mathSymbolTableFactory, MathDescription oldMathDesc, MathDescription newMathDesc) {
-	try {
-	    if (oldMathDesc.compareEqual(newMathDesc)){
-		    return new MathCompareResults(Decision.MathEquivalent_NATIVE);
-		}else{
-		    //System.out.println("------NATIVE MATHS ARE DIFFERENT----------------------");
-			//System.out.println("------old native MathDescription:\n"+oldMathDesc.getVCML_database());
-			//System.out.println("------new native MathDescription:\n"+newMathDesc.getVCML_database());
-			MathDescription strippedOldMath = MathDescription.createCanonicalMathDescription(mathSymbolTableFactory, oldMathDesc);
-			MathDescription strippedNewMath = MathDescription.createCanonicalMathDescription(mathSymbolTableFactory, newMathDesc);
-			if (strippedOldMath.compareEqual(strippedNewMath)){
-				return new MathCompareResults(Decision.MathEquivalent_FLATTENED);
-			}else{
-				Variable oldVars[] = (Variable[])org.vcell.util.BeanUtils.getArray(strippedOldMath.getVariables(),Variable.class);
-				Variable newVars[] = (Variable[])org.vcell.util.BeanUtils.getArray(strippedNewMath.getVariables(),Variable.class);
-				if (oldVars.length != newVars.length){
-					//
-					// number of state variables are not equal (canonical maths only have state variables)
-					//
-					return new MathCompareResults(Decision.MathDifferent_DIFFERENT_NUMBER_OF_VARIABLES);
+	public static MathDescription createCanonicalMathDescription(MathSymbolTableFactory mathSymbolTableFactory, MathDescription originalMathDescription) throws MathException, ExpressionException {
+		//
+		// clone current mathdescription
+		//
+		MathDescription newMath = new MathDescription(originalMathDescription);
+
+		newMath.makeCanonical(mathSymbolTableFactory);
+
+		return newMath;
+	}
+
+	public static void substituteCommonDiscontinuitiesInPlace(Expression exp1, Expression exp2, String varPrefix) throws ExpressionException {
+		Vector<Discontinuity> v1 = exp1.getDiscontinuities();
+		Vector<Discontinuity> v2 = exp2.getDiscontinuities();
+		if (!(v1.isEmpty() && v2.isEmpty())) {
+			HashSet<String> allDisc = new HashSet<String>();
+			for (int l = 0; l<v1.size(); l++) {
+				allDisc.add(v1.get(l).getDiscontinuityExp().infix());
+			}
+			for (int l = 0; l<v2.size(); l++) {
+				allDisc.add(v2.get(l).getDiscontinuityExp().infix());
+			}
+			int l = 0;
+			for (String discExp : allDisc) {
+				Optional<Discontinuity> discontinuity;
+				discontinuity = exp1.getDiscontinuities().stream()
+						.filter(disc -> disc.getDiscontinuityExp().infix().equals(discExp)).findFirst();
+				if (discontinuity.isPresent()) {
+					exp1.substituteInPlace(discontinuity.get().getDiscontinuityExp(), new Expression(varPrefix + l));
 				}
-				if (!org.vcell.util.Compare.isEqual(oldVars,newVars)){
-					//
-					// variable names are not equivalent (nothing much we can do)
-					//
-					return new MathCompareResults(Decision.MathDifferent_VARIABLES_DONT_MATCH);
+
+				discontinuity = exp2.getDiscontinuities().stream()
+						.filter(disc -> disc.getDiscontinuityExp().infix().equals(discExp)).findFirst();
+				if (discontinuity.isPresent()) {
+					exp2.substituteInPlace(discontinuity.get().getDiscontinuityExp(), new Expression(varPrefix + l));
+				}
+				exp2.substituteInPlace(new Expression(discExp),
+						new Expression(varPrefix + l));
+				l++;
+			}
+		}
+	}
+
+	public static boolean compareEquivalent(FastInvariant fastInvariant1, FastInvariant fastInvariant2) throws ExpressionException {
+		//
+		// It is ok if the two expressions are different by a scale factor (e.g. if X+Y is conserved, so is 2*X+2*Y, or 2*(X+Y))
+		//    1. estimate proportionality factor (average of 5 evaluations - with discontinuities removed)
+		//    2. scale one of the expressions by the proportionality factor to correct for the scaling.
+		//    3. compare the two expressions for equivalence
+		//
+		Expression oldFastInvExp = new Expression(fastInvariant1.getFunction());
+		Expression newFastInvExp = new Expression(fastInvariant2.getFunction());
+		MathUtilities.substituteCommonDiscontinuitiesInPlace(oldFastInvExp, newFastInvExp, "BOOLEAN_");
+
+		Set<String> symbolSet = new LinkedHashSet<>(Arrays.asList(oldFastInvExp.getSymbols()));
+		symbolSet.addAll(Arrays.asList(newFastInvExp.getSymbols()));
+		String[] symbols = symbolSet.toArray(new String[symbolSet.size()]);
+		SimpleSymbolTable symbolTable = new SimpleSymbolTable(symbols);
+		oldFastInvExp.bindExpression(symbolTable);
+		newFastInvExp.bindExpression(symbolTable);
+
+		Random random = new Random(0);
+		List<Double> ratios = new ArrayList<>();
+		final int NUM_SUCCESSFUL_TRIALS = 5;
+		final int MAX_TRIALS = 500;
+		Double estimatedRatio;
+		double[] values = new double[symbols.length];
+		for (int m = 0; m < MAX_TRIALS || ratios.size() < NUM_SUCCESSFUL_TRIALS; m++) {
+			for (int j = 0; j < values.length; j++) {
+				values[j] = random.nextDouble() + 1.0;
+			}
+			try {
+				double oldFastInvValue = oldFastInvExp.evaluateVector(values);
+				double newFastInvValue = newFastInvExp.evaluateVector(values);
+				if (oldFastInvValue != 0.0 && newFastInvValue != 0.0) {
+					ratios.add(oldFastInvValue / newFastInvValue);
 				}
+			} catch (ExpressionException e) {
+			}
+		}
+		if (ratios.size() >= NUM_SUCCESSFUL_TRIALS){
+			DoubleSummaryStatistics stats = ratios.stream().mapToDouble(r -> r).summaryStatistics();
+			estimatedRatio = stats.getAverage();
+		}else{
+			String msg = "fast invariant expressions could not be evaluated, exp1='"+fastInvariant1.getFunction().infix()+"', "+
+					"exp2='"+fastInvariant2.getFunction().infix()+"'";
+			lg.debug(msg);
+			return false;
+		}
+		Expression scaled_fastInvariant2 = Expression.mult(new Expression(estimatedRatio),fastInvariant2.getFunction());
+		System.out.println("MathDescription.compareEquivalent(): comparing "+fastInvariant1.getFunction().infix()+" with "+scaled_fastInvariant2.infix());
+		if (!ExpressionUtils.functionallyEquivalent(fastInvariant1.getFunction(), scaled_fastInvariant2)){
+			String msg = "fast invariant expressions are different, exp1='"+fastInvariant1.getFunction().infix()+"', "+
+					"exp2='"+fastInvariant2.getFunction().infix()+"'";
+			lg.debug(msg);
+			return false;
+		}
+		return true;
+	}
+
+
+	public static MathDescription createMathWithExpandedEquations(MathDescription originalMathDescription, Set<String> varNamesToKeep, Map<String, Variable.Domain> varDomainMap) throws MathException, ExpressionException {
+		//
+		// clone current mathdescription
+		//
+		MathDescription newMath = new MathDescription(originalMathDescription);
+
+		//
+		// for any dependent variables in the "varNamesToKeep" list, create appropriate Variable/Equation
+		//
+		// this assumes that the dependent 'Function' is a linear combination of existing state variables
+		//  e.g. Function depVar = K0 + K1*indepVar1 + K2*indepVar2 + ... + Kn*indepVarN
+		// if it doesn't fit this form, then math's are not equivalent.
+		//
+		HashSet<String> stateVarSet = newMath.getStateVariableNames();
+		// Build the list of variables to be added
+		ArrayList<Function> varsToAdd = new ArrayList<Function>();
+		for (Variable var : newMath.getVariableList()){
+			if (varNamesToKeep.contains(var.getName()) && var instanceof Function){
+				varsToAdd.add((Function)var);
+			}
+		}
+		for (Function function : varsToAdd){
 				//
-				// go through the list of SubDomains, and compare equations one by one and "correct" new one if possible
+				// get list of symbols that are state variables
 				//
-				SubDomain subDomainsOld[] = (SubDomain[])org.vcell.util.BeanUtils.getArray(strippedOldMath.getSubDomains(),SubDomain.class);
-				SubDomain subDomainsNew[] = (SubDomain[])org.vcell.util.BeanUtils.getArray(strippedNewMath.getSubDomains(),SubDomain.class);
-				if (subDomainsOld.length != subDomainsNew.length){
-					return new MathCompareResults(Decision.MathDifferent_DIFFERENT_NUMBER_OF_SUBDOMAINS);
-				}
-				for (int i = 0; i < subDomainsOld.length; i++){
-					for (int j = 0; j < oldVars.length; j++){
+				ArrayList<Variable> indepVarList = new ArrayList<Variable>();         // holds the "indepVar's"
+				ArrayList<Expression> coefficientList = new ArrayList<Expression>();      // holds the "K's"
+				Expression exp = function.getExpression();
+				exp.bindExpression(null);
+				Expression K0 = new Expression(exp);
+				K0.bindExpression(null);
+				String symbols[] = exp.getSymbols();
+				for (int j = 0; j < symbols.length; j++){
+					if (stateVarSet.contains(symbols[j])){
+						//
+						// store the independent variable (indepVar_i)
+						//
+						indepVarList.add(newMath.getVariable(symbols[j]));
+						Expression differential = exp.differentiate(symbols[j]);
+						differential = differential.flatten();
+						//
+						// store the coefficient (K_i)
+						//
+						coefficientList.add(differential);
 						//
-						// test equation for this subdomain and variable
+						// remove this term from the "constant" term (K0)
+						// (e.g. for expression "K0 + K1*V1 + ... + Ki*Vi + ... + Kn*Vn", Vi set to 0.0 and flattened)
+						// after each term is removed, only K0 is left
 						//
-						{
-						Equation oldEqu = subDomainsOld[i].getEquation(oldVars[j]);
-						Equation newEqu = subDomainsNew[i].getEquation(oldVars[j]);
-						if (!org.vcell.util.Compare.isEqualOrNull(oldEqu,newEqu)){
-							boolean bFoundDifference = false;
-							//
-							// equation didn't compare exactly, lets try to evaluate some instead
-							//
-							if (oldEqu==null){
-								//
-								// only one MathDescription had Equation for this Variable.
-								//
-								return new MathCompareResults(Decision.MathDifferent_EQUATION_ADDED);
+						K0.substituteInPlace(new Expression(symbols[j]),new Expression(0.0));
+						K0 = K0.flatten();
+					}
+				}
+				//
+				// compute fast invariant expression, may be needed later
+				//
+				Expression fastInvariantExp = new Expression(0.0);
+				for (int k = 0; k < indepVarList.size(); k++){
+					Variable indepVar = indepVarList.get(k);
+					Expression coefficient = coefficientList.get(k);
+					fastInvariantExp = Expression.add(fastInvariantExp,Expression.mult(new Expression(coefficient),new Expression(indepVar.getName())));
+				}
 
-							}
-							if (newEqu==null){
-								//
-								// only one MathDescription had Equation for this Variable.
-								//
-								return new MathCompareResults(Decision.MathDifferent_EQUATION_REMOVED);
+				//
+				// either all independent vars should be Volume, all should be Membrane, or all should be Filament
+				//
+				int countVolumeVars = 0;
+				int countMembraneVars = 0;
+				int countFilamentVars = 0;
+				int countPointVars = 0;
+				for (int j = 0; j < indepVarList.size(); j++){
+					if (indepVarList.get(j) instanceof VolVariable){
+						countVolumeVars++;
+					}else if (indepVarList.get(j) instanceof MemVariable){
+						countMembraneVars++;
+					}else if (indepVarList.get(j) instanceof FilamentVariable){
+						countFilamentVars++;
+					}else if (indepVarList.get(j) instanceof PointVariable){
+						countPointVars++;
+					}else{
+						throw new RuntimeException("create canonicalMath cannot handle dependent vars of type '"+indepVarList.get(j).getClass().getName()+"'");
+					}
+				}
+				Variable.Domain domain = varDomainMap.get(function.getName());
+				SubDomain functionSubdomain = (domain != null) ? newMath.getSubDomain(domain.getName()) : null;
 
+				//
+				// case: Volume Variable
+				// create VolVariable
+				// for each CompartmentSubDomain, create OdeEquation
+				// remove Function
+				//
+				if ((countVolumeVars > 0 && countVolumeVars == indepVarList.size()) || functionSubdomain instanceof CompartmentSubDomain){
+					VolVariable volVariable = new VolVariable(function.getName(),function.getDomain());
+					newMath.getVariableList().remove(function);
+					newMath.getVariableList().add(volVariable);
+					newMath.getVariableMap().remove(function.getName());
+					newMath.getVariableMap().put(volVariable.getName(), volVariable);
+					//
+					// determine which volume subdomains (CompartmentSubDomains) to add equations to
+					//    if domain information is available, then restrict the creation of equations to that subdomain,
+					//    else add equation to all subdomains of the same type (here: any CompartmentSubDomain)
+					//
+					Set<CompartmentSubDomain> compartmentSubDomains = new LinkedHashSet<>();
+					if (functionSubdomain != null){
+						compartmentSubDomains.add((CompartmentSubDomain) functionSubdomain);
+					}else if (countMembraneVars > 0){
+						for (Variable indepVar : indepVarList){
+							if (indepVar instanceof VolVariable && indepVar.getDomain() != null){
+								compartmentSubDomains.add((CompartmentSubDomain) newMath.getSubDomain(indepVar.getDomain().getName()));
 							}
-							Expression oldExps[] = (Expression[])org.vcell.util.BeanUtils.getArray(oldEqu.getExpressions(strippedOldMath),Expression.class);
-							Expression newExps[] = (Expression[])org.vcell.util.BeanUtils.getArray(newEqu.getExpressions(strippedNewMath),Expression.class);
-							if (oldExps.length != newExps.length){
-								return new MathCompareResults(Decision.MathDifferent_DIFFERENT_NUMBER_OF_EXPRESSIONS);
+						}
+					}else{
+						for (SubDomain subDomain : newMath.getSubDomainCollection()){
+							if (subDomain instanceof CompartmentSubDomain){
+								compartmentSubDomains.add((CompartmentSubDomain) subDomain);
 							}
-							for (int k = 0; k < oldExps.length; k++){
-								if (!oldExps[k].compareEqual(newExps[k])){
-									bFoundDifference = true;
-									if (!cbit.vcell.parser.ExpressionUtils.functionallyEquivalent(oldExps[k],newExps[k])){
-										//
-										// difference couldn't be reconciled
-										//
-										return new MathCompareResults(Decision.MathDifferent_DIFFERENT_EXPRESSION,
-												"expressions are different Old: '"+oldExps[k].infix()+"'\n"+
-												"expressions are different New: '"+newExps[k].infix()+"'");
-									}else{
-										//System.out.println("expressions are equivalent Old: '"+oldExps[k].infix()+"'\n"+
-														   //"expressions are equivalent New: '"+newExps[k].infix()+"'");
-									}
-								}
+						}
+					}
+					for (CompartmentSubDomain compartmentSubDomain : compartmentSubDomains){
+						//
+						// add an ODE where
+						//    initial value = K0 + Sum(coefficient_i*Var_i.init)
+						//    rate value = Sum(coefficient_i*Var_i.rate)
+						//
+						Expression initExp = new Expression(K0);
+						Expression rateExp = new Expression(0.0);
+						for (int k = 0; k < indepVarList.size(); k++){
+							Variable indepVar = indepVarList.get(k);
+							Equation indepVarEqu = compartmentSubDomain.getEquation(indepVar);
+							Expression coefficient = coefficientList.get(k);
+							initExp = Expression.add(initExp,Expression.mult(new Expression(coefficient),new Expression(indepVarEqu.getInitialExpression())));
+							rateExp = Expression.add(rateExp,Expression.mult(new Expression(coefficient),new Expression(indepVarEqu.getRateExpression())));
+						}
+						for (Variable var : newMath.getVariableList()){
+							if (var.getName().startsWith(function.getName()+ MathDescription.MATH_FUNC_INIT_SUFFIX_PREFIX)){
+								Variable initVariable = var;
+								initExp = new Expression(initVariable,null);
+								break;
 							}
-							//
-							// equation was not strictly "equal" but passed all tests, replace with old equation and move on
-							//
-							if (bFoundDifference){
-								subDomainsNew[i].replaceEquation(oldEqu);
-							}else{
-								//
-								// couldn't find the smoking gun, just plain bad
-								//
-								return new MathCompareResults(Decision.MathDifferent_UNKNOWN_DIFFERENCE_IN_EQUATION,
-										"couldn't find problem with equation for "+oldVars[j].getName()+" in compartment "+subDomainsOld[i].getName());
+						}
+						OdeEquation odeEquation = new OdeEquation(volVariable, initExp.flatten(), rateExp.flatten());
+						compartmentSubDomain.addEquation(odeEquation);
+						if (compartmentSubDomain.getFastSystem()!=null){
+							compartmentSubDomain.getFastSystem().addFastInvariant(new FastInvariant(fastInvariantExp));
+						}
+					}
+				//
+				// case: Membrane Variable
+				//
+				}else if ((countMembraneVars > 0 && countMembraneVars == indepVarList.size()) || functionSubdomain instanceof MembraneSubDomain){
+					MemVariable memVariable = new MemVariable(function.getName(),function.getDomain());
+					newMath.getVariableList().remove(function);
+					newMath.getVariableList().add(memVariable);
+					newMath.getVariableMap().remove(function.getName());
+					newMath.getVariableMap().put(memVariable.getName(), memVariable);
+					//
+					// determine which membrane subdomains to add equations to
+					//    if domain information is available, then restrict the creation of equations to that subdomain,
+					//    else add equation to all subdomains of the same type (here: any MembraneSubDomain)
+					//
+					Set<MembraneSubDomain> membraneSubDomains = new LinkedHashSet<>();
+					if (functionSubdomain != null){
+						membraneSubDomains.add((MembraneSubDomain) functionSubdomain);
+					}else if (countMembraneVars > 0){
+						for (Variable indepVar : indepVarList){
+							if (indepVar instanceof MemVariable && indepVar.getDomain() != null){
+								membraneSubDomains.add((MembraneSubDomain) newMath.getSubDomain(indepVar.getDomain().getName()));
 							}
 						}
+					}else{
+						for (SubDomain subDomain : newMath.getSubDomainCollection()){
+							if (subDomain instanceof MembraneSubDomain){
+								membraneSubDomains.add((MembraneSubDomain) subDomain);
+							}
 						}
+					}
+					for (MembraneSubDomain membraneSubDomain : membraneSubDomains){
 						//
-						// if a membrane, test jumpCondition for this subdomain and variable
+						// add an ODE where
+						//    initial value = K0 + Sum(coefficient_i*Var_i.init)
+						//    rate value = Sum(coefficient_i*Var_i.rate)
 						//
-						if (subDomainsOld[i] instanceof MembraneSubDomain && oldVars[j] instanceof VolVariable){
-							JumpCondition oldJumpCondition = ((MembraneSubDomain)subDomainsOld[i]).getJumpCondition((VolVariable)oldVars[j]);
-							JumpCondition newJumpCondition = ((MembraneSubDomain)subDomainsNew[i]).getJumpCondition((VolVariable)oldVars[j]);
-							if (!org.vcell.util.Compare.isEqualOrNull(oldJumpCondition,newJumpCondition)){
-								boolean bFoundDifference = false;
-								//
-								// equation didn't compare exactly, lets try to evaluate some instead
-								//
-								if (oldJumpCondition==null){
-									//
-									// only one MathDescription had Equation for this Variable.
-									//
-									return new MathCompareResults(Decision.MathDifferent_EQUATION_ADDED);
-								}
-								if (newJumpCondition==null){
-									//
-									// only one MathDescription had Equation for this Variable.
-									//
-									return new MathCompareResults(Decision.MathDifferent_EQUATION_REMOVED);
-								}
-								Expression oldExps[] = (Expression[])org.vcell.util.BeanUtils.getArray(oldJumpCondition.getExpressions(strippedOldMath),Expression.class);
-								Expression newExps[] = (Expression[])org.vcell.util.BeanUtils.getArray(newJumpCondition.getExpressions(strippedNewMath),Expression.class);
-								if (oldExps.length != newExps.length){
-									return new MathCompareResults(Decision.MathDifferent_DIFFERENT_NUMBER_OF_EXPRESSIONS);
-								}
-								for (int k = 0; k < oldExps.length; k++){
-									if (!oldExps[k].compareEqual(newExps[k])){
-										bFoundDifference = true;
-										if (!cbit.vcell.parser.ExpressionUtils.functionallyEquivalent(oldExps[k],newExps[k])){
-											//
-											// difference couldn't be reconciled
-											//
-											return new MathCompareResults(Decision.MathDifferent_DIFFERENT_EXPRESSION,
-													"expressions are different Old: '"+oldExps[k]+"'\n"+
-													"expressions are different New: '"+newExps[k]+"'");
-										}else{
-											//System.out.println("expressions are equivalent Old: '"+oldExps[k]+"'\n"+
-															   //"expressions are equivalent New: '"+newExps[k]+"'");
-										}
-									}
-								}
-								//
-								// equation was not strictly "equal" but passed all tests, replace with old equation and move on
-								//
-								if (bFoundDifference){
-									((MembraneSubDomain)subDomainsNew[i]).replaceJumpCondition(oldJumpCondition);
-								}else{
-									//
-									// couldn't find the smoking gun, just plain bad
-									//
-									return new MathCompareResults(Decision.MathDifferent_UNKNOWN_DIFFERENCE_IN_EQUATION,
-											"couldn't find problem with jumpCondition for "+oldVars[j].getName()+" in compartment "+subDomainsOld[i].getName());
-								}
+						Expression initExp = new Expression(K0);
+						Expression rateExp = new Expression(0.0);
+						for (int k = 0; k < indepVarList.size(); k++){
+							Variable indepVar = indepVarList.get(k);
+							Equation indepVarEqu = membraneSubDomain.getEquation(indepVar);
+							Expression coefficient = coefficientList.get(k);
+							initExp = Expression.add(initExp,Expression.mult(new Expression(coefficient),new Expression(indepVarEqu.getInitialExpression())));
+							rateExp = Expression.add(rateExp,Expression.mult(new Expression(coefficient),new Expression(indepVarEqu.getRateExpression())));
+						}
+						for (Variable var : newMath.getVariableList()){
+							if (var.getName().startsWith(function.getName()+ MathDescription.MATH_FUNC_INIT_SUFFIX_PREFIX)){
+								Variable initVariable = var;
+								initExp = new Expression(initVariable,null);
+								break;
 							}
 						}
+						OdeEquation odeEquation = new OdeEquation(memVariable, initExp.flatten(), rateExp.flatten());
+						membraneSubDomain.addEquation(odeEquation);
+						if (membraneSubDomain.getFastSystem()!=null){
+							membraneSubDomain.getFastSystem().addFastInvariant(new FastInvariant(fastInvariantExp));
+						}
 					}
+				//
+				// case: Filament Variable
+				//
+				}else if ((countFilamentVars > 0 && countFilamentVars == indepVarList.size()) || functionSubdomain instanceof FilamentSubDomain){
+					FilamentVariable filamentVariable = new FilamentVariable(function.getName(),function.getDomain());
+					newMath.getVariableList().remove(function);
+					newMath.getVariableList().add(filamentVariable);
+					newMath.getVariableMap().remove(function.getName());
+					newMath.getVariableMap().put(filamentVariable.getName(), filamentVariable);
 					//
-					// test fast system for subdomain
+					// determine which filament subdomains to add equations to
+					//    if domain information is available, then restrict the creation of equations to that subdomain,
+					//    else add equation to all subdomains of the same type (here: any FilamentSubDomain)
 					//
-					FastSystem oldFastSystem = subDomainsOld[i].getFastSystem();
-					FastSystem newFastSystem = subDomainsNew[i].getFastSystem();
-					if (!org.vcell.util.Compare.isEqualOrNull(oldFastSystem,newFastSystem)){
-						boolean bFoundDifference = false;
+					Set<FilamentSubDomain> filamentSubDomains = new LinkedHashSet<>();
+					if (functionSubdomain != null){
+						filamentSubDomains.add((FilamentSubDomain) functionSubdomain);
+					}else if (countFilamentVars > 0){
+						for (Variable indepVar : indepVarList){
+							if (indepVar instanceof FilamentVariable && indepVar.getDomain() != null){
+								filamentSubDomains.add((FilamentSubDomain) newMath.getSubDomain(indepVar.getDomain().getName()));
+							}
+						}
+					}else{
+						for (SubDomain subDomain : newMath.getSubDomainCollection()){
+							if (subDomain instanceof FilamentSubDomain){
+								filamentSubDomains.add((FilamentSubDomain) subDomain);
+							}
+						}
+					}
+					for (FilamentSubDomain filamentSubDomain : filamentSubDomains){
 						//
-						// fastSystems didn't compare exactly, lets try to evaluate some expressions instead
+						// add an ODE where
+						//    initial value = K0 + Sum(coefficient_i*Var_i.init)
+						//    rate value = Sum(coefficient_i*Var_i.rate)
 						//
-						if (oldFastSystem==null){
-							//
-							// only one MathDescription had Equation for this Variable.
-							//
-							return new MathCompareResults(Decision.MathDifferent_EQUATION_ADDED);
+						Expression initExp = new Expression(K0);
+						Expression rateExp = new Expression(0.0);
+						for (int k = 0; k < indepVarList.size(); k++){
+							Variable indepVar = indepVarList.get(k);
+							Equation indepVarEqu = filamentSubDomain.getEquation(indepVar);
+							Expression coefficient = coefficientList.get(k);
+							initExp = Expression.add(initExp,Expression.mult(new Expression(coefficient),new Expression(indepVarEqu.getInitialExpression())));
+							rateExp = Expression.add(rateExp,Expression.mult(new Expression(coefficient),new Expression(indepVarEqu.getRateExpression())));
 						}
-						if (newFastSystem==null){
-							//
-							// only one MathDescription had Equation for this Variable.
-							//
-							return new MathCompareResults(Decision.MathDifferent_EQUATION_REMOVED);
+						for (Variable var : newMath.getVariableList()){
+							if (var.getName().startsWith(function.getName()+ MathDescription.MATH_FUNC_INIT_SUFFIX_PREFIX)){
+								Variable initVariable = var;
+								initExp = new Expression(initVariable,null);
+								break;
+							}
 						}
-						Expression oldExps[] = oldFastSystem.getExpressions();
-						Expression newExps[] = newFastSystem.getExpressions();
-						if (oldExps.length != newExps.length){
-							return new MathCompareResults(Decision.MathDifferent_DIFFERENT_NUMBER_OF_EXPRESSIONS);
+						OdeEquation odeEquation = new OdeEquation(filamentVariable, initExp.flatten(), rateExp.flatten());
+						filamentSubDomain.addEquation(odeEquation);
+					}
+
+				//
+				// case: Point Variable
+				//
+				}else if ((countPointVars > 0 && countPointVars == indepVarList.size()) || functionSubdomain instanceof PointSubDomain){
+					PointVariable pointVariable = new PointVariable(function.getName(),function.getDomain());
+					newMath.getVariableList().remove(function);
+					newMath.getVariableList().add(pointVariable);
+					newMath.getVariableMap().remove(function.getName());
+					newMath.getVariableMap().put(pointVariable.getName(), pointVariable);
+					//
+					// determine which point subdomains to add equations to
+					//    if domain information is available, then restrict the creation of equations to that subdomain,
+					//    else add equation to all subdomains of the same type (here: any PointSubDomain)
+					//
+					Set<PointSubDomain> pointSubDomains = new LinkedHashSet<>();
+					if (functionSubdomain != null){
+						pointSubDomains.add((PointSubDomain) functionSubdomain);
+					}else if (countPointVars > 0){
+						for (Variable indepVar : indepVarList){
+							if (indepVar instanceof PointVariable && indepVar.getDomain() != null){
+								pointSubDomains.add((PointSubDomain) newMath.getSubDomain(indepVar.getDomain().getName()));
+							}
 						}
-						for (int k = 0; k < oldExps.length; k++){
-							if (!oldExps[k].compareEqual(newExps[k])){
-								bFoundDifference = true;
-								if (!cbit.vcell.parser.ExpressionUtils.functionallyEquivalent(oldExps[k],newExps[k])){
-									//
-									// difference couldn't be reconciled
-									//
-									return new MathCompareResults(Decision.MathDifferent_DIFFERENT_FASTRATE_EXPRESSION,
-											"FastSystem expressions are different Old: '"+oldExps[k]+"'\n"+
-											"FastSystem expressions are different New: '"+newExps[k]+"'");
-								}else{
-									//System.out.println("expressions are equivalent Old: '"+oldExps[k]+"'\n"+
-													   //"expressions are equivalent New: '"+newExps[k]+"'");
-								}
+					}else{
+						for (SubDomain subDomain : newMath.getSubDomainCollection()){
+							if (subDomain instanceof PointSubDomain){
+								pointSubDomains.add((PointSubDomain) subDomain);
 							}
 						}
+					}
+					for (PointSubDomain pointSubDomain : pointSubDomains){
 						//
-						// equation was not strictly "equal" but passed all tests, replace with old equation and move on
+						// add an ODE where
+						//    initial value = K0 + Sum(coefficient_i*Var_i.init)
+						//    rate value = Sum(coefficient_i*Var_i.rate)
 						//
-						if (bFoundDifference){
-							subDomainsNew[i].setFastSystem(oldFastSystem);
-						}else{
-							//
-							// couldn't find the smoking gun, just plain bad
-							//
-							return new MathCompareResults(Decision.MathDifferent_UNKNOWN_DIFFERENCE_IN_EQUATION,
-									"couldn't find problem with FastSystem for compartment "+subDomainsOld[i].getName());
+						Expression initExp = new Expression(K0);
+						Expression rateExp = new Expression(0.0);
+						for (int k = 0; k < indepVarList.size(); k++){
+							Variable indepVar = indepVarList.get(k);
+							Equation indepVarEqu = pointSubDomain.getEquation(indepVar);
+							Expression coefficient = coefficientList.get(k);
+							initExp = Expression.add(initExp,Expression.mult(new Expression(coefficient),new Expression(indepVarEqu.getInitialExpression())));
+							rateExp = Expression.add(rateExp,Expression.mult(new Expression(coefficient),new Expression(indepVarEqu.getRateExpression())));
+						}
+						for (Variable var : newMath.getVariableList()){
+							if (var.getName().startsWith(function.getName()+ MathDescription.MATH_FUNC_INIT_SUFFIX_PREFIX)){
+								Variable initVariable = var;
+								initExp = new Expression(initVariable,null);
+								break;
+							}
 						}
+						OdeEquation odeEquation = new OdeEquation(pointVariable, initExp.flatten(), rateExp.flatten());
+						pointSubDomain.addEquation(odeEquation);
 					}
-				}
 
 				//
-				// after repairing aspects of MathDescription, now see if same
 				//
-				if (strippedOldMath.compareEqual(strippedNewMath)){
-					return new MathCompareResults(Decision.MathEquivalent_NUMERICALLY);
+				//
 				}else{
-				    //System.out.println("------UNKNOWN DIFFERENCE IN MATH----------------------");
-					//System.out.println("------old flattened MathDescription:\n"+strippedOldMath.getVCML_database());
-					//System.out.println("------new flattened MathDescription:\n"+strippedNewMath.getVCML_database());
-					return new MathCompareResults(Decision.MathDifferent_UNKNOWN_DIFFERENCE_IN_MATH);
+					throw new RuntimeException("create canonicalMath cannot handle mixture of dependent vars types");
 				}
-			}
 		}
-	}catch (cbit.vcell.parser.DivideByZeroException e){
-		System.out.println("-------DIVIDE BY ZERO EXCEPTION-------------------------");
-		return new MathCompareResults(Decision.MathDifferent_FAILURE_FLATTENING_DIV_BY_ZERO);
-	}catch (Throwable e){
-		e.printStackTrace(System.out);
-		return new MathCompareResults(Decision.MathDifferent_FAILURE_FLATTENING_UNKNOWN,
-				"Exception: '"+e.getMessage()+"'");
-	}
-}
 
+		return newMath;
+	}
 }
diff --git a/vcell-core/src/main/java/cbit/vcell/mongodb/VCMongoDbDriver.java b/vcell-core/src/main/java/cbit/vcell/mongodb/VCMongoDbDriver.java
index bb9c2aa58..79b14b5ea 100644
--- a/vcell-core/src/main/java/cbit/vcell/mongodb/VCMongoDbDriver.java
+++ b/vcell-core/src/main/java/cbit/vcell/mongodb/VCMongoDbDriver.java
@@ -167,8 +167,10 @@ public class VCMongoDbDriver {
 			LinkedHashMap<String, String> contextMap = new LinkedHashMap<>();
 			Document dbObject = mongoMessage.getDbObject();
 			for (String field : dbObject.keySet()){
-				String value = dbObject.get(field).toString();
-				contextMap.put(field, value);
+				Object value = dbObject.get(field);
+				if (value != null) {
+					contextMap.put(field, value.toString());
+				}
 			}
 			try (final CloseableThreadContext.Instance ctc = CloseableThreadContext.putAll(contextMap)) {
 				lg.log(level, message);
diff --git a/vcell-core/src/main/java/cbit/vcell/solver/ode/DefaultODESolver.java b/vcell-core/src/main/java/cbit/vcell/solver/ode/DefaultODESolver.java
index c3a9089c1..5d37c0198 100644
--- a/vcell-core/src/main/java/cbit/vcell/solver/ode/DefaultODESolver.java
+++ b/vcell-core/src/main/java/cbit/vcell/solver/ode/DefaultODESolver.java
@@ -278,8 +278,8 @@ private Vector<StateVariable> createStateVariables() throws MathException, Expre
 									fieldSensVariables, 
 									simSymbolTable));
 	}
-	if (stateVariables.size() == 0) {
-		throw new MathException("there are no equations defined");
+	if (stateVariables.size() == 0 && this instanceof RungeKuttaFehlbergSolver) {
+		throw new MathException("only fixed time step solvers support functions-only - no-equations");
 	}
 	return(stateVariables);
 }
diff --git a/vcell-core/src/test/java/cbit/vcell/mapping/MathGenCompareTest.java b/vcell-core/src/test/java/cbit/vcell/mapping/MathGenCompareTest.java
index 7cfcc9de7..6ef1d3166 100644
--- a/vcell-core/src/test/java/cbit/vcell/mapping/MathGenCompareTest.java
+++ b/vcell-core/src/test/java/cbit/vcell/mapping/MathGenCompareTest.java
@@ -9,10 +9,7 @@ import cbit.vcell.solver.SimulationSymbolTable;
 import cbit.vcell.xml.XMLSource;
 import cbit.vcell.xml.XmlHelper;
 import cbit.vcell.xml.XmlParseException;
-import org.junit.AfterClass;
-import org.junit.Assert;
-import org.junit.BeforeClass;
-import org.junit.Test;
+import org.junit.*;
 import org.junit.experimental.categories.Category;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -38,6 +35,7 @@ public class MathGenCompareTest {
 		this.filename_colon_appname = filename_colon_appname;
 	}
 
+
 	@BeforeClass
 	public static void setup() throws IOException {
 		previousInstalldirPropertyValue = System.getProperty("vcell.installDir");
@@ -96,34 +94,81 @@ public class MathGenCompareTest {
 	/**
 	 * each file in the knownFaultsMap hold known problems in the current software
 	 */
-	public static Map<String, MathCompareResults.Decision> knownFaults() {
+	public static Map<String, MathCompareResults.Decision> knownLegacyFaults() {
+		HashMap<String, MathCompareResults.Decision> faults = new HashMap();
+		faults.put("lumped_reaction_proper_size_in_rate.vcml:Application0", MathCompareResults.Decision.MathDifferent_DIFFERENT_EXPRESSION); // =LEGACY= MathDifferent:DifferentExpression:expressions are different: ' - (0.001660538783162726 * s1)' vs ' -
+		faults.put("biomodel_47429473.vcml:NWASP at Lam Tip in 3D Geometry", MathCompareResults.Decision.MathDifferent_DIFFERENT_FASTINV_EXPRESSION); // =LEGACY= MathDifferent:DifferentFastInvExpression:could not find a match for fast invariant expression'Expres
+		faults.put("biomodel_55178308.vcml:Spatial 1 - 3D -  electrophysiology", MathCompareResults.Decision.MathDifferent_FAILURE_UNKNOWN); // =LEGACY= MathDifferent:FailedUnknown:line #630 Exception: variable Na not defined
+		faults.put("biomodel_59361239.vcml:full model", MathCompareResults.Decision.MathDifferent_DIFFERENT_EXPRESSION); // =LEGACY= MathDifferent:DifferentExpression:expressions are different: '( - (3.0199335548172735E-4 * ((66.2266
+		faults.put("biomodel_59361239.vcml:individual knockouts", MathCompareResults.Decision.MathDifferent_DIFFERENT_EXPRESSION); // =LEGACY= MathDifferent:DifferentExpression:expressions are different: '( - (3.0199335548172735E-4 * ((66.2266
+		faults.put("biomodel_59361239.vcml:time delay 60 s", MathCompareResults.Decision.MathDifferent_DIFFERENT_EXPRESSION); // =LEGACY= MathDifferent:DifferentExpression:expressions are different: '( - (3.0199335548172735E-4 * ((66.2266
+		faults.put("biomodel_59361239.vcml:receptor density", MathCompareResults.Decision.MathDifferent_DIFFERENT_EXPRESSION); // =LEGACY= MathDifferent:DifferentExpression:expressions are different: '( - (3.0199335548172735E-4 * ((66.2266
+		faults.put("biomodel_59361239.vcml:sensitivity analysis", MathCompareResults.Decision.MathDifferent_DIFFERENT_EXPRESSION); // =LEGACY= MathDifferent:DifferentExpression:expressions are different: '( - (3.0199335548172735E-4 * ((66.2266
+		faults.put("biomodel_59361239.vcml:individual knockouts with delay", MathCompareResults.Decision.MathDifferent_DIFFERENT_EXPRESSION); // =LEGACY= MathDifferent:DifferentExpression:expressions are different: '( - (3.0199335548172735E-4 * ((66.2266
+		faults.put("biomodel_59361239.vcml:integrin knockout", MathCompareResults.Decision.MathDifferent_DIFFERENT_EXPRESSION); // =LEGACY= MathDifferent:DifferentExpression:expressions are different: '( - (3.0199335548172735E-4 * ((6680.83
+		return faults;
+	}
+
+	public static Map<String, MathCompareResults.Decision> knownReductionFaults() {
 		HashMap<String, MathCompareResults.Decision> faults = new HashMap();
-		faults.put("lumped_reaction_proper_size_in_rate.vcml:Application0", MathCompareResults.Decision.MathDifferent_DIFFERENT_EXPRESSION);
-		faults.put("biomodel_18894555.vcml:compartmental", MathCompareResults.Decision.MathDifferent_DIFFERENT_EXPRESSION);
-		faults.put("biomodel_43726934.vcml:compartmental", MathCompareResults.Decision.MathDifferent_DIFFERENT_NUMBER_OF_VARIABLES);
-		faults.put("biomodel_47429473.vcml:Steady State Turnover", MathCompareResults.Decision.MathDifferent_DIFFERENT_EXPRESSION);
-		faults.put("biomodel_47429473.vcml:NWASP at Lam Tip in 3D Geometry", MathCompareResults.Decision.MathDifferent_DIFFERENT_FASTINV_EXPRESSION);
-		faults.put("biomodel_47429473.vcml:NWasp Activation Cap=1", MathCompareResults.Decision.MathDifferent_DIFFERENT_EXPRESSION);
-		faults.put("biomodel_47429473.vcml:NWasp Activation Cap=2", MathCompareResults.Decision.MathDifferent_DIFFERENT_EXPRESSION);
-		faults.put("biomodel_47429473.vcml:NWasp Activation Cap=4", MathCompareResults.Decision.MathDifferent_DIFFERENT_EXPRESSION);
-		faults.put("biomodel_47429473.vcml:Nwasp Activation Cap=4 Cof=10", MathCompareResults.Decision.MathDifferent_DIFFERENT_EXPRESSION);
-		faults.put("biomodel_47429473.vcml:NWasp Activation Cap=2 Cof=10", MathCompareResults.Decision.MathDifferent_DIFFERENT_EXPRESSION);
-		faults.put("biomodel_47429473.vcml:NWasp Activation Cap=4 Prof=20", MathCompareResults.Decision.MathDifferent_DIFFERENT_EXPRESSION);
-		faults.put("biomodel_47429473.vcml:NWasp Activation Cap=4 Prof=20 Cof=10", MathCompareResults.Decision.MathDifferent_DIFFERENT_EXPRESSION);
-		faults.put("biomodel_47429473.vcml:NWasp Activation Cap=1 Cof=10", MathCompareResults.Decision.MathDifferent_DIFFERENT_EXPRESSION);
-		faults.put("biomodel_47429473.vcml:NWasp Activation Cap=1 Prof=20", MathCompareResults.Decision.MathDifferent_DIFFERENT_EXPRESSION);
-		faults.put("biomodel_47429473.vcml:NWasp Activation Cap=1 Prof=20 Cof=10", MathCompareResults.Decision.MathDifferent_DIFFERENT_EXPRESSION);
-		faults.put("biomodel_47429473.vcml:NWasp Activation Cap=2 Prof=20", MathCompareResults.Decision.MathDifferent_DIFFERENT_EXPRESSION);
-		faults.put("biomodel_47429473.vcml:NWASP Activation Cap=2 Prof=20 Cof = 10", MathCompareResults.Decision.MathDifferent_DIFFERENT_EXPRESSION);
-		faults.put("biomodel_47429473.vcml:NWASP Activation Cap=1 Prof=20 No Arp Binding", MathCompareResults.Decision.MathDifferent_DIFFERENT_EXPRESSION);
-		faults.put("biomodel_55178308.vcml:Spatial 1 - 3D -  electrophysiology", MathCompareResults.Decision.MathDifferent_DIFFERENT_NUMBER_OF_VARIABLES);
-		faults.put("biomodel_59361239.vcml:full model", MathCompareResults.Decision.MathDifferent_DIFFERENT_EXPRESSION);
-		faults.put("biomodel_59361239.vcml:individual knockouts", MathCompareResults.Decision.MathDifferent_DIFFERENT_EXPRESSION);
-		faults.put("biomodel_59361239.vcml:time delay 60 s", MathCompareResults.Decision.MathDifferent_DIFFERENT_EXPRESSION);
-		faults.put("biomodel_59361239.vcml:receptor density", MathCompareResults.Decision.MathDifferent_DIFFERENT_EXPRESSION);
-		faults.put("biomodel_59361239.vcml:sensitivity analysis", MathCompareResults.Decision.MathDifferent_DIFFERENT_EXPRESSION);
-		faults.put("biomodel_59361239.vcml:individual knockouts with delay", MathCompareResults.Decision.MathDifferent_DIFFERENT_EXPRESSION);
-		faults.put("biomodel_59361239.vcml:integrin knockout", MathCompareResults.Decision.MathDifferent_DIFFERENT_EXPRESSION);
+		faults.put("biomodel_100961371.vcml:modelinOct2010", MathCompareResults.Decision.MathDifferent_DIFFERENT_EXPRESSION); // =REDUCED= MathDifferent:DifferentExpression:expressions are different: ' - (9.963232698976356E-4 * ((1.2000000
+		faults.put("biomodel_148700996.vcml:Fast B Compartmental", MathCompareResults.Decision.MathDifferent_DIFFERENT_FASTINV_EXPRESSION); // =REDUCED= MathDifferent:DifferentFastInvExpression:could not find a match for fast invariant expression'Expres
+		faults.put("biomodel_148700996.vcml:2DGeom nonuniform dens v2", MathCompareResults.Decision.MathDifferent_DIFFERENT_NUMBER_OF_EXPRESSIONS); // =REDUCED= MathDifferent:DifferentNumberOfExpressions:fast invariants have different number of expressions
+		faults.put("biomodel_156134818.vcml:unnamed", MathCompareResults.Decision.MathDifferent_DIFFERENT_EXPRESSION); // =REDUCED= MathDifferent:DifferentExpression:expressions are different: '( - (7.961783439490447E-5 * EGF_EGFR_2
+		faults.put("biomodel_17257105.vcml:compartmental", MathCompareResults.Decision.MathDifferent_DIFFERENT_EXPRESSION); // =REDUCED= MathDifferent:DifferentExpression:expressions are different: '((0.0011 * (L_b1AR_cell + L_b1AR_Gs_ce
+		faults.put("biomodel_17257105.vcml:spatial", MathCompareResults.Decision.MathDifferent_DIFFERENT_NUMBER_OF_EXPRESSIONS); // =REDUCED= MathDifferent:DifferentNumberOfExpressions:fast invariants have different number of expressions
+		faults.put("biomodel_209284198.vcml:fast compart with 3D S:V & V:V", MathCompareResults.Decision.MathDifferent_DIFFERENT_FASTINV_EXPRESSION); // =REDUCED= MathDifferent:DifferentFastInvExpression:could not find a match for fast invariant expression'Expres
+		faults.put("biomodel_2917788.vcml:Figure 4.3: Fast Approximation", MathCompareResults.Decision.MathDifferent_DIFFERENT_EXPRESSION); // =REDUCED= MathDifferent:DifferentExpression:expressions are different: '0.0' vs '( - ((20.0 * x_o) - (50.0 * x
+		faults.put("biomodel_32568171.vcml:Sim5_1", MathCompareResults.Decision.MathDifferent_DIFFERENT_NUMBER_OF_EXPRESSIONS); // =REDUCED= MathDifferent:DifferentNumberOfExpressions:fast invariants have different number of expressions
+		faults.put("biomodel_32568171.vcml:GoodModel17_5", MathCompareResults.Decision.MathDifferent_DIFFERENT_NUMBER_OF_EXPRESSIONS); // =REDUCED= MathDifferent:DifferentNumberOfExpressions:fast invariants have different number of expressions
+		faults.put("biomodel_32568171.vcml:Furaptra", MathCompareResults.Decision.MathDifferent_DIFFERENT_NUMBER_OF_EXPRESSIONS); // =REDUCED= MathDifferent:DifferentNumberOfExpressions:fast invariants have different number of expressions
+		faults.put("biomodel_32568171.vcml:SyGCaMP2_1AP", MathCompareResults.Decision.MathDifferent_DIFFERENT_NUMBER_OF_EXPRESSIONS); // =REDUCED= MathDifferent:DifferentNumberOfExpressions:fast invariants have different number of expressions
+		faults.put("biomodel_32568171.vcml:SyGCaMP2_20AP", MathCompareResults.Decision.MathDifferent_DIFFERENT_NUMBER_OF_EXPRESSIONS); // =REDUCED= MathDifferent:DifferentNumberOfExpressions:fast invariants have different number of expressions
+		faults.put("biomodel_32568171.vcml:Furaptra_1AP", MathCompareResults.Decision.MathDifferent_DIFFERENT_NUMBER_OF_EXPRESSIONS); // =REDUCED= MathDifferent:DifferentNumberOfExpressions:fast invariants have different number of expressions
+		faults.put("biomodel_32568171.vcml:SyGCaMP2_1AP_v2", MathCompareResults.Decision.MathDifferent_DIFFERENT_NUMBER_OF_EXPRESSIONS); // =REDUCED= MathDifferent:DifferentNumberOfExpressions:fast invariants have different number of expressions
+		faults.put("biomodel_32568171.vcml:SyGCaMP2_20AP_v2", MathCompareResults.Decision.MathDifferent_DIFFERENT_NUMBER_OF_EXPRESSIONS); // =REDUCED= MathDifferent:DifferentNumberOfExpressions:fast invariants have different number of expressions
+		faults.put("biomodel_32568171.vcml:SyGCaMP2_1AP_v3", MathCompareResults.Decision.MathDifferent_DIFFERENT_NUMBER_OF_EXPRESSIONS); // =REDUCED= MathDifferent:DifferentNumberOfExpressions:fast invariants have different number of expressions
+		faults.put("biomodel_32568171.vcml:SyGCaMP2_20AP_v3", MathCompareResults.Decision.MathDifferent_DIFFERENT_NUMBER_OF_EXPRESSIONS); // =REDUCED= MathDifferent:DifferentNumberOfExpressions:fast invariants have different number of expressions
+		faults.put("biomodel_32568171.vcml:Furaptra_1AP_v3", MathCompareResults.Decision.MathDifferent_DIFFERENT_NUMBER_OF_EXPRESSIONS); // =REDUCED= MathDifferent:DifferentNumberOfExpressions:fast invariants have different number of expressions
+		faults.put("biomodel_32568171.vcml:SyGCaMP2_20AP_v4", MathCompareResults.Decision.MathDifferent_DIFFERENT_NUMBER_OF_EXPRESSIONS); // =REDUCED= MathDifferent:DifferentNumberOfExpressions:fast invariants have different number of expressions
+		faults.put("biomodel_32568171.vcml:SyGCaMP2_80Hz_v4", MathCompareResults.Decision.MathDifferent_DIFFERENT_NUMBER_OF_EXPRESSIONS); // =REDUCED= MathDifferent:DifferentNumberOfExpressions:fast invariants have different number of expressions
+		faults.put("biomodel_32568356.vcml:SyGCaMP2_1AP", MathCompareResults.Decision.MathDifferent_DIFFERENT_NUMBER_OF_EXPRESSIONS); // =REDUCED= MathDifferent:DifferentNumberOfExpressions:fast invariants have different number of expressions
+		faults.put("biomodel_32568356.vcml:SyGCaMP2_20AP", MathCompareResults.Decision.MathDifferent_DIFFERENT_NUMBER_OF_EXPRESSIONS); // =REDUCED= MathDifferent:DifferentNumberOfExpressions:fast invariants have different number of expressions
+		faults.put("biomodel_32568356.vcml:SyGCaMP2_1AP_v2", MathCompareResults.Decision.MathDifferent_DIFFERENT_NUMBER_OF_EXPRESSIONS); // =REDUCED= MathDifferent:DifferentNumberOfExpressions:fast invariants have different number of expressions
+		faults.put("biomodel_32568356.vcml:SyGCaMP2_20AP_v2", MathCompareResults.Decision.MathDifferent_DIFFERENT_NUMBER_OF_EXPRESSIONS); // =REDUCED= MathDifferent:DifferentNumberOfExpressions:fast invariants have different number of expressions
+		faults.put("biomodel_32568356.vcml:SyGCaMP2_1AP_v3", MathCompareResults.Decision.MathDifferent_DIFFERENT_NUMBER_OF_EXPRESSIONS); // =REDUCED= MathDifferent:DifferentNumberOfExpressions:fast invariants have different number of expressions
+		faults.put("biomodel_32568356.vcml:SyGCaMP2_20AP_v4", MathCompareResults.Decision.MathDifferent_DIFFERENT_NUMBER_OF_EXPRESSIONS); // =REDUCED= MathDifferent:DifferentNumberOfExpressions:fast invariants have different number of expressions
+		faults.put("biomodel_32568356.vcml:SyGCaMP2_80Hz_v4", MathCompareResults.Decision.MathDifferent_DIFFERENT_NUMBER_OF_EXPRESSIONS); // =REDUCED= MathDifferent:DifferentNumberOfExpressions:fast invariants have different number of expressions
+		faults.put("biomodel_38086434.vcml:CALI, fine mesh", MathCompareResults.Decision.MathDifferent_DIFFERENT_NUMBER_OF_EXPRESSIONS); // =REDUCED= MathDifferent:DifferentNumberOfExpressions:fast invariants have different number of expressions
+		faults.put("biomodel_38086434.vcml:CALI, VASP=0, fine mesh", MathCompareResults.Decision.MathDifferent_DIFFERENT_NUMBER_OF_EXPRESSIONS); // =REDUCED= MathDifferent:DifferentNumberOfExpressions:fast invariants have different number of expressions
+		faults.put("biomodel_38086434.vcml:CALI experiment with FRAP intensities", MathCompareResults.Decision.MathDifferent_DIFFERENT_NUMBER_OF_EXPRESSIONS); // =REDUCED= MathDifferent:DifferentNumberOfExpressions:fast invariants have different number of expressions
+		faults.put("biomodel_38086434.vcml:CALI without KND CP=1.5", MathCompareResults.Decision.MathDifferent_DIFFERENT_NUMBER_OF_EXPRESSIONS); // =REDUCED= MathDifferent:DifferentNumberOfExpressions:fast invariants have different number of expressions
+		faults.put("biomodel_38086434.vcml:CALI experiment with R=5", MathCompareResults.Decision.MathDifferent_DIFFERENT_NUMBER_OF_EXPRESSIONS); // =REDUCED= MathDifferent:DifferentNumberOfExpressions:fast invariants have different number of expressions
+		faults.put("biomodel_49411430.vcml:muscle PI release", MathCompareResults.Decision.MathDifferent_DIFFERENT_EXPRESSION); // =REDUCED= MathDifferent:DifferentExpression:expressions are different: '((5.0E-4 * ADPPIFactin_PI_release) - (
+		faults.put("biomodel_49411430.vcml:POmbe actin pi release", MathCompareResults.Decision.MathDifferent_DIFFERENT_EXPRESSION); // =REDUCED= MathDifferent:DifferentExpression:expressions are different: '((5.0E-4 * ADPPIFactin_PI_release) - (
+		faults.put("biomodel_49411430.vcml:pombe actin polymerization", MathCompareResults.Decision.MathDifferent_DIFFERENT_EXPRESSION); // =REDUCED= MathDifferent:DifferentExpression:expressions are different: '((5.0E-4 * ADPPIFactin_PI_release) - (
+		faults.put("biomodel_49411430.vcml:0913", MathCompareResults.Decision.MathDifferent_DIFFERENT_EXPRESSION); // =REDUCED= MathDifferent:DifferentExpression:expressions are different: '((5.0E-4 * ADPPIFactin_PI_release) - (
+		faults.put("biomodel_49411430.vcml:0930", MathCompareResults.Decision.MathDifferent_DIFFERENT_EXPRESSION); // =REDUCED= MathDifferent:DifferentExpression:expressions are different: '((5.0E-4 * ADPPIFactin_PI_release) - (
+		faults.put("biomodel_49411430.vcml:1002", MathCompareResults.Decision.MathDifferent_DIFFERENT_EXPRESSION); // =REDUCED= MathDifferent:DifferentExpression:expressions are different: '((5.0E-4 * ADPPIFactin_PI_release) - (
+		faults.put("biomodel_59280306.vcml:compartmental", MathCompareResults.Decision.MathDifferent_DIFFERENT_EXPRESSION); // =REDUCED= MathDifferent:DifferentExpression:expressions are different: '((0.0011 * (L_b1AR_cell + L_b1AR_Gs_ce
+		faults.put("biomodel_59280306.vcml:spatial", MathCompareResults.Decision.MathDifferent_DIFFERENT_NUMBER_OF_EXPRESSIONS); // =REDUCED= MathDifferent:DifferentNumberOfExpressions:fast invariants have different number of expressions
+		faults.put("biomodel_60203358.vcml:AP", MathCompareResults.Decision.MathDifferent_DIFFERENT_EXPRESSION); // =REDUCED= MathDifferent:DifferentExpression:expressions are different: '((0.010526315789473684 * I1Na_Sarcopla
+		faults.put("biomodel_60203358.vcml:IKsum", MathCompareResults.Decision.MathDifferent_FAILURE_UNKNOWN); // =REDUCED= MathDifferent:FailedUnknown:failed to compare math: too many failed evaluations (0 of 20) (java.lang
+		faults.put("biomodel_60203358.vcml:AP with pulse", MathCompareResults.Decision.MathDifferent_DIFFERENT_EXPRESSION); // =REDUCED= MathDifferent:DifferentExpression:expressions are different: '((0.010526315789473684 * I1Na_Sarcopla
+		faults.put("biomodel_60227051.vcml:3D", MathCompareResults.Decision.MathDifferent_DIFFERENT_NUMBER_OF_EXPRESSIONS); // =REDUCED= MathDifferent:DifferentNumberOfExpressions:fast invariants have different number of expressions
+		faults.put("biomodel_60227051.vcml:2Dprova3D", MathCompareResults.Decision.MathDifferent_DIFFERENT_NUMBER_OF_EXPRESSIONS); // =REDUCED= MathDifferent:DifferentNumberOfExpressions:fast invariants have different number of expressions
+		faults.put("biomodel_60227051.vcml:3Dsph", MathCompareResults.Decision.MathDifferent_DIFFERENT_NUMBER_OF_EXPRESSIONS); // =REDUCED= MathDifferent:DifferentNumberOfExpressions:fast invariants have different number of expressions
+		faults.put("biomodel_60227051.vcml:3DclustB", MathCompareResults.Decision.MathDifferent_DIFFERENT_NUMBER_OF_EXPRESSIONS); // =REDUCED= MathDifferent:DifferentNumberOfExpressions:fast invariants have different number of expressions
+		faults.put("biomodel_60227051.vcml:3DclustBCh", MathCompareResults.Decision.MathDifferent_DIFFERENT_NUMBER_OF_EXPRESSIONS); // =REDUCED= MathDifferent:DifferentNumberOfExpressions:fast invariants have different number of expressions
+		faults.put("biomodel_60227051.vcml:3D clust Ch", MathCompareResults.Decision.MathDifferent_DIFFERENT_NUMBER_OF_EXPRESSIONS); // =REDUCED= MathDifferent:DifferentNumberOfExpressions:fast invariants have different number of expressions
+		faults.put("biomodel_60227051.vcml:3D clust ch piccolo", MathCompareResults.Decision.MathDifferent_DIFFERENT_NUMBER_OF_EXPRESSIONS); // =REDUCED= MathDifferent:DifferentNumberOfExpressions:fast invariants have different number of expressions
+		faults.put("biomodel_60227051.vcml:clust pic double", MathCompareResults.Decision.MathDifferent_DIFFERENT_NUMBER_OF_EXPRESSIONS); // =REDUCED= MathDifferent:DifferentNumberOfExpressions:fast invariants have different number of expressions
+		faults.put("biomodel_60227051.vcml:clust pic double sx dx", MathCompareResults.Decision.MathDifferent_DIFFERENT_NUMBER_OF_EXPRESSIONS); // =REDUCED= MathDifferent:DifferentNumberOfExpressions:fast invariants have different number of expressions
+		faults.put("biomodel_66264973.vcml:compartmental - IC-G2736X", MathCompareResults.Decision.MathDifferent_EQUATION_ADDED); // =REDUCED= MathDifferent:EquationAdded:only one mathDescription had equation for 'Compartment::ICpeptide_mAtaxi
+		faults.put("biomodel_66265579.vcml:compartmental - IC4", MathCompareResults.Decision.MathDifferent_DIFFERENT_EXPRESSION); // =REDUCED= MathDifferent:DifferentExpression:expressions are different: '0.0' vs '((0.005860724109735934 * ((17
+		faults.put("biomodel_91134339.vcml:test", MathCompareResults.Decision.MathDifferent_DIFFERENT_EXPRESSION); // =REDUCED= MathDifferent:DifferentExpression:expressions are different: '( - ((0.03 * C_Unnamed_compartment * (
+		faults.put("biomodel_9254662.vcml:FastBuffExpReceptor", MathCompareResults.Decision.MathDifferent_DIFFERENT_FASTINV_EXPRESSION); // =REDUCED= MathDifferent:DifferentFastInvExpression:could not find a match for fast invariant expression'Expres
+		faults.put("biomodel_98147638.vcml:compartmental - IC4", MathCompareResults.Decision.MathDifferent_DIFFERENT_EXPRESSION); // =REDUCED= MathDifferent:DifferentExpression:expressions are different: '0.0' vs '((0.005860724109735934 * ((17
 		return faults;
 	}
 
@@ -144,15 +189,18 @@ public class MathGenCompareTest {
 			}
 			BioModel biomodel = XmlHelper.XMLToBioModel(new XMLSource(vcmlStr));
 			for (SimulationContext simContext : biomodel.getSimulationContexts()){
-				appTestCases.add(filename+":"+simContext.getName());
+				String test_case_name = filename + ":" + simContext.getName();
+//if (!knownReductionFaults().containsKey(test_case_name)) continue;
+				appTestCases.add(test_case_name);
 			}
 		}
 		return appTestCases;
+//		return Arrays.asList("biomodel_12522025.vcml:purkinje9");
 	}
-	
-	
+
+
 	@Test
-	public void test_math_compare() throws Exception {
+	public void test_legacy_math_compare() throws Exception {
 		String[] tokens = filename_colon_appname.split(":");
 		String filename = tokens[0];
 		String simContextName = filename_colon_appname.substring(filename.length()+1);
@@ -180,6 +228,7 @@ public class MathGenCompareTest {
 		MathDescription originalMath = orig_simContext.getMathDescription();
 		MathDescription origMathClone = new MathDescription(originalMath); // test round trip to/from MathDescription.readFromDatabase()
 		SimulationContext new_simContext = transformed_biomodel.getSimulationContexts(orig_simContext.getName());
+		//new_simContext.setUsingMassConservationModelReduction(false);
 		new_simContext.updateAll(false);
 		MathDescription newMath = new_simContext.getMathDescription();
 		MathDescription newMathClone = new MathDescription(newMath); // test round trip to/from MathDescription.readFromDatabase()
@@ -189,25 +238,138 @@ public class MathGenCompareTest {
 		}else {
 			results = MathDescription.testEquivalency(SimulationSymbolTable.createMathSymbolTableFactory(), originalMath, newMath);
 		}
-		MathCompareResults.Decision knownFault = knownFaults().get(filename_colon_appname);
+		MathCompareResults.Decision knownFault = knownLegacyFaults().get(filename_colon_appname);
 		if (results.isEquivalent() && knownFault != null){
 			Assert.fail("math equivalent for '"+filename_colon_appname+"', but expecting '"+knownFault+"', remove known fault");
 		}
 		if (!results.isEquivalent()){
+			// try again using non-reduced math
+			new_simContext.setUsingMassConservationModelReduction(false);
+			new_simContext.updateAll(false);
+			newMath = new_simContext.getMathDescription();
+			MathCompareResults results2 = MathDescription.testEquivalency(SimulationSymbolTable.createMathSymbolTableFactory(), originalMath, newMath);
+
+			if (!results2.isEquivalent()) {
+				try (BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(knownProblemFile, true));) {
+					bufferedWriter.write("faults.put(\"" + filename_colon_appname + "\", MathCompareResults.Decision." + results2.decision + "); // =LEGACY= "
+							+ results2.toDatabaseStatus().substring(0, Math.min(results2.toDatabaseStatus().length(), 100)) + "\n");
+				}
+				if (knownFault == null) {
+					Assert.fail("'" + filename_colon_appname + "' expecting equivalent, " +
+							"computed '" + results2.decision + "', " +
+							"details: " + results2.toDatabaseStatus());
+				} else {
+					Assert.assertTrue("'" + filename_colon_appname + "' expecting '" + knownFault + "', " +
+									"computed '" + results2.decision + "', " +
+									"details: " + results2.toDatabaseStatus(),
+							knownFault == results2.decision);
+				}
+			}
+		}
+	}
+
+	@Test
+	@Ignore
+	public void test_identity_math_compare() throws Exception {
+		String[] tokens = filename_colon_appname.split(":");
+		String filename = tokens[0];
+		String simContextName = filename_colon_appname.substring(filename.length() + 1);
+		String vcmlStr;
+		try (InputStream testFileInputStream = VcmlTestSuiteFiles.getVcmlTestCase(filename);) {
+			vcmlStr = new BufferedReader(new InputStreamReader(testFileInputStream))
+					.lines().collect(Collectors.joining("\n"));
+		}
+
+		System.out.println("testing test case " + filename_colon_appname);
+
+		//
+		// read in 'reduced' model: generate math for application <<with>> model reduction from mass conservation
+		//
+		BioModel reducedBiomodel1 = XmlHelper.XMLToBioModel(new XMLSource(vcmlStr));
+		reducedBiomodel1.refreshDependencies();
+		SimulationContext reducedSimContext1 = reducedBiomodel1.getSimulationContext(simContextName);
+		reducedSimContext1.setUsingMassConservationModelReduction(true);
+		reducedSimContext1.updateAll(false);
+		MathDescription reducedMath1 = reducedSimContext1.getMathDescription();
+
+		BioModel reducedBiomodel2 = XmlHelper.XMLToBioModel(new XMLSource(vcmlStr));
+		reducedBiomodel2.refreshDependencies();
+		SimulationContext reducedSimContext2 = reducedBiomodel2.getSimulationContext(simContextName);
+		reducedSimContext2.setUsingMassConservationModelReduction(true);
+		reducedSimContext2.updateAll(false);
+		MathDescription reducedMath2 = reducedSimContext2.getMathDescription();
+
+		//
+		// compare identity (math against clone of itself - using the reduced model)
+		//
+		MathCompareResults identityCompareResults = MathDescription.testEquivalency(SimulationSymbolTable.createMathSymbolTableFactory(), reducedMath1, reducedMath2);
+		Assert.assertTrue("math not equivalent for '" + filename_colon_appname + "': " + identityCompareResults.toDatabaseStatus(), identityCompareResults.isEquivalent());
+	}
+
+	@Test
+	@Ignore
+	public void test_model_reduction_math_compare() throws Exception {
+		String[] tokens = filename_colon_appname.split(":");
+		String filename = tokens[0];
+		String simContextName = filename_colon_appname.substring(filename.length()+1);
+		String vcmlStr;
+		try (InputStream testFileInputStream = VcmlTestSuiteFiles.getVcmlTestCase(filename);) {
+			vcmlStr = new BufferedReader(new InputStreamReader(testFileInputStream))
+					.lines().collect(Collectors.joining("\n"));
+		}
+
+		System.out.println("testing test case "+filename_colon_appname);
+
+		//
+		// read in 'reduced' model: generate math for application <<with>> model reduction from mass conservation
+		//  - but skip entire test if not ODE or PDE (we don't do model reduction for stochastic models)
+		//
+		BioModel reducedBiomodel = XmlHelper.XMLToBioModel(new XMLSource(vcmlStr));
+		SimulationContext reducedSimContext = reducedBiomodel.getSimulationContext(simContextName);
+		if (reducedSimContext.getApplicationType() != SimulationContext.Application.NETWORK_DETERMINISTIC){
+			return; // not ODEs or PDEs, so no model reduction - nothing to test.
+		}
+		reducedBiomodel.refreshDependencies();
+		reducedSimContext.setUsingMassConservationModelReduction(true);
+		reducedSimContext.updateAll(false);
+		MathDescription reducedMath = reducedSimContext.getMathDescription();
+
+
+		//
+		// for ODE applications, ensure that disabling mass conservation compares as equivalent to generating with mass conservation
+		//
+		// read in 'full' model: generate math for application <<without>> model reduction from mass conservation
+		//
+		BioModel fullBiomodel = XmlHelper.XMLToBioModel(new XMLSource(vcmlStr));
+		fullBiomodel.refreshDependencies();
+		SimulationContext fullSimContext = fullBiomodel.getSimulationContexts(simContextName);
+		fullSimContext.setUsingMassConservationModelReduction(false);
+		fullSimContext.updateAll(false);
+		MathDescription fullMath = fullSimContext.getMathDescription();
+
+		MathCompareResults reductionCompareResults = MathDescription.testEquivalency(SimulationSymbolTable.createMathSymbolTableFactory(), reducedMath, fullMath);
+
+		MathCompareResults.Decision knownReductionFault = knownReductionFaults().get(filename_colon_appname);
+		if (reductionCompareResults.isEquivalent() && knownReductionFault != null){
+			Assert.fail("math equivalent for '"+filename_colon_appname+"', but expecting '"+knownReductionFault+"', remove known fault");
+		}
+		if (!reductionCompareResults.isEquivalent()){
 			try (BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(knownProblemFile, true));){
-				bufferedWriter.write("faults.put(\""+filename_colon_appname+"\", MathCompareResults.Decision."+results.decision+");\n");
+				bufferedWriter.write("faults.put(\""+filename_colon_appname+"\", MathCompareResults.Decision."+reductionCompareResults.decision+"); // =REDUCED= "
+						+reductionCompareResults.toDatabaseStatus().substring(0, Math.min(reductionCompareResults.toDatabaseStatus().length(), 100))+"\n");
 			}
-			if (knownFault == null) {
+			if (knownReductionFault == null) {
 				Assert.fail("'"+filename_colon_appname+"' expecting equivalent, " +
-						"computed '"+results.decision+"', " +
-						"details: " + results.toDatabaseStatus());
+						"computed '"+reductionCompareResults.decision+"', " +
+						"details: " + reductionCompareResults.toDatabaseStatus());
 			}else{
-				Assert.assertTrue("'"+filename_colon_appname+"' expecting '"+knownFault+"', " +
-								"computed '"+results.decision+"', " +
-								"details: " + results.toDatabaseStatus(),
-						knownFault == results.decision);
+				Assert.assertTrue("'"+filename_colon_appname+"' expecting '"+knownReductionFault+"', " +
+								"computed '"+reductionCompareResults.decision+"', " +
+								"details: " + reductionCompareResults.toDatabaseStatus(),
+						knownReductionFault == reductionCompareResults.decision);
 			}
 		}
 	}
 
+
 }
diff --git a/vcell-core/src/test/java/org/vcell/sbml/SEDMLExporterSBMLTest.java b/vcell-core/src/test/java/org/vcell/sbml/SEDMLExporterSBMLTest.java
index 2f2ab0523..cefc89db9 100644
--- a/vcell-core/src/test/java/org/vcell/sbml/SEDMLExporterSBMLTest.java
+++ b/vcell-core/src/test/java/org/vcell/sbml/SEDMLExporterSBMLTest.java
@@ -73,7 +73,6 @@ public class SEDMLExporterSBMLTest extends SEDMLExporterCommon {
 		faults.put("__export_adv_test.vcml", SEDML_FAULT.SIMULATION_NOT_FOUND_BY_NAME);  // roundtripped simulation not found with name 'spatialnoscan'
 		faults.put("biomodel_100596964.vcml", SEDML_FAULT.NO_MODELS_IN_OMEX);
 		faults.put("biomodel_100961371.vcml", SEDML_FAULT.NO_MODELS_IN_OMEX);
-		faults.put("biomodel_10829774.vcml", SEDML_FAULT.MATH_OVERRIDE_NAMES_DIFFERENT);  // simulation 'set1' in simContext 'maximumAmplificationFig6b'
 		faults.put("biomodel_113655498.vcml", SEDML_FAULT.NO_MODELS_IN_OMEX);
 		faults.put("biomodel_116929912.vcml", SEDML_FAULT.NO_MODELS_IN_OMEX);
 		faults.put("biomodel_116929971.vcml", SEDML_FAULT.NO_MODELS_IN_OMEX);
@@ -87,25 +86,21 @@ public class SEDMLExporterSBMLTest extends SEDMLExporterCommon {
 		faults.put("biomodel_13736736.vcml", SEDML_FAULT.NO_MODELS_IN_OMEX);
 		faults.put("biomodel_145545992.vcml", SEDML_FAULT.NO_MODELS_IN_OMEX);
 		faults.put("biomodel_148700996.vcml", SEDML_FAULT.MATH_OVERRIDE_NAMES_DIFFERENT); // simulation 'Full sim' in simContext 'Fast B Compartmental'
-		faults.put("biomodel_149491513.vcml", SEDML_FAULT.MATH_OVERRIDE_NAMES_DIFFERENT); // simulation 'ss check 1' in simContext 'steady state compartment'
 		faults.put("biomodel_154961582.vcml", SEDML_FAULT.NO_MODELS_IN_OMEX);
 		faults.put("biomodel_155016832.vcml", SEDML_FAULT.NO_MODELS_IN_OMEX);
 		faults.put("biomodel_156134818.vcml", SEDML_FAULT.NO_MODELS_IN_OMEX);
-		faults.put("biomodel_158495696.vcml", SEDML_FAULT.MATH_OVERRIDE_NAMES_DIFFERENT); // simulation 'Clofazimine' in simContext 'Matching w/ Matlab data'
 		faults.put("biomodel_16763273.vcml", SEDML_FAULT.NO_MODELS_IN_OMEX);
 		faults.put("biomodel_16804037.vcml", SEDML_FAULT.NO_MODELS_IN_OMEX);
 		faults.put("biomodel_168717401.vcml", SEDML_FAULT.NO_MODELS_IN_OMEX);
 		faults.put("biomodel_169993006.vcml", SEDML_FAULT.NO_MODELS_IN_OMEX);
 		faults.put("biomodel_17098642.vcml", SEDML_FAULT.NO_MODELS_IN_OMEX);
 		faults.put("biomodel_17257105.vcml", SEDML_FAULT.MATH_OVERRIDE_NAMES_DIFFERENT);  // simulation '1 uM iso' in simContext 'compartmental'
-		faults.put("biomodel_17263179.vcml", SEDML_FAULT.MATH_OVERRIDE_NAMES_DIFFERENT);  // simulation '0.1 uM iso DcAMP 300' in simContext 'Figure 3'
 		faults.put("biomodel_188880263.vcml", SEDML_FAULT.NO_MODELS_IN_OMEX);
 		faults.put("biomodel_18894555.vcml", SEDML_FAULT.MATH_OVERRIDE_NOT_EQUIVALENT);  // simulation 'baseline' in simContext 'compartmental'
 		faults.put("biomodel_189321805.vcml", SEDML_FAULT.NO_MODELS_IN_OMEX);  // new
 		faults.put("biomodel_189512756.vcml", SEDML_FAULT.NO_MODELS_IN_OMEX);
 		faults.put("biomodel_189513183.vcml", SEDML_FAULT.NO_MODELS_IN_OMEX);
 		faults.put("biomodel_20253928.vcml", SEDML_FAULT.NO_MODELS_IN_OMEX);
-		faults.put("biomodel_209284198.vcml", SEDML_FAULT.MATH_OVERRIDE_NAMES_DIFFERENT); // simulation 'Microinjection of FRanGDP' in simContext 'Normal model parameters'
 		faults.put("biomodel_211839191.vcml", SEDML_FAULT.NO_MODELS_IN_OMEX);
 		faults.put("biomodel_220138948.vcml", SEDML_FAULT.SIMULATION_NOT_FOUND_BY_NAME); // roundtripped simulation not found with name 'Uptake_Larger Jumps'
 		faults.put("biomodel_22403233.vcml", SEDML_FAULT.NO_MODELS_IN_OMEX);
@@ -114,12 +109,8 @@ public class SEDMLExporterSBMLTest extends SEDMLExporterCommon {
 		faults.put("biomodel_22403250.vcml", SEDML_FAULT.NO_MODELS_IN_OMEX);
 		faults.put("biomodel_22403358.vcml", SEDML_FAULT.NO_MODELS_IN_OMEX);
 		faults.put("biomodel_22403576.vcml", SEDML_FAULT.NO_MODELS_IN_OMEX);
-		faults.put("biomodel_22523922.vcml", SEDML_FAULT.MATH_OVERRIDE_NAMES_DIFFERENT);  // simulation 'Simulation 3' in simContext 'pyro_nonspacial'
 		faults.put("biomodel_225440511.vcml", SEDML_FAULT.NO_MODELS_IN_OMEX);
 		faults.put("biomodel_232498815.vcml", SEDML_FAULT.NO_MODELS_IN_OMEX);
-		faults.put("biomodel_26454052.vcml", SEDML_FAULT.MATH_OVERRIDE_NAMES_DIFFERENT);  // simulation 'Spine_using spine parameter values that fall in ranges of Harris et al (28)' in simContext '1D PIP2_fits 3D model that uses constructed 3D geometry'
-		faults.put("biomodel_26454463.vcml", SEDML_FAULT.MATH_OVERRIDE_NAMES_DIFFERENT);  // simulation 'lat diff & stim syn - decreased IP3' in simContext '3D PIP2_constructed geometry'
-		faults.put("biomodel_27088120.vcml", SEDML_FAULT.MATH_OVERRIDE_NAMES_DIFFERENT);  // simulation 'Simulation36' in simContext 'DecayPC12-4block'
 		faults.put("biomodel_2912851.vcml", SEDML_FAULT.NO_MODELS_IN_OMEX);
 		faults.put("biomodel_2913730.vcml", SEDML_FAULT.NO_MODELS_IN_OMEX);
 		faults.put("biomodel_2915537.vcml", SEDML_FAULT.NO_MODELS_IN_OMEX);
@@ -128,20 +119,16 @@ public class SEDMLExporterSBMLTest extends SEDMLExporterCommon {
 		faults.put("biomodel_2917999.vcml", SEDML_FAULT.NO_MODELS_IN_OMEX);
 		faults.put("biomodel_2930915.vcml", SEDML_FAULT.NO_MODELS_IN_OMEX);
 		faults.put("biomodel_2962862.vcml", SEDML_FAULT.NO_MODELS_IN_OMEX);
-		faults.put("biomodel_29897263.vcml", SEDML_FAULT.MATH_OVERRIDE_NAMES_DIFFERENT);  // simulation 'less GEF, broader range' in simContext 'Simulations for switch paper'
 		faults.put("biomodel_32568171.vcml", SEDML_FAULT.NO_MODELS_IN_OMEX);
 		faults.put("biomodel_32568356.vcml", SEDML_FAULT.NO_MODELS_IN_OMEX);
 		faults.put("biomodel_34826524.vcml", SEDML_FAULT.MATH_OVERRIDE_NOT_EQUIVALENT); // simulation 'Cap=1, cof, prof varied' in simContext 'Steady State Turnover'
-		faults.put("biomodel_34855932.vcml", SEDML_FAULT.MATH_OVERRIDE_NAMES_DIFFERENT); // simulation 'INIT' in simContext 'cell4'
-		faults.put("biomodel_36053554.vcml", SEDML_FAULT.MATH_OVERRIDE_NAMES_DIFFERENT); // simulation 'EEE' in simContext 'steady-state'
-		faults.put("biomodel_36230715.vcml", SEDML_FAULT.MATH_OVERRIDE_NAMES_DIFFERENT); // simulation 'clonidine dose response' in simContext 'Simulations for switch paper'
+		faults.put("biomodel_34855932.vcml", SEDML_FAULT.MATH_OVERRIDE_NOT_EQUIVALENT); // simulation 'INIT' in simContext 'cell4'
 		faults.put("biomodel_38086434.vcml", SEDML_FAULT.NO_MODELS_IN_OMEX); // new
 		faults.put("biomodel_55178308.vcml", SEDML_FAULT.NO_MODELS_IN_OMEX); // new
 		faults.put("biomodel_55396830.vcml", SEDML_FAULT.NO_MODELS_IN_OMEX);
 		faults.put("biomodel_59280306.vcml", SEDML_FAULT.MATH_OVERRIDE_NAMES_DIFFERENT); // simulation '1 uM iso' in simContext 'compartmental'
 		faults.put("biomodel_60203358.vcml", SEDML_FAULT.NO_MODELS_IN_OMEX);
 		faults.put("biomodel_60227051.vcml", SEDML_FAULT.NO_MODELS_IN_OMEX);
-		faults.put("biomodel_62849940.vcml", SEDML_FAULT.MATH_OVERRIDE_NAMES_DIFFERENT); // simulation 'intramolecular' in simContext 'Temporal stimulation (pulse)'
 		faults.put("biomodel_63307133.vcml", SEDML_FAULT.NO_MODELS_IN_OMEX);
 		faults.put("biomodel_65311813.vcml", SEDML_FAULT.SIMCONTEXT_NOT_FOUND_BY_NAME); // simulation 'Copy of Copy of Simulation 101x101x36' in simContext '3d image'
 		faults.put("biomodel_66264973.vcml", SEDML_FAULT.MATH_OVERRIDE_NAMES_DIFFERENT); // simulation 'Simulation - local sequestration' in simContext 'compartmental - IC-G2736X'
@@ -151,17 +138,11 @@ public class SEDMLExporterSBMLTest extends SEDMLExporterCommon {
 		faults.put("biomodel_7803961.vcml", SEDML_FAULT.NO_MODELS_IN_OMEX);
 		faults.put("biomodel_7803976.vcml", SEDML_FAULT.NO_MODELS_IN_OMEX);
 		faults.put("biomodel_81284732.vcml", SEDML_FAULT.NO_MODELS_IN_OMEX);
-		faults.put("biomodel_81992349.vcml", SEDML_FAULT.MATH_OVERRIDE_NAMES_DIFFERENT); // simulation 'Rolipram2' in simContext 'Compart'
 		faults.put("biomodel_82250339.vcml", SEDML_FAULT.NO_MODELS_IN_OMEX);
 		faults.put("biomodel_83446023.vcml", SEDML_FAULT.MATH_OVERRIDE_NOT_EQUIVALENT); // simulation 'Simulation0' in simContext 'compartmental'
-		faults.put("biomodel_83932806.vcml", SEDML_FAULT.MATH_OVERRIDE_NAMES_DIFFERENT); // simulation 'Fig 6A influx pKa 9.6, logKow=1.88, c=1' in simContext 'Matching w/ Matlab data'
 		faults.put("biomodel_84982474.vcml", SEDML_FAULT.DIFF_NUMBER_OF_BIOMODELS); // not supported nonspatial histogram
-		faults.put("biomodel_84985561.vcml", SEDML_FAULT.MATH_OVERRIDE_NAMES_DIFFERENT); // simulation 'copy of Simulation 2' in simContext 'comp'
-		faults.put("biomodel_89712092.vcml", SEDML_FAULT.MATH_OVERRIDE_NAMES_DIFFERENT); // simulation 'ss check 1' in simContext 'steady state compartment'
-		faults.put("biomodel_89712092_nonspatial.vcml", SEDML_FAULT.MATH_OVERRIDE_NAMES_DIFFERENT); // simulation 'First Try' in simContext 'uncaging'
 		faults.put("biomodel_9254662.vcml", SEDML_FAULT.NO_MODELS_IN_OMEX);
 		faults.put("biomodel_92705462.vcml", SEDML_FAULT.SIMCONTEXT_NOT_FOUND_BY_NAME);  // roundtripped simulationContext not found with name 'stoch'
-		faults.put("biomodel_94891280.vcml", SEDML_FAULT.MATH_OVERRIDE_NAMES_DIFFERENT);
 		faults.put("biomodel_98147638.vcml", SEDML_FAULT.NO_MODELS_IN_OMEX);
 		faults.put("biomodel_98150237.vcml", SEDML_FAULT.NO_MODELS_IN_OMEX); // new
 		faults.put("biomodel_98174143.vcml", SEDML_FAULT.NO_MODELS_IN_OMEX);
diff --git a/vcell-math/src/main/java/cbit/vcell/parser/ExpressionUtils.java b/vcell-math/src/main/java/cbit/vcell/parser/ExpressionUtils.java
index 6ad42e612..3cf8d2ed3 100644
--- a/vcell-math/src/main/java/cbit/vcell/parser/ExpressionUtils.java
+++ b/vcell-math/src/main/java/cbit/vcell/parser/ExpressionUtils.java
@@ -620,7 +620,7 @@ public static boolean functionallyEquivalent(Expression exp1, Expression exp2, b
 //		}
 //		else
 //		{
-			if (discont1.size() != 0)
+		if ((discont1.size() != 0) || (discont2.size() !=0))
 			{
 				Expression productOfdiscont1 = new Expression(1);
 				Expression productOfdiscont2 = new Expression(1);
diff --git a/vcell-server/src/main/java/cbit/vcell/modeldb/MathVerifier.java b/vcell-server/src/main/java/cbit/vcell/modeldb/MathVerifier.java
index 729692ef0..c9abc2f40 100644
--- a/vcell-server/src/main/java/cbit/vcell/modeldb/MathVerifier.java
+++ b/vcell-server/src/main/java/cbit/vcell/modeldb/MathVerifier.java
@@ -456,7 +456,6 @@ public class MathVerifier {
                     issueString = buffer.toString();
                 }
 
-                MathCompareResults testIfSameResult = cbit.vcell.math.MathUtilities.testIfSame(SimulationSymbolTable.createMathSymbolTableFactory(), origMathDesc, newMathDesc);
                 mathCompareResults_latest = MathDescription.testEquivalency(SimulationSymbolTable.createMathSymbolTableFactory(), origMathDesc, newMathDesc);
                 System.out.println(">>>BioModel(" + bioModelFromDB.getVersion().getVersionKey() + ") '" + bioModelFromDB.getName() + "':" + bioModelFromDB.getVersion().getDate() + ", Application(" + simContextFromDB.getKey() + ") '" + simContextFromDB.getName() + "' <<EQUIV=" + mathCompareResults_latest.isEquivalent() + ">>: " + mathCompareResults_latest.toDatabaseStatus());
                 MathDescription mathDesc_4_8 = null;
@@ -1191,7 +1190,6 @@ public class MathVerifier {
                         }
                         simContextNewMath.setMathDescription(newMathDesc);
 
-                        MathCompareResults testIfSameResults = cbit.vcell.math.MathUtilities.testIfSame(SimulationSymbolTable.createMathSymbolTableFactory(), origMathDesc, newMathDesc);
                         mathCompareResults = MathDescription.testEquivalency(SimulationSymbolTable.createMathSymbolTableFactory(), origMathDesc, newMathDesc);
                         StringBuffer buffer = new StringBuffer();
                         buffer.append(">>>BioModel(" + bioModelFromDB.getVersion().getVersionKey() + ") '" + bioModelFromDB.getName() + "':" + bioModelFromDB.getVersion().getDate() + ", Application(" + simContextFromDB.getKey() + ") '" + simContextFromDB.getName() + "' <<EQUIV=" + mathCompareResults.isEquivalent() + ">>: " + mathCompareResults.toDatabaseStatus());
@@ -1468,7 +1466,6 @@ public class MathVerifier {
                         }
                         simContextNewMath.setMathDescription(newMathDesc);
 
-                        MathCompareResults testIfSameResults = cbit.vcell.math.MathUtilities.testIfSame(SimulationSymbolTable.createMathSymbolTableFactory(), origMathDesc, newMathDesc);
                         mathCompareResults = MathDescription.testEquivalency(SimulationSymbolTable.createMathSymbolTableFactory(), origMathDesc, newMathDesc);
                         StringBuffer buffer = new StringBuffer();
                         buffer.append(">>>BioModel(" + bioModelFromDB.getVersion().getVersionKey() + ") '" + bioModelFromDB.getName() + "':" + bioModelFromDB.getVersion().getDate() + ", Application(" + simContextFromDB.getKey() + ") '" + simContextFromDB.getName() + "' <<EQUIV=" + mathCompareResults.isEquivalent() + ">>: " + mathCompareResults.toDatabaseStatus());

['vcell-core/src/main/java/cbit/vcell/solver/ode/DefaultODESolver.java', 'vcell-client/src/main/java/cbit/vcell/math/gui/MathDebuggerPanel.java', 'vcell-core/src/main/java/cbit/vcell/math/MathUtilities.java', '.github/workflows/ci_cd.yml', 'vcell-cli/src/main/java/org/vcell/cli/vcml/VcmlValidator.java', 'vcell-core/src/main/java/cbit/vcell/mongodb/VCMongoDbDriver.java', 'vcell-core/src/test/java/org/vcell/sbml/SEDMLExporterSBMLTest.java', 'vcell-core/src/main/java/cbit/vcell/math/MathDescription.java', 'vcell-core/src/test/java/cbit/vcell/mapping/MathGenCompareTest.java', 'vcell-core/src/main/java/cbit/vcell/mapping/AbstractMathMapping.java', 'vcell-server/src/main/java/cbit/vcell/modeldb/MathVerifier.java', 'vcell-math/src/main/java/cbit/vcell/parser/ExpressionUtils.java']