From 6f04921ef02643488595a70e5a5d397c9cb96707 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Gonz=C3=A1lez?= Date: Mon, 9 Mar 2026 18:32:39 +0100 Subject: [PATCH] Preparing DerivedFormula evaluation --- lib/ai/formula_screen.dart | 10 +- lib/formula_evaluator.dart | 4 +- lib/formula_models.dart | 295 +++++++++++----------------------- lib/set_utils.dart | 165 +++++++++++++++++++ test/formula_models_test.dart | 1 + 5 files changed, 271 insertions(+), 204 deletions(-) create mode 100644 lib/set_utils.dart diff --git a/lib/ai/formula_screen.dart b/lib/ai/formula_screen.dart index 21aed33..4a7c6b1 100644 --- a/lib/ai/formula_screen.dart +++ b/lib/ai/formula_screen.dart @@ -116,8 +116,14 @@ class _FormulaScreenState extends State { inputValues[input.name] = convertedValue; } - final evaluator = FormulaEvaluator(); - final result = evaluator.evaluate(formula, inputValues); + late final dynamic result; + if( formula is DerivedFormula) { + result = formulaSolver(formula, formula.output.name, inputValues,); + } + else { + final evaluator = FormulaEvaluator(); + result = evaluator.evaluate(formula, inputValues); + } // Convert output to selected unit if needed String? unit = formula.output.unit; diff --git a/lib/formula_evaluator.dart b/lib/formula_evaluator.dart index 492ffc1..a52073b 100644 --- a/lib/formula_evaluator.dart +++ b/lib/formula_evaluator.dart @@ -276,9 +276,9 @@ Number formulaSolver( String variableToSolve, Map fixedInputValues, { Number hint = 0, - Number step = 10, + Number step = 100, Number maxDelta = 0.01, - int maxTries = 100, + int maxTries = 1000, }) { if (variableToSolve == formula.output.name) { return FormulaEvaluator().evaluate(formula, fixedInputValues); diff --git a/lib/formula_models.dart b/lib/formula_models.dart index 22a8242..7d4af83 100644 --- a/lib/formula_models.dart +++ b/lib/formula_models.dart @@ -1,176 +1,15 @@ import 'package:d4rt/d4rt.dart'; import 'package:collection/collection.dart'; import 'package:d4rt_formulas/d4rt_formulas.dart'; +import 'package:d4rt_formulas/set_utils.dart'; import 'dart:math'; import 'package:uuid/uuid.dart'; typedef Number = double; -abstract class SetUtils { - static Object safeGet(Map map, String key) { - if (!map.containsKey(key)) { - throw ArgumentError("Key not found: $key -- $map"); - } - return map[key] ?? "Not possible!!!"; - } - - static String stringValue(Map map, String key) { - return safeGet(map, key).toString(); - } - - static List listValue(Map map, String key) { - return safeGet(map, key) as List; - } - - static Number numberValue(Map map, String key) { - return double.parse(stringValue(map, key)); - } - - /// Parses a d4rt array literal (containing maps and arrays) to a List - /// using d4rt - static List parseD4rtLiteral(String arrayStringLiteral) { - var d4rt = D4rt(); - final buffer = StringBuffer(); - buffer.write("main(){ return $arrayStringLiteral; }"); - final code = buffer.toString(); - - final List list = d4rt.execute(source: code); - - return list; - } - - /// Escapes special characters in a string for use in D4RT literals - @deprecated - static String escapeD4rtString(String input) { - return input - .replaceAll(r'\\', r'\\\\') // escape backslashes first - .replaceAll('\n', r'\\n') - .replaceAll('\r', r'\\r') - .replaceAll('\t', r'\\t') - .replaceAll('"', r'\"'); - } - - /// Parses corpus elements from an array string literal. - /// Determines if each element is a formula or a unit and converts accordingly. - static List parseCorpusElements(String arrayStringLiteral) { - final List elements = parseD4rtLiteral(arrayStringLiteral); - - final List result = []; - for (final element in elements) { - if (element is Map) { - if (element.containsKey('d4rtCode')) { - result.add(Formula.fromSet(element)); - } else - if (element.containsKey('name') && element.containsKey('symbol')) { - result.add(UnitSpec.fromSet(element)); - } else { - throw ArgumentError('Unknown element type: $element'); - } - } else { - throw ArgumentError('Element must be a Map: $element'); - } - } - - return result; - } - - /// Pretty prints a dynamic value (Set, Array, string or number) as a Dart literal. - /// Uses JSON-like formatting but for Dart language, with proper indentation. - static String prettyPrint(dynamic value, {int indent = 0}) { - if (value is String) { - return _prettyPrintString(value, indent); - } else if (value is num) { - return _prettyPrintNumber(value, indent); - } else if (value is Set) { - return _prettyPrintSet(value, indent); - } else if (value is List) { - return _prettyPrintArray(value, indent); - } else if (value is Map) { - return _prettyPrintMap(value, indent); - } else { - return value.toString(); - } - } - - /// Pretty prints a simple string, escaping special characters if needed. - static String _prettyPrintString(String s, int indent) { - // Check if the string needs raw string formatting (newlines, $, backslashes, quotes) - final needsRawString = s.contains('\n') || - s.contains(r'$') || - s.contains(r'\\') || - s.contains('"'); - - if (needsRawString) { - return _prettyPrintRawString(s, indent); - } - - // Simple string with escaped quotes - return '"${s.replaceAll('"', r'\"')}"'; - //' - } - - /// Pretty prints a number. - static String _prettyPrintNumber(num n, int indent) { - return n.toString(); - } - - /// Pretty prints a Set as a Dart set literal. - static String _prettyPrintSet(Set s, int indent) { - if (s.isEmpty) { - return '{}'; - } - - final indentStr = ' ' * indent; - final innerIndent = ' ' * (indent + 1); - - final elements = s.map((e) => '$innerIndent${prettyPrint(e, indent: indent + 1)}').join(',\n'); - return '{$elements\n$indentStr}'; - } - - /// Pretty prints an Array/List as a Dart list literal. - static String _prettyPrintArray(List a, int indent) { - if (a.isEmpty) { - return '[]'; - } - - final indentStr = ' ' * indent; - final innerIndent = ' ' * (indent + 1); - - final elements = a.map((e) => '$innerIndent${prettyPrint(e, indent: indent + 1)}').join(',\n'); - return '[\n$elements\n$indentStr]'; - } - - /// Pretty prints a Map as a Dart map literal. - static String _prettyPrintMap(Map m, int indent) { - if (m.isEmpty) { - return '{}'; - } - - final indentStr = ' ' * indent; - final innerIndent = ' ' * (indent + 1); - - final entries = m.entries.map((e) { - final key = prettyPrint(e.key, indent: indent + 1); - final value = prettyPrint(e.value, indent: indent + 1); - return '$innerIndent$key: $value'; - }).join(',\n'); - - return '{\n$entries\n$indentStr}'; - } - - /// Pretty prints a raw string (for strings containing newlines, $, backslashes, etc.) - /// Uses Dart's raw string syntax r"""...""" - static String _prettyPrintRawString(String s, int indent) { - // Escape triple quotes by replacing """ with ""\" - final escaped = s.replaceAll('"""', r'""\\"'); - return 'r"""$escaped"""'; - } -} - - /// Abstract base class for formula elements abstract class FormulaElement { - Map toMap(); + Map toMap(); String toStringLiteral() { final map = toMap(); @@ -198,7 +37,6 @@ class UnitSpec extends FormulaElement { }; } - UnitSpec({ required this.name, required this.baseUnit, @@ -220,12 +58,7 @@ class UnitSpec extends FormulaElement { if (theSet.containsKey("factor")) { Number factorFromUnitToBase = SetUtils.numberValue(theSet, "factor"); - return UnitSpec( - name: name, - baseUnit: baseUnit, - symbol: symbol, - factorFromUnitToBase: factorFromUnitToBase, - ); + return UnitSpec(name: name, baseUnit: baseUnit, symbol: symbol, factorFromUnitToBase: factorFromUnitToBase); } else if (theSet.containsKey("toBase")) { String codeFromBaseToUnit = SetUtils.stringValue(theSet, "fromBase"); String codeFromUnitToBase = SetUtils.stringValue(theSet, "toBase"); @@ -249,10 +82,9 @@ class UnitSpec extends FormulaElement { return units.toList(growable: false); } - } -class VariableSpec extends FormulaElement{ +class VariableSpec extends FormulaElement { final String name; final String? unit; final List? values; @@ -262,9 +94,9 @@ class VariableSpec extends FormulaElement{ return { 'name': name, if (unit != null) 'unit': unit, - if (values != null) 'values': List.from(values!,growable: false), + if (values != null) 'values': List.from(values!, growable: false), }; - } + } VariableSpec({required this.name, this.unit, this.values}) { validate(); @@ -294,21 +126,99 @@ class VariableSpec extends FormulaElement{ @override int get hashCode => Object.hash(unit, name, values != null ? const DeepCollectionEquality().hash(values!) : 0); - - } String _generateUuidV4() => Uuid().v4(); -class Formula extends FormulaElement { +abstract class FormulaInterface { + String get uuid; + + String get name; + + String? get description; + + List get input; + + VariableSpec get output; + + String get d4rtCode; + + List get tags; + + Map toMap(); + + Formula get originalFormula; +} + +class DerivedFormula implements FormulaInterface { + @override + String get uuid => originalFormula.uuid; + + @override + String get name => originalFormula.name; + + @override + String? get description => originalFormula.description; + + @override + List get input => _input; + + @override + VariableSpec get output => _output; + + @override + String get d4rtCode => "signal('no code for derived formula, use formulaSolver')"; + + @override + List get tags => originalFormula.tags; + + @override + final Formula originalFormula; + + @override + Map toMap() => originalFormula.toMap(); + + String outputName; + late List _input; + late VariableSpec _output; + + DerivedFormula({required this.outputName, required this.originalFormula}) { + + 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()}"); + } + + var newInput = List.from(originalFormula.input).where((v) => v.name != outputName).toList(); + newInput.add(originalFormula.output); + _input = newInput.toList(growable: false); + _output = originalFormula.input.firstWhere( + (v) => v.name == outputName, + orElse: () => throw ArgumentError("New output variable $outputName not found in original formula input"), + ); + } +} + +class Formula extends FormulaElement implements FormulaInterface { + @override final String uuid; + @override final String name; + @override final String? description; + @override final List input; + @override final VariableSpec output; + @override final String d4rtCode; + @override final List tags; + @override + Formula get originalFormula => this; + @override Map toMap() { return { @@ -335,9 +245,7 @@ class Formula extends FormulaElement { } void validate() { - if (name - .trim() - .isEmpty) { + if (name.trim().isEmpty) { throw ArgumentError('Formula name cannot be empty'); } } @@ -348,16 +256,12 @@ class Formula extends FormulaElement { @override bool operator ==(Object other) => - identical(this, other) || - other is Formula && - runtimeType == other.runtimeType && - uuid == other.uuid; + identical(this, other) || other is Formula && runtimeType == other.runtimeType && uuid == other.uuid; @override int get hashCode => uuid.hashCode; - List inputVarNames() => - input.map((v) => v.name).toList(growable: false); + List inputVarNames() => input.map((v) => v.name).toList(growable: false); factory Formula.fromStringLiteral(String setStringLiteral) { var d4rt = D4rt(); @@ -389,29 +293,21 @@ class Formula extends FormulaElement { if (allowed != null) { final types = allowed.map((v) => v.runtimeType).toSet(); if (types.length > 1) { - throw ArgumentError( - 'Allowed values must be all Strings or all Numbers'); + throw ArgumentError('Allowed values must be all Strings or all Numbers'); } - if (!types.contains(String) && !types.contains(double) && - !types.contains(int)) { + if (!types.contains(String) && !types.contains(double) && !types.contains(int)) { throw ArgumentError('Allowed values must be Strings or Numbers'); } } - return VariableSpec( - name: name, - unit: unit, - values: allowed?.toList(growable: false), - ); + return VariableSpec(name: name, unit: unit, values: allowed?.toList(growable: false)); } String? uuid = theSet['uuid'] as String?; String name = SetUtils.stringValue(theSet, "name"); String? description = theSet["description"] as String?; - List tags = (theSet["tags"] as List? ?? []).map((t) => - t.toString()).toList(); + List tags = (theSet["tags"] as List? ?? []).map((t) => t.toString()).toList(); final List inputSet = SetUtils.listValue(theSet, "input"); - List input = inputSet.map((v) => parseVar(v as Map)).toList( - growable: false); + List input = inputSet.map((v) => parseVar(v as Map)).toList(growable: false); Map outputSet = theSet['output'] as Map; VariableSpec output = parseVar(outputSet); String d4rtCode = SetUtils.stringValue(theSet, "d4rtCode"); @@ -427,4 +323,3 @@ class Formula extends FormulaElement { ); } } - diff --git a/lib/set_utils.dart b/lib/set_utils.dart new file mode 100644 index 0000000..2c2ca0b --- /dev/null +++ b/lib/set_utils.dart @@ -0,0 +1,165 @@ + +import 'package:d4rt/d4rt.dart'; + +import 'formula_models.dart'; + +abstract class SetUtils { + static Object safeGet(Map map, String key) { + if (!map.containsKey(key)) { + throw ArgumentError("Key not found: $key -- $map"); + } + return map[key] ?? "Not possible!!!"; + } + + static String stringValue(Map map, String key) { + return safeGet(map, key).toString(); + } + + static List listValue(Map map, String key) { + return safeGet(map, key) as List; + } + + static Number numberValue(Map map, String key) { + return double.parse(stringValue(map, key)); + } + + /// Parses a d4rt array literal (containing maps and arrays) to a List + /// using d4rt + static List parseD4rtLiteral(String arrayStringLiteral) { + var d4rt = D4rt(); + final buffer = StringBuffer(); + buffer.write("main(){ return $arrayStringLiteral; }"); + final code = buffer.toString(); + + final List list = d4rt.execute(source: code); + + return list; + } + + /// Escapes special characters in a string for use in D4RT literals + @deprecated + static String escapeD4rtString(String input) { + return input + .replaceAll(r'\\', r'\\\\') // escape backslashes first + .replaceAll('\n', r'\\n') + .replaceAll('\r', r'\\r') + .replaceAll('\t', r'\\t') + .replaceAll('"', r'\"'); + } + + /// Parses corpus elements from an array string literal. + /// Determines if each element is a formula or a unit and converts accordingly. + static List parseCorpusElements(String arrayStringLiteral) { + final List elements = parseD4rtLiteral(arrayStringLiteral); + + final List result = []; + for (final element in elements) { + if (element is Map) { + if (element.containsKey('d4rtCode')) { + result.add(Formula.fromSet(element)); + } else + if (element.containsKey('name') && element.containsKey('symbol')) { + result.add(UnitSpec.fromSet(element)); + } else { + throw ArgumentError('Unknown element type: $element'); + } + } else { + throw ArgumentError('Element must be a Map: $element'); + } + } + + return result; + } + + /// Pretty prints a dynamic value (Set, Array, string or number) as a Dart literal. + /// Uses JSON-like formatting but for Dart language, with proper indentation. + static String prettyPrint(dynamic value, {int indent = 0}) { + if (value is String) { + return _prettyPrintString(value, indent); + } else if (value is num) { + return _prettyPrintNumber(value, indent); + } else if (value is Set) { + return _prettyPrintSet(value, indent); + } else if (value is List) { + return _prettyPrintArray(value, indent); + } else if (value is Map) { + return _prettyPrintMap(value, indent); + } else { + return value.toString(); + } + } + + /// Pretty prints a simple string, escaping special characters if needed. + static String _prettyPrintString(String s, int indent) { + // Check if the string needs raw string formatting (newlines, $, backslashes, quotes) + final needsRawString = s.contains('\n') || + s.contains(r'$') || + s.contains(r'\\') || + s.contains('"'); + + if (needsRawString) { + return _prettyPrintRawString(s, indent); + } + + // Simple string with escaped quotes + return '"${s.replaceAll('"', r'\"')}"'; + //' + } + + /// Pretty prints a number. + static String _prettyPrintNumber(num n, int indent) { + return n.toString(); + } + + /// Pretty prints a Set as a Dart set literal. + static String _prettyPrintSet(Set s, int indent) { + if (s.isEmpty) { + return '{}'; + } + + final indentStr = ' ' * indent; + final innerIndent = ' ' * (indent + 1); + + final elements = s.map((e) => '$innerIndent${prettyPrint(e, indent: indent + 1)}').join(',\n'); + return '{$elements\n$indentStr}'; + } + + /// Pretty prints an Array/List as a Dart list literal. + static String _prettyPrintArray(List a, int indent) { + if (a.isEmpty) { + return '[]'; + } + + final indentStr = ' ' * indent; + final innerIndent = ' ' * (indent + 1); + + final elements = a.map((e) => '$innerIndent${prettyPrint(e, indent: indent + 1)}').join(',\n'); + return '[\n$elements\n$indentStr]'; + } + + /// Pretty prints a Map as a Dart map literal. + static String _prettyPrintMap(Map m, int indent) { + if (m.isEmpty) { + return '{}'; + } + + final indentStr = ' ' * indent; + final innerIndent = ' ' * (indent + 1); + + final entries = m.entries.map((e) { + final key = prettyPrint(e.key, indent: indent + 1); + final value = prettyPrint(e.value, indent: indent + 1); + return '$innerIndent$key: $value'; + }).join(',\n'); + + return '{\n$entries\n$indentStr}'; + } + + /// Pretty prints a raw string (for strings containing newlines, $, backslashes, etc.) + /// Uses Dart's raw string syntax r"""...""" + static String _prettyPrintRawString(String s, int indent) { + // Escape triple quotes by replacing """ with ""\" + final escaped = s.replaceAll('"""', r'""\\"'); + return 'r"""$escaped"""'; + } +} \ No newline at end of file diff --git a/test/formula_models_test.dart b/test/formula_models_test.dart index fb1ae67..52fd255 100644 --- a/test/formula_models_test.dart +++ b/test/formula_models_test.dart @@ -1,6 +1,7 @@ import 'package:d4rt_formulas/corpus.dart'; import 'package:d4rt_formulas/defaults/default_corpus.dart'; import 'package:d4rt_formulas/formula_evaluator.dart'; +import 'package:d4rt_formulas/set_utils.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:d4rt_formulas/formula_models.dart';