First step to derivedformula
This commit is contained in:
parent
f85e1888ab
commit
e4f79ccab6
5 changed files with 118 additions and 35 deletions
11
TODO.md
11
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 _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
|
||||||
- [ ] 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.
|
- [ ] 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';
|
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,25 +155,29 @@ class _FormulaScreenState extends State<FormulaScreen> {
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.edit),
|
icon: const Icon(Icons.edit),
|
||||||
onPressed: () {
|
onPressed: formula is DerivedFormula
|
||||||
Navigator.push(
|
? null
|
||||||
context,
|
: () {
|
||||||
MaterialPageRoute(
|
Navigator.push(
|
||||||
builder: (context) =>
|
context,
|
||||||
FormulaEditor(
|
MaterialPageRoute(
|
||||||
formula: formula,
|
builder: (context) =>
|
||||||
corpus: widget.corpus,
|
FormulaEditor(
|
||||||
onSave: (updatedFormula) {
|
formula: formula as Formula,
|
||||||
widget.onSave?.call(updatedFormula);
|
corpus: widget.corpus,
|
||||||
setState(() {
|
onSave: (updatedFormula) {
|
||||||
formula = updatedFormula;
|
widget.onSave?.call(updatedFormula);
|
||||||
});
|
setState(() {
|
||||||
},
|
formula = updatedFormula;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
);
|
},
|
||||||
},
|
tooltip: formula is DerivedFormula
|
||||||
tooltip: 'Edit Formula',
|
? '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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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") {
|
DerivedFormula({required this.outputName, required FormulaInterface originalFormula}) {
|
||||||
|
|
||||||
|
this.originalFormula = FormulaInterface.getRootFormula(originalFormula);
|
||||||
|
|
||||||
|
if( !isDerivable(this.originalFormula) ){
|
||||||
throw ArgumentError(
|
throw ArgumentError(
|
||||||
"Derived formulas are not supported for formulas with string inputs, because we can't solve for them. Original formula: ${originalFormula
|
"Derived formulas are not supported for formulas with string inputs, because we can't solve for them. Original formula: ${originalFormula.toString()}");
|
||||||
.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);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue