solver: Add support for multiple equation solving

Add the possibility for the solver to contain a list of equations.
The `NextEq` command rotates the equation list.

Add the `EvalEq` command to evaluate the equation.

Add `EvalEq` and `NextEq` to the solving menu.

Fixes: #1050

Signed-off-by: Christophe de Dinechin <christophe@dinechin.org>
This commit is contained in:
Christophe de Dinechin 2024-07-28 16:23:13 +02:00
parent 4ddbd8e862
commit 85b9be68c8
9 changed files with 215 additions and 33 deletions

View file

@ -2130,6 +2130,17 @@ expression_p expression::current_equation(bool error,
return nullptr; return nullptr;
} }
id eqty = obj->type(); id eqty = obj->type();
if (eqty == ID_list || eqty == ID_array)
{
obj = list_p(obj)->at(0);
if (!obj)
{
rt.no_equation_error();
return nullptr;
}
eqty = obj->type();
}
if (eqty == ID_equation) if (eqty == ID_equation)
{ {
obj = equation_p(obj)->value(); obj = equation_p(obj)->value();
@ -2441,6 +2452,24 @@ expression_p expression::as_difference_for_solve() const
} }
expression_p expression::left_of_equation() const
// ----------------------------------------------------------------------------
// For the solver, transform A=B into A
// ----------------------------------------------------------------------------
{
return rewrites(X == Y, X);
}
expression_p expression::right_of_equation() const
// ----------------------------------------------------------------------------
// For the solver, transform A=B into A
// ----------------------------------------------------------------------------
{
return rewrites(X == Y, Y);
}
expression_p expression::expand() const expression_p expression::expand() const
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Run various rewrites to expand terms // Run various rewrites to expand terms

View file

@ -250,6 +250,8 @@ struct expression : program
expression_p reorder_terms() const; expression_p reorder_terms() const;
expression_p simplify() const; expression_p simplify() const;
expression_p as_difference_for_solve() const; // Transform A=B into A-B expression_p as_difference_for_solve() const; // Transform A=B into A-B
expression_p left_of_equation() const; // Transform A=B into A
expression_p right_of_equation() const; // Transform A=B into B
expression_p strip_units(bool keep_constants=false) const; expression_p strip_units(bool keep_constants=false) const;
object_p outermost_operator() const; object_p outermost_operator() const;
size_t render(renderer &r, bool quoted = false) const size_t render(renderer &r, bool quoted = false) const

View file

@ -1024,6 +1024,8 @@ OP(AlgebraVariable, "ⓧ") ALIAS(AlgebraVariable, "VX")
OP(StoreAlgebraVariable, "Storeⓧ") ALIAS(StoreAlgebraVariable, "StoVX") OP(StoreAlgebraVariable, "Storeⓧ") ALIAS(StoreAlgebraVariable, "StoVX")
OP(StEq, "▶Equation") ALIAS(StEq, "StoreEquation") OP(StEq, "▶Equation") ALIAS(StEq, "StoreEquation")
OP(RcEq, "Equation▶") ALIAS(RcEq, "RecallEquation") OP(RcEq, "Equation▶") ALIAS(RcEq, "RecallEquation")
OP(NextEq, "NextEquation")
OP(EvalEq, "EvaluateEquation")

View file

@ -557,7 +557,7 @@ list_p list::append(object_p o) const
} }
bool list::expand_without_size() const bool list::expand_without_size(size_t *size) const
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Expand items on the stack, but do not add the size // Expand items on the stack, but do not add the size
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
@ -571,6 +571,8 @@ bool list::expand_without_size() const
return false; return false;
} }
} }
if (size)
*size = rt.depth() - depth;
return true; return true;
} }
@ -818,9 +820,9 @@ HELP_BODY(list)
// //
// ============================================================================ // ============================================================================
object::result to_list(uint depth) list_p to_list_object(uint depth)
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// // Make a list from the stack as an object
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
{ {
scribble scr; scribble scr;
@ -831,12 +833,27 @@ object::result to_list(uint depth)
size_t objsz = obj->size(); size_t objsz = obj->size();
byte_p objp = byte_p(obj); byte_p objp = byte_p(obj);
if (!rt.append(objsz, objp)) if (!rt.append(objsz, objp))
return object::ERROR; return nullptr;
} }
} }
object_g list = list::make(scr.scratch(), scr.growth());
if (rt.drop(depth) && rt.push(list)) if (list_p result = list::make(scr.scratch(), scr.growth()))
return object::OK; {
rt.drop(depth);
return result;
}
return nullptr;
}
object::result to_list(uint depth)
// ----------------------------------------------------------------------------
// Make a list on the stack
// ----------------------------------------------------------------------------
{
if (object_g list = to_list_object(depth))
if (rt.push(list))
return object::OK;
return object::ERROR; return object::ERROR;
} }

