This commit is contained in:
Álvaro González 2025-09-07 13:59:03 +02:00
parent bdabc7928b
commit 29208f8c40
9 changed files with 410 additions and 41 deletions

View file

@ -31,6 +31,61 @@ 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"
}
]
```

View file

@ -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

96
lib/corpus.dart Normal file
View file

@ -0,0 +1,96 @@
import 'package:d4rt/d4rt.dart';
import 'package:collection/collection.dart';
import 'package:d4rt_formulas/d4rt_formulas.dart';
class Multimap<K, V> extends DelegatingMap<K, List<V>> {
final Map<K, List<V>> _map;
Multimap(super.map) : _map = map;
factory Multimap.create() {
return Multimap({});
}
@override
List<V>? operator [](Object? key) {
if (_map.containsKey(key)) {
return super[key];
}
final List<V> newList = [];
super[key as K] = newList;
return super[key];
}
}
class UnitCorpus {
final Multimap<String, String> _baseToUnits = Multimap.create();
final Map<String, UnitSpec> _allUnits = {};
void loadUnits(List<UnitSpec> 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;
}
}

View file

@ -1,6 +1,106 @@
import 'package:d4rt/d4rt.dart';
import 'package:collection/collection.dart';
abstract class SetUtils {
static Object safeGet(Map<Object?, Object?> map, String key) {
if (!map.containsKey(key)) {
throw ArgumentError("Key not found: $key -- $map");
}
return map[key] ?? "Not possible!!!";
}
static String stringValue(Map<Object?, Object?> map, String key) {
return safeGet(map, key).toString();
}
static List<Object?> listValue(Map<Object?, Object?> map, String key) {
return safeGet(map, key) as List<Object?>;
}
static Number numberValue(Map<Object?, Object?> 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<Object?, Object?> 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<UnitSpec> fromArrayStringLiteral(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 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<String> 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<Object?, Object?> setLiteral = d4rt.execute(source: code);
@ -76,48 +176,34 @@ class Formula {
return Formula.fromSet(setLiteral);
}
static List<Formula> fromArrayStringLiteral( String arrayStringLiteral ){
static List<Formula> 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<Object?> 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<Object?, Object?> theSet) {
Object safeGet(Map<Object?, Object?> map, String key){
if( !map.containsKey(key) ){
throw ArgumentError( "Key not found: $key -- $map" );
}
return map[key] ?? "Not possible!!!";
}
String stringValue(Map<Object?, Object?> map, String key){
return safeGet(map, key).toString();
}
List<Object?> listValue(Map<Object?, Object?> map, String key){
return safeGet(map,key) as List<Object?>;
}
VariableSpec parseVar(Map<Object?, Object?> 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<Object?> inputSet = listValue( theSet, "input");
List<VariableSpec> input = inputSet.map( (v) => parseVar(v as Map)).toList(growable: false);
String name = SetUtils.stringValue(theSet, "name");
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");
VariableSpec output = parseVar(outputSet);
String d4rtCode = theSet.get("d4rtCode");
String d4rtCode = SetUtils.stringValue(theSet, "d4rtCode");
return Formula(
name: name,

View file

@ -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}
]

View file

@ -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",
}
]

View file

@ -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:

View file

@ -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:

View file

@ -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
});
}