towards a full example of corpus
This commit is contained in:
parent
1da336e71a
commit
ba0476ed26
18 changed files with 203 additions and 161 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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<String?> 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
|
||||
|
|
|
|||
|
|
@ -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});
|
||||
|
||||
|
|
|
|||
|
|
@ -22,12 +22,38 @@ class Multimap<K, V> extends DelegatingMap<K, List<V>> {
|
|||
}
|
||||
}
|
||||
|
||||
class FormulaCorpus{
|
||||
class Corpus{
|
||||
final Multimap<String, Formula> _tags = Multimap.create();
|
||||
final Map<String, Formula> _allFormulas = {};
|
||||
}
|
||||
|
||||
class UnitCorpus {
|
||||
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] ){
|
||||
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<Formula> getTagFormulas(String tag){
|
||||
if( _tags[tag] == null ){
|
||||
return [];
|
||||
}
|
||||
return _tags[tag]?.toList(growable:false) as List<Formula>;
|
||||
}
|
||||
|
||||
final Multimap<String, String> _baseToUnits = Multimap.create();
|
||||
final Map<String, UnitSpec> _allUnits = {};
|
||||
|
||||
|
|
@ -42,19 +68,18 @@ class UnitCorpus {
|
|||
}
|
||||
|
||||
List<String> unitsOfSameMagnitude(String unit){
|
||||
final base = this[unit].baseUnit;
|
||||
final base = getUnit(unit).baseUnit;
|
||||
return _baseToUnits[base] as List<String>;
|
||||
}
|
||||
|
||||
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<UnitSpec> allUnits() => _allUnits.values;
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
46
lib/defaults/default_corpus.dart
Normal file
46
lib/defaults/default_corpus.dart
Normal file
|
|
@ -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<Corpus> createDefaultCorpus() async{
|
||||
final corpus = Corpus();
|
||||
|
||||
Future<void> 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<void> 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;
|
||||
}
|
||||
109
lib/defaults/formulas.d4rt
Normal file
109
lib/defaults/formulas.d4rt
Normal file
|
|
@ -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)
|
||||
|
||||
''',
|
||||
"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
|
||||
|
||||
''',
|
||||
"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
|
||||
|
||||
''',
|
||||
"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"
|
||||
"",
|
||||
"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²)
|
||||
|
||||
''',
|
||||
"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"]
|
||||
},
|
||||
]
|
||||
|
|
@ -196,8 +196,8 @@ class Formula {
|
|||
factory Formula.fromSet(Map<Object?, Object?> theSet) {
|
||||
VariableSpec parseVar(Map<Object?, Object?> 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");
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
''',
|
||||
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
|
||||
|
||||
''',
|
||||
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
|
||||
|
||||
''',
|
||||
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"
|
||||
"",
|
||||
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²)
|
||||
|
||||
''',
|
||||
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"]
|
||||
},
|
||||
]
|
||||
|
|
@ -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<UnitCorpus>(
|
||||
future: createTestCorpus(),
|
||||
home: FutureBuilder<Corpus>(
|
||||
future: createDefaultCorpus(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
if (snapshot.hasError) {
|
||||
|
|
@ -24,31 +25,6 @@ void main() {
|
|||
));
|
||||
}
|
||||
|
||||
Future<UnitCorpus> 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});
|
||||
|
|
|
|||
|
|
@ -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<UnitCorpus> 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<Corpus> createTestCorpus() async {
|
||||
return createDefaultCorpus();
|
||||
}
|
||||
|
||||
test("Parses unit", () {
|
||||
|
|
|
|||
Loading…
Reference in a new issue