Merge branch 'master' of ssh://codeberg.org/alvarogonzalezsotillo/d4rt_formulas
This commit is contained in:
commit
22e5590c88
17 changed files with 506 additions and 266 deletions
10
TODO.md
10
TODO.md
|
|
@ -47,6 +47,16 @@
|
|||
- [R] There is one row for the ouput variable, similar to the row for the input variable
|
||||
- [R] d4rtCode is a text area with dart syntax highligthing
|
||||
- [R] At the botton, a button allows to test the edited Formula, launching a FormulaScreen
|
||||
- [X] Create SetUtils.prettyPrint(): receives a dynamic Set, Array, string or number. Convert to a dart representation of than value (a set/array literal), json-like, but for dart language. Do it recursivelly on local functions to that method:
|
||||
- _prettyPrintString(String s, int indent): Only for simple strings
|
||||
- _prettyPrintNumber(Number n, int indent)
|
||||
- _prettyPrintSet(Set s, int indent)
|
||||
- _prettyPrintArray(dynamic[] a, int indent)
|
||||
- _prettyPrintRawString(String s, int indent): Use _prettyPrintRawString when the string contains newlines, $, backlash...
|
||||
- [X] Add a field to Formula: UUID.
|
||||
- A constructor without UUID will generate a new random UUID. A constructor with UUID will use the provided UUID.
|
||||
- The field should be used in database and everywhere instead of the name. The name is not unique anymore, but the UUID is.
|
||||
- This will be used to identify formulas, instead of the name. This way, we can have formulas with the same name but different UUIDs. The name is not unique anymore. Corpus will be a list of UUIDs, instead of a list of formulas. The corpus.getFormula() method will return the first formula with that name.
|
||||
- [ ] 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
|
||||
- [ ] Refresh FormulaList each time it gets focus, so formulas are updated from corpus
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ Calculates the magnitude of the electrostatic force between two point charges.
|
|||
|
||||
Formula: $F = k \dfrac{q_1 q_2}{r^2}$ where $k = 8.9875517923\times10^9\ \mathrm{N\,m^2/C^2}$.
|
||||
|
||||
Inputs: `q1`, `q2` in coulombs; `r` in meters.
|
||||
Inputs: $q_1$, $q_2$ in coulombs; $r$ in meters.
|
||||
Output: Force `F` in newtons (N).""",
|
||||
"input": [
|
||||
{"name": "q1", "unit": "coulomb"},
|
||||
|
|
|
|||
|
|
@ -1,17 +0,0 @@
|
|||
import 'package:d4rt/d4rt.dart';
|
||||
|
||||
void main() {
|
||||
final code = '''
|
||||
int fib(int n) {
|
||||
if (n <= 1) return n;
|
||||
return fib(n - 1) + fib(n - 2);
|
||||
}
|
||||
main() {
|
||||
return fib(6);
|
||||
}
|
||||
''';
|
||||
|
||||
final interpreter = D4rt();
|
||||
final result = interpreter.execute(source: code);
|
||||
print('Result: $result'); // Result: 8
|
||||
}
|
||||
|
|
@ -51,16 +51,15 @@ class _FormulaListState extends State<FormulaList> {
|
|||
}).toList();
|
||||
}
|
||||
|
||||
String _formulaAndDependenciesToStringLiteral(Formula formula) {
|
||||
// Get the formula and its dependencies
|
||||
final dependencies = widget.corpus.withDependencies(formula);
|
||||
return SetUtils.prettyPrint(dependencies.map((f) => f.toMap()).toList());
|
||||
}
|
||||
|
||||
void _shareFormula(Formula formula) async {
|
||||
try {
|
||||
// Get the formula and its dependencies
|
||||
final dependencies = widget.corpus.withDependencies(formula);
|
||||
|
||||
// Convert each dependency to its string literal representation
|
||||
final literals = dependencies.map((element) => element.toStringLiteral()).toList();
|
||||
|
||||
// Create an array string literal containing all the elements
|
||||
final exportString = '[${literals.join(', ')}]';
|
||||
final exportString = _formulaAndDependenciesToStringLiteral(formula);
|
||||
|
||||
// Share the string
|
||||
await share_plus.SharePlus.instance.share(
|
||||
|
|
@ -93,14 +92,7 @@ class _FormulaListState extends State<FormulaList> {
|
|||
|
||||
void _copyFormula(Formula formula) async {
|
||||
try {
|
||||
// Get the formula and its dependencies
|
||||
final dependencies = widget.corpus.withDependencies(formula);
|
||||
|
||||
// Convert each dependency to its string literal representation
|
||||
final literals = dependencies.map((element) => element.toStringLiteral()).toList();
|
||||
|
||||
// Create an array string literal containing all the elements
|
||||
final exportString = '[${literals.join(', ')}]';
|
||||
final exportString = _formulaAndDependenciesToStringLiteral(formula);
|
||||
|
||||
// Copy to clipboard
|
||||
await Clipboard.setData(ClipboardData(text: exportString));
|
||||
|
|
|
|||
|
|
@ -63,10 +63,10 @@ class D4rtEditingController extends TextEditingController {
|
|||
if( _validateAsD4rtExpression(text) && _lastValue is StringResult ){
|
||||
return true;
|
||||
}
|
||||
if( _validateAsD4rtExpression('"' + text + '"') && _lastValue is StringResult ){
|
||||
if( _validateAsD4rtExpression('"$text"') && _lastValue is StringResult ){
|
||||
return true;
|
||||
}
|
||||
if( _validateAsD4rtExpression("'" + text + "'") && _lastValue is StringResult ){
|
||||
if( _validateAsD4rtExpression("'$text'") && _lastValue is StringResult ){
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -25,12 +25,13 @@ class Multimap<K, V> extends DelegatingMap<K, List<V>> {
|
|||
|
||||
class Corpus{
|
||||
final Multimap<String, Formula> _tags = Multimap.create();
|
||||
// Map formulas by uuid
|
||||
final Map<String, Formula> _allFormulas = {};
|
||||
|
||||
void loadFormulas(List<Formula> formulas, {bool replaceOnDuplicates = true, bool checkUnits = true}) {
|
||||
for (final formula in formulas) {
|
||||
if (!replaceOnDuplicates && _allFormulas.containsKey(formula.name)) {
|
||||
throw ArgumentError("Duplicate formula:$formula");
|
||||
if (!replaceOnDuplicates && _allFormulas.containsKey(formula.uuid)) {
|
||||
throw ArgumentError("Duplicate formula:${formula}");
|
||||
}
|
||||
|
||||
if( checkUnits ){
|
||||
|
|
@ -41,7 +42,7 @@ class Corpus{
|
|||
}
|
||||
}
|
||||
|
||||
_allFormulas[formula.name] = formula;
|
||||
_allFormulas[formula.uuid] = formula;
|
||||
for( final tag in formula.tags ){
|
||||
_tags[tag]?.add(formula);
|
||||
}
|
||||
|
|
@ -59,8 +60,18 @@ class Corpus{
|
|||
return _allFormulas.values.toList(growable:false);
|
||||
}
|
||||
|
||||
/// Returns first formula with the given name (preserves old API semantics).
|
||||
Formula? getFormula(String name) {
|
||||
return _allFormulas.get(name);
|
||||
try {
|
||||
return _allFormulas.values.firstWhere((f) => f.name == name);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns formula by uuid
|
||||
Formula? getFormulaByUuid(String uuid) {
|
||||
return _allFormulas[uuid];
|
||||
}
|
||||
|
||||
/// Updates a formula in the corpus
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import '../formula_models.dart';
|
||||
import 'corpus_database_interface.dart';
|
||||
import 'formulas_database.dart';
|
||||
import 'package:d4rt_formulas/formula_models.dart' as models;
|
||||
|
|
@ -11,7 +12,7 @@ extension CorpusDatabaseExtension on FormulasDatabase {
|
|||
|
||||
for (final element in elements) {
|
||||
try {
|
||||
final parsed = models.parseCorpusElements('[${element.elementText}]');
|
||||
final parsed = SetUtils.parseCorpusElements('[${element.elementText}]');
|
||||
print("PARSED:$element");
|
||||
parsedElements.addAll(parsed);
|
||||
} catch (e) {
|
||||
|
|
|
|||
|
|
@ -7,11 +7,6 @@ import 'formula_models.dart';
|
|||
import 'error_handler.dart';
|
||||
import 'd4rt_bridge.dart';
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// Exception thrown when formula evaluation fails
|
||||
class FormulaEvaluationException implements Exception {
|
||||
final String message;
|
||||
|
|
@ -20,26 +15,30 @@ class FormulaEvaluationException implements Exception {
|
|||
const FormulaEvaluationException(this.message, [this.cause]);
|
||||
|
||||
@override
|
||||
String toString() => 'FormulaEvaluationException: $message'
|
||||
String toString() =>
|
||||
'FormulaEvaluationException: $message'
|
||||
'${cause != null ? ' (caused by: $cause)' : ''}';
|
||||
}
|
||||
|
||||
class MyMath{
|
||||
class MyMath {
|
||||
static Number myLog(Number x) => Math.log(x);
|
||||
static Number myPow(Number b, Number e) => Math.pow(b,e) as Number;
|
||||
|
||||
static Number myPow(Number b, Number e) => Math.pow(b, e) as Number;
|
||||
}
|
||||
|
||||
class FormulaResult{
|
||||
class FormulaResult {
|
||||
const FormulaResult();
|
||||
}
|
||||
|
||||
class StringResult extends FormulaResult{
|
||||
class StringResult extends FormulaResult {
|
||||
final String value;
|
||||
|
||||
const StringResult(this.value);
|
||||
}
|
||||
|
||||
class NumberResult extends FormulaResult{
|
||||
class NumberResult extends FormulaResult {
|
||||
final Number value;
|
||||
|
||||
const NumberResult(this.value);
|
||||
}
|
||||
|
||||
|
|
@ -48,31 +47,31 @@ class FormulaEvaluator {
|
|||
|
||||
static D4rt createDefaultInterpreter() => D4rt();
|
||||
|
||||
FormulaEvaluator([D4rt? interpreter]) : _interpreter = interpreter ?? createDefaultInterpreter(){
|
||||
FormulaEvaluator([D4rt? interpreter]) : _interpreter = interpreter ?? createDefaultInterpreter() {
|
||||
prepareInterpreter(_interpreter);
|
||||
}
|
||||
|
||||
static Number _getNumberValueOf(String s){
|
||||
static Number _getNumberValueOf(String s) {
|
||||
return double.parse(s);
|
||||
}
|
||||
|
||||
static void prepareInterpreter(D4rt interpreter){
|
||||
static void prepareInterpreter(D4rt interpreter) {
|
||||
final myMathDefinition = BridgedClass(
|
||||
nativeType: MyMath,
|
||||
name: 'MyMath',
|
||||
staticMethods: {
|
||||
'myPow': (visitor, positionalArgs, namedArgs) {
|
||||
final Number base = _getNumberValueOf( positionalArgs[0].toString() );
|
||||
final Number exp = _getNumberValueOf( positionalArgs[1].toString() );
|
||||
return MyMath.myPow(base,exp);
|
||||
final Number base = _getNumberValueOf(positionalArgs[0].toString());
|
||||
final Number exp = _getNumberValueOf(positionalArgs[1].toString());
|
||||
return MyMath.myPow(base, exp);
|
||||
},
|
||||
'myLog': (visitor, positionalArgs, namedArgs) {
|
||||
final Number x = _getNumberValueOf( positionalArgs[0].toString() );
|
||||
final Number x = _getNumberValueOf(positionalArgs[0].toString());
|
||||
return MyMath.myLog(x);
|
||||
},
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
interpreter.registerBridgedClass(myMathDefinition, "package:d4rt_formulas.dart");
|
||||
registerD4rtBridgeBridges(interpreter);
|
||||
}
|
||||
|
|
@ -80,7 +79,8 @@ class FormulaEvaluator {
|
|||
static FormulaResult evaluateExpression(String code, [D4rt? interpreter]) {
|
||||
final d4rtInterpreter = interpreter ?? createDefaultInterpreter();
|
||||
prepareInterpreter(d4rtInterpreter);
|
||||
final d4rtCode = """
|
||||
final d4rtCode =
|
||||
"""
|
||||
$preamble
|
||||
main()
|
||||
{
|
||||
|
|
@ -90,7 +90,7 @@ class FormulaEvaluator {
|
|||
}""";
|
||||
print("evaluateExpression:\n$d4rtCode");
|
||||
final result = d4rtInterpreter.execute(source: d4rtCode);
|
||||
switch ( result ){
|
||||
switch (result) {
|
||||
case int value:
|
||||
return NumberResult(value.toDouble());
|
||||
case Number value:
|
||||
|
|
@ -98,32 +98,26 @@ class FormulaEvaluator {
|
|||
case String value:
|
||||
return StringResult(value);
|
||||
default:
|
||||
throw FormulaEvaluationException( "Unexpected result type: ${result.runtimeType} -- $result" );
|
||||
throw FormulaEvaluationException("Unexpected result type: ${result.runtimeType} -- $result");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
dynamic evaluate(Formula formula, Map<String, dynamic> inputValues) {
|
||||
_validateInputValues(formula, inputValues);
|
||||
final completeSource = _buildCompleteSource(formula, inputValues);
|
||||
try {
|
||||
final result = _interpreter.execute(source: completeSource);
|
||||
return result;
|
||||
}
|
||||
catch (e, stack) {
|
||||
} catch (e, stack) {
|
||||
// SPECIAL CASE: If the error message starts with signalMagicString, treat it as a signal message and return it instead of throwing an exception
|
||||
// SEE signal() function in the generated d4rt code above for how this is used
|
||||
print( "#######################");
|
||||
if(e.toString().contains(signalMagicString)){
|
||||
print( "***********************");
|
||||
if (e.toString().contains(signalMagicString)) {
|
||||
final signalMessage = e.toString().split(signalMagicString).last.trim();
|
||||
return signalMessage;
|
||||
}
|
||||
|
||||
errorHandler.notify("$e\n$completeSource", stack);
|
||||
throw FormulaEvaluationException(
|
||||
'Error evaluating formula "${formula.name}": $e',
|
||||
e,
|
||||
);
|
||||
throw FormulaEvaluationException('Error evaluating formula "${formula.name}": $e', e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -152,7 +146,7 @@ class FormulaEvaluator {
|
|||
// Convert input value to string for comparison since allowed values are stored as strings
|
||||
final inputValueAsString = inputValue.toString();
|
||||
final containsValue = values.any((allowedValue) => allowedValue.toString() == inputValueAsString);
|
||||
|
||||
|
||||
if (!containsValue) {
|
||||
throw FormulaEvaluationException(
|
||||
'Invalid value for variable "${vs.name}" in formula "${formula.name}". '
|
||||
|
|
@ -164,15 +158,14 @@ class FormulaEvaluator {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
List<String> getInputVariableOrder(Formula formula) {
|
||||
return formula.inputVarNames()..sort();
|
||||
}
|
||||
|
||||
static final String signalMagicString = "###";
|
||||
|
||||
static final String preamble = """
|
||||
static final String preamble =
|
||||
"""
|
||||
import 'dart:math';
|
||||
import "package:d4rt_formulas.dart";
|
||||
import "package:formulas/runtime_bridge.dart";
|
||||
|
|
@ -181,7 +174,7 @@ class FormulaEvaluator {
|
|||
|
||||
""";
|
||||
|
||||
static const reservedVariableNames = { "variableValues", "indexOf", "variableAllowedValues"} ;
|
||||
static const reservedVariableNames = {"variableValues", "indexOf", "variableAllowedValues"};
|
||||
|
||||
String _buildCompleteSource(Formula formula, Map<String, dynamic> inputValues) {
|
||||
final buffer = StringBuffer();
|
||||
|
|
@ -190,14 +183,12 @@ class FormulaEvaluator {
|
|||
$preamble
|
||||
main()
|
||||
{
|
||||
"""
|
||||
);
|
||||
|
||||
""");
|
||||
|
||||
for (final entry in inputValues.entries) {
|
||||
final varName = entry.key;
|
||||
final value = entry.value;
|
||||
|
||||
|
||||
if (value is String) {
|
||||
final escapedValue = value.replaceAll('"', '\\"');
|
||||
buffer.writeln("""
|
||||
|
|
@ -276,6 +267,124 @@ class FormulaEvaluator {
|
|||
}
|
||||
""");
|
||||
|
||||
return buffer.toString();
|
||||
}
|
||||
}
|
||||
return buffer.toString();
|
||||
}
|
||||
}
|
||||
|
||||
Number formulaSolver(
|
||||
Formula formula,
|
||||
String variableToSolve,
|
||||
Map<String, dynamic> fixedInputValues, {
|
||||
Number hint = 0,
|
||||
Number step = 10,
|
||||
Number maxDelta = 0.01,
|
||||
int maxTries = 100,
|
||||
}) {
|
||||
if (variableToSolve == formula.output.name) {
|
||||
return FormulaEvaluator().evaluate(formula, fixedInputValues);
|
||||
}
|
||||
|
||||
if (!formula.inputVarNames().contains(variableToSolve)) {
|
||||
throw ArgumentError(
|
||||
'Variable "$variableToSolve" is not an input or output variable of the formula "${formula.name}".',
|
||||
);
|
||||
}
|
||||
|
||||
final modifiedInputValues = Map<String, dynamic>.from(fixedInputValues);
|
||||
var evaluator = FormulaEvaluator();
|
||||
Number f(Number x) {
|
||||
modifiedInputValues[variableToSolve] = x;
|
||||
final result = evaluator.evaluate(formula, modifiedInputValues);
|
||||
if (result is Number) {
|
||||
return result;
|
||||
} else {
|
||||
throw FormulaEvaluationException(
|
||||
'Expected formula evaluation to return a number, but got: $result ${result.runtimeType}',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
var fixedFormulaOutput = fixedInputValues[formula.output.name];
|
||||
|
||||
return functionSolver(
|
||||
(Number x) => f(x) - fixedFormulaOutput,
|
||||
hint: hint,
|
||||
step: step,
|
||||
maxDelta: maxDelta,
|
||||
maxTries: maxTries,
|
||||
);
|
||||
}
|
||||
|
||||
class NoSolutionException implements Exception {
|
||||
final String message;
|
||||
|
||||
const NoSolutionException(this.message);
|
||||
|
||||
@override
|
||||
String toString() => 'NoSolutionException: $message';
|
||||
}
|
||||
|
||||
Number functionSolver(
|
||||
Number Function(Number) f, {
|
||||
Number hint = 0,
|
||||
Number step = 10,
|
||||
Number maxDelta = 0.01,
|
||||
int maxTries = 100,
|
||||
}) {
|
||||
Number sign(Number x) => switch (x) {
|
||||
> 0 => 1,
|
||||
< 0 => -1,
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
Number binarySearch(Number low, Number high) {
|
||||
var yLow = f(low);
|
||||
var yHigh = f(high);
|
||||
assert(sign(yLow) != sign(yHigh));
|
||||
|
||||
int count = 0;
|
||||
while ((high - low).abs() > maxDelta) {
|
||||
count += 1;
|
||||
if (count > maxTries) {
|
||||
throw NoSolutionException("Failed to find a root after $maxTries tries.");
|
||||
}
|
||||
var mid = (low + high) / 2;
|
||||
var yMid = f(mid);
|
||||
if (sign(yMid) == sign(f(low))) {
|
||||
low = mid;
|
||||
yLow = yMid;
|
||||
} else {
|
||||
high = mid;
|
||||
yHigh = yMid;
|
||||
}
|
||||
}
|
||||
return (low + high) / 2;
|
||||
}
|
||||
|
||||
List<Number> searchApproximately(Number x1, Number x2) {
|
||||
var y1 = f(x1);
|
||||
var y2 = f(x2);
|
||||
int count = 0;
|
||||
while (sign(y1) == sign(y2)) {
|
||||
count += 1;
|
||||
if (count > maxTries) {
|
||||
throw NoSolutionException("Failed to find a root after $maxTries tries.");
|
||||
}
|
||||
if (y1.abs() < y2.abs()) {
|
||||
x2 = x1;
|
||||
x1 = x1 - step;
|
||||
y2 = y1;
|
||||
y1 = f(x1);
|
||||
} else {
|
||||
x1 = x2;
|
||||
x2 = x2 + step;
|
||||
y1 = y2;
|
||||
y2 = f(x2);
|
||||
}
|
||||
}
|
||||
return [x1, x2];
|
||||
}
|
||||
|
||||
var approx = searchApproximately(hint, hint + step);
|
||||
return binarySearch(approx[0], approx[1]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
import 'package:d4rt/d4rt.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:d4rt_formulas/d4rt_formulas.dart';
|
||||
import 'dart:math';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
typedef Number = double;
|
||||
|
||||
abstract class SetUtils {
|
||||
static Object safeGet(Map<Object?, Object?> map, String key) {
|
||||
|
|
@ -21,69 +25,160 @@ abstract class SetUtils {
|
|||
static Number numberValue(Map<Object?, Object?> map, String key) {
|
||||
return double.parse(stringValue(map, key));
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses a d4rt array literal (containing maps and arrays) to a List<Object?>
|
||||
/// using d4rt
|
||||
List<Object?> parseD4rtLiteral(String arrayStringLiteral) {
|
||||
var d4rt = D4rt();
|
||||
final buffer = StringBuffer();
|
||||
buffer.write("main(){ return $arrayStringLiteral; }");
|
||||
final code = buffer.toString();
|
||||
/// Parses a d4rt array literal (containing maps and arrays) to a List<Object?>
|
||||
/// using d4rt
|
||||
static List<Object?> parseD4rtLiteral(String arrayStringLiteral) {
|
||||
var d4rt = D4rt();
|
||||
final buffer = StringBuffer();
|
||||
buffer.write("main(){ return $arrayStringLiteral; }");
|
||||
final code = buffer.toString();
|
||||
|
||||
final List<Object?> list = d4rt.execute(source: code);
|
||||
final List<Object?> list = d4rt.execute(source: code);
|
||||
|
||||
return list;
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/// Escapes special characters in a string for use in D4RT literals
|
||||
String escapeD4rtString(String input) {
|
||||
return input
|
||||
.replaceAll(r'\', r'\\') // Escape backslashes first
|
||||
.replaceAll('\n', r'\n') // Escape newlines
|
||||
.replaceAll('\r', r'\r') // Escape carriage returns
|
||||
.replaceAll('\t', r'\t') // Escape tabs
|
||||
.replaceAll('"', r'\"'); // Escape quotes last
|
||||
}
|
||||
/// 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.
|
||||
List<FormulaElement> parseCorpusElements(String arrayStringLiteral) {
|
||||
final List<Object?> elements = parseD4rtLiteral(arrayStringLiteral);
|
||||
/// Parses corpus elements from an array string literal.
|
||||
/// Determines if each element is a formula or a unit and converts accordingly.
|
||||
static List<FormulaElement> parseCorpusElements(String arrayStringLiteral) {
|
||||
final List<Object?> elements = parseD4rtLiteral(arrayStringLiteral);
|
||||
|
||||
final List<FormulaElement> result = [];
|
||||
for (final element in elements) {
|
||||
if (element is Map<Object?, Object?>) {
|
||||
// Check if it's a formula by looking for required formula properties
|
||||
// Formulas typically have 'd4rtCode' and 'input'/'output' properties
|
||||
if (element.containsKey('d4rtCode')) {
|
||||
result.add(Formula.fromSet(element));
|
||||
}
|
||||
// Units typically have 'name', 'symbol', and 'baseUnit' properties
|
||||
else if (element.containsKey('name') && element.containsKey('symbol')) {
|
||||
result.add(UnitSpec.fromSet(element));
|
||||
}
|
||||
else {
|
||||
throw ArgumentError('Unknown element type: $element');
|
||||
final List<FormulaElement> result = [];
|
||||
for (final element in elements) {
|
||||
if (element is Map<Object?, Object?>) {
|
||||
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 {
|
||||
throw ArgumentError('Element must be a Map: $element');
|
||||
return value.toString();
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
/// 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"""';
|
||||
}
|
||||
}
|
||||
|
||||
typedef Number = double;
|
||||
|
||||
/// Abstract base class for formula elements
|
||||
abstract class FormulaElement {
|
||||
/// Creates a string literal representation of the FormulaElement that can be parsed
|
||||
/// by the D4RT parser to recreate the same FormulaElement object.
|
||||
String toStringLiteral();
|
||||
Map<String,dynamic> toMap();
|
||||
|
||||
String toStringLiteral() {
|
||||
final map = toMap();
|
||||
return SetUtils.prettyPrint(map);
|
||||
}
|
||||
}
|
||||
|
||||
class UnitSpec implements FormulaElement {
|
||||
class UnitSpec extends FormulaElement {
|
||||
final String name;
|
||||
final String baseUnit;
|
||||
final String symbol;
|
||||
|
|
@ -91,6 +186,19 @@ class UnitSpec implements FormulaElement {
|
|||
final String? codeFromUnitToBase;
|
||||
final String? codeFromBaseToUnit;
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
"name": name,
|
||||
"baseUnit": baseUnit,
|
||||
"symbol": symbol,
|
||||
if (factorFromUnitToBase != null) 'factor': factorFromUnitToBase,
|
||||
if (codeFromUnitToBase != null) 'toBase': codeFromUnitToBase,
|
||||
if (codeFromBaseToUnit != null) 'fromBase': codeFromBaseToUnit,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
UnitSpec({
|
||||
required this.name,
|
||||
required this.baseUnit,
|
||||
|
|
@ -104,7 +212,7 @@ class UnitSpec implements FormulaElement {
|
|||
String name = SetUtils.stringValue(theSet, "name");
|
||||
String symbol = SetUtils.stringValue(theSet, "symbol");
|
||||
|
||||
if( theSet.containsKey("isBase") ){
|
||||
if (theSet.containsKey("isBase")) {
|
||||
return UnitSpec(name: name, baseUnit: name, symbol: symbol, factorFromUnitToBase: 1);
|
||||
}
|
||||
|
||||
|
|
@ -118,76 +226,56 @@ class UnitSpec implements FormulaElement {
|
|||
symbol: symbol,
|
||||
factorFromUnitToBase: factorFromUnitToBase,
|
||||
);
|
||||
}
|
||||
else if( theSet.containsKey("toBase")) {
|
||||
String codeFromBaseToUnit = SetUtils.stringValue(
|
||||
theSet,
|
||||
"fromBase",
|
||||
} else if (theSet.containsKey("toBase")) {
|
||||
String codeFromBaseToUnit = SetUtils.stringValue(theSet, "fromBase");
|
||||
String codeFromUnitToBase = SetUtils.stringValue(theSet, "toBase");
|
||||
|
||||
return UnitSpec(
|
||||
name: name,
|
||||
baseUnit: baseUnit,
|
||||
symbol: symbol,
|
||||
codeFromBaseToUnit: codeFromBaseToUnit,
|
||||
codeFromUnitToBase: codeFromUnitToBase,
|
||||
);
|
||||
String codeFromUnitToBase = SetUtils.stringValue(
|
||||
theSet,
|
||||
"toBase",
|
||||
);
|
||||
|
||||
return UnitSpec(name: name,
|
||||
baseUnit: baseUnit,
|
||||
symbol: symbol,
|
||||
codeFromBaseToUnit: codeFromBaseToUnit,
|
||||
codeFromUnitToBase: codeFromUnitToBase);
|
||||
} else {
|
||||
throw ArgumentError("Need factor or toBase/fromBase");
|
||||
}
|
||||
else{
|
||||
throw ArgumentError( "Need factor or toBase/fromBase");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
static List<UnitSpec> fromArrayStringLiteral(String arrayStringLiteral) {
|
||||
final List<Object?> list = parseD4rtLiteral(arrayStringLiteral);
|
||||
final List<Object?> list = SetUtils.parseD4rtLiteral(arrayStringLiteral);
|
||||
|
||||
final units = list.map((set) => UnitSpec.fromSet(set as Map));
|
||||
|
||||
return units.toList(growable: false);
|
||||
}
|
||||
|
||||
@override
|
||||
String toStringLiteral() {
|
||||
final buffer = StringBuffer('{');
|
||||
buffer.write('"name": "${escapeD4rtString(name)}", "symbol": "${escapeD4rtString(symbol)}"');
|
||||
|
||||
if (name == baseUnit && factorFromUnitToBase == 1) {
|
||||
// This is a base unit
|
||||
buffer.write(', "isBase": true');
|
||||
} else {
|
||||
buffer.write(', "baseUnit": "${escapeD4rtString(baseUnit)}"');
|
||||
|
||||
if (factorFromUnitToBase != null) {
|
||||
buffer.write(', "factor": $factorFromUnitToBase');
|
||||
} else if (codeFromUnitToBase != null && codeFromBaseToUnit != null) {
|
||||
buffer.write(', "toBase": "${escapeD4rtString(codeFromUnitToBase!)}", "fromBase": "${escapeD4rtString(codeFromBaseToUnit!)}"');
|
||||
}
|
||||
}
|
||||
|
||||
buffer.write('}');
|
||||
return buffer.toString();
|
||||
}
|
||||
}
|
||||
|
||||
class VariableSpec {
|
||||
class VariableSpec extends FormulaElement{
|
||||
final String name;
|
||||
final String? unit;
|
||||
final List<dynamic>? values;
|
||||
|
||||
VariableSpec({required this.name, this.unit, this.values}){
|
||||
@override
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
'name': name,
|
||||
if (unit != null) 'unit': unit,
|
||||
if (values != null) 'values': List.from(values!,growable: false),
|
||||
};
|
||||
}
|
||||
|
||||
VariableSpec({required this.name, this.unit, this.values}) {
|
||||
validate();
|
||||
}
|
||||
|
||||
void validate(){
|
||||
if( FormulaEvaluator.reservedVariableNames.contains(name) ){
|
||||
void validate() {
|
||||
if (FormulaEvaluator.reservedVariableNames.contains(name)) {
|
||||
throw ArgumentError("$name: is a reserved variable name for FormulaEvaluator");
|
||||
}
|
||||
final valuesValid = values != null && values?.isNotEmpty == true;
|
||||
if( unit == null && !valuesValid ){
|
||||
if (unit == null && !valuesValid) {
|
||||
throw ArgumentError("$name: at least unit or allowedValues should be valid");
|
||||
}
|
||||
}
|
||||
|
|
@ -207,31 +295,13 @@ class VariableSpec {
|
|||
@override
|
||||
int get hashCode => Object.hash(unit, name, values != null ? const DeepCollectionEquality().hash(values!) : 0);
|
||||
|
||||
@override
|
||||
String toStringLiteral() {
|
||||
final buffer = StringBuffer('{');
|
||||
buffer.write('"name": "${escapeD4rtString(name)}"');
|
||||
|
||||
if (unit != null) {
|
||||
buffer.write(', "unit": "${escapeD4rtString(unit!)}"');
|
||||
}
|
||||
|
||||
if (values != null && values!.isNotEmpty) {
|
||||
buffer.write(', "values": [${values!.map((value) {
|
||||
if (value is String) {
|
||||
return '"${escapeD4rtString(value)}"';
|
||||
} else {
|
||||
return value.toString();
|
||||
}
|
||||
}).join(", ")}]');
|
||||
}
|
||||
|
||||
buffer.write('}');
|
||||
return buffer.toString();
|
||||
}
|
||||
}
|
||||
|
||||
class Formula implements FormulaElement {
|
||||
String _generateUuidV4() => Uuid().v4();
|
||||
|
||||
class Formula extends FormulaElement {
|
||||
final String uuid;
|
||||
final String name;
|
||||
final String? description;
|
||||
final List<VariableSpec> input;
|
||||
|
|
@ -239,42 +309,52 @@ class Formula implements FormulaElement {
|
|||
final String d4rtCode;
|
||||
final List<String> tags;
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toMap() {
|
||||
// UUID NOT INCLUDED ON PURPOSE
|
||||
return {
|
||||
'name': name,
|
||||
if (description != null) 'description': description,
|
||||
'input': input.map((v) => v.toMap()).toList(growable: false),
|
||||
'output': output.toMap(),
|
||||
'd4rtCode': d4rtCode,
|
||||
if (tags.isNotEmpty) 'tags': List.from(tags, growable: false),
|
||||
};
|
||||
}
|
||||
|
||||
Formula({
|
||||
String? uuid = null,
|
||||
required this.name,
|
||||
this.description,
|
||||
required this.input,
|
||||
required this.output,
|
||||
required this.d4rtCode,
|
||||
this.tags = const [],
|
||||
}) {
|
||||
}) : uuid = uuid ?? _generateUuidV4() {
|
||||
validate();
|
||||
}
|
||||
|
||||
void validate() {
|
||||
if (name.trim().isEmpty) {
|
||||
if (name
|
||||
.trim()
|
||||
.isEmpty) {
|
||||
throw ArgumentError('Formula name cannot be empty');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() =>
|
||||
'Formula(name: $name, description: $description, input: $input, output: $output, d4rtCode: $d4rtCode, tags: $tags)';
|
||||
'Formula(uuid: $uuid, name: $name, description: $description, input: $input, output: $output, d4rtCode: $d4rtCode, tags: $tags)';
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is Formula &&
|
||||
runtimeType == other.runtimeType &&
|
||||
name == other.name &&
|
||||
description == other.description &&
|
||||
output == other.output &&
|
||||
ListEquality().equals(input, other.input) &&
|
||||
d4rtCode == other.d4rtCode &&
|
||||
ListEquality().equals(tags, other.tags);
|
||||
other is Formula &&
|
||||
runtimeType == other.runtimeType &&
|
||||
uuid == other.uuid;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
Object.hash(name, description, ListEquality().hash(input), output, d4rtCode, ListEquality().hash(tags));
|
||||
int get hashCode => uuid.hashCode;
|
||||
|
||||
List<String> inputVarNames() =>
|
||||
input.map((v) => v.name).toList(growable: false);
|
||||
|
|
@ -291,7 +371,7 @@ class Formula implements FormulaElement {
|
|||
}
|
||||
|
||||
static List<Formula> fromArrayStringLiteral(String arrayStringLiteral) {
|
||||
final List<Object?> list = parseD4rtLiteral(arrayStringLiteral);
|
||||
final List<Object?> list = SetUtils.parseD4rtLiteral(arrayStringLiteral);
|
||||
|
||||
final formulas = list.map((set) => Formula.fromSet(set as Map));
|
||||
|
||||
|
|
@ -309,9 +389,11 @@ class Formula implements 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');
|
||||
}
|
||||
}
|
||||
|
|
@ -322,18 +404,20 @@ class Formula implements FormulaElement {
|
|||
);
|
||||
}
|
||||
|
||||
String? uuid = theSet['uuid'] as String?;
|
||||
String name = SetUtils.stringValue(theSet, "name");
|
||||
String? description = theSet ["description"] as String?;
|
||||
List<String> tags = (theSet["tags"] as List<Object?>? ?? []).map((t) => t.toString()).toList();
|
||||
String? description = theSet["description"] as String?;
|
||||
List<String> tags = (theSet["tags"] as List<Object?>? ?? []).map((t) =>
|
||||
t.toString()).toList();
|
||||
final List<Object?> inputSet = SetUtils.listValue(theSet, "input");
|
||||
List<VariableSpec> input = inputSet
|
||||
.map((v) => parseVar(v as Map))
|
||||
.toList(growable: false);
|
||||
Map<Object?, Object?> outputSet = theSet.get("output");
|
||||
List<VariableSpec> input = inputSet.map((v) => parseVar(v as Map)).toList(
|
||||
growable: false);
|
||||
Map<Object?, Object?> outputSet = theSet['output'] as Map<Object?, Object?>;
|
||||
VariableSpec output = parseVar(outputSet);
|
||||
String d4rtCode = SetUtils.stringValue(theSet, "d4rtCode");
|
||||
|
||||
return Formula(
|
||||
uuid: uuid,
|
||||
name: name,
|
||||
description: description,
|
||||
tags: tags,
|
||||
|
|
@ -342,30 +426,5 @@ class Formula implements FormulaElement {
|
|||
d4rtCode: d4rtCode,
|
||||
);
|
||||
}
|
||||
|
||||
/// Creates a string literal representation of the Formula that can be parsed
|
||||
/// by the D4RT parser to recreate the same Formula object.
|
||||
@override
|
||||
String toStringLiteral() {
|
||||
final inputStrings = input.map((varSpec) => varSpec.toStringLiteral()).toList();
|
||||
|
||||
final buffer = StringBuffer('{');
|
||||
buffer.write('"name": "$name"');
|
||||
|
||||
if (description != null) {
|
||||
buffer.write(', "description": r"""${description!}"""');
|
||||
}
|
||||
|
||||
buffer.write(', "input": [${inputStrings.join(", ")}]');
|
||||
buffer.write(', "output": ${output.toStringLiteral()}');
|
||||
|
||||
buffer.write(', "d4rtCode": r"""$d4rtCode"""');
|
||||
|
||||
if (tags.isNotEmpty) {
|
||||
buffer.write(', "tags": [${tags.map((tag) => '"${escapeD4rtString(tag)}"').join(", ")}]');
|
||||
}
|
||||
|
||||
buffer.write('}');
|
||||
return buffer.toString();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import 'package:d4rt_formulas/d4rt_formulas.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'database/database_service.dart';
|
||||
import 'package:drift/drift.dart' as drift;
|
||||
import 'service_locator.dart';
|
||||
|
||||
import 'ai/formula_list.dart';
|
||||
|
|
|
|||
|
|
@ -990,7 +990,7 @@ packages:
|
|||
source: hosted
|
||||
version: "3.1.5"
|
||||
uuid:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: uuid
|
||||
sha256: "1fef9e8e11e2991bb773070d4656b7bd5d850967a2456cfc83cf47925ba79489"
|
||||
|
|
|
|||
|
|
@ -41,8 +41,7 @@ dependencies:
|
|||
flutter_markdown_plus:
|
||||
flutter_markdown_plus_latex:
|
||||
flutter_code_editor:
|
||||
|
||||
# Drift dependencies for database support
|
||||
uuid:
|
||||
drift:
|
||||
sqlite3_flutter_libs:
|
||||
path_provider:
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import 'package:test/test.dart';
|
||||
import 'package:d4rt/d4rt.dart';
|
||||
import 'dart:math' as Math;
|
||||
|
||||
|
||||
|
||||
void main(){
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:d4rt_formulas/database/database_service.dart';
|
||||
import 'package:d4rt_formulas/service_locator.dart';
|
||||
|
||||
void main() {
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ void main() {
|
|||
);
|
||||
|
||||
final literal = originalUnit.toStringLiteral();
|
||||
final parsedList = parseD4rtLiteral('[${literal}]');
|
||||
final parsedList = SetUtils.parseD4rtLiteral('[${literal}]');
|
||||
final parsedMap = parsedList[0] as Map<Object?, Object?>;
|
||||
final parsedUnit = UnitSpec.fromSet(parsedMap);
|
||||
|
||||
|
|
@ -246,22 +246,22 @@ void main() {
|
|||
|
||||
test('Corpus.withDependencies returns formula and its dependencies', () async {
|
||||
final corpus = await testCorpus;
|
||||
|
||||
|
||||
// Get a formula that has units associated with it
|
||||
final formula = corpus.getFormula("Newton's Second Law");
|
||||
expect(formula, isNotNull);
|
||||
|
||||
|
||||
// Call withDependencies method
|
||||
final dependencies = corpus.withDependencies(formula!);
|
||||
|
||||
|
||||
// Check that the formula itself is included
|
||||
expect(dependencies.any((element) => element is Formula && element.name == formula.name), true);
|
||||
|
||||
|
||||
// Check that units from input and output are included
|
||||
for (final inputVar in formula.input) {
|
||||
if (inputVar.unit != null) {
|
||||
expect(dependencies.any((element) => element is UnitSpec && element.name == inputVar.unit), true);
|
||||
|
||||
|
||||
// Check that units with same base unit are included
|
||||
final unitsWithSameBase = corpus.unitsOfSameMagnitude(inputVar.unit!);
|
||||
for (final unitName in unitsWithSameBase) {
|
||||
|
|
@ -269,20 +269,21 @@ void main() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (formula.output.unit != null) {
|
||||
expect(dependencies.any((element) => element is UnitSpec && element.name == formula.output.unit), true);
|
||||
|
||||
|
||||
// Check that units with same base unit as output are included
|
||||
final outputUnitsWithSameBase = corpus.unitsOfSameMagnitude(formula.output.unit!);
|
||||
for (final unitName in outputUnitsWithSameBase) {
|
||||
expect(dependencies.any((element) => element is UnitSpec && element.name == unitName), true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Verify that there are no duplicates by checking the length of the list vs the set
|
||||
final uniqueDependencies = dependencies.toSet();
|
||||
expect(dependencies.length, equals(uniqueDependencies.length));
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
79
test/formula_solver_test.dart
Normal file
79
test/formula_solver_test.dart
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
import 'package:d4rt_formulas/formula_evaluator.dart';
|
||||
import 'package:d4rt_formulas/formula_models.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'dart:math' as Math;
|
||||
|
||||
|
||||
void main() {
|
||||
|
||||
group("Formulas", (){
|
||||
|
||||
test("Solve x^2 formula", () {
|
||||
final formula = Formula(
|
||||
name: 'Test x^2',
|
||||
input: [
|
||||
VariableSpec(name: 'x', unit: 'scalar'),
|
||||
],
|
||||
output: VariableSpec(name: 'y', unit: 'scalar'),
|
||||
d4rtCode: 'y = x*x;',
|
||||
);
|
||||
|
||||
var solution = formulaSolver(formula, "x", {"y": 25}, maxDelta: 1e-10);
|
||||
expect( solution, closeTo(5, 1e-10));
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
group('Native functions', () {
|
||||
test("Solve x^2", () {
|
||||
Number f(Number x) => x * x;
|
||||
var root = functionSolver(f, hint: 10, step: 1);
|
||||
expect(root, closeTo(0, 0.1));
|
||||
});
|
||||
|
||||
|
||||
test("Solve (x-1000)^2", () {
|
||||
Number f(Number x) => (x - 1000) * (x - 1000);
|
||||
var root = functionSolver(f, hint: 10, step: 1, maxTries: 1000);
|
||||
expect(root, closeTo(1000, 0.1));
|
||||
});
|
||||
|
||||
test("Solve x^2 + 1", () {
|
||||
Number f(Number x) => x * x + 1;
|
||||
|
||||
expect(() => functionSolver(f, hint: 10, step: 1),
|
||||
throwsA(isA<NoSolutionException>()));
|
||||
});
|
||||
|
||||
test("Solve (x-2)(x-10", () {
|
||||
Number f(Number x) => (x - 2) * (x - 10);
|
||||
|
||||
expect(functionSolver(f, hint: 10, step: 1), closeTo(10, 0.1));
|
||||
});
|
||||
|
||||
test('Solve sqrt(x) = 2 => x = 4', () {
|
||||
Number f(Number x) => Math.sqrt(x) - 2;
|
||||
var root = functionSolver(f, hint: 5, step: 1);
|
||||
expect(root, closeTo(4, 0.1));
|
||||
});
|
||||
|
||||
test('Solve sin(x) = 0 near pi (hint 3)', () {
|
||||
Number f(Number x) => Math.sin(x);
|
||||
var root = functionSolver(f, hint: 3, step: 1);
|
||||
expect(root, closeTo(Math.pi, 0.01));
|
||||
});
|
||||
|
||||
test('Solve tan(x) = 1 => x = pi/4', () {
|
||||
Number f(Number x) => Math.tan(x) - 1;
|
||||
var root = functionSolver(f, hint: 0, step: 1);
|
||||
expect(root, closeTo(Math.pi / 4, 0.01));
|
||||
});
|
||||
|
||||
test('Solve exp(x) = 2 => x = ln(2)', () {
|
||||
Number f(Number x) => Math.exp(x) - 2;
|
||||
var root = functionSolver(f, hint: 1, step: 1);
|
||||
expect(root, closeTo(Math.log(2), 0.01));
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@ import 'package:d4rt_formulas/corpus.dart';
|
|||
import 'package:d4rt_formulas/defaults/default_corpus.dart';
|
||||
import 'package:d4rt_formulas/formula_evaluator.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:d4rt_formulas/formula_models.dart';
|
||||
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
|
|
|||
Loading…
Reference in a new issue