Building an Equation Solver with the Parser and Transformer

ID: 1461

				
					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))` & sets defaults before it hits the parser
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);

// --- Demo Executions ---
System.Collections.Generic.List<string> eqList = new() {
   "EqSolve(x + 5 = 125)", // Using default range [-10000,10000]
   "EqSolve(x^2 + 5 = 105)", // Picks one result from the default range
   "EqSolve(x^2 + 5 = 105, 0, 100)", // Restricts to positive root
   "EqSolve(x^2 + 5 = 105, -100, 0)", // Restricts to negative root
   "EqSolve(x^2 + 1000 = 5)", // No existing solution
   "EqSolve(40 + MyVar * 6 = 88, for MyVar)" // Uses custom variable 'MyVar' instead of 'x'
};

foreach(var eq in eqList) {
   Console.WriteLine(uc.ExpressionTransformer.Transform(eq)); // Displays transformed expression
   Console.WriteLine($"Result: {uc.EvalStr(eq)}"); // Returns result
}
				
			
EqSolve(x + 5 - (125), -10000,  10000,  x)
Result: 120
EqSolve(x^2 + 5 - (105), -10000,  10000,  x)
Result: 10
EqSolve(x^2 + 5 - (105), 0, 100,  x)
Result: 10
EqSolve(x^2 + 5 - (105), -100, 0,  x)
Result: -10
EqSolve(x^2 + 1000 - (5), -10000,  10000,  x)
Result: No solution found in the given range.
EqSolve(40 + MyVar * 6 - (88), -10000,  10000, MyVar)
Result: 8
				
					#include <iostream>
#include "uCalc.h"

using namespace std;
using namespace uCalcSoftware;

void ucalc_call EqSolveCb(uCalcBase::Callback cb) { // Callback based on the Bisection Method
   auto expr = cb.ArgExpr(1);     // ByExpr: Unevaluated Expression object (lazy evaluation)
   auto a = cb.Arg(2);            // Argument 2: Range Minimum
   auto b = cb.Arg(3);            // Argument 3: Range Maximum
   auto variable = cb.ArgItem(4); // ByHandle: The variable Item object

   // Helper to update the variable in the uCalc engine and evaluate the expression
   auto EvaluateAt = [&](double val) -> double {
      variable.Value(val);
      return expr.Evaluate();
   };

   // 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)) swap(a, b);

   auto midpoint = 0.0;
   auto fMidpoint = 0.0;

   // Bisection loop
   for (int i = 0; i <= 100; i++) {
      midpoint = (a + b) / 2;
      fMidpoint = EvaluateAt(midpoint);

      if (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 (abs(fMidpoint) > 1e-5) cb.Error().Raise("No solution found in the given range.");
   cb.Return(round(midpoint * 10000000.0) / 10000000.0); // Return the final solved value
}
int main() {
   uCalc uc;
   // 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))` & sets defaults before it hits the parser
   auto 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);

   // --- Demo Executions ---
   vector<string> eqList = {
      "EqSolve(x + 5 = 125)", // Using default range [-10000,10000]
      "EqSolve(x^2 + 5 = 105)", // Picks one result from the default range
      "EqSolve(x^2 + 5 = 105, 0, 100)", // Restricts to positive root
      "EqSolve(x^2 + 5 = 105, -100, 0)", // Restricts to negative root
      "EqSolve(x^2 + 1000 = 5)", // No existing solution
      "EqSolve(40 + MyVar * 6 = 88, for MyVar)" // Uses custom variable 'MyVar' instead of 'x'
   };

   for(auto eq : eqList) {
      cout << uc.ExpressionTransformer().Transform(eq) << endl; // Displays transformed expression
      cout << "Result: " << uc.EvalStr(eq) << endl; // Returns result
   }
}
				
			
EqSolve(x + 5 - (125), -10000,  10000,  x)
Result: 120
EqSolve(x^2 + 5 - (105), -10000,  10000,  x)
Result: 10
EqSolve(x^2 + 5 - (105), 0, 100,  x)
Result: 10
EqSolve(x^2 + 5 - (105), -100, 0,  x)
Result: -10
EqSolve(x^2 + 1000 - (5), -10000,  10000,  x)
Result: No solution found in the given range.
EqSolve(40 + MyVar * 6 - (88), -10000,  10000, MyVar)
Result: 8
				
					Imports System
Imports uCalcSoftware
Public Module Program
   
   Public Sub EqSolveCb(ByVal cb As uCalc.Callback)REM // Callback based on the Bisection Method
      Dim expr = cb.ArgExpr(1)     '// ByExpr: Unevaluated Expression object (lazy evaluation)
      Dim a = cb.Arg(2)            '// Argument 2: Range Minimum
      Dim b = cb.Arg(3)            '// Argument 3: Range Maximum
      Dim variable = cb.ArgItem(4) '// ByHandle: The variable Item object
      
      '// Helper to update the variable in the uCalc engine and evaluate the expression
      Dim EvaluateAt = Function (val as Double) As Double
         variable.Value(val)   '// Push the new test value to the variable
         return expr.Evaluate() '// Evaluate the pre-parsed expression
      End Function
      
      '// 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) Then Dim temp = a : a = b : b = temp
         
         Dim midpoint = 0.0
         Dim fMidpoint = 0.0
         
         '// Bisection loop
         For i  As Integer = 0 To 100
            midpoint = (a + b) / 2
            fMidpoint = EvaluateAt(midpoint)
            
            If Math.Abs(fMidpoint) < 1e-7 Then Exit For REM // Stop if close enough to 0

      REM// Narrow the bounds (compact logic!)
               If fMidpoint < 0 Then a = midpoint Else b = midpoint
                  Next
                  
                  If Math.Abs(fMidpoint) > 1e-5 Then cb.Error.Raise("No solution found in the given range.")
                     cb.Return(Math.Round(midpoint, 7)) '// Return the final solved value 
                  End Sub
                  Public Sub Main()
                     Dim uc As New uCalc()
                     '// 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))` & sets defaults before it hits the parser
                     Dim 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)", AddressOf EqSolveCb)
                     
                     '// --- Demo Executions ---
                     Dim eqList As New List(Of String) From {
                     "EqSolve(x + 5 = 125)", '// Using default range [-10000,10000]
                     "EqSolve(x^2 + 5 = 105)", '// Picks one result from the default range
                     "EqSolve(x^2 + 5 = 105, 0, 100)", '// Restricts to positive root
                     "EqSolve(x^2 + 5 = 105, -100, 0)", '// Restricts to negative root
                     "EqSolve(x^2 + 1000 = 5)", '// No existing solution
                     "EqSolve(40 + MyVar * 6 = 88, for MyVar)" '// Uses custom variable 'MyVar' instead of 'x'
                     }
                     
                     For Each eq In eqList
                        Console.WriteLine(uc.ExpressionTransformer.Transform(eq)) '// Displays transformed expression
                        Console.WriteLine($"Result: {uc.EvalStr(eq)}") '// Returns result
                     Next
                  End Sub
               End Module
				
			
EqSolve(x + 5 - (125), -10000,  10000,  x)
Result: 120
EqSolve(x^2 + 5 - (105), -10000,  10000,  x)
Result: 10
EqSolve(x^2 + 5 - (105), 0, 100,  x)
Result: 10
EqSolve(x^2 + 5 - (105), -100, 0,  x)
Result: -10
EqSolve(x^2 + 1000 - (5), -10000,  10000,  x)
Result: No solution found in the given range.
EqSolve(40 + MyVar * 6 - (88), -10000,  10000, MyVar)
Result: 8