towards a full example of corpus

This commit is contained in:
Álvaro González 2025-09-20 16:46:21 +02:00
parent 1da336e71a
commit ba0476ed26
18 changed files with 203 additions and 161 deletions

View file

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

View file

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

View file

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

View file

@ -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 = {};
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>;
}
class UnitCorpus {
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;
}

View 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
View 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)
![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"]
},
]

View file

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

View file

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

View file

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

View file

@ -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", () {