d4t_formulas/lib/corpus.dart

285 lines
8.2 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();
2026-03-04 18:52:31 +00:00
// Map formulas by uuid
2025-09-16 16:22:29 +00:00
final Map<String, Formula> _allFormulas = {};
void loadFormulas(List<Formula> formulas, {bool replaceOnDuplicates = true, bool checkUnits = true}) {
2025-09-20 14:46:21 +00:00
for (final formula in formulas) {
2026-03-04 18:52:31 +00:00
if (!replaceOnDuplicates && _allFormulas.containsKey(formula.uuid)) {
throw ArgumentError("Duplicate formula:${formula}");
2025-09-20 14:46:21 +00:00
}
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
}
}
}
2026-03-04 18:52:31 +00:00
_allFormulas[formula.uuid] = formula;
2025-09-20 14:46:21 +00:00
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-03-04 18:52:31 +00:00
/// Returns first formula with the given name (preserves old API semantics).
2026-01-25 18:03:57 +00:00
Formula? getFormula(String name) {
2026-03-04 18:52:31 +00:00
try {
return _allFormulas.values.firstWhere((f) => f.name == name);
} catch (e) {
return null;
}
}
/// Returns formula by uuid
Formula? getFormulaByUuid(String uuid) {
return _allFormulas[uuid];
2026-01-25 18:03:57 +00:00
}
2026-02-26 20:49:32 +00:00
/// Updates a formula in the corpus
void updateFormula(Formula formula) {
if (!_allFormulas.containsKey(formula.uuid)) {
2026-03-08 19:17:49 +00:00
throw ArgumentError("Formula not found: ${formula.uuid}");
2026-02-26 20:49:32 +00:00
}
// Remove old tags
final oldFormula = _allFormulas[formula.uuid]!;
2026-02-26 20:49:32 +00:00
for (final tag in oldFormula.tags) {
2026-03-08 19:17:49 +00:00
_tags[tag]?.removeWhere((f) => f.uuid == formula.uuid);
2026-02-26 20:49:32 +00:00
}
// Update the formula
_allFormulas[formula.uuid] = formula;
2026-03-08 19:17:49 +00:00
2026-02-26 20:49:32 +00:00
// Add new tags
for (final tag in formula.tags) {
_tags[tag]?.add(formula);
}
}
2026-03-08 19:17:49 +00:00
/// Adds a new formula to the corpus
void addFormula(Formula formula) {
_allFormulas[formula.uuid] = formula;
for (final tag in formula.tags) {
_tags[tag]?.add(formula);
}
}
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.preamble}\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
/// Loads corpus from database elements
static Future<Corpus> fromDatabaseElements(List<FormulaElement> elements) async {
final corpus = Corpus();
corpus.loadFormulaElements(elements);
return corpus;
}
/// Returns the formula, the units of the formula, and all the units from the corpus with the same base unit.
List<FormulaElement> withDependencies(Formula formula) {
final result = <FormulaElement>{};
// Add the formula itself
result.add(formula);
// Helper function to add units and their base equivalents
void addUnitsAndBaseEquivalents(String? unitName) {
if (unitName != null) {
final unit = getUnit(unitName);
result.add(unit);
// Add all units with the same base unit
final unitsWithSameBase = unitsOfSameMagnitude(unitName);
result.addAll(unitsWithSameBase.map((name) => getUnit(name)));
}
}
// Process input variable units
formula.input.where((inputVar) => inputVar.unit != null).forEach((inputVar) {
addUnitsAndBaseEquivalents(inputVar.unit);
});
// Process output variable unit
addUnitsAndBaseEquivalents(formula.output.unit);
return result.toList();
}
2025-09-07 11:59:03 +00:00
}