magnitude -> unit

This commit is contained in:
Álvaro González 2025-09-15 21:42:15 +02:00
parent 0933745b1f
commit e08474a7eb
8 changed files with 60 additions and 394 deletions

View file

@ -1,2 +1,2 @@
aider --git --watch-files --read CLAUDE.md --notifications aider --git --watch-files --read CLAUDE.md --notifications --no-auto-commits

View file

@ -19,10 +19,10 @@ void main() {
final newtonFormula = Formula( final newtonFormula = Formula(
name: "Newton's Second Law", name: "Newton's Second Law",
input: [ input: [
VariableSpec(name: 'm', magnitude: 'mass'), VariableSpec(name: 'm', unit: 'mass'),
VariableSpec(name: 'a', magnitude: 'acceleration'), VariableSpec(name: 'a', unit: 'acceleration'),
], ],
output: VariableSpec(name: 'F', magnitude: 'force'), output: VariableSpec(name: 'F', unit: 'force'),
d4rtCode: ''' d4rtCode: '''
return m * a; return m * a;
''', ''',
@ -50,11 +50,11 @@ void main() {
final discriminantFormula = Formula( final discriminantFormula = Formula(
name: 'Quadratic Discriminant', name: 'Quadratic Discriminant',
input: [ input: [
VariableSpec(name: 'a', magnitude: 'coefficient'), VariableSpec(name: 'a', unit: 'coefficient'),
VariableSpec(name: 'b', magnitude: 'coefficient'), VariableSpec(name: 'b', unit: 'coefficient'),
VariableSpec(name: 'c', magnitude: 'coefficient'), VariableSpec(name: 'c', unit: 'coefficient'),
], ],
output : VariableSpec(name: 'discriminant', magnitude: 'scalar'), output : VariableSpec(name: 'discriminant', unit: 'scalar'),
d4rtCode: ''' d4rtCode: '''
return b * b - 4 * a * c; return b * b - 4 * a * c;
''', ''',
@ -89,9 +89,9 @@ void main() {
final circleAreaFormula = Formula( final circleAreaFormula = Formula(
name: 'Circle Area', name: 'Circle Area',
input: [ input: [
VariableSpec(name: 'r', magnitude: 'length'), VariableSpec(name: 'r', unit: 'length'),
], ],
output: VariableSpec(name: 'A', magnitude: 'area'), output: VariableSpec(name: 'A', unit: 'area'),
d4rtCode: ''' d4rtCode: '''
var pi = 3.14159265359; var pi = 3.14159265359;
@ -132,12 +132,12 @@ void main() {
final compoundInterestFormula = Formula( final compoundInterestFormula = Formula(
name: 'Compound Interest', name: 'Compound Interest',
input: [ input: [
VariableSpec(name: 'P', magnitude: 'currency'), // Principal VariableSpec(name: 'P', unit: 'currency'), // Principal
VariableSpec(name: 'r', magnitude: 'rate'), // Annual interest rate VariableSpec(name: 'r', unit: 'rate'), // Annual interest rate
VariableSpec(name: 'n', magnitude: 'count'), // Times compounded per year VariableSpec(name: 'n', unit: 'count'), // Times compounded per year
VariableSpec(name: 't', magnitude: 'time'), // Time in years VariableSpec(name: 't', unit: 'time'), // Time in years
], ],
output: VariableSpec(name: 'A', magnitude: 'currency'), // Final amount output: VariableSpec(name: 'A', unit: 'currency'), // Final amount
d4rtCode: ''' d4rtCode: '''
// A = P * (1 + r/n)^(n*t) // A = P * (1 + r/n)^(n*t)
var rate_per_period = r / n; var rate_per_period = r / n;

View file

@ -1,333 +0,0 @@
import 'package:flutter/material.dart';
import '../formula_models.dart';
class FormulaWidget extends StatelessWidget {
final Formula formula;
final double fontSize;
final Color? textColor;
final Color? backgroundColor;
final EdgeInsets padding;
final bool showMagnitudes;
final bool showCode;
const FormulaWidget({
super.key,
required this.formula,
this.fontSize = 16.0,
this.textColor,
this.backgroundColor,
this.padding = const EdgeInsets.all(16.0),
this.showMagnitudes = true,
this.showCode = false,
});
@override
Widget build(BuildContext context) {
return Card(
color: backgroundColor,
child: Padding(
padding: padding,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
// Formula name
Text(
formula.name,
style: TextStyle(
fontSize: fontSize * 1.2,
fontWeight: FontWeight.bold,
color: textColor ?? Theme.of(context).textTheme.headlineSmall?.color,
),
),
const SizedBox(height: 12),
// Formula equation
_buildFormulaEquation(context),
if (showMagnitudes) ...[
const SizedBox(height: 16),
_buildMagnitudesSection(context),
],
if (showCode) ...[
const SizedBox(height: 16),
_buildCodeSection(context),
],
],
),
),
);
}
Widget _buildFormulaEquation(BuildContext context) {
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainerHighest.withOpacity(0.3),
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: Theme.of(context).dividerColor,
width: 1,
),
),
child: Row(
children: [
// Output variable
_buildVariableChip(formula.output, isOutput: true, context: context),
const SizedBox(width: 12),
// Equals sign
Text(
'=',
style: TextStyle(
fontSize: fontSize * 1.5,
fontWeight: FontWeight.bold,
color: textColor ?? Theme.of(context).textTheme.bodyLarge?.color,
),
),
const SizedBox(width: 12),
// Function notation
Text(
'${formula.name}(',
style: TextStyle(
fontSize: fontSize,
fontStyle: FontStyle.italic,
color: textColor ?? Theme.of(context).textTheme.bodyLarge?.color,
),
),
// Input variables
Expanded(
child: Wrap(
spacing: 8,
runSpacing: 4,
children: [
for (int i = 0; i < formula.input.length; i++) ...[
_buildVariableChip(formula.input[i], context: context),
if (i < formula.input.length - 1)
Text(
',',
style: TextStyle(
fontSize: fontSize,
color: textColor ?? Theme.of(context).textTheme.bodyLarge?.color,
),
),
],
],
),
),
Text(
')',
style: TextStyle(
fontSize: fontSize,
fontStyle: FontStyle.italic,
color: textColor ?? Theme.of(context).textTheme.bodyLarge?.color,
),
),
],
),
);
}
Widget _buildVariableChip(VariableSpec variable, {bool isOutput = false, required BuildContext context}) {
final bool hasMagnitude = variable.magnitude != VariableSpec.MAGNITUDELESS;
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: isOutput
? Theme.of(context).colorScheme.primary.withOpacity(0.1)
: Theme.of(context).colorScheme.secondary.withOpacity(0.1),
borderRadius: BorderRadius.circular(6),
border: Border.all(
color: isOutput
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.secondary,
width: 1,
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
variable.name,
style: TextStyle(
fontSize: fontSize * 0.9,
fontWeight: FontWeight.w600,
color: isOutput
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.secondary,
),
),
if (hasMagnitude && showMagnitudes) ...[
const SizedBox(width: 4),
Text(
'[${variable.magnitude}]',
style: TextStyle(
fontSize: fontSize * 0.8,
fontStyle: FontStyle.italic,
color: (isOutput
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.secondary).withOpacity(0.7),
),
),
],
],
),
);
}
Widget _buildMagnitudesSection(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Variables:',
style: TextStyle(
fontSize: fontSize * 0.9,
fontWeight: FontWeight.w600,
color: textColor ?? Theme.of(context).textTheme.titleSmall?.color,
),
),
const SizedBox(height: 8),
Wrap(
spacing: 12,
runSpacing: 6,
children: [
...formula.input.map((variable) => _buildVariableInfo(variable, context)),
_buildVariableInfo(formula.output, context, isOutput: true),
],
),
],
);
}
Widget _buildVariableInfo(VariableSpec variable, BuildContext context, {bool isOutput = false}) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
isOutput ? Icons.arrow_forward : Icons.input,
size: fontSize * 0.8,
color: isOutput
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.secondary,
),
const SizedBox(width: 4),
Text(
'${variable.name}: ',
style: TextStyle(
fontSize: fontSize * 0.85,
fontWeight: FontWeight.w500,
color: textColor ?? Theme.of(context).textTheme.bodyMedium?.color,
),
),
Text(
variable.magnitude == VariableSpec.MAGNITUDELESS ? 'dimensionless' : variable.magnitude,
style: TextStyle(
fontSize: fontSize * 0.85,
fontStyle: FontStyle.italic,
color: (textColor ?? Theme.of(context).textTheme.bodyMedium?.color)?.withOpacity(0.8),
),
),
],
);
}
Widget _buildCodeSection(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Implementation:',
style: TextStyle(
fontSize: fontSize * 0.9,
fontWeight: FontWeight.w600,
color: textColor ?? Theme.of(context).textTheme.titleSmall?.color,
),
),
const SizedBox(height: 8),
Container(
width: double.infinity,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainerHighest.withOpacity(0.5),
borderRadius: BorderRadius.circular(6),
),
child: Text(
formula.d4rtCode,
style: TextStyle(
fontSize: fontSize * 0.8,
fontFamily: 'monospace',
color: textColor ?? Theme.of(context).textTheme.bodySmall?.color,
),
),
),
],
);
}
}
// Example usage and demo
class FormulaDisplayDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Create sample formulas
final sampleFormulas = [
Formula(
name: 'calculateArea',
input: [
VariableSpec(name: 'length', magnitude: 'meters'),
VariableSpec(name: 'width', magnitude: 'meters'),
],
output: VariableSpec(name: 'area', magnitude: 'square_meters'),
d4rtCode: 'return length * width;',
),
Formula(
name: 'pythagoras',
input: [
VariableSpec(name: 'a', magnitude: 'meters'),
VariableSpec(name: 'b', magnitude: 'meters'),
],
output: VariableSpec(name: 'c', magnitude: 'meters'),
d4rtCode: 'return sqrt(a * a + b * b);',
),
Formula(
name: 'normalize',
input: [
VariableSpec(name: 'value', magnitude: VariableSpec.MAGNITUDELESS),
VariableSpec(name: 'max', magnitude: VariableSpec.MAGNITUDELESS),
],
output: VariableSpec(name: 'normalized', magnitude: VariableSpec.MAGNITUDELESS),
d4rtCode: 'return value / max;',
),
];
return Scaffold(
appBar: AppBar(
title: const Text('Formula Display Demo'),
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
children: [
for (final formula in sampleFormulas) ...[
FormulaWidget(
formula: formula,
showCode: true,
),
const SizedBox(height: 16),
],
],
),
),
);
}
}

