diff --git a/lib/ai/formula_screen.dart b/lib/ai/formula_screen.dart index 292d018..fe4757c 100644 --- a/lib/ai/formula_screen.dart +++ b/lib/ai/formula_screen.dart @@ -9,7 +9,7 @@ import 'unit_dropdown.dart'; class FormulaScreen extends StatefulWidget { final Formula formula; - final UnitCorpus corpus; + final Corpus corpus; const FormulaScreen({ super.key, diff --git a/lib/ai/unit_dropdown.dart b/lib/ai/unit_dropdown.dart index d13338e..830b250 100644 --- a/lib/ai/unit_dropdown.dart +++ b/lib/ai/unit_dropdown.dart @@ -3,7 +3,7 @@ import '../../formula_models.dart'; import '../../corpus.dart'; class UnitDropdown extends StatelessWidget { - final UnitCorpus corpus; + final Corpus corpus; final VariableSpec variable; final String? selectedUnit; final ValueChanged onUnitChanged; @@ -19,7 +19,7 @@ class UnitDropdown extends StatelessWidget { @override Widget build(BuildContext context) { final unitNames = corpus.unitsOfSameMagnitude(variable.unit); - final availableUnits = unitNames.map((name) => corpus.get(name)).toList(); + final availableUnits = unitNames.map((name) => corpus.getUnit(name)).toList(); return SizedBox( width: 200, // Constrain dropdown width diff --git a/lib/ai/unit_list.dart b/lib/ai/unit_list.dart index fad8749..8ca03ed 100644 --- a/lib/ai/unit_list.dart +++ b/lib/ai/unit_list.dart @@ -4,7 +4,7 @@ import '../formula_models.dart'; import 'formula_screen.dart'; class UnitList extends StatefulWidget { - final UnitCorpus corpus; + final Corpus corpus; const UnitList({super.key, required this.corpus}); diff --git a/lib/corpus.dart b/lib/corpus.dart index 16ec384..2a13df5 100644 --- a/lib/corpus.dart +++ b/lib/corpus.dart @@ -22,12 +22,38 @@ class Multimap extends DelegatingMap> { } } -class FormulaCorpus{ +class Corpus{ final Multimap _tags = Multimap.create(); final Map _allFormulas = {}; -} -class UnitCorpus { + void loadFormulas(List 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] ){ + if( getUnit(inputVar.unit) == null ){ + throw ArgumentError( "Unit not found: ${inputVar.unit}"); + } + } + } + + _allFormulas[formula.name] = formula; + for( final tag in formula.tags ){ + _tags[tag]?.add(formula); + } + } + } + + List getTagFormulas(String tag){ + if( _tags[tag] == null ){ + return []; + } + return _tags[tag]?.toList(growable:false) as List; + } + final Multimap _baseToUnits = Multimap.create(); final Map _allUnits = {}; @@ -42,19 +68,18 @@ class UnitCorpus { } List unitsOfSameMagnitude(String unit){ - final base = this[unit].baseUnit; + final base = getUnit(unit).baseUnit; return _baseToUnits[base] as List; } - UnitSpec operator [](String unit) { + + UnitSpec getUnit(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};"); @@ -64,7 +89,7 @@ class UnitCorpus { } Number _convertToBase(Number x, String fromUnit) { - final unit = this[fromUnit]; + final unit = getUnit(fromUnit); if (unit.factorFromUnitToBase != null) { return x * (unit.factorFromUnitToBase as Number); @@ -81,7 +106,7 @@ class UnitCorpus { } Number _convertFromBase(Number x, String toUnit) { - final unit = this[toUnit]; + final unit = getUnit(toUnit); if (unit.factorFromUnitToBase != null) { return x / (unit.factorFromUnitToBase as Number); @@ -105,4 +130,6 @@ class UnitCorpus { } Iterable allUnits() => _allUnits.values; + + } diff --git a/lib/defaults/default_corpus.dart b/lib/defaults/default_corpus.dart new file mode 100644 index 0000000..fffefd4 --- /dev/null +++ b/lib/defaults/default_corpus.dart @@ -0,0 +1,46 @@ +import 'dart:convert' show utf8; + +import 'package:resource_portable/resource_portable.dart' show Resource; + +import '../corpus.dart'; +import '../formula_models.dart'; + +Future createDefaultCorpus() async{ + final corpus = Corpus(); + + Future loadUnits() async { + final unitResources = [ + "lib/defaults/units/area.d4rt.units", + "lib/defaults/units/distance.d4rt.units", + "lib/defaults/units/energy.d4rt.units", + "lib/defaults/units/pressure.d4rt.units", + "lib/defaults/units/temperature.d4rt.units", + "lib/defaults/units/velocity.d4rt.units", + "lib/defaults/units/mass.d4rt.units", + "lib/defaults/units/angle.d4rt.units", + ]; + + for (final unitRes in unitResources) { + final resource = Resource(unitRes); + final literal = await resource.readAsString(encoding: utf8); + final units = UnitSpec.fromArrayStringLiteral(literal); + corpus.loadUnits(units); + } + } + + Future loadFormulas() async { + final formulaResources = ["lib/defaults/formulas.d4rt"]; + + for (final formRes in formulaResources) { + final resource = Resource(formRes); + final literal = await resource.readAsString(encoding: utf8); + final formulas = Formula.fromArrayStringLiteral(literal); + corpus.loadFormulas(formulas); + } + } + + await loadUnits(); + await loadFormulas(); + + return corpus; +} diff --git a/lib/defaults/formulas.d4rt b/lib/defaults/formulas.d4rt new file mode 100644 index 0000000..c3f7a48 --- /dev/null +++ b/lib/defaults/formulas.d4rt @@ -0,0 +1,109 @@ +[ + // Free fall distance (vertical) + { + "name": "Free Fall Distance", + "description": ''' +Calculates vertical displacement under constant gravity + +`h = ½gt²` + +Where: +- `g` = Gravitational acceleration (9.81 m/s² on Earth) +- `t` = Time in free fall (seconds) + +![Free Fall Diagram](https://upload.wikimedia.org/wikipedia/commons/thumb/7/72/Free-fall.svg/1200px-Free-fall.svg.png)''', + "input": [ + {"name": "t", "unit": "second"}, // Time in seconds + {"name": "g", "unit": "m/s²"} // Gravitational acceleration + ], + "output": {"name": "h", "unit": "m"}, // Height in meters + "d4rtCode": "h = 0.5 * g * pow(t, 2)", + "tags": ["physics", "kinematics"] + }, + + // Newton's Law of Universal Gravitation + { + "name": "Gravitational Force", + "description": ''' +Newton's law of universal gravitation + +`F = G(m₁m₂)/r²` + +Where: +- `G` = Gravitational constant (6.674×10⁻¹¹ N·m²/kg²) +- `m₁`, `m₂` = Masses of two objects +- `r` = Distance between centers of masses + +![Gravitation](https://upload.wikimedia.org/wikipedia/commons/thumb/3/33/NewtonsLawOfUniversalGravitation.svg/1200px-NewtonsLawOfUniversalGravitation.svg.png)''', + "input": [ + {"name": "m1", "unit": "kg"}, // Mass 1 + {"name": "m2", "unit": "kg"}, // Mass 2 + {"name": "r", "unit": "m"} // Distance between masses + ], + "output": {"name": "F", "unit": "N"}, // Force in newtons + "d4rtCode": "F = (6.67430e-11 * m1 * m2) / pow(r, 2)", + "tags": ["physics", "astronomy", "gravity"] + }, + + // Kinetic Energy + { + "name": "Kinetic Energy", + "description": ''' +Energy possessed by a moving object + +`KE = ½mv²` + +Where: +- `m` = Mass of object +- `v` = Velocity of object + +![Kinetic Energy](https://upload.wikimedia.org/wikipedia/commons/thumb/4/44/Kinetic_energy.svg/1200px-Kinetic_energy.svg.png)''', + "input": [ + {"name": "m", "unit": "kg"}, // Mass + {"name": "v", "unit": "m/s"} // Velocity + ], + "output": {"name": "KE", "unit": "J"}, // Energy in joules + "d4rtCode": "KE = 0.5 * m * pow(v, 2)", + "tags": ["physics", "energy", "mechanics"] + }, + + // Projectile Motion Range + { + "name": "Projectile Range", + "description": "Calculates horizontal distance of projectile motion\n\n" + "`R = (v² sin(2θ))/g`\n\n" + "Where:\n" + "- `v` = Initial velocity\n" + "- `θ` = Launch angle\n" + "- `g` = Gravitational acceleration\n\n" + "![Projectile Motion](https://upload.wikimedia.org/wikipedia/commons/thumb/5/52/Projectile_motion_diagram.png/800px-Projectile_motion_diagram.png)", + "input": [ + {"name": "v", "unit": "m/s"}, // Initial velocity + {"name": "θ", "unit": "deg"} // Launch angle + ], + "output": {"name": "R", "unit": "m"}, // Horizontal distance + "d4rtCode": "R = (pow(v, 2) * sin(2 * radians(θ))) / 9.80665", + "tags": ["physics", "kinematics", "projectile"] + }, + + { + "name": "Newton's Second Law", + "description": ''' +Force equals mass times acceleration + +`F = m * a` + +Where: +- `m` = Mass of object (kg) +- `a` = Acceleration (m/s²) + +![Newton's Second Law](https://upload.wikimedia.org/wikipedia/commons/thumb/7/73/Newtonslawsofmotion.jpg/800px-Newtonslawsofmotion.jpg)''', + "input": [ + {"name": "m", "unit": "kg"}, // Mass + {"name": "a", "unit": "m/s²"} // Acceleration + ], + "output": {"name": "F", "unit": "N"}, // Force in newtons + "d4rtCode": "F = m * a", + "tags": ["physics", "mechanics", "newton"] + }, +] diff --git a/lib/units/angle.d4rt.units b/lib/defaults/units/angle.d4rt.units similarity index 100% rename from lib/units/angle.d4rt.units rename to lib/defaults/units/angle.d4rt.units diff --git a/lib/units/area.d4rt.units b/lib/defaults/units/area.d4rt.units similarity index 100% rename from lib/units/area.d4rt.units rename to lib/defaults/units/area.d4rt.units diff --git a/lib/units/distance.d4rt.units b/lib/defaults/units/distance.d4rt.units similarity index 100% rename from lib/units/distance.d4rt.units rename to lib/defaults/units/distance.d4rt.units diff --git a/lib/units/energy.d4rt.units b/lib/defaults/units/energy.d4rt.units similarity index 100% rename from lib/units/energy.d4rt.units rename to lib/defaults/units/energy.d4rt.units diff --git a/lib/units/mass.d4rt.units b/lib/defaults/units/mass.d4rt.units similarity index 100% rename from lib/units/mass.d4rt.units rename to lib/defaults/units/mass.d4rt.units diff --git a/lib/units/pressure.d4rt.units b/lib/defaults/units/pressure.d4rt.units similarity index 100% rename from lib/units/pressure.d4rt.units rename to lib/defaults/units/pressure.d4rt.units diff --git a/lib/units/temperature.d4rt.units b/lib/defaults/units/temperature.d4rt.units similarity index 100% rename from lib/units/temperature.d4rt.units rename to lib/defaults/units/temperature.d4rt.units diff --git a/lib/units/velocity.d4rt.units b/lib/defaults/units/velocity.d4rt.units similarity index 100% rename from lib/units/velocity.d4rt.units rename to lib/defaults/units/velocity.d4rt.units diff --git a/lib/formula_models.dart b/lib/formula_models.dart index 5d38601..dd75a82 100644 --- a/lib/formula_models.dart +++ b/lib/formula_models.dart @@ -196,8 +196,8 @@ class Formula { factory Formula.fromSet(Map theSet) { VariableSpec parseVar(Map varSpec) { String name = SetUtils.stringValue(varSpec, "name"); - String magnitude = SetUtils.stringValue(varSpec, "magnitude"); - return VariableSpec(name: name, unit: magnitude); + String unit = SetUtils.stringValue(varSpec, "unit"); + return VariableSpec(name: name, unit: unit); } String name = SetUtils.stringValue(theSet, "name"); diff --git a/lib/formulas.d4rt b/lib/formulas.d4rt deleted file mode 100644 index c5e3913..0000000 --- a/lib/formulas.d4rt +++ /dev/null @@ -1,109 +0,0 @@ -[ - // Free fall distance (vertical) - { - name: "Free Fall Distance", - description: ''' -Calculates vertical displacement under constant gravity - -`h = ½gt²` - -Where: -- `g` = Gravitational acceleration (9.81 m/s² on Earth) -- `t` = Time in free fall (seconds) - -![Free Fall Diagram](https://upload.wikimedia.org/wikipedia/commons/thumb/7/72/Free-fall.svg/1200px-Free-fall.svg.png)''', - input: [ - {name: "t", unit: "s"}, // Time in seconds - {name: "g", unit: "m/s²"} // Gravitational acceleration - ], - output: {name: "h", unit: "m"}, // Height in meters - d4rtCode: "h = 0.5 * g * pow(t, 2)", - tags: ["physics", "kinematics"] - }, - - // Newton's Law of Universal Gravitation - { - name: "Gravitational Force", - description: ''' -Newton's law of universal gravitation - -`F = G(m₁m₂)/r²` - -Where: -- `G` = Gravitational constant (6.674×10⁻¹¹ N·m²/kg²) -- `m₁`, `m₂` = Masses of two objects -- `r` = Distance between centers of masses - -![Gravitation](https://upload.wikimedia.org/wikipedia/commons/thumb/3/33/NewtonsLawOfUniversalGravitation.svg/1200px-NewtonsLawOfUniversalGravitation.svg.png)''', - input: [ - {name: "m1", unit: "kg"}, // Mass 1 - {name: "m2", unit: "kg"}, // Mass 2 - {name: "r", unit: "m"} // Distance between masses - ], - output: {name: "F", unit: "N"}, // Force in newtons - d4rtCode: "F = (6.67430e-11 * m1 * m2) / pow(r, 2)", - tags: ["physics", "astronomy", "gravity"] - }, - - // Kinetic Energy - { - name: "Kinetic Energy", - description: ''' -Energy possessed by a moving object - -`KE = ½mv²` - -Where: -- `m` = Mass of object -- `v` = Velocity of object - -![Kinetic Energy](https://upload.wikimedia.org/wikipedia/commons/thumb/4/44/Kinetic_energy.svg/1200px-Kinetic_energy.svg.png)''', - input: [ - {name: "m", unit: "kg"}, // Mass - {name: "v", unit: "m/s"} // Velocity - ], - output: {name: "KE", unit: "J"}, // Energy in joules - d4rtCode: "KE = 0.5 * m * pow(v, 2)", - tags: ["physics", "energy", "mechanics"] - }, - - // Projectile Motion Range - { - name: "Projectile Range", - description: "Calculates horizontal distance of projectile motion\n\n" - "`R = (v² sin(2θ))/g`\n\n" - "Where:\n" - "- `v` = Initial velocity\n" - "- `θ` = Launch angle\n" - "- `g` = Gravitational acceleration\n\n" - "![Projectile Motion](https://upload.wikimedia.org/wikipedia/commons/thumb/5/52/Projectile_motion_diagram.png/800px-Projectile_motion_diagram.png)", - input: [ - {name: "v", unit: "m/s"}, // Initial velocity - {name: "θ", unit: "deg"} // Launch angle - ], - output: {name: "R", unit: "m"}, // Horizontal distance - d4rtCode: "R = (pow(v, 2) * sin(2 * radians(θ))) / 9.80665", - tags: ["physics", "kinematics", "projectile"] - }, - - { - name: "Newton's Second Law", - description: ''' -Force equals mass times acceleration - -`F = m * a` - -Where: -- `m` = Mass of object (kg) -- `a` = Acceleration (m/s²) - -![Newton's Second Law](https://upload.wikimedia.org/wikipedia/commons/thumb/7/73/Newtonslawsofmotion.jpg/800px-Newtonslawsofmotion.jpg)''', - input: [ - {name: "m", unit: "kg"}, // Mass - {name: "a", unit: "m/s²"} // Acceleration - ], - output: {name: "F", unit: "N"}, // Force in newtons - d4rtCode: "F = m * a", - tags: ["physics", "mechanics", "newton"] - }, -] diff --git a/lib/main.dart b/lib/main.dart index 6c3dd83..f67246c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -6,11 +6,12 @@ import 'dart:convert'; import 'ai/unit_list.dart'; import 'corpus.dart'; +import 'defaults/default_corpus.dart'; void main() { runApp(MaterialApp( - home: FutureBuilder( - future: createTestCorpus(), + home: FutureBuilder( + future: createDefaultCorpus(), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.done) { if (snapshot.hasError) { @@ -24,31 +25,6 @@ void main() { )); } -Future createTestCorpus() async { - final corpus = UnitCorpus(); - final resources = [ - "lib/units/area.d4rt.units", - "lib/units/distance.d4rt.units", - "lib/units/energy.d4rt.units", - "lib/units/pressure.d4rt.units", - "lib/units/temperature.d4rt.units", - "lib/units/velocity.d4rt.units", - "lib/units/mass.d4rt.units", - "lib/units/angle.d4rt.units" - ]; - - for (final resourcePath in resources) { - try { - final resource = Resource(resourcePath); - final literal = await resource.readAsString(encoding: utf8); - final units = UnitSpec.fromArrayStringLiteral(literal); - corpus.loadUnits(units); - } catch (e) { - print('Error loading $resourcePath: $e'); - } - } - return corpus; -} class MyApp extends StatelessWidget { const MyApp({super.key}); diff --git a/test/formula_models_test.dart b/test/formula_models_test.dart index 52d6fea..666c895 100644 --- a/test/formula_models_test.dart +++ b/test/formula_models_test.dart @@ -1,4 +1,5 @@ import 'package:d4rt_formulas/corpus.dart'; +import 'package:d4rt_formulas/defaults/default_corpus.dart'; import 'package:d4rt_formulas/formula_evaluator.dart'; import 'package:test/test.dart'; import 'package:d4rt_formulas/formula_models.dart'; @@ -8,16 +9,8 @@ import 'package:resource_portable/resource.dart' show Resource; void main() { - Future createTestCorpus() async { - final corpus = UnitCorpus(); - final resources = ["lib/units/distance.d4rt.units", "lib/units/temperature.d4rt.units"]; - for( final r in resources ) { - final resource = Resource(r); - final literal = await resource.readAsString(encoding: utf8); - final units = UnitSpec.fromArrayStringLiteral(literal); - corpus.loadUnits(units); - } - return corpus; + Future createTestCorpus() async { + return createDefaultCorpus(); } test("Parses unit", () {