View file

@ -217,7 +217,7 @@ struct list : text
} }
bool expand_without_size() const; bool expand_without_size(size_t *size = nullptr) const;
bool expand() const; bool expand() const;
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// Expand items to the stack, and return number of them // Expand items to the stack, and return number of them
@ -358,6 +358,7 @@ inline list_g operator*(list_r x, uint y)
object::result to_list(uint depth); object::result to_list(uint depth);
list_p to_list_object(uint depth);
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Convert `depth` items to a list // Convert `depth` items to a list
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------

View file

@ -1197,24 +1197,24 @@ MENU(SolverMenu,
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// The solver menu / application // The solver menu / application
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
"Eq▶", ID_RcEq, "Eq▶", ID_RcEq,
"", ID_AlgebraVariable, "", ID_AlgebraVariable,
"Root", ID_Root, "Root", ID_Root,
"Solve", ID_SolvingMenu, "EvalEq", ID_EvalEq,
"Num", ID_NumericalSolverMenu, "NxtEq", ID_NextEq,
"Symb", ID_SymbolicSolverMenu, "Solve", ID_SolvingMenu,
"▶Eq", ID_StEq, "▶Eq", ID_StEq,
"Stoⓧ", ID_StoreAlgebraVariable, "Stoⓧ", ID_StoreAlgebraVariable,
"Diff", ID_DifferentialSolverMenu, "Symb", ID_SymbolicSolverMenu,
"Poly", ID_PolynomialSolverMenu, "Diff", ID_DifferentialSolverMenu,
"Linear", ID_LinearSolverMenu, "Poly", ID_PolynomialSolverMenu,
"Multi", ID_MultiSolverMenu, "Linear", ID_LinearSolverMenu,
"Finance", ID_FinanceSolverMenu, "Multi", ID_MultiSolverMenu,
"Plot", ID_PlotMenu, "Finance", ID_FinanceSolverMenu,
"L.R.", ID_StatisticsMenu, "Plot", ID_PlotMenu,
"Eqns", ID_EquationsMenu, "Eqns", ID_EquationsMenu,
SolverImprecision::label, ID_SolverImprecision, SolverImprecision::label, ID_SolverImprecision,
SolverIterations::label, ID_SolverIterations); SolverIterations::label, ID_SolverIterations);

View file

