symja_android_library
symja_android_library copied to clipboard
Small values in equation are solved to zero
Describe the bug
When solving an equation were either side computes to a value below a certain threshold the solution becomes zero, although the value is non zero if the corresponding side is evaluated directly.
I already tried to evaluate with an increased numericPrecision
(using EvalEngine.setNumericPrecision()
) but this only increased the runtime and did not improve the result.
Nevertheless I would expect the numericPrecision
to influence the number of precise significant digits of the solution (regardless of the magnitude) and not the minimal absolute value that is computed correctly. It shouldn't matter if my value is 3.6*10^-1
or 3.6*10^-100
as long as the number of significant digits is low.
To Reproduce
Example Symja Java Code:
term3
is the original expression, while term2
and term1
are simplified versions that show the same behavior.
EvalEngine e = new EvalEngine(true);
System.out.println("numericMode: " + e.isNumericMode());
System.out.println("numericPrecision: " + e.getNumericPrecision());
String term1 = "1.29600*10^-17";
String term2 = "(3.6*10^-9)^2";
String term3 = "(1 / (4 * pi * 3 * 10^6 * 2.2 * 10^9))^2";
IExpr power = e.parse(term1);
IExpr solve = e.parse("Solve(x==" + term1 + ",x)");
System.out.println(e.evaluateNIL(solve));
System.out.println(e.evaluateNIL(power));
Error print-out:
numericMode: false
numericPrecision: 15
{{x->0}}
1.45376*10^-34
respectively 1.29600*10^-17
Expected behavior
It is expected that the equation is solved to the same value as if the term was evaluated directly. Expected (correct) result expression:
{{x->1.29600*10^-17}}
1.29600*10^-17
Java
- Version: Java 11
- OS: Windows 10
Because the Solve
function at first tries a "symbolic approach", it rationalizes all Java double values to IRational
instances.
Only if the symbolic evaluation doesn't succeed a FindRoot
function is called.
If you knew you have only univariate numeric inputs, you can call FindRoot
directly for these equations.
With the new commit which introduces the new parameter Config.ZERO_IN_OUTPUT_FORMAT
which parameterizes if the double value should be printed as 0.0
in the output and the Config.DOUBLE_TOLERANCE
which parameterizes if a numeric value is considered as 0 in the isZero()
methods.
Config.ZERO_IN_OUTPUT_FORMAT = 1.0e-100;
Config.DOUBLE_TOLERANCE = 1.0e-100;
EvalEngine e = new EvalEngine(true);
System.out.println("numericMode: " + e.isNumericMode());
System.out.println("numericPrecision: " + e.getNumericPrecision());
String term1 = "1.29600*10^-17";
String term2 = "(3.6*10^-9)^2";
String term3 = "(1 / (4 * pi * 3 * 10^6 * 2.2 * 10^9))^2";
IExpr power = e.parse(term1);
IExpr solve = e.parse("FindRoot(x-" + term1 + ", {x,1})");
IExpr evaledSolve = e.evaluate(solve);
System.out.println("evaledSolve 1: " + evaledSolve);
IExpr evaledPower = e.evaluate(power);
System.out.println("evaledPower: " + evaledPower);
solve = e.parse("Solve(x==-" + term1 + ", x)");
evaledSolve = e.evaluate(solve);
System.out.println("evaledSolve 2: " + evaledSolve);
Can you try the variants with these settings?
Config.ZERO_IN_OUTPUT_FORMAT = 1.0e-100;
Config.DOUBLE_TOLERANCE = 1.0e-100;
Config.DEFAULT_ROOTS_CHOP_DELTA = 1.0e-100;
EvalEngine e = new EvalEngine(true);
System.out.println("numericMode: " + e.isNumericMode());
System.out.println("numericPrecision: " + e.getNumericPrecision());
String term1 = "1.29600*10^-17";
String term2 = "(3.6*10^-9)^2";
String term3 = "(1 / (4 * pi * 3 * 10^6 * 2.2 * 10^9))^2";
IExpr power = e.parse(term1);
// IExpr solve = e.parse("Solve(x==" + term1 + ",x)");
IExpr solve = e.parse("FindRoot(x-" + term1 + ", {x,1})");
IExpr evaledSolve = e.evaluate(solve);
System.out.println("evaledRoot 1: " + evaledSolve);
IExpr evaledPower = e.evaluate(power);
System.out.println("evaledPower: " + evaledPower);
solve = e.parse("Solve(x==" + term1 + ", x)");
evaledSolve = e.evaluate(solve);
System.out.println("evaledSolve 2: " + evaledSolve);
IExpr nsolve = e.parse("NSolve(x==" + term1 + ", x)");
IExpr evaledNSolve = e.evaluate(nsolve);
System.out.println("evaledNSolve 3: " + evaledNSolve);
Because the
Solve
function at first tries a "symbolic approach", it rationalizes all Java double values toIRational
instances. Only if the symbolic evaluation doesn't succeed aFindRoot
function is called.If you knew you have only univariate numeric inputs, you can call
FindRoot
directly for these equations.
Would it be possible respectively reasonable to not rationalize double values and to perform all symbolic transformations using doubles? The parser still can detect integer values or fractions as such. Or could this have an negative effect on other calculations.
Alternatively if rationalization has to be done, wouldn't it be better to rationalize mantissa and exponent of a double independently?
For example 1.29600*10^-17
could be split into its mantissa 1.29600
and its exponent 10^-17
. The mantissa would then be rationalized to 1296/1000
and 10^-17
stays as it is, so that the result is 1296/1000 * 10^-17
?
With the new commit which introduces the new parameter
Config.ZERO_IN_OUTPUT_FORMAT
which parameterizes if the double value should be printed as0.0
in the output and theConfig.DOUBLE_TOLERANCE
which parameterizes if a numeric value is considered as 0 in theisZero()
methods.Config.ZERO_IN_OUTPUT_FORMAT = 1.0e-100; Config.DOUBLE_TOLERANCE = 1.0e-100; EvalEngine e = new EvalEngine(true); System.out.println("numericMode: " + e.isNumericMode()); System.out.println("numericPrecision: " + e.getNumericPrecision()); String term1 = "1.29600*10^-17"; String term2 = "(3.6*10^-9)^2"; String term3 = "(1 / (4 * pi * 3 * 10^6 * 2.2 * 10^9))^2"; IExpr power = e.parse(term1); IExpr solve = e.parse("FindRoot(x-" + term1 + ", {x,1})"); IExpr evaledSolve = e.evaluate(solve); System.out.println("evaledSolve 1: " + evaledSolve); IExpr evaledPower = e.evaluate(power); System.out.println("evaledPower: " + evaledPower); solve = e.parse("Solve(x==-" + term1 + ", x)"); evaledSolve = e.evaluate(solve); System.out.println("evaledSolve 2: " + evaledSolve);
This gives me the following output with the current master (17757be70fe062331669e17a1729d936c6c19d0c):
numericMode: false
numericPrecision: 15
evaledSolve 1: {x->1.2960000000000002E-17}
evaledPower: 1.29600*10^-17
evaledSolve 2: {{x->0.0}}
Can you try the variants with these settings?
Config.ZERO_IN_OUTPUT_FORMAT = 1.0e-100; Config.DOUBLE_TOLERANCE = 1.0e-100; Config.DEFAULT_ROOTS_CHOP_DELTA = 1.0e-100; EvalEngine e = new EvalEngine(true); System.out.println("numericMode: " + e.isNumericMode()); System.out.println("numericPrecision: " + e.getNumericPrecision()); String term1 = "1.29600*10^-17"; String term2 = "(3.6*10^-9)^2"; String term3 = "(1 / (4 * pi * 3 * 10^6 * 2.2 * 10^9))^2"; IExpr power = e.parse(term1); // IExpr solve = e.parse("Solve(x==" + term1 + ",x)"); IExpr solve = e.parse("FindRoot(x-" + term1 + ", {x,1})"); IExpr evaledSolve = e.evaluate(solve); System.out.println("evaledRoot 1: " + evaledSolve); IExpr evaledPower = e.evaluate(power); System.out.println("evaledPower: " + evaledPower); solve = e.parse("Solve(x==" + term1 + ", x)"); evaledSolve = e.evaluate(solve); System.out.println("evaledSolve 2: " + evaledSolve); IExpr nsolve = e.parse("NSolve(x==" + term1 + ", x)"); IExpr evaledNSolve = e.evaluate(nsolve); System.out.println("evaledNSolve 3: " + evaledNSolve);
This results in:
numericMode: false
numericPrecision: 15
evaledRoot 1: {x->1.4537589480362972E-34}
evaledPower: 1.45376*10^-34
evaledSolve 2: {{x->0.0}}
evaledNSolve 3: {{x->1.4537589480362972E-34}}
I also want to mention that we process the IAST/IExpr objects directly in Java and not as strings so at least in this case it is not relevant how they are printed. Nevertheless the double values in the Num objects are zero so it corresponds to the string-display and in general an accurate toString conversion is of course desired.
With the latest changes the input passed to the EvalEngine
can be exactly rationalized before being evaluated using a code like the following:
String term1 = "1.29600*10^-17";
String term2 = "(3.6*10^-9)^2";
String term3 = "(1 / (4 * pi * 3 * 10^6 * 2.2 * 10^9))^2";
EvalEngine engine = new EvalEngine(true);
IExpr solve = engine.parse("Solve(x==" + term3 + ",x)");
solve = F.subst(solve, e -> {
if (e.isInexactNumber() && e.isReal()) {
double value = e.evalDouble();
return F.fractionExact(value, false);
}
return F.NIL;
});
IExpr solutionSets = engine.evaluateNIL(solve);
System.out.println("Solution-sets: " + solutionSets);
IExpr solution = solutionSets.getAt(1).getAt(1).getAt(2);
System.out.println("solution: " + solution.evalDouble());
@axkr do you plan to use this method for rationalization within the Solve
command or at least to make this available with a specialized Rationalization Symja command?
For our application this is not critical since I plan to perform the rationalization at compile-time when we parse our equations and before we generate the source code to create the AST.
do you plan to use this method for rationalization within the
Solve
command or at least to make this available with a specialized Rationalization Symja command? For our application this is not critical since I plan to perform the rationalization at compile-time when we parse our equations and before we generate the source code to create the AST.
Yes please, you can replace the call to NumberTheory.rationalize()
in SolveUtils.java
.
Close for now. Please reopen if necessary.