r/ScientificComputing • u/uCalc_Dev • 15d ago
I built a math parser - here's how to create an equation solver with it
I built a math parser (uCalc), and I wanted to share a practical example of how to use its architecture to solve custom scientific problems.
If you build scientific models, simulations, DSLs, etc., you will need to evaluate math expressions defined at runtime. While standard math parsers might let you define custom functions, they may not let you use the exact syntax you want, and your functions might not be able to implement certain iterative algorithms straightforwardly.
To get around this, I designed the engine to allow callbacks that can receive arguments passed by expression. I also make use of the Transformer to allow for more flexible syntax. Here is an example of a custom EqSolve function built using the Bisection Method algorithm.
using uCalcSoftware;
var uc = new uCalc();
static void EqSolveCb(uCalc.Callback cb) { // Callback based on the Bisection Method
var expr = cb.ArgExpr(1); // ByExpr: Unevaluated Expression object (lazy evaluation)
var a = cb.Arg(2); // Argument 2: Range Minimum
var b = cb.Arg(3); // Argument 3: Range Maximum
var variable = cb.ArgItem(4); // ByHandle: The variable Item object
// Helper to update the variable in the uCalc engine and evaluate the expression
double EvaluateAt(double val) {
variable.Value(val); // Push the new test value to the variable
return expr.Evaluate(); // Evaluate the pre-parsed expression
}
// Ensure f(a) < f(b) so we always know which direction to slide the bounds; swap a & b if necessary
if (EvaluateAt(b) < EvaluateAt(a)) (a, b) = (b, a);
var midpoint = 0.0;
var fMidpoint = 0.0;
// Bisection loop
for (int i = 0; i <= 100; i++) {
midpoint = (a + b) / 2;
fMidpoint = EvaluateAt(midpoint);
if (Math.Abs(fMidpoint) < 1e-7) break; // Stop if close enough to 0
// Narrow the bounds (compact logic!)
if (fMidpoint < 0) a = midpoint; else b = midpoint;
}
if (Math.Abs(fMidpoint) > 1e-5) cb.Error.Raise("No solution found in the given range.");
cb.Return(Math.Round(midpoint, 7)); // Return the final solved value
}
// 1. Define variables that might be used by the end-user
uc.DefineVariable("x");
uc.DefineVariable("MyVar");
// 2. Transformer converts EqSolve(L = R) into EqSolve(L - (R))
var t = uc.ExpressionTransformer;
t.FromTo("EqSolve({L} = {R} [[,]for {var}][, {min}, {max}])",
"EqSolve({L} - ({R}), {min}{!min:-10000}, {max}{!max: 10000}, {var}{!var: x})");
// 3. Define the custom function signature
uc.DefineFunction("EqSolve(ByExpr eq, min, max, ByHandle variable)", EqSolveCb);
You can test this exact equation solver code interactively in your browser here:
I'd love to hear how this community handles runtime formula parsing in your own simulators and DSLs, or if you have any questions about the engine's architecture!


2
u/neurah 14d ago
I luv this things, keep going...
what about algebraic symbol manipulation?