From e4f79ccab658adf1453a4f034f2a50408ed8096b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Gonz=C3=A1lez?= Date: Tue, 10 Mar 2026 16:47:03 +0100 Subject: [PATCH] First step to derivedformula --- TODO.md | 11 +++-- lib/ai/formula_screen.dart | 96 ++++++++++++++++++++++++++++---------- lib/error_handler.dart | 11 +++++ lib/formula_evaluator.dart | 5 +- lib/formula_models.dart | 30 +++++++++--- 5 files changed, 118 insertions(+), 35 deletions(-) diff --git a/TODO.md b/TODO.md index 15717b7..41b07b9 100644 --- a/TODO.md +++ b/TODO.md @@ -60,8 +60,11 @@ - [R] When _FormulaScreenState._evaluateFormula() detect an error, instead of show an SnackBar, show a ExpansionTile with "⚠️ There were an error. Show details..." with the details of the exception. The ExpansionTile will be invisible if there is no error. - [R] When FormulaEditor._save formula, ensure formula is updated in the initial FormulaList - [R] In FormulaEditor, add a button to "Save as copy", additional to the existing button "Save". It doesnt matter if the copy has the same name as the original formula, since they are identified by a internal UUID. -- [ ] Add a button for each input variable in FormulaScreen. - - This button will create a DerivedFormula, with the input variable as output, and the rest of the input variables as inputs. +- [R] Add a button for each input variable in FormulaScreen. + - This button will create a DerivedFormula, with the input variable as output, and the rest of the input variables as inputs. - The DerivedFormula will then be displayed in the FormulaScreen -- [ ] If the Formula displayed in FormulaScreen is a DerivedFormula, the edit button will be disabled -- [ ] Add a uuid column to the table or FormulaElements, so it is not necessary to load all the formulas to find a formula by uuid. This will improve performance when updating and deleting. \ No newline at end of file +- [R] If the Formula displayed in FormulaScreen is a DerivedFormula, the edit button will be disabled +- [ ] When a formula is derived in FormulaScreen, the new FormulaScreen is not pushed in navigator, it replacles the current FormulaScreen +- [ ] In FormulaScreen, a Formula cant be derived if DerivedFormula.isDerivable() returns false +- [ ] The algorithm of formulaSolver should be https://en.wikipedia.org/wiki/Newton%27s_method +- [ ] Add a uuid column to the table or FormulaElements, so it is not necessary to load all the formulas to find a formula by uuid. This will improve performance when updating and deleting. diff --git a/lib/ai/formula_screen.dart b/lib/ai/formula_screen.dart index 4a7c6b1..a600de0 100644 --- a/lib/ai/formula_screen.dart +++ b/lib/ai/formula_screen.dart @@ -12,7 +12,7 @@ import 'unit_dropdown.dart'; import 'formula_editor.dart'; class FormulaScreen extends StatefulWidget { - final Formula initialFormula; + final FormulaInterface initialFormula; final Corpus corpus; final Function(Formula)? onSave; // Callback when formula is saved @@ -31,13 +31,13 @@ class _FormulaScreenState extends State { String? _result; String? _selectedOutputUnit; bool _isDescriptionExpanded = false; // Track description expansion state - late Formula _formula; + late FormulaInterface _formula; - Formula get formula => _formula; + FormulaInterface get formula => _formula; String? _errorMessage; // Track error message for expansion tile bool _isErrorExpanded = false; // Track error expansion state - set formula(Formula newFormula) { + set formula(FormulaInterface newFormula) { _formula = newFormula; // Initialize controllers and units with listeners @@ -121,8 +121,9 @@ class _FormulaScreenState extends State { result = formulaSolver(formula, formula.output.name, inputValues,); } else { + print( "TODO: MAYBE ONLY FORMULASOLVER IS NECCESSARY"); final evaluator = FormulaEvaluator(); - result = evaluator.evaluate(formula, inputValues); + result = evaluator.evaluate(formula as Formula, inputValues); } // Convert output to selected unit if needed @@ -154,25 +155,29 @@ class _FormulaScreenState extends State { actions: [ IconButton( icon: const Icon(Icons.edit), - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => - FormulaEditor( - formula: formula, - corpus: widget.corpus, - onSave: (updatedFormula) { - widget.onSave?.call(updatedFormula); - setState(() { - formula = updatedFormula; - }); - }, + onPressed: formula is DerivedFormula + ? null + : () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + FormulaEditor( + formula: formula as Formula, + corpus: widget.corpus, + onSave: (updatedFormula) { + widget.onSave?.call(updatedFormula); + setState(() { + formula = updatedFormula; + }); + }, + ), ), - ), - ); - }, - tooltip: 'Edit Formula', + ); + }, + tooltip: formula is DerivedFormula + ? 'Cannot edit derived formula' + : 'Edit Formula', ), ], ), @@ -426,7 +431,15 @@ class _FormulaScreenState extends State { ), ), const SizedBox(width: 8), - if (variable.unit != null) + if (variable.unit != null && !isCategorical) + IconButton( + icon: const Icon(Icons.swap_horiz), + tooltip: 'Solve for ${variable.name}', + onPressed: () { + _solveForVariable(variable); + }, + ), + if (variable.unit != null && !isCategorical) UnitDropdown( corpus: widget.corpus, variable: variable, @@ -442,4 +455,39 @@ class _FormulaScreenState extends State { ), ); } + + void _solveForVariable(VariableSpec variable) { + // Check if the formula is already a DerivedFormula + if (formula is DerivedFormula) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Cannot create derived formula from another derived formula'), + duration: Duration(seconds: 2), + ), + ); + return; + } + + try { + // Create a DerivedFormula with this input variable as output + final derivedFormula = DerivedFormula( + outputName: variable.name, + originalFormula: formula, + ); + + // Navigate to the new DerivedFormula screen + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => FormulaScreen( + formula: derivedFormula, + corpus: widget.corpus, + onSave: widget.onSave, + ), + ), + ); + } catch (e, st) { + errorHandler.notify(e,st); + } + } } diff --git a/lib/error_handler.dart b/lib/error_handler.dart index 8874bda..78b340c 100644 --- a/lib/error_handler.dart +++ b/lib/error_handler.dart @@ -9,6 +9,17 @@ class ErrorHandler { /// Callback function to handle errors - can be overridden for custom behavior void Function(Object error, [StackTrace? stackTrace])? onError; + + // TODO: SET A DEFAULT ONERROR LIKE + // ScaffoldMessenger.of(context).showSnackBar( + // SnackBar( + // content: Text('Error creating derived formula: $e'), + // duration: const Duration(seconds: 3), + // ), + // ); + // } + + /// Notifies the error handler of an exception void notify(Object error, [StackTrace? stackTrace]) { // Print the exception to stdout as requested diff --git a/lib/formula_evaluator.dart b/lib/formula_evaluator.dart index a52073b..9b45f2f 100644 --- a/lib/formula_evaluator.dart +++ b/lib/formula_evaluator.dart @@ -272,7 +272,7 @@ class FormulaEvaluator { } Number formulaSolver( - Formula formula, + FormulaInterface formulaInterface, String variableToSolve, Map fixedInputValues, { Number hint = 0, @@ -280,6 +280,9 @@ Number formulaSolver( Number maxDelta = 0.01, int maxTries = 1000, }) { + + var formula = FormulaInterface.getRootFormula(formulaInterface); + if (variableToSolve == formula.output.name) { return FormulaEvaluator().evaluate(formula, fixedInputValues); } diff --git a/lib/formula_models.dart b/lib/formula_models.dart index 7d4af83..c82f39b 100644 --- a/lib/formula_models.dart +++ b/lib/formula_models.dart @@ -148,6 +148,16 @@ abstract class FormulaInterface { Map toMap(); Formula get originalFormula; + + static Formula getRootFormula( FormulaInterface fi ){ + if( fi is DerivedFormula ){ + return getRootFormula(fi.originalFormula); + } + if( fi is Formula ){ + return fi as Formula; + } + throw ArgumentError("Is not a known Formula subclass: ${fi} ${fi.runtimeType}"); + } } class DerivedFormula implements FormulaInterface { @@ -155,7 +165,7 @@ class DerivedFormula implements FormulaInterface { String get uuid => originalFormula.uuid; @override - String get name => originalFormula.name; + String get name => "${originalFormula.name} (Solving for ${_output.name})"; @override String? get description => originalFormula.description; @@ -173,7 +183,7 @@ class DerivedFormula implements FormulaInterface { List get tags => originalFormula.tags; @override - final Formula originalFormula; + late final Formula originalFormula; @override Map toMap() => originalFormula.toMap(); @@ -182,14 +192,22 @@ class DerivedFormula implements FormulaInterface { late List _input; late VariableSpec _output; - DerivedFormula({required this.outputName, required this.originalFormula}) { + static bool isDerivable(Formula f){ + return f.input.every( (vs) => vs.unit != "string") && f.output.unit != "string"; + } - if( originalFormula.input.any( (vs) => vs.unit == "string") || originalFormula.output.unit == "string") { + DerivedFormula({required this.outputName, required FormulaInterface originalFormula}) { + + this.originalFormula = FormulaInterface.getRootFormula(originalFormula); + + if( !isDerivable(this.originalFormula) ){ throw ArgumentError( - "Derived formulas are not supported for formulas with string inputs, because we can't solve for them. Original formula: ${originalFormula - .toString()}"); + "Derived formulas are not supported for formulas with string inputs, because we can't solve for them. Original formula: ${originalFormula.toString()}"); } + _init(); + } + void _init(){ var newInput = List.from(originalFormula.input).where((v) => v.name != outputName).toList(); newInput.add(originalFormula.output); _input = newInput.toList(growable: false);