d4t_formulas/lib/corpus.dart

209 lines
6 KiB
Dart
Raw Normal View History

2025-09-07 11:59:03 +00:00
import 'package:d4rt/d4rt.dart';
import 'package:collection/collection.dart';
import 'package:d4rt_formulas/d4rt_formulas.dart';
2026-02-11 08:07:13 +00:00
import 'formula_models.dart';
2025-09-07 11:59:03 +00:00
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];
}
}
2025-09-20 14:46:21 +00:00
class Corpus{
2025-09-16 16:22:29 +00:00
final Multimap<String, Formula> _tags = Multimap.create();
final Map<String, Formula> _allFormulas = {};
2025-09-20 14:46:21 +00:00
void loadFormulas(List<Formula> formulas, {bool replaceOnDuplicates = false, bool checkUnits = true}) {
for (final formula in formulas) {
if (!replaceOnDuplicates && _allFormulas.containsKey(formula.name)) {
throw ArgumentError("Duplicate formula:$formula");
}
if( checkUnits ){
for( final inputVar in formula.input + [formula.output] ){
2025-11-09 19:29:58 +00:00
if( inputVar.unit != null && !_allUnits.containsKey(inputVar.unit) ){
throw ArgumentError( "Unit not found in formula ${formula.name}: ${inputVar.unit}");
2025-09-20 14:46:21 +00:00
}
}
}
_allFormulas[formula.name] = formula;
for( final tag in formula.tags ){
_tags[tag]?.add(formula);
}
}
}
List<Formula> getTagFormulas(String tag){
if( _tags[tag] == null ){
return [];
}
return _tags[tag]?.toList(growable:false) as List<Formula>;
}
List<Formula> getFormulas(){
return _allFormulas.values.toList(growable:false);
}
2026-01-25 18:03:57 +00:00
Formula? getFormula(String name) {
return _allFormulas.get(name);
}
2025-09-07 11:59:03 +00:00
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)) {
2025-09-07 12:04:42 +00:00
throw ArgumentError("Duplicate unit:$unit");
2025-09-07 11:59:03 +00:00
}
_allUnits[unit.name] = unit;
_baseToUnits[unit.baseUnit]?.add(unit.name);
}
}
2025-11-09 19:29:58 +00:00
List<String> unitsOfSameMagnitude(String? unit){
if( unit == null ){
return ["scalar"];
2025-11-09 19:29:58 +00:00
}
2025-09-20 14:46:21 +00:00
final base = getUnit(unit).baseUnit;
2025-09-14 14:40:27 +00:00
return _baseToUnits[base] as List<String>;
}
2025-09-20 14:46:21 +00:00
UnitSpec getUnit(String unit) {
2025-09-07 11:59:03 +00:00
if (!_allUnits.containsKey(unit)) {
print( _allUnits.keys.join(",") );
2025-09-07 12:04:42 +00:00
throw ArgumentError("Unit not found:$unit");
2025-09-07 11:59:03 +00:00
}
return _allUnits.get(unit);
}
String _converterFromCodeStringAsExpression(Number x, String codeString) {
2025-09-07 11:59:03 +00:00
final buffer = StringBuffer();
buffer.writeln("final x = $x;");
2025-09-07 11:59:03 +00:00
buffer.writeln("main(){return $codeString;}");
final code = buffer.toString();
return code;
}
String _converterFromCodeStringAsStatement(Number x, String codeString) {
final buffer = StringBuffer();
buffer.writeln("final x = $x;");
buffer.writeln("main(){ $codeString; return x; }");
final code = buffer.toString();
return code;
}
2025-09-07 11:59:03 +00:00
Number _convertToBase(Number x, String fromUnit) {
2025-09-20 14:46:21 +00:00
final unit = getUnit(fromUnit);
2025-09-07 11:59:03 +00:00
if (unit.factorFromUnitToBase != null) {
return x * (unit.factorFromUnitToBase as Number);
}
if (unit.codeFromUnitToBase == null) {
throw ArgumentError("Unit has no codeFromUnitToBase: $unit");
}
final ret = _convertUsingCode(x, unit.codeFromUnitToBase as String);
2025-11-09 19:29:58 +00:00
return ret;
2025-09-07 11:59:03 +00:00
}
Number _convertFromBase(Number x, String toUnit) {
2025-09-20 14:46:21 +00:00
final unit = getUnit(toUnit);
2025-09-07 11:59:03 +00:00
if (unit.factorFromUnitToBase != null) {
return x / (unit.factorFromUnitToBase as Number);
}
if (unit.codeFromBaseToUnit == null) {
throw ArgumentError("Unit has no codeFromBaseToUnit: $unit");
}
final ret = _convertUsingCode(x, unit.codeFromBaseToUnit as String);
2025-11-09 19:29:58 +00:00
return ret;
2025-09-07 11:59:03 +00:00
}
Number _convertUsingCode(Number x, String code ){
late String completeSourceExpression;
late String completeSourceStatement;
try {
completeSourceExpression = _converterFromCodeStringAsExpression(x, code);
final ret = _evaluate(completeSourceExpression);
return ret;
}
2025-11-09 19:29:58 +00:00
catch(e1, stack1){
try{
completeSourceStatement = _converterFromCodeStringAsStatement(x, code);
final ret = _evaluate(completeSourceStatement);
return ret;
}
2025-11-09 19:29:58 +00:00
catch( e2, stack2 ){
2026-02-09 15:57:53 +00:00
errorHandler.notify(e1.toString() + "\n" + completeSourceExpression, stack1);
errorHandler.notify(e2.toString() + "\n" + completeSourceStatement, stack2);
throw FormulaEvaluationException( "Evaluation as statement and expression failed" );
}
}
}
static Number _evaluate(String code, [D4rt? interpreter]) {
final d4rtInterpreter = interpreter ?? FormulaEvaluator.createDefaultInterpreter();
FormulaEvaluator.prepareInterpreter(d4rtInterpreter);
final completeCode = "${FormulaEvaluator.d4rtImports}\n$code";
final result = d4rtInterpreter.execute(source: completeCode);
return result.toDouble();
}
2025-09-07 11:59:03 +00:00
Number convert(Number x, String fromUnit, String toUnit) {
final xBase = _convertToBase(x, fromUnit);
final xTo = _convertFromBase(xBase, toUnit);
2025-10-14 17:21:35 +00:00
//print( "convert: x:${x}${fromUnit} xTo:${xTo}${toUnit}");
2025-09-07 11:59:03 +00:00
return xTo;
}
2025-09-10 15:17:28 +00:00
Iterable<UnitSpec> allUnits() => _allUnits.values;
2025-09-20 14:46:21 +00:00
2026-02-09 16:34:20 +00:00
/// Loads formula elements, making sure to load units first, then formulas
/// to avoid dependency issues.
2026-02-11 08:07:13 +00:00
void loadFormulaElements(List<FormulaElement> elements) {
2026-02-09 16:34:20 +00:00
List<UnitSpec> units = [];
List<Formula> formulas = [];
// Separate units and formulas
for (final element in elements) {
if (element is UnitSpec) {
units.add(element);
} else if (element is Formula) {
formulas.add(element);
} else {
throw ArgumentError('Element must be either UnitSpec or Formula: $element');
}
}
// Load units first to satisfy dependencies
loadUnits(units);
// Then load formulas
loadFormulas(formulas);
}
2025-09-20 14:46:21 +00:00
2025-09-07 11:59:03 +00:00
}