First step to derivedformula

This commit is contained in:
Álvaro González 2026-03-10 16:47:03 +01:00
parent f85e1888ab
commit e4f79ccab6
5 changed files with 118 additions and 35 deletions

View file

@ -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 _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] 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. - [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. - [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. - 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 - The DerivedFormula will then be displayed in the FormulaScreen
- [ ] If the Formula displayed in FormulaScreen is a DerivedFormula, the edit button will be disabled - [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. - [ ] 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.

View file

@ -12,7 +12,7 @@ import 'unit_dropdown.dart';
import 'formula_editor.dart'; import 'formula_editor.dart';
class FormulaScreen extends StatefulWidget { class FormulaScreen extends StatefulWidget {
final Formula initialFormula; final FormulaInterface initialFormula;
final Corpus corpus; final Corpus corpus;
final Function(Formula)? onSave; // Callback when formula is saved final Function(Formula)? onSave; // Callback when formula is saved
@ -31,13 +31,13 @@ class _FormulaScreenState extends State<FormulaScreen> {
String? _result; String? _result;
String? _selectedOutputUnit; String? _selectedOutputUnit;
bool _isDescriptionExpanded = false; // Track description expansion state 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 String? _errorMessage; // Track error message for expansion tile
bool _isErrorExpanded = false; // Track error expansion state bool _isErrorExpanded = false; // Track error expansion state
set formula(Formula newFormula) { set formula(FormulaInterface newFormula) {
_formula = newFormula; _formula = newFormula;
// Initialize controllers and units with listeners // Initialize controllers and units with listeners
@ -121,8 +121,9 @@ class _FormulaScreenState extends State<FormulaScreen> {
result = formulaSolver(formula, formula.output.name, inputValues,); result = formulaSolver(formula, formula.output.name, inputValues,);
} }
else { else {
print( "TODO: MAYBE ONLY FORMULASOLVER IS NECCESSARY");
final evaluator = FormulaEvaluator(); final evaluator = FormulaEvaluator();
result = evaluator.evaluate(formula, inputValues); result = evaluator.evaluate(formula as Formula, inputValues);
} }
// Convert output to selected unit if needed // Convert output to selected unit if needed
@ -154,13 +155,15 @@ class _FormulaScreenState extends State<FormulaScreen> {
actions: [ actions: [
IconButton( IconButton(
icon: const Icon(Icons.edit), icon: const Icon(Icons.edit),
onPressed: () { onPressed: formula is DerivedFormula
? null
: () {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => builder: (context) =>
FormulaEditor( FormulaEditor(
formula: formula, formula: formula as Formula,
corpus: widget.corpus, corpus: widget.corpus,
onSave: (updatedFormula) { onSave: (updatedFormula) {
widget.onSave?.call(updatedFormula); widget.onSave?.call(updatedFormula);
@ -172,7 +175,9 @@ class _FormulaScreenState extends State<FormulaScreen> {
), ),
); );
}, },
tooltip: 'Edit Formula', tooltip: formula is DerivedFormula
? 'Cannot edit derived formula'
: 'Edit Formula',
), ),
], ],
), ),
@ -426,7 +431,15 @@ class _FormulaScreenState extends State<FormulaScreen> {
), ),
), ),
const SizedBox(width: 8), 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( UnitDropdown(
corpus: widget.corpus, corpus: widget.corpus,
variable: variable, variable: variable,
@ -442,4 +455,39 @@ class _FormulaScreenState extends State<FormulaScreen> {
), ),
); );
} }
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);
}
}
} }

View file

@ -9,6 +9,17 @@ class ErrorHandler {
/// Callback function to handle errors - can be overridden for custom behavior /// Callback function to handle errors - can be overridden for custom behavior
void Function(Object error, [StackTrace? stackTrace])? onError; 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 /// Notifies the error handler of an exception
void notify(Object error, [StackTrace? stackTrace]) { void notify(Object error, [StackTrace? stackTrace]) {
// Print the exception to stdout as requested // Print the exception to stdout as requested

View file

@ -272,7 +272,7 @@ class FormulaEvaluator {
} }
Number formulaSolver( Number formulaSolver(
Formula formula, FormulaInterface formulaInterface,
String variableToSolve, String variableToSolve,
Map<String, dynamic> fixedInputValues, { Map<String, dynamic> fixedInputValues, {
Number hint = 0, Number hint = 0,
@ -280,6 +280,9 @@ Number formulaSolver(
Number maxDelta = 0.01, Number maxDelta = 0.01,
int maxTries = 1000, int maxTries = 1000,
}) { }) {
var formula = FormulaInterface.getRootFormula(formulaInterface);
if (variableToSolve == formula.output.name) { if (variableToSolve == formula.output.name) {
return FormulaEvaluator().evaluate(formula, fixedInputValues); return FormulaEvaluator().evaluate(formula, fixedInputValues);
} }

View file

@ -148,6 +148,16 @@ abstract class FormulaInterface {
Map<String, dynamic> toMap(); Map<String, dynamic> toMap();
Formula get originalFormula; 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 { class DerivedFormula implements FormulaInterface {
@ -155,7 +165,7 @@ class DerivedFormula implements FormulaInterface {
String get uuid => originalFormula.uuid; String get uuid => originalFormula.uuid;
@override @override
String get name => originalFormula.name; String get name => "${originalFormula.name} (Solving for ${_output.name})";
@override @override
String? get description => originalFormula.description; String? get description => originalFormula.description;
@ -173,7 +183,7 @@ class DerivedFormula implements FormulaInterface {
List<String> get tags => originalFormula.tags; List<String> get tags => originalFormula.tags;
@override @override
final Formula originalFormula; late final Formula originalFormula;
@override @override
Map<String, dynamic> toMap() => originalFormula.toMap(); Map<String, dynamic> toMap() => originalFormula.toMap();
@ -182,14 +192,22 @@ class DerivedFormula implements FormulaInterface {
late List<VariableSpec> _input; late List<VariableSpec> _input;
late VariableSpec _output; 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") {
throw ArgumentError(
"Derived formulas are not supported for formulas with string inputs, because we can't solve for them. Original formula: ${originalFormula
.toString()}");
} }
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()}");
}
_init();
}
void _init(){
var newInput = List<VariableSpec>.from(originalFormula.input).where((v) => v.name != outputName).toList(); var newInput = List<VariableSpec>.from(originalFormula.input).where((v) => v.name != outputName).toList();
newInput.add(originalFormula.output); newInput.add(originalFormula.output);
_input = newInput.toList(growable: false); _input = newInput.toList(growable: false);