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

View file

@ -87,7 +87,7 @@ class FormulaEvaluator {
/// Gets the magnitude of the single output variable from the formula
String getOutputVariableMagnitude(Formula formula) {
// 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)

View file

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

View file

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