View file

@ -51,10 +51,10 @@ class _UnitListState extends State<UnitList> {
final sampleFormula = Formula( final sampleFormula = Formula(
name: "Kinetic Energy", name: "Kinetic Energy",
input: [ input: [
VariableSpec(name: 'mass', magnitude: 'kilogram'), VariableSpec(name: 'mass', unit: 'kilogram'),
VariableSpec(name: 'velocity', magnitude: 'meters_per_second'), VariableSpec(name: 'velocity', unit: 'meters_per_second'),
], ],
output: VariableSpec(name: 'energy', magnitude: 'joule'), output: VariableSpec(name: 'energy', unit: 'joule'),
d4rtCode: "energy = 0.5 * mass * velocity * velocity;", d4rtCode: "energy = 0.5 * mass * velocity * velocity;",
); );

View file

@ -87,7 +87,7 @@ class FormulaEvaluator {
/// Gets the magnitude of the single output variable from the formula /// Gets the magnitude of the single output variable from the formula
String getOutputVariableMagnitude(Formula formula) { String getOutputVariableMagnitude(Formula formula) {
// Formula construction already ensures exactly one output variable // Formula construction already ensures exactly one output variable
return formula.output.magnitude; return formula.output.unit;
} }
/// Gets the ordered list of input variable names (alphabetically sorted) /// Gets the ordered list of input variable names (alphabetically sorted)

View file

@ -101,24 +101,24 @@ class UnitSpec {
class VariableSpec { class VariableSpec {
final String name; final String name;
final String magnitude; final String unit;
static final MAGNITUDELESS = "magnitudeless"; static final MAGNITUDELESS = "magnitudeless";
VariableSpec({required this.name, required this.magnitude}); VariableSpec({required this.name, required this.unit});
@override @override
String toString() => 'var($name: $magnitude)'; String toString() => 'var($name: $unit)';
@override @override
bool operator ==(Object other) => bool operator ==(Object other) =>
identical(this, other) || identical(this, other) ||
other is VariableSpec && other is VariableSpec &&
runtimeType == other.runtimeType && runtimeType == other.runtimeType &&
magnitude == other.magnitude && unit == other.unit &&
name == other.name; name == other.name;
@override @override
int get hashCode => Object.hash(magnitude, name); int get hashCode => Object.hash(unit, name);
} }
class Formula { class Formula {
@ -191,7 +191,7 @@ class Formula {
VariableSpec parseVar(Map<Object?, Object?> varSpec) { VariableSpec parseVar(Map<Object?, Object?> varSpec) {
String name = SetUtils.stringValue(varSpec, "name"); String name = SetUtils.stringValue(varSpec, "name");
String magnitude = SetUtils.stringValue(varSpec, "magnitude"); String magnitude = SetUtils.stringValue(varSpec, "magnitude");
return VariableSpec(name: name, magnitude: magnitude); return VariableSpec(name: name, unit: magnitude);
} }
String name = SetUtils.stringValue(theSet, "name"); String name = SetUtils.stringValue(theSet, "name");

View file

@ -1,4 +1,3 @@
import 'package:d4rt_formulas/ai/FormulaWidget.dart';
import 'package:d4rt_formulas/formula_models.dart'; import 'package:d4rt_formulas/formula_models.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';

View file

@ -15,10 +15,10 @@ void main() {
final formula = Formula( final formula = Formula(
name: "Newton's second law", name: "Newton's second law",
input: [ input: [
VariableSpec(name: 'm', magnitude: 'mass'), VariableSpec(name: 'm', unit: 'mass'),
VariableSpec(name: 'a', magnitude: 'acceleration'), VariableSpec(name: 'a', unit: 'acceleration'),
], ],
output: VariableSpec(name: 'F', magnitude: 'force'), output: VariableSpec(name: 'F', unit: 'force'),
d4rtCode: ''' d4rtCode: '''
F = a * m; F = a * m;
''', ''',
@ -36,10 +36,10 @@ void main() {
final formula = Formula( final formula = Formula(
name: 'Simple addition', name: 'Simple addition',
input: [ input: [
VariableSpec(name: 'x', magnitude: 'scalar'), VariableSpec(name: 'x', unit: 'scalar'),
VariableSpec(name: 'y', magnitude: 'scalar'), VariableSpec(name: 'y', unit: 'scalar'),
], ],
output: VariableSpec(name: 'result', magnitude: 'scalar'), output: VariableSpec(name: 'result', unit: 'scalar'),
d4rtCode: ''' d4rtCode: '''
result = x + y; result = x + y;
''', ''',
@ -53,8 +53,8 @@ void main() {
test('handles single input variable', () { test('handles single input variable', () {
final formula = Formula( final formula = Formula(
name: 'Square function', name: 'Square function',
input: [VariableSpec(name: 'n', magnitude: 'scalar')], input: [VariableSpec(name: 'n', unit: 'scalar')],
output: VariableSpec(name: 'result', magnitude: 'scalar'), output: VariableSpec(name: 'result', unit: 'scalar'),
d4rtCode: ''' d4rtCode: '''
result = n * n; result = n * n;
''', ''',
@ -68,11 +68,11 @@ void main() {
final formula = Formula( final formula = Formula(
name: 'Quadratic formula discriminant', name: 'Quadratic formula discriminant',
input: [ input: [
VariableSpec(name: 'a', magnitude: 'scalar'), VariableSpec(name: 'a', unit: 'scalar'),
VariableSpec(name: 'b', magnitude: 'scalar'), VariableSpec(name: 'b', unit: 'scalar'),
VariableSpec(name: 'c', magnitude: 'scalar'), VariableSpec(name: 'c', unit: 'scalar'),
], ],
output: VariableSpec(name: 'discriminant', magnitude: 'scalar'), output: VariableSpec(name: 'discriminant', unit: 'scalar'),
d4rtCode: ''' d4rtCode: '''
discriminant = b * b - 4 * a * c; discriminant = b * b - 4 * a * c;
''', ''',
@ -89,11 +89,11 @@ void main() {
final formula = Formula( final formula = Formula(
name: 'Test order', name: 'Test order',
input: [ input: [
VariableSpec(name: 'z', magnitude: 'scalar'), VariableSpec(name: 'z', unit: 'scalar'),
VariableSpec(name: 'a', magnitude: 'scalar'), VariableSpec(name: 'a', unit: 'scalar'),
VariableSpec(name: 'b', magnitude: 'scalar'), VariableSpec(name: 'b', unit: 'scalar'),
], ],
output: VariableSpec(name: 'result', magnitude: 'scalar'), output: VariableSpec(name: 'result', unit: 'scalar'),
d4rtCode: 'result = a + b + z;', d4rtCode: 'result = a + b + z;',
); );
@ -105,11 +105,11 @@ void main() {
final formula = Formula( final formula = Formula(
name: 'Test argument order', name: 'Test argument order',
input: [ input: [
VariableSpec(name: 'z', magnitude: 'scalar'), VariableSpec(name: 'z', unit: 'scalar'),
VariableSpec(name: 'a', magnitude: 'scalar'), VariableSpec(name: 'a', unit: 'scalar'),
VariableSpec(name: 'y', magnitude: 'scalar'), VariableSpec(name: 'y', unit: 'scalar'),
], ],
output: VariableSpec(name: 'result', magnitude: 'scalar'), output: VariableSpec(name: 'result', unit: 'scalar'),
d4rtCode: ''' d4rtCode: '''
// Variables: a=1, y=2, z=3 // Variables: a=1, y=2, z=3
result = a * 100 + y * 10 + z; result = a * 100 + y * 10 + z;
@ -128,10 +128,10 @@ void main() {
final formula = Formula( final formula = Formula(
name: 'Test formula', name: 'Test formula',
input: [ input: [
VariableSpec(name: 'x', magnitude: 'scalar'), VariableSpec(name: 'x', unit: 'scalar'),
VariableSpec(name: 'y', magnitude: 'scalar'), VariableSpec(name: 'y', unit: 'scalar'),
], ],
output: VariableSpec(name: 'result', magnitude: 'scalar'), output: VariableSpec(name: 'result', unit: 'scalar'),
d4rtCode: 'result = x + y;', d4rtCode: 'result = x + y;',
); );
@ -144,8 +144,8 @@ void main() {
test('throws exception for invalid d4rt code', () { test('throws exception for invalid d4rt code', () {
final formula = Formula( final formula = Formula(
name: 'Invalid code formula', name: 'Invalid code formula',
input: [VariableSpec(name: 'x', magnitude: 'scalar')], input: [VariableSpec(name: 'x', unit: 'scalar')],
output: VariableSpec(name: 'result', magnitude: 'scalar'), output: VariableSpec(name: 'result', unit: 'scalar'),
d4rtCode: 'invalid dart code here!', d4rtCode: 'invalid dart code here!',
); );
@ -160,8 +160,8 @@ void main() {
test('getOutputVariableName returns the single output variable name', () { test('getOutputVariableName returns the single output variable name', () {
final formula = Formula( final formula = Formula(
name: 'Test', name: 'Test',
input: [VariableSpec(name: 'x', magnitude: 'scalar')], input: [VariableSpec(name: 'x', unit: 'scalar')],
output: VariableSpec(name: 'force', magnitude: 'Newton'), output: VariableSpec(name: 'force', unit: 'Newton'),
d4rtCode: 'force = x;', d4rtCode: 'force = x;',
); );
@ -173,8 +173,8 @@ void main() {
() { () {
final formula = Formula( final formula = Formula(
name: 'Test', name: 'Test',
input: [VariableSpec(name: 'x', magnitude: 'scalar')], input: [VariableSpec(name: 'x', unit: 'scalar')],
output: VariableSpec(name: 'force', magnitude: 'Newton'), output: VariableSpec(name: 'force', unit: 'Newton'),
d4rtCode: 'force = x;', d4rtCode: 'force = x;',
); );
@ -185,8 +185,8 @@ void main() {
test('utility methods work correctly with valid formulas', () { test('utility methods work correctly with valid formulas', () {
final validFormula = Formula( final validFormula = Formula(
name: 'Valid Formula', name: 'Valid Formula',
input: [VariableSpec(name: 'x', magnitude: 'scalar')], input: [VariableSpec(name: 'x', unit: 'scalar')],
output: VariableSpec(name: 'result', magnitude: 'Newton'), output: VariableSpec(name: 'result', unit: 'Newton'),
d4rtCode: 'result = x;', d4rtCode: 'result = x;',
); );
@ -200,8 +200,8 @@ void main() {
test('handles integer values', () { test('handles integer values', () {
final formula = Formula( final formula = Formula(
name: 'Integer test', name: 'Integer test',
input: [VariableSpec(name: 'n', magnitude: 'count')], input: [VariableSpec(name: 'n', unit: 'count')],
output: VariableSpec(name: 'result', magnitude: 'count'), output: VariableSpec(name: 'result', unit: 'count'),
d4rtCode: 'result = n + 1;', d4rtCode: 'result = n + 1;',
); );
@ -212,8 +212,8 @@ void main() {
test('handles double values', () { test('handles double values', () {
final formula = Formula( final formula = Formula(
name: 'Double test', name: 'Double test',
input: [VariableSpec(name: 'x', magnitude: 'length')], input: [VariableSpec(name: 'x', unit: 'length')],
output: VariableSpec(name: 'result', magnitude: 'area'), output: VariableSpec(name: 'result', unit: 'area'),
d4rtCode: 'result = x * x;', d4rtCode: 'result = x * x;',
); );