@ -31,6 +31,7 @@
#include "algebraic.h" #include "algebraic.h"
#include "arithmetic.h" #include "arithmetic.h"
#include "array.h"
#include "compare.h" #include "compare.h"
#include "equations.h" #include "equations.h"
#include "expression.h" #include "expression.h"
@ -83,8 +84,6 @@ COMMAND_BODY(Root)
rt.type_error(); rt.type_error();
return ERROR; return ERROR;
} }
if (eqty == ID_expression)
eqobj = expression_p(+eqobj)->as_difference_for_solve();
// Drop input parameters // Drop input parameters
rt.drop(3); rt.drop(3);
@ -130,6 +129,10 @@ algebraic_p solve(program_g eq, algebraic_g goal, object_g guess)
object::id gty = guess->type(); object::id gty = guess->type();
save<bool> nodates(unit::nodates, true); save<bool> nodates(unit::nodates, true);
// Convert A=B+C into A-(B+C)
if (eq->type() == object::ID_expression)
eq = expression_p(+eq)->as_difference_for_solve();
// Check if low and hight values were given explicitly // Check if low and hight values were given explicitly
if (gty == object::ID_list || gty == object::ID_array) if (gty == object::ID_list || gty == object::ID_array)
{ {
@ -424,9 +427,19 @@ COMMAND_BODY(StEq)
if (object_p obj = rt.top()) if (object_p obj = rt.top())
{ {
id objty = obj->type(); id objty = obj->type();
if (objty == ID_list || objty == ID_array)
{
for (object_p obj: *list_p(obj))
{
objty = obj->type();
if (objty != ID_expression && objty != ID_polynomial &&
objty != ID_equation)
rt.type_error();
}
}
if (objty != ID_expression && objty != ID_polynomial && if (objty != ID_expression && objty != ID_polynomial &&
objty != ID_equation) objty != ID_equation)
rt.type_error(); rt.type_error();
else if (directory::store_here(static_object(ID_Equation), obj)) else if (directory::store_here(static_object(ID_Equation), obj))
if (rt.drop()) if (rt.drop())
return OK; return OK;
@ -447,6 +460,80 @@ COMMAND_BODY(RcEq)
} }
COMMAND_BODY(NextEq)
// ----------------------------------------------------------------------------
// Cycle equations in the Eq variable if it's a list
// ----------------------------------------------------------------------------
{
object_p eqname = static_object(ID_Equation);
object_p obj = directory::recall_all(eqname, false);
if (obj)
{
id ty = obj->type();
if (ty == ID_list || ty == ID_array)
{
size_t sz = 0;
if (list_p(obj)->expand_without_size(&sz))
{
rt.roll(sz);
list_g now = to_list_object(sz);
if (directory::store_here(eqname, now))
{
ui.menu_refresh(ID_SolvingMenu);
return OK;
}
}
}
}
return ERROR;
}
COMMAND_BODY(EvalEq)
// ----------------------------------------------------------------------------
// Evaluate the current equation
// ----------------------------------------------------------------------------
{
if (expression_g expr = expression::current_equation(true))
{
// We will run programs, do not save stack, etc.
settings::PrepareForFunctionEvaluation willEvaluateFunctions;
expression_g diff = expr->as_difference_for_solve();
if (+diff != +expr)
{
expression_g l = expr->left_of_equation();
expression_g r = expr->right_of_equation();
algebraic_g lv = l->evaluate();
algebraic_g rv = r->evaluate();
if (lv && rv)
{
object_g ltag = tag::make("Left", +lv);
object_g rtag = tag::make("Right", +rv);
lv = lv - rv;
object_g diff = tag::make("Diff", +lv);
if (ltag && rtag && diff)
{
list_g result = list::make(ID_array, ltag, rtag, diff);
if (result && rt.push(+result))
return OK;
}
}
rt.invalid_equation_error();
}
else
{
algebraic_g value = expr->evaluate();
if (value)
if (tag_p tagged = tag::make("Expr", +value))
if (rt.push(tagged))
return OK;
}
}
return ERROR;
}
MENU_BODY(SolvingMenu) MENU_BODY(SolvingMenu)
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Process the MENU command for SolvingMenu // Process the MENU command for SolvingMenu
@ -455,7 +542,7 @@ MENU_BODY(SolvingMenu)
expression_p expr = expression::current_equation(false); expression_p expr = expression::current_equation(false);
list_g vars = expr ? expr->names() : nullptr; list_g vars = expr ? expr->names() : nullptr;
size_t nitems = vars ? vars->items() : 0; size_t nitems = vars ? vars->items() : 0;
items_init(mi, nitems, 3, 1); items_init(mi, nitems+1, 3, 1);
if (!vars) if (!vars)
return false; return false;
@ -464,6 +551,7 @@ MENU_BODY(SolvingMenu)
// First row: Store variables // First row: Store variables
mi.plane = 0; mi.plane = 0;
mi.planes = 1; mi.planes = 1;
menu::items(mi, "EvalEq", ID_EvalEq);
for (auto name : *vars) for (auto name : *vars)
if (symbol_p sym = name->as<symbol>()) if (symbol_p sym = name->as<symbol>())
menu::items(mi, sym, menu::ID_SolvingMenuStore); menu::items(mi, sym, menu::ID_SolvingMenuStore);
@ -473,6 +561,7 @@ MENU_BODY(SolvingMenu)
mi.planes = 2; mi.planes = 2;
mi.skip = skip; mi.skip = skip;
mi.index = mi.plane * ui.NUM_SOFTKEYS; mi.index = mi.plane * ui.NUM_SOFTKEYS;
menu::items(mi, "NextEq", ID_NextEq);
for (auto name : *vars) for (auto name : *vars)
if (symbol_p sym = name->as<symbol>()) if (symbol_p sym = name->as<symbol>())
menu::items(mi, sym, menu::ID_SolvingMenuSolve); menu::items(mi, sym, menu::ID_SolvingMenuSolve);
@ -482,6 +571,7 @@ MENU_BODY(SolvingMenu)
mi.planes = 3; mi.planes = 3;
mi.skip = skip; mi.skip = skip;
mi.index = mi.plane * ui.NUM_SOFTKEYS; mi.index = mi.plane * ui.NUM_SOFTKEYS;
menu::items(mi, "Eq▶", ID_RcEq);
for (auto name : *vars) for (auto name : *vars)
{ {
if (symbol_g sym = name->as<symbol>()) if (symbol_g sym = name->as<symbol>())
@ -497,7 +587,7 @@ MENU_BODY(SolvingMenu)
} }
// Add markers // Add markers
for (uint k = 0; k < ui.NUM_SOFTKEYS - (mi.pages > 1); k++) for (uint k = 1; k < ui.NUM_SOFTKEYS - (mi.pages > 1); k++)
{ {
ui.marker(k + 0 * ui.NUM_SOFTKEYS, L'', true); ui.marker(k + 0 * ui.NUM_SOFTKEYS, L'', true);
ui.marker(k + 1 * ui.NUM_SOFTKEYS, L'?', false); ui.marker(k + 1 * ui.NUM_SOFTKEYS, L'?', false);
@ -592,7 +682,7 @@ COMMAND_BODY(SolvingMenuRecall)
int key = ui.evaluating; int key = ui.evaluating;
if (key >= KEY_F1 && key <= KEY_F6) if (key >= KEY_F1 && key <= KEY_F6)
{ {
uint index = key - KEY_F1 + 5 * ui.page(); uint index = key - KEY_F1 + 5 * ui.page() - 1;
if (symbol_g sym = expression_variable(index)) if (symbol_g sym = expression_variable(index))
if (object_p value = directory::recall_all(sym, true)) if (object_p value = directory::recall_all(sym, true))
if (tag_p tagged = tagged_value(sym, value)) if (tag_p tagged = tagged_value(sym, value))
@ -622,7 +712,7 @@ COMMAND_BODY(SolvingMenuStore)
int key = ui.evaluating; int key = ui.evaluating;
if (key >= KEY_F1 && key <= KEY_F6) if (key >= KEY_F1 && key <= KEY_F6)
{ {
uint index = key - KEY_F1 + 5 * ui.page(); uint index = key - KEY_F1 + 5 * ui.page() - 1;
if (algebraic_p entry = expression_variable_or_unit(index)) if (algebraic_p entry = expression_variable_or_unit(index))
{ {
if (object_p value = tag::strip(rt.pop())) if (object_p value = tag::strip(rt.pop()))
@ -686,7 +776,7 @@ COMMAND_BODY(SolvingMenuSolve)
int key = ui.evaluating; int key = ui.evaluating;
if (key >= KEY_F1 && key <= KEY_F6) if (key >= KEY_F1 && key <= KEY_F6)
{ {
uint index = key - KEY_F1 + 5 * ui.page(); uint index = key - KEY_F1 + 5 * ui.page() - 1;
if (symbol_g sym = expression_variable(index)) if (symbol_g sym = expression_variable(index))
{ {
object_g value = directory::recall_all(sym, false); object_g value = directory::recall_all(sym, false);

View file

@ -40,6 +40,8 @@ COMMAND_DECLARE(Root,3);
COMMAND_DECLARE(StEq, 1); COMMAND_DECLARE(StEq, 1);
COMMAND_DECLARE(RcEq, 0); COMMAND_DECLARE(RcEq, 0);
COMMAND_DECLARE(NextEq, 0);
COMMAND_DECLARE(EvalEq, 0);
struct SolvingMenu : menu struct SolvingMenu : menu

View file

@ -1,3 +1,16 @@
«
«
0 25 for i
i 1_m/s * 1_km/yr convert drop
next
»
TEVAL
»
'UnitBenchmark' STO
@@ --------------------------------------------------------------------------- @@ ---------------------------------------------------------------------------
@@ @@
@@ Beep @@ Beep
@ -706,3 +719,29 @@
next next
» »
'RandomXYPlot' STO 'RandomXYPlot' STO
@@ ---------------------------------------------------------------------------
@@
@@ Multiple equations solver
@@
@@ ---------------------------------------------------------------------------
{ 'SQ(AA)+SQ(BB)=SQ(CC)'
'sin(α)/a=sin(β)/b'
'sin(β)/b=sin(γ)/c'
'sin(α)/b=sin(γ)/c'
'α+β+γ=180'
's=(a+b+c)/2'
'A=sqrt(s*(s-a)*(s-b)*(s-c))'
'sq(c)=sq(a)+sq(b)-2*a*b*cos(γ)'
'sq(b)=sq(a)+sq(c)-2*a*c*cos(β)'
'sq(a)=sq(b)+sq(c)-2*b*c*cos(α)'
} STEQ
10_m 'AA' STO
85_ft 'BB' STO
1_cm 'CC' STO
SolvingMenu