mirror of
https://github.com/c3d/DB48X-on-DM42.git
synced 2024-09-28 03:20:53 +02:00
solver: Implement solving menu
The `SolvingMenu` makes it possible to quickly solve equations with multiple variables. It is typically accessed from the `SolverMenu` (currently under key F4). The menu shows the variables in the current equation, as defined by the `'EQ'` variable (aka `Equation`). This can be accessed with the `StEQ` and `RcEQ` commands. For each variable in the equation, the function keys work as follow: * The unshifted key sets the variable value * The shifted value solves for the given variable * The second-shifted value recalls the variable value The menu also shows the current value in menu label. Fixes: #925 Signed-off-by: Christophe de Dinechin <christophe@dinechin.org>
This commit is contained in:
parent
391901c66c
commit
45742061b6
9 changed files with 351 additions and 42 deletions
|
@ -59,6 +59,8 @@ ERROR(directory_path, "Directory not in path")
|
|||
ERROR(name_exists, "Name already exists")
|
||||
ERROR(invalid_name, "Invalid name")
|
||||
ERROR(undefined_name, "Undefined name")
|
||||
ERROR(some_undefined_name, "Some variables are undefined")
|
||||
ERROR(some_invalid_name, "Some variables are invalid")
|
||||
ERROR(recursion, "Too many recursive calls")
|
||||
ERROR(return_without_caller, "Return without a caller")
|
||||
ERROR(invalid_local, "Invalid local")
|
||||
|
|
|
@ -39,6 +39,7 @@
|
|||
#include "settings.h"
|
||||
#include "unit.h"
|
||||
#include "utf8.h"
|
||||
#include "variables.h"
|
||||
|
||||
RECORDER(equation, 16, "Processing of equations and algebraic objects");
|
||||
RECORDER(equation_error,16, "Errors with equations");
|
||||
|
@ -2060,6 +2061,31 @@ GRAPH_BODY(expression)
|
|||
}
|
||||
|
||||
|
||||
expression_p expression::current_equation()
|
||||
// ----------------------------------------------------------------------------
|
||||
// Return content of EQ variable
|
||||
// ----------------------------------------------------------------------------
|
||||
{
|
||||
object_p obj = directory::recall_all(static_object(ID_Equation), false);
|
||||
if (!obj)
|
||||
{
|
||||
if (!rt.error())
|
||||
rt.no_equation_error();
|
||||
return nullptr;
|
||||
}
|
||||
id eqty = obj->type();
|
||||
if (eqty != ID_expression && eqty != ID_polynomial)
|
||||
{
|
||||
rt.type_error();
|
||||
return nullptr;
|
||||
}
|
||||
if (eqty == ID_expression)
|
||||
obj = expression_p(obj)->as_difference_for_solve();
|
||||
|
||||
return expression_p(obj);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ============================================================================
|
||||
//
|
||||
|
|
|
@ -111,6 +111,8 @@ struct expression : program
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
static expression_p current_equation();
|
||||
|
||||
|
||||
|
||||
// ========================================================================
|
||||
|
|
|
@ -1002,11 +1002,12 @@ SETTING(ErrorBeepDuration, 0, 500, 50)
|
|||
//
|
||||
// ============================================================================
|
||||
|
||||
|
||||
OP(AlgebraConfiguration, "Ⓓ") ALIAS(AlgebraConfiguration, "CASDir")
|
||||
OP(AlgebraVariable, "ⓧ") ALIAS(AlgebraVariable, "VX")
|
||||
ALIAS(AlgebraVariable, "RclVX")
|
||||
OP(StoreAlgebraVariable, "Storeⓧ") ALIAS(StoreAlgebraVariable, "StoVX")
|
||||
OP(StEq, "▶Equation") ALIAS(StEq, "StoreEquation")
|
||||
OP(RcEq, "Equation▶") ALIAS(RcEq, "RecallEquation")
|
||||
|
||||
|
||||
|
||||
|
@ -1026,6 +1027,9 @@ CMD(VariablesMenuExecute)
|
|||
CMD(VariablesMenuRecall)
|
||||
CMD(VariablesMenuStore)
|
||||
|
||||
CMD(SolvingMenuStore)
|
||||
CMD(SolvingMenuSolve)
|
||||
CMD(SolvingMenuRecall)
|
||||
|
||||
|
||||
|
||||
|
@ -1110,6 +1114,7 @@ MENU(TextMenu)
|
|||
// Special menus
|
||||
MENU(EditMenu)
|
||||
CMD(VariablesMenu)
|
||||
CMD(SolvingMenu)
|
||||
CMD(ToolsMenu)
|
||||
CMD(Catalog)
|
||||
|
||||
|
|
15
src/menu.cc
15
src/menu.cc
|
@ -1179,20 +1179,21 @@ MENU(SolverMenu,
|
|||
// ----------------------------------------------------------------------------
|
||||
// The solver menu / application
|
||||
// ----------------------------------------------------------------------------
|
||||
"Eq", ID_Equation,
|
||||
"Indep", ID_Unimplemented,
|
||||
"Eq▶", ID_RcEq,
|
||||
"ⓧ", ID_AlgebraVariable,
|
||||
"Root", ID_Root,
|
||||
"MultiR", ID_Unimplemented,
|
||||
"PolyR", ID_Unimplemented,
|
||||
"Solve", ID_SolvingMenu,
|
||||
"Num", ID_NumericalSolverMenu,
|
||||
|
||||
"Diff", ID_DifferentialSolverMenu,
|
||||
"Symb", ID_SymbolicSolverMenu,
|
||||
|
||||
"▶Eq", ID_StEq,
|
||||
"Stoⓧ", ID_StoreAlgebraVariable,
|
||||
"Diff", ID_DifferentialSolverMenu,
|
||||
"Poly", ID_PolynomialSolverMenu,
|
||||
"Linear", ID_LinearSolverMenu,
|
||||
"Multi", ID_MultiSolverMenu,
|
||||
"Finance", ID_FinanceSolverMenu,
|
||||
|
||||
"Finance", ID_FinanceSolverMenu,
|
||||
"Plot", ID_PlotMenu,
|
||||
"L.R.", ID_StatisticsMenu,
|
||||
"Eqns", ID_EquationsMenu,
|
||||
|
|
256
src/solve.cc
256
src/solve.cc
|
@ -36,10 +36,12 @@
|
|||
#include "expression.h"
|
||||
#include "functions.h"
|
||||
#include "integer.h"
|
||||
#include "list.h"
|
||||
#include "recorder.h"
|
||||
#include "settings.h"
|
||||
#include "symbol.h"
|
||||
#include "tag.h"
|
||||
#include "variables.h"
|
||||
|
||||
RECORDER(solve, 16, "Numerical solver");
|
||||
RECORDER(solve_error, 16, "Numerical solver errors");
|
||||
|
@ -313,3 +315,257 @@ algebraic_p solve(program_g eq, symbol_g name, object_g guess)
|
|||
rt.no_solution_error();
|
||||
return lx;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ============================================================================
|
||||
//
|
||||
// Solving menu
|
||||
//
|
||||
// ============================================================================
|
||||
|
||||
COMMAND_BODY(StEq)
|
||||
// ----------------------------------------------------------------------------
|
||||
// Store expression in `Equation` variable
|
||||
// ----------------------------------------------------------------------------
|
||||
{
|
||||
if (object_p obj = rt.top())
|
||||
{
|
||||
id objty = obj->type();
|
||||
if (objty != ID_expression && objty != ID_polynomial)
|
||||
rt.type_error();
|
||||
else if (directory::store_here(static_object(ID_Equation), obj))
|
||||
if (rt.drop())
|
||||
return OK;
|
||||
}
|
||||
return ERROR;
|
||||
}
|
||||
|
||||
|
||||
COMMAND_BODY(RcEq)
|
||||
// ----------------------------------------------------------------------------
|
||||
// Store expression in `Equation` variable
|
||||
// ----------------------------------------------------------------------------
|
||||
{
|
||||
if (expression_p expr = expression::current_equation())
|
||||
if (rt.push(expr))
|
||||
return OK;
|
||||
return ERROR;
|
||||
}
|
||||
|
||||
|
||||
MENU_BODY(SolvingMenu)
|
||||
// ----------------------------------------------------------------------------
|
||||
// Process the MENU command for SolvingMenu
|
||||
// ----------------------------------------------------------------------------
|
||||
{
|
||||
expression_p expr = expression::current_equation();
|
||||
if (!expr)
|
||||
return false;
|
||||
|
||||
list_g vars = expr->names();
|
||||
if (!vars)
|
||||
return false;
|
||||
|
||||
size_t nitems = vars->items();
|
||||
items_init(mi, nitems, 3, 1);
|
||||
|
||||
uint skip = mi.skip;
|
||||
|
||||
// First row: Store variables
|
||||
mi.plane = 0;
|
||||
mi.planes = 1;
|
||||
for (auto name : *vars)
|
||||
if (symbol_p sym = name->as<symbol>())
|
||||
menu::items(mi, sym, menu::ID_SolvingMenuStore);
|
||||
|
||||
// Second row: Solve for variables
|
||||
mi.plane = 1;
|
||||
mi.planes = 2;
|
||||
mi.skip = skip;
|
||||
mi.index = mi.plane * ui.NUM_SOFTKEYS;
|
||||
for (auto name : *vars)
|
||||
if (symbol_p sym = name->as<symbol>())
|
||||
menu::items(mi, sym, menu::ID_SolvingMenuSolve);
|
||||
|
||||
// Third row: Recall variable
|
||||
mi.plane = 2;
|
||||
mi.planes = 3;
|
||||
mi.skip = skip;
|
||||
mi.index = mi.plane * ui.NUM_SOFTKEYS;
|
||||
for (auto name : *vars)
|
||||
{
|
||||
if (symbol_g sym = name->as<symbol>())
|
||||
{
|
||||
settings::SaveDisplayDigits sdd(3);
|
||||
object_p value = directory::recall_all(sym, false);
|
||||
if (!value)
|
||||
value = symbol::make("?");
|
||||
if (value)
|
||||
sym = value->as_symbol(false);
|
||||
menu::items(mi, sym, menu::ID_SolvingMenuRecall);
|
||||
}
|
||||
}
|
||||
|
||||
// Add markers
|
||||
for (uint k = 0; k < ui.NUM_SOFTKEYS - (mi.pages > 1); k++)
|
||||
{
|
||||
ui.marker(k + 0 * ui.NUM_SOFTKEYS, L'▶', true);
|
||||
ui.marker(k + 1 * ui.NUM_SOFTKEYS, L'?', false);
|
||||
ui.marker(k + 2 * ui.NUM_SOFTKEYS, L'▶', false);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static symbol_p expression_variable(uint index)
|
||||
// ----------------------------------------------------------------------------
|
||||
// Return the variable in EQ for a given index
|
||||
// ----------------------------------------------------------------------------
|
||||
{
|
||||
if (expression_p expr = expression::current_equation())
|
||||
if (list_g vars = expr->names())
|
||||
if (object_p obj = vars->at(index))
|
||||
if (symbol_p sym = obj->as<symbol>())
|
||||
return sym;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
static tag_p tagged_value(symbol_p sym, object_p value)
|
||||
// ----------------------------------------------------------------------------
|
||||
// Tag a solver value with a symbol
|
||||
// ----------------------------------------------------------------------------
|
||||
{
|
||||
size_t nlen = 0;
|
||||
gcutf8 ntxt = sym->value(&nlen);
|
||||
tag_p tagged = tag::make(ntxt, nlen, value);
|
||||
return tagged;
|
||||
}
|
||||
|
||||
|
||||
static bool is_well_defined(expression_r expr, symbol_r sym)
|
||||
// ----------------------------------------------------------------------------
|
||||
// Check if all variables but the one we solve for are defined
|
||||
// ----------------------------------------------------------------------------
|
||||
{
|
||||
list_p vars = expr->names();
|
||||
for (auto var : *vars)
|
||||
{
|
||||
if (symbol_p vsym = var->as<symbol>())
|
||||
{
|
||||
if (!sym->is_same_as(vsym))
|
||||
{
|
||||
if (!directory::recall_all(var, false))
|
||||
{
|
||||
rt.some_undefined_name_error();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
rt.some_invalid_name_error();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
COMMAND_BODY(SolvingMenuRecall)
|
||||
// ----------------------------------------------------------------------------
|
||||
// Recall a variable from the SolvingMenu
|
||||
// ----------------------------------------------------------------------------
|
||||
{
|
||||
int key = ui.evaluating;
|
||||
if (key >= KEY_F1 && key <= KEY_F6)
|
||||
{
|
||||
uint index = key - KEY_F1 + 5 * ui.page();
|
||||
if (symbol_g sym = expression_variable(index))
|
||||
if (object_p value = directory::recall_all(sym, true))
|
||||
if (tag_p tagged = tagged_value(sym, value))
|
||||
if (rt.push(tagged))
|
||||
return OK;
|
||||
}
|
||||
|
||||
return ERROR;
|
||||
}
|
||||
|
||||
|
||||
INSERT_BODY(SolvingMenuRecall)
|
||||
// ----------------------------------------------------------------------------
|
||||
// Insert the name of a variable with `Recall` after it
|
||||
// ----------------------------------------------------------------------------
|
||||
{
|
||||
int key = ui.evaluating;
|
||||
return ui.insert_softkey(key, " '", "' Recall ", false);
|
||||
}
|
||||
|
||||
|
||||
COMMAND_BODY(SolvingMenuStore)
|
||||
// ----------------------------------------------------------------------------
|
||||
// Store a variable from the SolvingMenu
|
||||
// ----------------------------------------------------------------------------
|
||||
{
|
||||
int key = ui.evaluating;
|
||||
if (key >= KEY_F1 && key <= KEY_F6)
|
||||
{
|
||||
uint index = key - KEY_F1 + 5 * ui.page();
|
||||
if (symbol_p sym = expression_variable(index))
|
||||
if (object_p value = rt.pop())
|
||||
if (directory::store_here(sym, value))
|
||||
if (ui.menu_refresh())
|
||||
return OK;
|
||||
}
|
||||
return ERROR;
|
||||
}
|
||||
|
||||
|
||||
INSERT_BODY(SolvingMenuStore)
|
||||
// ----------------------------------------------------------------------------
|
||||
// Insert the name of a variable with `Store` after it
|
||||
// ----------------------------------------------------------------------------
|
||||
{
|
||||
int key = ui.evaluating;
|
||||
return ui.insert_softkey(key, " '", "' Store ", false);
|
||||
}
|
||||
|
||||
|
||||
COMMAND_BODY(SolvingMenuSolve)
|
||||
// ----------------------------------------------------------------------------
|
||||
// Solve for a given variable
|
||||
// ----------------------------------------------------------------------------
|
||||
{
|
||||
int key = ui.evaluating;
|
||||
if (key >= KEY_F1 && key <= KEY_F6)
|
||||
{
|
||||
uint index = key - KEY_F1 + 5 * ui.page();
|
||||
if (symbol_g sym = expression_variable(index))
|
||||
{
|
||||
object_g value = directory::recall_all(sym, false);
|
||||
if (!value)
|
||||
value = integer::make(0);
|
||||
if (expression_g eq = expression::current_equation())
|
||||
if (value && is_well_defined(eq, sym))
|
||||
if (algebraic_g result = solve(+eq, sym, value))
|
||||
if (directory::store_here(sym, result))
|
||||
if (tag_p tagged = tagged_value(sym, result))
|
||||
if (rt.push(tagged))
|
||||
if (ui.menu_refresh())
|
||||
return OK;
|
||||
}
|
||||
}
|
||||
|
||||
return ERROR;
|
||||
}
|
||||
|
||||
|
||||
INSERT_BODY(SolvingMenuSolve)
|
||||
// ----------------------------------------------------------------------------
|
||||
// Insert the name of a variable
|
||||
// ----------------------------------------------------------------------------
|
||||
{
|
||||
int key = ui.evaluating;
|
||||
return ui.insert_softkey(key, " EQ '", "' 0 Root ", false);
|
||||
}
|
||||
|
|
28
src/solve.h
28
src/solve.h
|
@ -6,7 +6,7 @@
|
|||
//
|
||||
// File Description:
|
||||
//
|
||||
//
|
||||
// Numerical solver and solver menu
|
||||
//
|
||||
//
|
||||
//
|
||||
|
@ -31,10 +31,36 @@
|
|||
|
||||
#include "algebraic.h"
|
||||
#include "command.h"
|
||||
#include "menu.h"
|
||||
#include "symbol.h"
|
||||
|
||||
algebraic_p solve(program_g eq, symbol_g name, object_g guess);
|
||||
|
||||
COMMAND_DECLARE(Root,3);
|
||||
|
||||
COMMAND_DECLARE(StEq, 1);
|
||||
COMMAND_DECLARE(RcEq, 0);
|
||||
|
||||
|
||||
struct SolvingMenu : menu
|
||||
// ----------------------------------------------------------------------------
|
||||
// The solving menu is built dynamically from current expression
|
||||
// ----------------------------------------------------------------------------
|
||||
// The SolvingMenu shows expression variables in the menu
|
||||
// For each variable, the function key has the three following features:
|
||||
// - Unshifted sets the variable value
|
||||
// - Shifted solves for the variable
|
||||
// - XShifted recalls the variable value
|
||||
{
|
||||
SolvingMenu(id type = ID_SolvingMenu) : menu(type) {}
|
||||
|
||||
public:
|
||||
OBJECT_DECL(SolvingMenu);
|
||||
MENU_DECL(SolvingMenu);
|
||||
};
|
||||
|
||||
COMMAND_DECLARE_INSERT(SolvingMenuStore,1);
|
||||
COMMAND_DECLARE_INSERT(SolvingMenuSolve,0);
|
||||
COMMAND_DECLARE_INSERT(SolvingMenuRecall,0);
|
||||
|
||||
#endif // SOLVE_H
|
||||
|
|
|
@ -468,6 +468,21 @@ object_p directory::recall_all(object_p name, bool report_missing)
|
|||
}
|
||||
|
||||
|
||||
bool directory::store_here(object_p name, object_p value)
|
||||
// ----------------------------------------------------------------------------
|
||||
// Store a variable in the current directory
|
||||
// ----------------------------------------------------------------------------
|
||||
{
|
||||
directory *dir = rt.variables(0);
|
||||
if (!dir)
|
||||
{
|
||||
rt.no_directory_error();
|
||||
return false;
|
||||
}
|
||||
return dir->store(name, value);
|
||||
}
|
||||
|
||||
|
||||
size_t directory::purge(object_p name)
|
||||
// ----------------------------------------------------------------------------
|
||||
// Purge a name (and associated value) from the directory
|
||||
|
@ -691,17 +706,10 @@ COMMAND_BODY(Sto)
|
|||
// Store a global variable into current directory
|
||||
// ----------------------------------------------------------------------------
|
||||
{
|
||||
directory *dir = rt.variables(0);
|
||||
if (!dir)
|
||||
{
|
||||
rt.no_directory_error();
|
||||
return ERROR;
|
||||
}
|
||||
|
||||
// Check that we have two objects in the stack
|
||||
object_p name = rt.stack(0);
|
||||
object_p value = rt.stack(1);
|
||||
if (name && value && dir->store(name, value))
|
||||
if (name && value && directory::store_here(name, value))
|
||||
{
|
||||
rt.drop(2);
|
||||
return OK;
|
||||
|
@ -735,13 +743,6 @@ static object::result store_op(object::id op)
|
|||
// Store with a given operation
|
||||
// ----------------------------------------------------------------------------
|
||||
{
|
||||
directory *dir = rt.variables(0);
|
||||
if (!dir)
|
||||
{
|
||||
rt.no_directory_error();
|
||||
return object::ERROR;
|
||||
}
|
||||
|
||||
object_g name = rt.stack(0);
|
||||
object_g value = rt.stack(1);
|
||||
if (!name || !value)
|
||||
|
@ -755,7 +756,7 @@ static object::result store_op(object::id op)
|
|||
if (object::result res = cmd->evaluate())
|
||||
return res;
|
||||
value = rt.pop();
|
||||
if (value && dir->store(name, value))
|
||||
if (value && directory::store_here(name, value))
|
||||
return object::OK;
|
||||
return object::ERROR;
|
||||
}
|
||||
|
@ -772,19 +773,11 @@ static object::result store_op(object::id op, object_p cstval)
|
|||
// Store with a given operation
|
||||
// ----------------------------------------------------------------------------
|
||||
{
|
||||
directory *dir = rt.variables(0);
|
||||
if (!dir)
|
||||
{
|
||||
rt.no_directory_error();
|
||||
return object::ERROR;
|
||||
}
|
||||
|
||||
object_g name = rt.stack(0);
|
||||
object_g value = cstval;
|
||||
if (!name || !value)
|
||||
return object::ERROR;
|
||||
object_g existing = directory::recall_all(name, true);
|
||||
|
||||
if (!existing)
|
||||
return object::ERROR;
|
||||
rt.stack(0, existing);
|
||||
|
@ -793,7 +786,7 @@ static object::result store_op(object::id op, object_p cstval)
|
|||
if (object::result res = cmd->evaluate())
|
||||
return res;
|
||||
value = rt.top();
|
||||
if (value && dir->store(name, value))
|
||||
if (value && directory::store_here(name, value))
|
||||
return object::OK;
|
||||
return object::ERROR;
|
||||
}
|
||||
|
@ -822,13 +815,6 @@ static object::result recall_op(object::id op)
|
|||
// Store with a given operation
|
||||
// ----------------------------------------------------------------------------
|
||||
{
|
||||
directory *dir = rt.variables(0);
|
||||
if (!dir)
|
||||
{
|
||||
rt.no_directory_error();
|
||||
return object::ERROR;
|
||||
}
|
||||
|
||||
object_g name = rt.stack(0);
|
||||
if (!name)
|
||||
return object::ERROR;
|
||||
|
|
|
@ -106,6 +106,11 @@ struct directory : list
|
|||
// Check if a name exists in the directory, return value ptr if it does
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
static bool store_here(object_p name, object_p value);
|
||||
// ------------------------------------------------------------------------
|
||||
// Store in the current directory, or fail if it does not exist
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
object_p lookup(object_p name) const;
|
||||
// ------------------------------------------------------------------------
|
||||
// Check if a name exists in the directory, return name ptr if it does
|
||||
|
@ -230,7 +235,7 @@ public:
|
|||
MENU_DECL(VariablesMenu);
|
||||
};
|
||||
|
||||
COMMAND_DECLARE_INSERT(VariablesMenuExecute,-1);
|
||||
COMMAND_DECLARE_INSERT(VariablesMenuExecute,~0);
|
||||
COMMAND_DECLARE_INSERT(VariablesMenuRecall,0);
|
||||
COMMAND_DECLARE_INSERT(VariablesMenuStore,1);
|
||||
|
||||
|
|
Loading…
Reference in a new issue