First step to derivedformula
This commit is contained in:
parent
f85e1888ab
commit
e4f79ccab6
5 changed files with 118 additions and 35 deletions
7
TODO.md
7
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.
|
||||
- [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
|
||||
- [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.
|
||||
|
|
@ -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<FormulaScreen> {
|
|||
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<FormulaScreen> {
|
|||
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<FormulaScreen> {
|
|||
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<FormulaScreen> {
|
|||
),
|
||||
),
|
||||
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<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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -272,7 +272,7 @@ class FormulaEvaluator {
|
|||
}
|
||||
|
||||
Number formulaSolver(
|
||||
Formula formula,
|
||||
FormulaInterface formulaInterface,
|
||||
String variableToSolve,
|
||||
Map<String, dynamic> 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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -148,6 +148,16 @@ abstract class FormulaInterface {
|
|||
Map<String, dynamic> 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<String> get tags => originalFormula.tags;
|
||||
|
||||
@override
|
||||
final Formula originalFormula;
|
||||
late final Formula originalFormula;
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toMap() => originalFormula.toMap();
|
||||
|
|
@ -182,14 +192,22 @@ class DerivedFormula implements FormulaInterface {
|
|||
late List<VariableSpec> _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<VariableSpec>.from(originalFormula.input).where((v) => v.name != outputName).toList();
|
||||
newInput.add(originalFormula.output);
|
||||
_input = newInput.toList(growable: false);
|
||||
|
|
|
|||
Loading…
Reference in a new issue