diff --git a/README.md b/README.md index f3e695f..f595f98 100644 --- a/README.md +++ b/README.md @@ -31,10 +31,65 @@ The file is a dart array of formulas. Each formula is a dart set literal }, "d4rt_code": "F = m*a;" + }, + { + "name": 'Test argument order', + "input": [ + 'z':{ "magnitude": 'scalar'}, + 'a':{ "magnitude": 'scalar'}, + 'y':{ "magnitude": 'scalar'}, + ], + "output": { 'result', "magnitude": 'scalar' }, + "d4rtCode": ''' + result = a * 100 + y * 10 + z; + ''', } ] ``` +# Unit file description + +```dart +[ + { + "name": 'meter', + "symbol": 'm', + "isBase": true + }, + { + "name": 'inch', + "symbol" 'in', + "baseUnit": 'meter', + "factor": 0.0254 + }, + { + "name": 'nautical mile', + "symbol": 'Nm', + "baseUnit": 'meter', + "factor": 1852 + }, + { + "name": 'Kelvin', + "symbol": "Kº", + "isBase": true, + }, + { + "name": 'Celsius', + "symbol": "Cº", + "baseUnit" : "Kelvin", + "toBase": "x + 273.15", + "fromBase": "x - 273.15" + }, + { + "name": 'Fahrenheit', + "symbol": "Fº", + "baseUnit" : "Kelvin", + "toBase": "(x - 32) × 5/9 + 273.15", + "fromBase": "x - 273.15) * 9/5 + 32" + } +] +``` + ## Features ### Formula Search and Computation diff --git a/firejail-warp-terminal.profile b/firejail-warp-terminal.profile index 89bd667..5406c7b 100644 --- a/firejail-warp-terminal.profile +++ b/firejail-warp-terminal.profile @@ -1,5 +1,5 @@ # launch as: firejail --profile=firejail-warp-terminal.profile /opt/warpdotdev/warp-terminal/warp -private /home/alvaro/repos/d4rt-formulas/ +private /home/alvaro/repos/d4rt_formulas/ blacklist /datos-1T blacklist /datos-luks/ blacklist /media @@ -8,3 +8,4 @@ blacklist /media # noroot # don't run as root caps.drop all # drop Linux capabilities # seccomp # enable syscall filtering +First version of a formula widget \ No newline at end of file diff --git a/lib/corpus.dart b/lib/corpus.dart new file mode 100644 index 0000000..0089404 --- /dev/null +++ b/lib/corpus.dart @@ -0,0 +1,96 @@ +import 'package:d4rt/d4rt.dart'; +import 'package:collection/collection.dart'; +import 'package:d4rt_formulas/d4rt_formulas.dart'; + +class Multimap extends DelegatingMap> { + final Map> _map; + + Multimap(super.map) : _map = map; + + factory Multimap.create() { + return Multimap({}); + } + + @override + List? operator [](Object? key) { + if (_map.containsKey(key)) { + return super[key]; + } + final List newList = []; + super[key as K] = newList; + return super[key]; + } +} + +class UnitCorpus { + final Multimap _baseToUnits = Multimap.create(); + final Map _allUnits = {}; + + void loadUnits(List units, [bool replaceOnDuplicates = false]) { + for (final unit in units) { + if (!replaceOnDuplicates && _allUnits.containsKey(unit.name)) { + throw ArgumentError("Duplicate unit:${unit}"); + } + _allUnits[unit.name] = unit; + _baseToUnits[unit.baseUnit]?.add(unit.name); + } + } + + UnitSpec operator [](String unit) { + if (!_allUnits.containsKey(unit)) { + throw ArgumentError("Unit not found:${unit}"); + } + return _allUnits.get(unit); + } + + UnitSpec get(String unit) => this[unit]; + + String _converterFromCodeString(Number x, String codeString) { + final buffer = StringBuffer(); + buffer.writeln("final x = ${x};"); + buffer.writeln("main(){return $codeString;}"); + final code = buffer.toString(); + return code; + } + + Number _convertToBase(Number x, String fromUnit) { + final unit = this[fromUnit]; + + if (unit.factorFromUnitToBase != null) { + return x * (unit.factorFromUnitToBase as Number); + } + + if (unit.codeFromUnitToBase == null) { + throw ArgumentError("Unit has no codeFromUnitToBase: $unit"); + } + + final d4rt = D4rt(); + final completeSource = _converterFromCodeString(x, unit.codeFromUnitToBase as String); + final ret = d4rt.execute(source: completeSource); + return ret as Number; + } + + Number _convertFromBase(Number x, String toUnit) { + final unit = this[toUnit]; + + if (unit.factorFromUnitToBase != null) { + return x / (unit.factorFromUnitToBase as Number); + } + + if (unit.codeFromBaseToUnit == null) { + throw ArgumentError("Unit has no codeFromBaseToUnit: $unit"); + } + + final d4rt = D4rt(); + final completeSource = _converterFromCodeString(x, unit.codeFromBaseToUnit as String); + final ret = d4rt.execute(source: completeSource); + return ret as Number; + } + + Number convert(Number x, String fromUnit, String toUnit) { + final xBase = _convertToBase(x, fromUnit); + final xTo = _convertFromBase(xBase, toUnit); + + return xTo; + } +} diff --git a/lib/formula_models.dart b/lib/formula_models.dart index bea9371..4788486 100644 --- a/lib/formula_models.dart +++ b/lib/formula_models.dart @@ -1,6 +1,106 @@ import 'package:d4rt/d4rt.dart'; import 'package:collection/collection.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)); + } +} + +typedef Number = double; + + +class UnitSpec { + final String name; + final String baseUnit; + final String symbol; + final Number? factorFromUnitToBase; + final String? codeFromUnitToBase; + final String? codeFromBaseToUnit; + + static final BASEUNIT="BASEUNIT"; + + UnitSpec({ + required this.name, + required this.baseUnit, + required this.symbol, + this.factorFromUnitToBase, + this.codeFromBaseToUnit, + this.codeFromUnitToBase, + }); + + factory UnitSpec.fromSet(Map theSet) { + String name = SetUtils.stringValue(theSet, "name"); + String symbol = SetUtils.stringValue(theSet, "symbol"); + + if( theSet.containsKey("isBase") ){ + return UnitSpec(name: name, baseUnit: BASEUNIT, symbol: symbol, factorFromUnitToBase: 1); + } + + String baseUnit = SetUtils.stringValue(theSet, "baseUnit"); + + if (theSet.containsKey("factor")) { + Number factorFromUnitToBase = SetUtils.numberValue(theSet, "factor"); + 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", + ); + + return UnitSpec(name: name, + baseUnit: baseUnit, + symbol: symbol, + codeFromBaseToUnit: codeFromBaseToUnit, + codeFromUnitToBase: codeFromUnitToBase); + } + else{ + throw ArgumentError( "Need factor or toBase/fromBase"); + } + + + } + + static List fromArrayStringLiteral(String arrayStringLiteral) { + var d4rt = D4rt(); + final buffer = StringBuffer(); + buffer.write("main(){ return $arrayStringLiteral; }"); + final code = buffer.toString(); + + final List list = d4rt.execute(source: code); + + final units = list.map((set) => UnitSpec.fromSet(set as Map)); + + return units.toList(growable: false); + } + +} + class VariableSpec { final String name; final String magnitude; @@ -65,10 +165,10 @@ class Formula { List inputVarNames() => input.map((v) => v.name).toList(growable: false); - factory Formula.fromStringLiteral( String setStringLiteral ){ + factory Formula.fromStringLiteral(String setStringLiteral) { var d4rt = D4rt(); final buffer = StringBuffer(); - buffer.write( "main(){ return $setStringLiteral; }"); + buffer.write("main(){ return $setStringLiteral; }"); final code = buffer.toString(); final Map setLiteral = d4rt.execute(source: code); @@ -76,48 +176,34 @@ class Formula { return Formula.fromSet(setLiteral); } - static List fromArrayStringLiteral( String arrayStringLiteral ){ + static List fromArrayStringLiteral(String arrayStringLiteral) { var d4rt = D4rt(); final buffer = StringBuffer(); - buffer.write( "main(){ return $arrayStringLiteral; }"); + buffer.write("main(){ return $arrayStringLiteral; }"); final code = buffer.toString(); final List list = d4rt.execute(source: code); - final formulas = list.map( (set) => Formula.fromSet(set as Map) ); + final formulas = list.map((set) => Formula.fromSet(set as Map)); return formulas.toList(growable: false); } factory Formula.fromSet(Map theSet) { - - Object safeGet(Map map, String key){ - if( !map.containsKey(key) ){ - throw ArgumentError( "Key not found: $key -- $map" ); - } - return map[key] ?? "Not possible!!!"; - } - - String stringValue(Map map, String key){ - return safeGet(map, key).toString(); - } - - List listValue(Map map, String key){ - return safeGet(map,key) as List; - } - VariableSpec parseVar(Map varSpec) { - String name = stringValue(varSpec, "name"); - String magnitude = stringValue(varSpec, "magnitude"); + String name = SetUtils.stringValue(varSpec, "name"); + String magnitude = SetUtils.stringValue(varSpec, "magnitude"); return VariableSpec(name: name, magnitude: magnitude); } - String name = stringValue( theSet, "name" ); - final List inputSet = listValue( theSet, "input"); - List input = inputSet.map( (v) => parseVar(v as Map)).toList(growable: false); + String name = SetUtils.stringValue(theSet, "name"); + final List inputSet = SetUtils.listValue(theSet, "input"); + List input = inputSet + .map((v) => parseVar(v as Map)) + .toList(growable: false); Map outputSet = theSet.get("output"); VariableSpec output = parseVar(outputSet); - String d4rtCode = theSet.get("d4rtCode"); + String d4rtCode = SetUtils.stringValue(theSet, "d4rtCode"); return Formula( name: name, diff --git a/lib/units/distance.d4rt.units b/lib/units/distance.d4rt.units new file mode 100644 index 0000000..0e26a90 --- /dev/null +++ b/lib/units/distance.d4rt.units @@ -0,0 +1,41 @@ +[ + {"name": "meter", "symbol": "m", "isBase": true}, + {"name": "inch", "symbol": "in", "baseUnit": "meter", "factor" : 0.0254 }, + {"name": "kilometer", "symbol": "km", "baseUnit": "meter", "factor": 1000}, + { + "name": "nautical mile", + "symbol": "Nm", + "baseUnit": "meter", + "factor": 1852, + }, + { + "name": "light-year", + "symbol": "ly", + "baseUnit": "meter", + "factor": 9.461e15, + }, + { + "name": "astronomical unit", + "symbol": "AU", + "baseUnit": "meter", + "factor": 1.496e11, + }, + {"name": "rod", "symbol": "rd", "baseUnit": "meter", "factor": 5.0292}, + {"name": "chain", "symbol": "ch", "baseUnit": "meter", "factor": 20.1168}, + {"name": "furlong", "symbol": "fur", "baseUnit": "meter", "factor": 201.168}, + {"name": "league", "symbol": "lea", "baseUnit": "meter", "factor": 4828.032}, + {"name": "elbow", "symbol": "elb", "baseUnit": "meter", "factor": 0.4572}, + {"name": "hand", "symbol": "hh", "baseUnit": "meter", "factor": 0.1016}, + {"name": "cubit", "symbol": "cub", "baseUnit": "meter", "factor": 0.4572}, + {"name": "span", "symbol": "sp", "baseUnit": "meter", "factor": 0.2286}, + {"name": "fathom", "symbol": "ftm", "baseUnit": "meter", "factor": 1.8288}, + {"name": "finger", "symbol": "fng", "baseUnit": "meter", "factor": 0.0222}, + { + "name": "Roman mile", + "symbol": "mil.rom", + "baseUnit": "meter", + "factor": 1479.5, + }, + {"name": "Chinese li", "symbol": "li", "baseUnit": "meter", "factor": 500}, + {"name": "Japanese ri", "symbol": "ri", "baseUnit": "meter", "factor": 3927} +] diff --git a/lib/units/temperature.d4rt.units b/lib/units/temperature.d4rt.units new file mode 100644 index 0000000..84cf5e9 --- /dev/null +++ b/lib/units/temperature.d4rt.units @@ -0,0 +1,74 @@ +[ + {"name": "Kelvin", "symbol": "K", "isBase": true}, + { + "name": "Celsius", + "symbol": "°C", + "baseUnit": "Kelvin", + "toBase": "x + 273.15", + "fromBase": "x - 273.15", + }, + { + "name": "Fahrenheit", + "symbol": "°F", + "baseUnit": "Kelvin", + "toBase": "(x - 32) * 5/9 + 273.15", + "fromBase": "(x - 273.15) * 9/5 + 32", + }, + { + "name": "Rankine", + "symbol": "°R", + "baseUnit": "Kelvin", + "toBase": "x * 5/9", + "fromBase": "x * 9/5", + }, + { + "name": "Réaumur", + "symbol": "°Ré", + "baseUnit": "Kelvin", + "toBase": "x * 5/4 + 273.15", + "fromBase": "(x - 273.15) * 4/5", + }, + { + "name": "Delisle", + "symbol": "°D", + "baseUnit": "Kelvin", + "toBase": "373.15 - x * 2/3", + "fromBase": "(373.15 - x) * 3/2", + }, + { + "name": "Rømer", + "symbol": "°Rø", + "baseUnit": "Kelvin", + "toBase": "(x - 7.5) * 40/21 + 273.15", + "fromBase": "(x - 273.15) * 21/40 + 7.5", + }, + { + "name": "Gas Mark", + "symbol": "GM", + "baseUnit": "Kelvin", + "toBase": """ + if (x < 1) { + double celsius = (243 - 25 * (log(1 / x) / log(2))) / 1.8; + return celsius + 273.15; // convert Celsius to Kelvin + } else { + double celsius = x * 14 + 121; + return celsius + 273.15; + } + """, + "fromBase": """ + double celsius = x - 273.15; // convert Kelvin to Celsius + if (celsius < 135) { // corresponds to G < 1 + return pow(2, (1.8 * celsius - 243) / 25); + } else { + return (celsius - 121) / 14; + } + """ + }, + { + "name": "Solar Temperature", + "symbol": "°Sol", + "baseUnit": "Kelvin", + "toBase": "x * 1000000", + "fromBase": "x / 1000000", + } +] diff --git a/pubspec.lock b/pubspec.lock index 5bc3a6b..76ccc2a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -66,7 +66,7 @@ packages: source: hosted version: "1.1.2" collection: - dependency: transitive + dependency: "direct main" description: name: collection sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" @@ -171,6 +171,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.3" + http: + dependency: transitive + description: + name: http + sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007 + url: "https://pub.dev" + source: hosted + version: "1.5.0" http_multi_server: dependency: transitive description: @@ -315,6 +323,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.0" + resource_portable: + dependency: "direct main" + description: + name: resource_portable + sha256: "8ffa33b0769645e26d82c8f4e79adde3dd34b9e0bba981166d4e3798f39cffd9" + url: "https://pub.dev" + source: hosted + version: "3.1.2" shelf: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 34c42ec..67fff6c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -34,6 +34,7 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.8 + resource_portable: d4rt: flutter_d4rt: diff --git a/test/formula_models_test.dart b/test/formula_models_test.dart index 2ee92f3..77af9e5 100644 --- a/test/formula_models_test.dart +++ b/test/formula_models_test.dart @@ -1,7 +1,11 @@ - +import 'package:d4rt_formulas/corpus.dart'; import 'package:d4rt_formulas/formula_evaluator.dart'; +import 'package:flutter/services.dart'; import 'package:test/test.dart'; import 'package:d4rt_formulas/formula_models.dart'; +import 'dart:convert' show utf8; + +import 'package:resource_portable/resource.dart' show Resource; void main() { @@ -68,13 +72,13 @@ void main() { final setLiteral = { "name": "Newton's second law", "input": [ - { "name": 'm', "magnitude": 'mass'}, - { "name": 'a', "magnitude": 'acceleration'} + {"name": 'm', "magnitude": 'mass'}, + {"name": 'a', "magnitude": 'acceleration'}, ], - "output": { "name": 'F', "magnitude": 'force'}, + "output": {"name": 'F', "magnitude": 'force'}, "d4rtCode": ''' F = a * m; - ''' + ''', }; final formula = Formula.fromSet(setLiteral); @@ -88,7 +92,7 @@ void main() { expect(result, 98.0); // F = m * a = 10 * 9.8 = 98 N }); - test( 'd4rt parses formula from literal', (){ + test('d4rt parses formula from literal', () { final literal = """ { "name": "Newton's second law", @@ -112,11 +116,9 @@ void main() { }); expect(result, 98.0); // F = m * a = 10 * 9.8 = 98 N - }); - - test( 'd4rt parses formula from list literal', (){ + test('d4rt parses formula from list literal', () { final literal = """ [ { @@ -155,8 +157,5 @@ void main() { }); expect(result, 98.0); // F = m * a = 10 * 9.8 = 98 N - }); - } -