Merge branch 'feature/more-copilot-formulas' of ssh://codeberg.org/alvarogonzalezsotillo/d4rt_formulas into feature/more-copilot-formulas
This commit is contained in:
commit
1ae1f9b6d7
19 changed files with 1955 additions and 759 deletions
12
Makefile
12
Makefile
|
|
@ -17,25 +17,25 @@ clean-container: build-container
|
|||
pub-get-container: build-container
|
||||
./flutterw pub get
|
||||
|
||||
test: pub-get-container
|
||||
test:
|
||||
./flutterw test
|
||||
|
||||
build-builders: build-container
|
||||
./flutterw pub run build_runner build --delete-conflicting-outputs
|
||||
|
||||
build-android-release-container: pub-get-container
|
||||
build-android-release-container:
|
||||
./flutterw build apk --release
|
||||
|
||||
build-linux-debug-container: pub-get-container
|
||||
build-linux-debug-container:
|
||||
./flutterw build linux --debug
|
||||
|
||||
build-web-debug-container: pub-get-container
|
||||
build-web-debug-container:
|
||||
./flutterw build web --debug
|
||||
|
||||
run-linux-debug-container: pub-get-container
|
||||
run-linux-debug-container:
|
||||
./flutterw run -d linux
|
||||
|
||||
run-web-debug-container: pub-get-container
|
||||
run-web-debug-container:
|
||||
./flutterw run --web-port $${WEB_PORT:-8081} -d web-server
|
||||
|
||||
run-linux-debug-native:
|
||||
|
|
|
|||
18
TODO.md
18
TODO.md
|
|
@ -32,4 +32,20 @@
|
|||
- [X] Add a Share button to the formula list. It will export the array string literal of the formula with the units from Corpus.withDependencies().
|
||||
- [X] Replace flutter-markdown with flutter-markdown-plus
|
||||
- [X] Heron's formula: investigate why a=3, b=40, c=5 yields NaN. Root cause: input values don't form a valid triangle (violate triangle inequality: 3+5=8 is not > 40). Added documentation note to the formula description.
|
||||
- [ ] Change Share package to share_plus, because share is deprecated.
|
||||
- [X] Refactor ./assets/formulas d4rt files:
|
||||
- [X] Pretty print files as dart literals (like JSON, but allow raw strings r"""like this""")
|
||||
- [X] Ensure there is no formula duplicates. If necesary, move or delete the formula in file formulas.d4rt
|
||||
- [X] defaultCorpus must load all formula files
|
||||
- [X] Create a formula in ./assets/formulas/networking.d4art: input is a string with ip address and mask, output is ip subnet of this address and broadcast address.
|
||||
- [R] Develop a new screen that edits a formula in ./lib/ai directory:
|
||||
- [R] FormulaEditor initializes with a Formula
|
||||
- [R] A textfield allows editing the "name" of the formula
|
||||
- [R] A text area allows editing the "description". A button pops up a preview of the markdown.
|
||||
- [R] There is one row per input variable. The "name" is a textfield. A first drop down allows to select the base unit, and a second dropdown is populated with all derived units of the selected base unit, and the base unit. The unit of the input variable is the derived unit.
|
||||
- [R] Each input variable can be deleted with a button
|
||||
- [R] A button after the inputs variables allows to insert a new input variable
|
||||
- [R] There is one row for the ouput variable, similar to the row for the input variable
|
||||
- [R] d4rtCode is a text area with dart syntax highligthing
|
||||
- [R] At the botton, a button allows to test the edited Formula, launching a FormulaScreen
|
||||
- [ ] When _FormulaScreenState._evaluateFormula() detect an error, instead of show an SnackBar, show a ExpansionTile with "⚠️ There were an error. Show details..." with the details of the exception. The ExpansionTile will be invisible if there is no error.
|
||||
- [ ] Investigate https://pub.dev/packages/quantity
|
||||
|
|
|
|||
507
assets/formula-element-format.md
Normal file
507
assets/formula-element-format.md
Normal file
|
|
@ -0,0 +1,507 @@
|
|||
# Formula and Unit File Format Guide
|
||||
|
||||
This document describes the format for contributing formulas and units to the d4rt_formulas project. It is intended for formula contributors and developers.
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Overview](#overview)
|
||||
2. [Formula Files](#formula-files)
|
||||
3. [Unit Files](#unit-files)
|
||||
4. [Writing Descriptions](#writing-descriptions)
|
||||
5. [Best Practices](#best-practices)
|
||||
6. [Examples](#examples)
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
The project uses two types of asset files:
|
||||
|
||||
| File Type | Location | Extension | Format |
|
||||
|--------------|--------------------|---------------|---------------------------------|
|
||||
| **Formulas** | `assets/formulas/` | `.d4rt` | Dart array literals (JSON-like) |
|
||||
| **Units** | `assets/units/` | `.d4rt.units` | Dart array literals (JSON-like) |
|
||||
|
||||
Both formats use Dart set/array literals with map entries. Files are parsed at runtime to populate the formula calculator.
|
||||
|
||||
---
|
||||
|
||||
## Formula Files
|
||||
|
||||
### File Structure
|
||||
|
||||
Formula files are organized by topic (e.g., `geometry.d4rt`, `electromagnetism.d4rt`). Each file contains a Dart array literal with formula objects:
|
||||
|
||||
```dart
|
||||
[
|
||||
{
|
||||
"name": "Formula Name",
|
||||
"description": r"""Markdown description with LaTeX""",
|
||||
"input": [
|
||||
{"name": "variable1", "unit": "unit_name"},
|
||||
{"name": "variable2", "unit": "unit_name"}
|
||||
],
|
||||
"output": {"name": "result", "unit": "unit_name"},
|
||||
"d4rtCode": "result = expression;",
|
||||
"tags": ["tag1", "tag2"]
|
||||
},
|
||||
// More formulas...
|
||||
]
|
||||
```
|
||||
|
||||
### Formula Object Fields
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|---------------|--------|----------|------------------------------------------------------------------------------------------|
|
||||
| `name` | String | Yes | Human-readable formula name |
|
||||
| `description` | String | Yes | Markdown description with LaTeX math (see [Writing Descriptions](#writing-descriptions)) |
|
||||
| `input` | Array | Yes | List of input variables with their units |
|
||||
| `output` | Object | Yes | Output variable name and unit |
|
||||
| `d4rtCode` | String | Yes | Dart code that computes the result |
|
||||
| `tags` | Array | Yes | Categorization tags for search/filter |
|
||||
|
||||
### Input/Output Format
|
||||
|
||||
**Input variables:**
|
||||
```dart
|
||||
"input": [
|
||||
{"name": "m", "unit": "kilogram"},
|
||||
{"name": "a", "unit": "meters per square second"}
|
||||
]
|
||||
```
|
||||
|
||||
**Output variable:**
|
||||
```dart
|
||||
"output": {"name": "F", "unit": "newton"}
|
||||
```
|
||||
|
||||
### Unit Names
|
||||
|
||||
Unit names must match entries in the `assets/units/` directory. Use the full unit name (lowercase), not the symbol:
|
||||
|
||||
| Correct | Incorrect |
|
||||
|-----------------------|-----------|
|
||||
| `"meter"` | `"m"` |
|
||||
| `"kilogram"` | `"kg"` |
|
||||
| `"meters per second"` | `"m/s"` |
|
||||
| `"square meter"` | `"m²"` |
|
||||
|
||||
### Dart Code (`d4rtCode`)
|
||||
|
||||
The `d4rtCode` field contains valid Dart code that:
|
||||
- Uses input variable names directly
|
||||
- Assigns the result to the output variable name
|
||||
- Can use Dart's `math` library functions (`sin`, `cos`, `sqrt`, `pow`, `pi`, etc.)
|
||||
|
||||
**Simple formula:**
|
||||
```dart
|
||||
"d4rtCode": "F = m * a;"
|
||||
```
|
||||
|
||||
**Multi-line formula:**
|
||||
```dart
|
||||
"d4rtCode": """
|
||||
var radians = angle * (pi / 180);
|
||||
result = sin(radians);
|
||||
"""
|
||||
```
|
||||
|
||||
**With validation:**
|
||||
```dart
|
||||
"d4rtCode": """
|
||||
if (a + b < c) {
|
||||
signal("Invalid triangle: sides do not satisfy triangle inequality");
|
||||
}
|
||||
var s = (a + b + c) / 2;
|
||||
A = sqrt(s * (s - a) * (s - b) * (s - c));
|
||||
"""
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Unit Files
|
||||
|
||||
### File Structure
|
||||
|
||||
Unit files define units of measurement organized by category (e.g., `distance.d4rt.units`, `force.d4rt.units`). Each file contains a Dart array literal with unit objects:
|
||||
|
||||
```dart
|
||||
[
|
||||
{"name": "meter", "symbol": "m", "isBase": true},
|
||||
{"name": "kilometer", "symbol": "km", "baseUnit": "meter", "factor": 1000},
|
||||
// More units...
|
||||
]
|
||||
```
|
||||
|
||||
### Unit Object Fields
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|------------|---------|-------------|---------------------------------------------------------------------|
|
||||
| `name` | String | Yes | Full unit name (lowercase) |
|
||||
| `symbol` | String | Yes | Unit symbol for display |
|
||||
| `isBase` | Boolean | Conditional | `true` if this is a base unit (no conversion needed) |
|
||||
| `baseUnit` | String | Conditional | Name of the base unit for conversion |
|
||||
| `factor` | Number | Conditional | Multiplication factor to convert to base unit |
|
||||
| `toBase` | String | Conditional | Expression/code to convert to base unit (for complex conversions) |
|
||||
| `fromBase` | String | Conditional | Expression/code to convert from base unit (for complex conversions) |
|
||||
|
||||
### Base Units vs Derived Units
|
||||
|
||||
**Base units** define the reference for a category:
|
||||
```dart
|
||||
{"name": "meter", "symbol": "m", "isBase": true}
|
||||
{"name": "newton", "symbol": "N", "isBase": true}
|
||||
{"name": "joule", "symbol": "J", "isBase": true}
|
||||
{"name": "Kelvin", "symbol": "K", "isBase": true}
|
||||
```
|
||||
|
||||
**Derived units** specify conversion to their base unit. There are two types:
|
||||
|
||||
#### Simple Linear Conversions (using `factor`)
|
||||
|
||||
For units where conversion is a simple multiplication:
|
||||
|
||||
```dart
|
||||
{"name": "kilometer", "symbol": "km", "baseUnit": "meter", "factor": 1000}
|
||||
{"name": "inch", "symbol": "in", "baseUnit": "meter", "factor": 0.0254}
|
||||
{"name": "pound-force", "baseUnit": "newton", "factor": 4.44822}
|
||||
```
|
||||
|
||||
The `factor` converts **from** the defined unit **to** the base unit:
|
||||
|
||||
```dart
|
||||
// 1 kilometer = 1000 meters
|
||||
{"name": "kilometer", "baseUnit": "meter", "factor": 1000}
|
||||
|
||||
// 1 inch = 0.0254 meters
|
||||
{"name": "inch", "baseUnit": "meter", "factor": 0.0254}
|
||||
```
|
||||
|
||||
#### Complex Conversions (using `toBase` and `fromBase`)
|
||||
|
||||
For units requiring non-linear conversions (e.g., temperature scales), use `toBase` and `fromBase` expressions. The variable `x` represents the value to convert.
|
||||
|
||||
**Example: Celsius to Kelvin**
|
||||
```dart
|
||||
{
|
||||
"name": "Celsius",
|
||||
"symbol": "°C",
|
||||
"baseUnit": "Kelvin",
|
||||
"toBase": "x + 273.15", // °C → K
|
||||
"fromBase": "x - 273.15", // K → °C
|
||||
}
|
||||
```
|
||||
|
||||
**Example: Fahrenheit to Kelvin**
|
||||
```dart
|
||||
{
|
||||
"name": "Fahrenheit",
|
||||
"symbol": "°F",
|
||||
"baseUnit": "Kelvin",
|
||||
"toBase": "(x - 32) * 5/9 + 273.15", // °F → K
|
||||
"fromBase": "(x - 273.15) * 9/5 + 32", // K → °F
|
||||
}
|
||||
```
|
||||
|
||||
**Example: Multi-line conversion (Gas Mark to Kelvin)**
|
||||
```dart
|
||||
{
|
||||
"name": "Gas Mark",
|
||||
"symbol": "GM",
|
||||
"baseUnit": "Kelvin",
|
||||
"toBase": r"""
|
||||
if (x < 1) {
|
||||
double celsius = (243 - 25 * (log(1 / x) / log(2))) / 1.8;
|
||||
return celsius + 273.15;
|
||||
} else {
|
||||
double celsius = x * 14 + 121;
|
||||
return celsius + 273.15;
|
||||
}
|
||||
""",
|
||||
"fromBase": """
|
||||
double celsius = x - 273.15;
|
||||
if (celsius < 135) {
|
||||
return pow(2, (1.8 * celsius - 243) / 25);
|
||||
} else {
|
||||
return (celsius - 121) / 14;
|
||||
}
|
||||
"""
|
||||
}
|
||||
```
|
||||
|
||||
### Common Temperature Conversions
|
||||
|
||||
| Unit | toBase (→ K) | fromBase (← K) |
|
||||
|------------|------------------------------|------------------------------|
|
||||
| Celsius | `x + 273.15` | `x - 273.15` |
|
||||
| Fahrenheit | `(x - 32) * 5/9 + 273.15` | `(x - 273.15) * 9/5 + 32` |
|
||||
| Rankine | `x * 5/9` | `x * 9/5` |
|
||||
| Réaumur | `x * 5/4 + 273.15` | `(x - 273.15) * 4/5` |
|
||||
| Delisle | `373.15 - x * 2/3` | `(373.15 - x) * 3/2` |
|
||||
| Rømer | `(x - 7.5) * 40/21 + 273.15` | `(x - 273.15) * 21/40 + 7.5` |
|
||||
|
||||
---
|
||||
|
||||
## Writing Descriptions
|
||||
|
||||
The `description` field uses **raw Dart string literals** (`r"""..."""`) with **Markdown** and **LaTeX** math.
|
||||
|
||||
### Format
|
||||
|
||||
```dart
|
||||
"description": r"""
|
||||
Short description of the formula.
|
||||
|
||||
$$F = m \cdot a$$
|
||||
|
||||
Where:
|
||||
- $F$: Force (Newtons)
|
||||
- $m$: Mass (kilograms)
|
||||
- $a$: Acceleration (m/s²)
|
||||
|
||||
Additional context or notes.""",
|
||||
```
|
||||
|
||||
### LaTeX Math
|
||||
|
||||
Use **MathJax/KaTeX** syntax for mathematical expressions:
|
||||
|
||||
| Type | Syntax | Example |
|
||||
|---------------------|-----------------------------|--------------------------|
|
||||
| Inline math | `$...$` | `$F = ma$` |
|
||||
| Display math | `$$...$$` | `$$E = mc^2$$` |
|
||||
| Fractions | `\frac{a}{b}` | `$$\frac{1}{2}mv^2$$` |
|
||||
| Subscripts | `x_i` | `$v_0$` |
|
||||
| Superscripts | `x^2` | `$a^2 + b^2$` |
|
||||
| Greek letters | `\alpha`, `\beta`, `\theta` | `$$\sin(\theta)$$` |
|
||||
| Special symbols | `\cdot`, `\times`, `\pm` | `$m \cdot a$` |
|
||||
| Units in math | `\mathrm{m/s^2}` | `$9.81\ \mathrm{m/s^2}$` |
|
||||
| Scientific notation | `\times 10^{-11}` | `$6.674\times 10^{-11}$` |
|
||||
|
||||
### Including Images
|
||||
|
||||
Add Wikipedia or other educational images using Markdown:
|
||||
|
||||
```markdown
|
||||

|
||||
```
|
||||
|
||||
**Example:**
|
||||
```dart
|
||||
"description": r"""
|
||||
Newton's law of universal gravitation.
|
||||
|
||||
$$F = G\frac{m_1m_2}{r^2}$$
|
||||
|
||||
""",
|
||||
```
|
||||
|
||||
### Description Structure
|
||||
|
||||
A well-structured description includes:
|
||||
|
||||
1. **Opening sentence** - Brief statement of what the formula calculates
|
||||
2. **LaTeX formula** - The mathematical expression in display mode
|
||||
3. **Variable definitions** - List of all variables with units
|
||||
4. **Additional context** - Notes, assumptions, or applications
|
||||
5. **Image** (optional) - Diagram or illustration
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### For Formulas
|
||||
|
||||
1. **Use clear variable names** - Single letters for physics conventions (`F`, `m`, `a`), descriptive names when clarity matters
|
||||
2. **Match units precisely** - Ensure input/output units match what the formula expects
|
||||
3. **Add validation** - Use `signal()` for invalid inputs (e.g., triangle inequality)
|
||||
4. **Include tags** - Add relevant tags for discoverability
|
||||
5. **Use LaTeX for all math** - Even simple formulas should have LaTeX representation
|
||||
6. **Add images** - Include diagrams from Wikipedia when helpful
|
||||
7. **Comment your code** - Use `//` comments before each formula object
|
||||
|
||||
### For Units
|
||||
|
||||
1. **Use lowercase names** - `"meter"` not `"Meter"`
|
||||
2. **Include common conversions** - Add both metric and imperial units when relevant
|
||||
3. **Use standard symbols** - Follow SI conventions where applicable
|
||||
4. **Document the factor** - Ensure conversion factors are accurate
|
||||
|
||||
### For Descriptions
|
||||
|
||||
1. **Be concise but complete** - Explain what the formula does and what each variable means
|
||||
2. **Use consistent formatting** - Follow the established pattern in existing files
|
||||
3. **Include units in variable definitions** - Always specify units for each variable
|
||||
4. **Add context** - Explain when/why the formula is used
|
||||
5. **Note assumptions** - Mention any constraints or special conditions
|
||||
|
||||
---
|
||||
|
||||
## Examples
|
||||
|
||||
### Complete Formula Example
|
||||
|
||||
```dart
|
||||
// Newton's Second Law
|
||||
{
|
||||
"name": "Newton's Second Law",
|
||||
"description": r"""
|
||||
Force equals mass times acceleration.
|
||||
|
||||
$$F = m \cdot a$$
|
||||
|
||||
Where:
|
||||
- $m$: Mass of object ($\mathrm{kg}$)
|
||||
- $a$: Acceleration ($\mathrm{m/s^2}$)
|
||||
|
||||
""",
|
||||
"input": [
|
||||
{"name": "m", "unit": "kilogram"},
|
||||
{"name": "a", "unit": "meters per square second"}
|
||||
],
|
||||
"output": {"name": "F", "unit": "newton"},
|
||||
"d4rtCode": "F = m * a;",
|
||||
"tags": ["physics", "mechanics", "newton"]
|
||||
}
|
||||
```
|
||||
|
||||
### Complete Unit Example
|
||||
|
||||
```dart
|
||||
[
|
||||
{
|
||||
"name": "newton",
|
||||
"symbol": "N",
|
||||
"isBase": true
|
||||
},
|
||||
{
|
||||
"name": "kilonewton",
|
||||
"symbol": "kN",
|
||||
"baseUnit": "newton",
|
||||
"factor": 1000
|
||||
},
|
||||
{
|
||||
"name": "pound-force",
|
||||
"symbol": "lbf",
|
||||
"baseUnit": "newton",
|
||||
"factor": 4.44822
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### Multi-line Dart Code Example
|
||||
|
||||
```dart
|
||||
// Cosine Rule
|
||||
{
|
||||
"name": "Cosine Rule",
|
||||
"description": r"""
|
||||
Generalization of the Pythagorean theorem for any triangle.
|
||||
|
||||
$$c^2 = a^2 + b^2 - 2ab\cos(C)$$
|
||||
|
||||
Where:
|
||||
- $a$, $b$, $c$: Sides of the triangle
|
||||
- $C$: Angle opposite to side $c$""",
|
||||
"input": [
|
||||
{"name": "a", "unit": "meter"},
|
||||
{"name": "b", "unit": "meter"},
|
||||
{"name": "C", "unit": "degree"}
|
||||
],
|
||||
"output": {"name": "c", "unit": "meter"},
|
||||
"d4rtCode": """
|
||||
var angleCRad = C * (pi / 180);
|
||||
c = sqrt(pow(a, 2) + pow(b, 2) - 2*a*b*cos(angleCRad));
|
||||
""",
|
||||
"tags": ["trigonometry", "triangle", "cosine"]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## File Organization
|
||||
|
||||
### Formula Categories
|
||||
|
||||
| File | Topic |
|
||||
|----------------------------------|--------------------------------|
|
||||
| `formulas.d4rt` | General physics formulas |
|
||||
| `geometry.d4rt` | Geometric calculations |
|
||||
| `electromagnetism.d4rt` | Electric and magnetic formulas |
|
||||
| `energy_and_power.d4rt` | Energy, work, and power |
|
||||
| `thermodynamics.d4rt` | Heat and thermodynamics |
|
||||
| `fluids_and_pressure.d4rt` | Fluid mechanics |
|
||||
| `optics.d4rt` | Light and optics |
|
||||
| `trigonometry.d4rt` | Trigonometric relations |
|
||||
| `materials_elasticity.d4rt` | Material properties |
|
||||
| `medical_and_bio.d4rt` | Medical/biological formulas |
|
||||
| `networking.d4rt` | Network calculations |
|
||||
| `conversions_and_constants.d4rt` | Physical constants |
|
||||
| `misc_math.d4rt` | Miscellaneous mathematics |
|
||||
|
||||
### Unit Categories
|
||||
|
||||
| File | Unit Type |
|
||||
|--------------------------|------------------------|
|
||||
| `distance.d4rt.units` | Length/distance |
|
||||
| `mass.d4rt.units` | Mass |
|
||||
| `time.d4rt.units` | Time |
|
||||
| `force.d4rt.units` | Force |
|
||||
| `energy.d4rt.units` | Energy |
|
||||
| `power.d4rt.units` | Power |
|
||||
| `pressure.d4rt.units` | Pressure |
|
||||
| `velocity.d4rt.units` | Speed/velocity |
|
||||
| `area.d4rt.units` | Area |
|
||||
| `volume.d4rt.units` | Volume |
|
||||
| `temperature.d4rt.units` | Temperature |
|
||||
| `angle.d4rt.units` | Angles |
|
||||
| `frequency.d4rt.units` | Frequency |
|
||||
| `electricity.d4rt.units` | Electrical units |
|
||||
| `derived.d4rt.units` | Derived/compound units |
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### Common LaTeX Symbols
|
||||
|
||||
| Symbol | LaTeX | Symbol | LaTeX |
|
||||
|--------|-----------|--------|----------|
|
||||
| × | `\times` | · | `\cdot` |
|
||||
| ± | `\pm` | ÷ | `\div` |
|
||||
| ≤ | `\leq` | ≥ | `\geq` |
|
||||
| √ | `\sqrt{}` | ∞ | `\infty` |
|
||||
| π | `\pi` | θ | `\theta` |
|
||||
| α | `\alpha` | β | `\beta` |
|
||||
| Δ | `\Delta` | δ | `\delta` |
|
||||
| Σ | `\Sigma` | σ | `\sigma` |
|
||||
| Ω | `\Omega` | ω | `\omega` |
|
||||
|
||||
### Common Dart Math Functions
|
||||
|
||||
| Function | Description |
|
||||
|---------------------------------|-----------------------------------|
|
||||
| `sin(x)`, `cos(x)`, `tan(x)` | Trigonometric functions (radians) |
|
||||
| `asin(x)`, `acos(x)`, `atan(x)` | Inverse trig functions |
|
||||
| `sqrt(x)` | Square root |
|
||||
| `pow(x, y)` | x raised to power y |
|
||||
| `log(x)` | Natural logarithm |
|
||||
| `log10(x)` | Base-10 logarithm |
|
||||
| `abs(x)` | Absolute value |
|
||||
| `exp(x)` | e raised to power x |
|
||||
| `pi` | π constant |
|
||||
|
||||
---
|
||||
|
||||
## Contributing
|
||||
|
||||
1. **Choose the right file** - Add formulas to the appropriate category file
|
||||
2. **Follow the format** - Match the structure of existing entries
|
||||
3. **Test your code** - Ensure `d4rtCode` is valid Dart syntax
|
||||
4. **Add description** - Include complete LaTeX documentation
|
||||
5. **Tag appropriately** - Add relevant tags for searchability
|
||||
6. **Review** - Check existing formulas for consistency
|
||||
|
||||
For questions or clarifications, refer to existing formulas in the `assets/formulas/` directory as examples.
|
||||
|
|
@ -1,2 +1,21 @@
|
|||
[
|
||||
// Temperature Converter
|
||||
{
|
||||
"name": "Temperature converter",
|
||||
"description": r"""
|
||||
Simple temperature converter example that returns the input value (Kelvin) as output.
|
||||
|
||||
Formula: $$T_{out} = T_{in}$$
|
||||
|
||||
Inputs: `Input` in kelvin (K).
|
||||
Output: `Output` in kelvin (K).
|
||||
|
||||
""",
|
||||
"input": [
|
||||
{"name": "Input", "unit": "Kelvin"}
|
||||
],
|
||||
"output": {"name": "Output", "unit": "Kelvin"},
|
||||
"d4rtCode": "Output = Input;",
|
||||
"tags": ["converter", "temperature"]
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,20 +1,59 @@
|
|||
[
|
||||
{"name":"Coulomb's Law","input":[{"name":"q1","unit":"coulomb"},{"name":"q2","unit":"coulomb"},{"name":"r","unit":"meter"}],"output":{"name":"F","unit":"newton"},"d4rtCode":"F = (8.9875517923e9 * q1 * q2) / pow(r, 2);","description":r"""
|
||||
Calculates the magnitude of the electrostatic force between two point charges.
|
||||
Formula: $F = k \dfrac{q_1 q_2}{r^2}$ where $k = 8.9875517923\times10^9\ \mathrm{N\,m^2/C^2}$.
|
||||
Inputs: `q1`, `q2` in coulombs; `r` in meters.
|
||||
Output: Force `F` in newtons (N).
|
||||
""","tags":["physics","electricity","electrostatics"]},
|
||||
{"name":"Ohm's Law","input":[{"name":"I","unit":"ampere"},{"name":"R","unit":"ohm"}],"output":{"name":"V","unit":"volt"},"d4rtCode":"V = I * R;","description":r"""
|
||||
Relates voltage, current and resistance for a linear resistor.
|
||||
Formula: $V = I\,R$.
|
||||
Inputs: current `I` in amperes (A), resistance `R` in ohms (Ω).
|
||||
Output: voltage `V` in volts (V).
|
||||
""","tags":["physics","electricity","electronics"]},
|
||||
{"name":"Electric Power","input":[{"name":"V","unit":"volt"},{"name":"I","unit":"ampere"}],"output":{"name":"P","unit":"watt"},"d4rtCode":"P = V * I;","description":r"""
|
||||
Calculates electrical power delivered to a load.
|
||||
Formula: $P = V\,I$ (also $P = I^2 R$ or $P = V^2 / R$ when substituting Ohm's law).
|
||||
Inputs: voltage `V` in volts (V), current `I` in amperes (A).
|
||||
Output: power `P` in watts (W).
|
||||
""","tags":["physics","electricity","electronics"]}
|
||||
// Coulomb's Law
|
||||
{
|
||||
"name": "Coulomb's Law",
|
||||
"description": r"""
|
||||
Calculates the magnitude of the electrostatic force between two point charges.
|
||||
|
||||
Formula: $F = k \dfrac{q_1 q_2}{r^2}$ where $k = 8.9875517923\times10^9\ \mathrm{N\,m^2/C^2}$.
|
||||
|
||||
Inputs: `q1`, `q2` in coulombs; `r` in meters.
|
||||
Output: Force `F` in newtons (N).""",
|
||||
"input": [
|
||||
{"name": "q1", "unit": "coulomb"},
|
||||
{"name": "q2", "unit": "coulomb"},
|
||||
{"name": "r", "unit": "meter"}
|
||||
],
|
||||
"output": {"name": "F", "unit": "newton"},
|
||||
"d4rtCode": "F = (8.9875517923e9 * q1 * q2) / pow(r, 2);",
|
||||
"tags": ["physics", "electricity", "electrostatics"]
|
||||
},
|
||||
|
||||
// Ohm's Law
|
||||
{
|
||||
"name": "Ohm's Law",
|
||||
"description": r"""
|
||||
Relates voltage, current and resistance for a linear resistor.
|
||||
|
||||
Formula: $V = I\,R$.
|
||||
|
||||
Inputs: current `I` in amperes (A), resistance `R` in ohms (Ω).
|
||||
Output: voltage `V` in volts (V).""",
|
||||
"input": [
|
||||
{"name": "I", "unit": "ampere"},
|
||||
{"name": "R", "unit": "ohm"}
|
||||
],
|
||||
"output": {"name": "V", "unit": "volt"},
|
||||
"d4rtCode": "V = I * R;",
|
||||
"tags": ["physics", "electricity", "electronics"]
|
||||
},
|
||||
|
||||
// Electric Power
|
||||
{
|
||||
"name": "Electric Power",
|
||||
"description": r"""
|
||||
Calculates electrical power delivered to a load.
|
||||
|
||||
Formula: $P = V\,I$ (also $P = I^2 R$ or $P = V^2 / R$ when substituting Ohm's law).
|
||||
|
||||
Inputs: voltage `V` in volts (V), current `I` in amperes (A).
|
||||
Output: power `P` in watts (W).""",
|
||||
"input": [
|
||||
{"name": "V", "unit": "volt"},
|
||||
{"name": "I", "unit": "ampere"}
|
||||
],
|
||||
"output": {"name": "P", "unit": "watt"},
|
||||
"d4rtCode": "P = V * I;",
|
||||
"tags": ["physics", "electricity", "electronics"]
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,2 +1,98 @@
|
|||
[
|
||||
// Kinetic Energy
|
||||
{
|
||||
"name": "Kinetic Energy",
|
||||
"description": r"""
|
||||
Energy possessed by a moving object.
|
||||
|
||||
$$KE = \frac{1}{2}mv^2$$
|
||||
|
||||
Where:
|
||||
- $m$: Mass (kg)
|
||||
- $v$: Velocity (m/s)
|
||||
|
||||
""",
|
||||
"input": [
|
||||
{"name": "m", "unit": "kilogram"},
|
||||
{"name": "v", "unit": "meters per second"}
|
||||
],
|
||||
"output": {"name": "KE", "unit": "joule"},
|
||||
"d4rtCode": "KE = 0.5 * m * pow(v, 2);",
|
||||
"tags": ["physics", "energy", "mechanics"]
|
||||
},
|
||||
|
||||
// Work
|
||||
{
|
||||
"name": "Work",
|
||||
"description": r"""
|
||||
Energy transferred when a force moves an object.
|
||||
|
||||
$$W = F d \cos(\theta)$$
|
||||
|
||||
Where:
|
||||
- $W$: Work (Joules)
|
||||
- $F$: Force (Newtons)
|
||||
- $d$: Displacement (meters)
|
||||
- $\theta$: Angle between force and displacement
|
||||
|
||||
""",
|
||||
"input": [
|
||||
{"name": "F", "unit": "newton"},
|
||||
{"name": "d", "unit": "meter"},
|
||||
{"name": "theta", "unit": "degree"}
|
||||
],
|
||||
"output": {"name": "W", "unit": "joule"},
|
||||
"d4rtCode": """
|
||||
var thetaRad = theta * (pi / 180);
|
||||
W = F * d * cos(thetaRad);
|
||||
""",
|
||||
"tags": ["physics", "energy", "mechanics"]
|
||||
},
|
||||
|
||||
// Power
|
||||
{
|
||||
"name": "Power",
|
||||
"description": r"""
|
||||
Rate at which work is done or energy is transferred.
|
||||
|
||||
$$P = \frac{W}{t}$$
|
||||
|
||||
Where:
|
||||
- $P$: Power (Watts)
|
||||
- $W$: Work or energy (Joules)
|
||||
- $t$: Time (seconds)
|
||||
|
||||
""",
|
||||
"input": [
|
||||
{"name": "W", "unit": "joule"},
|
||||
{"name": "t", "unit": "second"}
|
||||
],
|
||||
"output": {"name": "P", "unit": "watt"},
|
||||
"d4rtCode": "P = W / t;",
|
||||
"tags": ["physics", "energy", "mechanics"]
|
||||
},
|
||||
|
||||
// Mass-Energy Equivalence
|
||||
{
|
||||
"name": "Mass-Energy Equivalence",
|
||||
"description": r"""
|
||||
Einstein's mass-energy equivalence relation.
|
||||
|
||||
$$E = mc^2$$
|
||||
|
||||
Where:
|
||||
- $E$: Energy (Joules)
|
||||
- $m$: Mass (kg)
|
||||
- $c$: Speed of light $299{,}792{,}458\ \mathrm{m/s}$
|
||||
|
||||
This shows mass can be converted to energy and vice versa.
|
||||
|
||||
""",
|
||||
"input": [
|
||||
{"name": "m", "unit": "kilogram"}
|
||||
],
|
||||
"output": {"name": "E", "unit": "joule"},
|
||||
"d4rtCode": "E = m * pow(299792458, 2);",
|
||||
"tags": ["physics", "relativity", "energy"]
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
[
|
||||
{"name":"Density","description":r"""
|
||||
// Density
|
||||
{
|
||||
"name": "Density",
|
||||
"description": r"""
|
||||
Mass per unit volume of a substance.
|
||||
|
||||
$$\rho = \frac{m}{V}$$
|
||||
|
|
@ -11,9 +14,20 @@ Where:
|
|||
|
||||
Density is an intrinsic property of materials.
|
||||
|
||||

|
||||
""","input":[{"name":"m","unit":"kilogram"},{"name":"V","unit":"cubic meter"}],"output":{"name":"rho","unit":"kilogram per cubic meter"},"d4rtCode":"rho = m / V;","tags":["physics","mechanics","material"]},
|
||||
{"name":"Pressure","description":r"""
|
||||
""",
|
||||
"input": [
|
||||
{"name": "m", "unit": "kilogram"},
|
||||
{"name": "V", "unit": "cubic meter"}
|
||||
],
|
||||
"output": {"name": "rho", "unit": "kilogram per cubic meter"},
|
||||
"d4rtCode": "rho = m / V;",
|
||||
"tags": ["physics", "mechanics", "material"]
|
||||
},
|
||||
|
||||
// Pressure
|
||||
{
|
||||
"name": "Pressure",
|
||||
"description": r"""
|
||||
Force applied perpendicular to a surface per unit area.
|
||||
|
||||
$$P = \frac{F}{A}$$
|
||||
|
|
@ -25,9 +39,20 @@ Where:
|
|||
|
||||
Pressure is transmitted equally in all directions in fluids.
|
||||
|
||||

|
||||
""","input":[{"name":"F","unit":"newton"},{"name":"A","unit":"square meter"}],"output":{"name":"P","unit":"pascal"},"d4rtCode":"P = F / A;","tags":["physics","mechanics","fluid"]},
|
||||
{"name":"Buoyant Force","description":r"""
|
||||
""",
|
||||
"input": [
|
||||
{"name": "F", "unit": "newton"},
|
||||
{"name": "A", "unit": "square meter"}
|
||||
],
|
||||
"output": {"name": "P", "unit": "pascal"},
|
||||
"d4rtCode": "P = F / A;",
|
||||
"tags": ["physics", "mechanics", "fluid"]
|
||||
},
|
||||
|
||||
// Buoyant Force
|
||||
{
|
||||
"name": "Buoyant Force",
|
||||
"description": r"""
|
||||
Upward force exerted on an object immersed in a fluid (Archimedes' principle).
|
||||
|
||||
$$F_b = \rho g V$$
|
||||
|
|
@ -40,7 +65,14 @@ Where:
|
|||
|
||||
An object floats when buoyant force equals its weight.
|
||||
|
||||

|
||||
|
||||
""","input":[{"name":"rho","unit":"kilogram per cubic meter"},{"name":"g","unit":"meters per square second"},{"name":"V","unit":"cubic meter"}],"output":{"name":"Fb","unit":"newton"},"d4rtCode":"Fb = rho * g * V;","tags":["physics","fluid","mechanics"]}
|
||||
""",
|
||||
"input": [
|
||||
{"name": "rho", "unit": "kilogram per cubic meter"},
|
||||
{"name": "g", "unit": "meters per square second"},
|
||||
{"name": "V", "unit": "cubic meter"}
|
||||
],
|
||||
"output": {"name": "Fb", "unit": "newton"},
|
||||
"d4rtCode": "Fb = rho * g * V;",
|
||||
"tags": ["physics", "fluid", "mechanics"]
|
||||
}
|
||||
]
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,13 +1,22 @@
|
|||
[
|
||||
// Geometry formulas extracted from formulas.d4rt
|
||||
// Area of Circle
|
||||
{
|
||||
"name": "Area of Circle",
|
||||
"description": r'''\nArea enclosed by a circle\n\n$$A = \pi r^2$$\n\nWhere:\n- $A$: Area (square meters)\n- $r$: Radius (meters)\n- $\pi$: Pi ($\approx 3.14159$)\n\nThe area is proportional to the square of the radius.''',
|
||||
"description": r"""
|
||||
Area enclosed by a circle
|
||||
|
||||
$$A = \pi r^2$$
|
||||
|
||||
Where:
|
||||
- $A$: Area (square meters)
|
||||
- $r$: Radius (meters)
|
||||
- $\pi$: Pi ($\approx 3.14159$)
|
||||
|
||||
The area is proportional to the square of the radius.""",
|
||||
"input": [
|
||||
{"name": "r", "unit": "meter"} // Radius
|
||||
{"name": "r", "unit": "meter"}
|
||||
],
|
||||
"output": {"name": "A", "unit": "square meter"}, // Area
|
||||
"output": {"name": "A", "unit": "square meter"},
|
||||
"d4rtCode": "A = pi * pow(r, 2);",
|
||||
"tags": ["geometry", "circle", "area"]
|
||||
},
|
||||
|
|
@ -15,11 +24,21 @@
|
|||
// Circumference of Circle
|
||||
{
|
||||
"name": "Circumference of Circle",
|
||||
"description": r'''\nPerimeter (distance around) a circle\n\n$$C = 2\pi r$$\n\nWhere:\n- $C$: Circumference (meters)\n- $r$: Radius (meters)\n- $\pi$: Pi ($\approx 3.14159$)\n\nThe circumference is proportional to the radius.''',
|
||||
"description": r"""
|
||||
Perimeter (distance around) a circle
|
||||
|
||||
$$C = 2\pi r$$
|
||||
|
||||
Where:
|
||||
- $C$: Circumference (meters)
|
||||
- $r$: Radius (meters)
|
||||
- $\pi$: Pi ($\approx 3.14159$)
|
||||
|
||||
The circumference is proportional to the radius.""",
|
||||
"input": [
|
||||
{"name": "r", "unit": "meter"} // Radius
|
||||
{"name": "r", "unit": "meter"}
|
||||
],
|
||||
"output": {"name": "C", "unit": "meter"}, // Circumference
|
||||
"output": {"name": "C", "unit": "meter"},
|
||||
"d4rtCode": "C = 2 * pi * r;",
|
||||
"tags": ["geometry", "circle", "perimeter"]
|
||||
},
|
||||
|
|
@ -27,12 +46,22 @@
|
|||
// Area of Triangle
|
||||
{
|
||||
"name": "Area of Triangle",
|
||||
"description": r'''\nArea enclosed by a triangle\n\n$$A = \frac{1}{2}bh$$\n\nWhere:\n- $A$: Area (square meters)\n- $b$: Base length (meters)\n- $h$: Height perpendicular to base (meters)\n\nThis formula works for any triangle.''',
|
||||
"description": r"""
|
||||
Area enclosed by a triangle
|
||||
|
||||
$$A = \frac{1}{2}bh$$
|
||||
|
||||
Where:
|
||||
- $A$: Area (square meters)
|
||||
- $b$: Base length (meters)
|
||||
- $h$: Height perpendicular to base (meters)
|
||||
|
||||
This formula works for any triangle.""",
|
||||
"input": [
|
||||
{"name": "b", "unit": "meter"}, // Base
|
||||
{"name": "h", "unit": "meter"} // Height
|
||||
{"name": "b", "unit": "meter"},
|
||||
{"name": "h", "unit": "meter"}
|
||||
],
|
||||
"output": {"name": "A", "unit": "square meter"}, // Area
|
||||
"output": {"name": "A", "unit": "square meter"},
|
||||
"d4rtCode": "A = 0.5 * b * h;",
|
||||
"tags": ["geometry", "triangle", "area"]
|
||||
},
|
||||
|
|
@ -40,12 +69,22 @@
|
|||
// Area of Rectangle
|
||||
{
|
||||
"name": "Area of Rectangle",
|
||||
"description": r'''\nArea enclosed by a rectangle\n\n$$A = lw$$\n\nWhere:\n- $A$: Area (square meters)\n- $l$: Length (meters)\n- $w$: Width (meters)\n\nThe area is the product of length and width.''',
|
||||
"description": r"""
|
||||
Area enclosed by a rectangle
|
||||
|
||||
$$A = lw$$
|
||||
|
||||
Where:
|
||||
- $A$: Area (square meters)
|
||||
- $l$: Length (meters)
|
||||
- $w$: Width (meters)
|
||||
|
||||
The area is the product of length and width.""",
|
||||
"input": [
|
||||
{"name": "l", "unit": "meter"}, // Length
|
||||
{"name": "w", "unit": "meter"} // Width
|
||||
{"name": "l", "unit": "meter"},
|
||||
{"name": "w", "unit": "meter"}
|
||||
],
|
||||
"output": {"name": "A", "unit": "square meter"}, // Area
|
||||
"output": {"name": "A", "unit": "square meter"},
|
||||
"d4rtCode": "A = l * w;",
|
||||
"tags": ["geometry", "rectangle", "area"]
|
||||
},
|
||||
|
|
@ -53,13 +92,23 @@
|
|||
// Area of Trapezoid
|
||||
{
|
||||
"name": "Area of Trapezoid",
|
||||
"description": r'''\nArea enclosed by a trapezoid\n\n$$A = \frac{1}{2}(a+b)h$$\n\nWhere:\n- $A$: Area (square meters)\n- $a, b$: Lengths of parallel sides (meters)\n- $h$: Height (perpendicular distance between parallel sides, meters)\n\nThe area is the average of parallel sides times height.''',
|
||||
"description": r"""
|
||||
Area enclosed by a trapezoid
|
||||
|
||||
$$A = \frac{1}{2}(a+b)h$$
|
||||
|
||||
Where:
|
||||
- $A$: Area (square meters)
|
||||
- $a, b$: Lengths of parallel sides (meters)
|
||||
- $h$: Height (perpendicular distance between parallel sides, meters)
|
||||
|
||||
The area is the average of parallel sides times height.""",
|
||||
"input": [
|
||||
{"name": "a", "unit": "meter"}, // Parallel side 1
|
||||
{"name": "b", "unit": "meter"}, // Parallel side 2
|
||||
{"name": "h", "unit": "meter"} // Height
|
||||
{"name": "a", "unit": "meter"},
|
||||
{"name": "b", "unit": "meter"},
|
||||
{"name": "h", "unit": "meter"}
|
||||
],
|
||||
"output": {"name": "A", "unit": "square meter"}, // Area
|
||||
"output": {"name": "A", "unit": "square meter"},
|
||||
"d4rtCode": "A = 0.5 * (a + b) * h;",
|
||||
"tags": ["geometry", "trapezoid", "area"]
|
||||
},
|
||||
|
|
@ -67,12 +116,23 @@
|
|||
// Area of Regular Polygon
|
||||
{
|
||||
"name": "Area of Regular Polygon",
|
||||
"description": r'''\nArea of a regular polygon with n sides\n\n$$A = \frac{1}{4}ns^2\cot(\frac{\pi}{n})$$\n\nWhere:\n- $A$: Area (square meters)\n- $n$: Number of sides\n- $s$: Side length (meters)\n- $\pi$: Pi ($\approx 3.14159$)\n\nThis formula works for any regular polygon (equal sides and angles).''',
|
||||
"description": r"""
|
||||
Area of a regular polygon with n sides
|
||||
|
||||
$$A = \frac{1}{4}ns^2\cot(\frac{\pi}{n})$$
|
||||
|
||||
Where:
|
||||
- $A$: Area (square meters)
|
||||
- $n$: Number of sides
|
||||
- $s$: Side length (meters)
|
||||
- $\pi$: Pi ($\approx 3.14159$)
|
||||
|
||||
This formula works for any regular polygon (equal sides and angles).""",
|
||||
"input": [
|
||||
{"name": "n", "unit": "scalar"}, // Number of sides
|
||||
{"name": "s", "unit": "meter"} // Side length
|
||||
{"name": "n", "unit": "scalar"},
|
||||
{"name": "s", "unit": "meter"}
|
||||
],
|
||||
"output": {"name": "A", "unit": "square meter"}, // Area
|
||||
"output": {"name": "A", "unit": "square meter"},
|
||||
"d4rtCode": "A = 0.25 * n * pow(s, 2) * (cos(pi/n) / sin(pi/n));",
|
||||
"tags": ["geometry", "polygon", "area"]
|
||||
},
|
||||
|
|
@ -80,11 +140,20 @@
|
|||
// Sum of Interior Angles of Polygon
|
||||
{
|
||||
"name": "Sum of Interior Angles",
|
||||
"description": r'''\nSum of interior angles of a polygon\n\n$$S = (n - 2) \times 180°$$\n\nWhere:\n- $S$: Sum of interior angles (degrees)\n- $n$: Number of sides\n\nThis formula works for any simple polygon.''',
|
||||
"description": r"""
|
||||
Sum of interior angles of a polygon
|
||||
|
||||
$$S = (n - 2) \times 180°$$
|
||||
|
||||
Where:
|
||||
- $S$: Sum of interior angles (degrees)
|
||||
- $n$: Number of sides
|
||||
|
||||
This formula works for any simple polygon.""",
|
||||
"input": [
|
||||
{"name": "n", "unit": "scalar"} // Number of sides
|
||||
{"name": "n", "unit": "scalar"}
|
||||
],
|
||||
"output": {"name": "S", "unit": "degree"}, // Sum of angles
|
||||
"output": {"name": "S", "unit": "degree"},
|
||||
"d4rtCode": "S = (n - 2) * 180;",
|
||||
"tags": ["geometry", "polygon", "angles"]
|
||||
},
|
||||
|
|
@ -92,13 +161,25 @@
|
|||
// Heron's Formula (Area of Triangle)
|
||||
{
|
||||
"name": "Heron's Formula",
|
||||
"description": r'''\nArea of a triangle using only side lengths\n\n$$A = \sqrt{s(s-a)(s-b)(s-c)}$$\n\nWhere:\n- $A$: Area (square meters)\n- $a, b, c$: Side lengths (meters)\n- $s$: Semi-perimeter $= \frac{a+b+c}{2}$\n\nThis formula is useful when height is unknown.\n\n**Note:** The side lengths must satisfy the triangle inequality: the sum of any two sides must be greater than the third side (a+b>c, a+c>b, b+c>a). If this condition is not met, the formula returns NaN.''',
|
||||
"description": r"""
|
||||
Area of a triangle using only side lengths
|
||||
|
||||
$$A = \sqrt{s(s-a)(s-b)(s-c)}$$
|
||||
|
||||
Where:
|
||||
- $A$: Area (square meters)
|
||||
- $a, b, c$: Side lengths (meters)
|
||||
- $s$: Semi-perimeter $= \frac{a+b+c}{2}$
|
||||
|
||||
This formula is useful when height is unknown.
|
||||
|
||||
**Note:** The side lengths must satisfy the triangle inequality: the sum of any two sides must be greater than the third side (a+b>c, a+c>b, b+c>a). If this condition is not met, the formula returns NaN.""",
|
||||
"input": [
|
||||
{"name": "a", "unit": "meter"}, // Side 1
|
||||
{"name": "b", "unit": "meter"}, // Side 2
|
||||
{"name": "c", "unit": "meter"} // Side 3
|
||||
{"name": "a", "unit": "meter"},
|
||||
{"name": "b", "unit": "meter"},
|
||||
{"name": "c", "unit": "meter"}
|
||||
],
|
||||
"output": {"name": "A", "unit": "square meter"}, // Area
|
||||
"output": {"name": "A", "unit": "square meter"},
|
||||
"d4rtCode": """
|
||||
if( a + b < c || a+c < b || b+c < a ){
|
||||
signal( "There is not a valid triangle with those longitudes" );
|
||||
|
|
|
|||
|
|
@ -1,4 +1,25 @@
|
|||
[
|
||||
{"name":"Hooke's Law","input":[{"name":"k","unit":"newton per meter"},{"name":"x","unit":"meter"}],"output":{"name":"F","unit":"newton"},"d4rtCode":"F = -k * x;","tags":["physics","elasticity","oscillations"]}
|
||||
// Hooke's Law
|
||||
{
|
||||
"name": "Hooke's Law",
|
||||
"description": r"""
|
||||
Force exerted by a spring is proportional to its displacement.
|
||||
|
||||
$$F = -kx$$
|
||||
|
||||
Where:
|
||||
- $F$: Restoring force (Newtons)
|
||||
- $k$: Spring constant (N/m)
|
||||
- $x$: Displacement from equilibrium (meters)
|
||||
|
||||
The negative sign indicates the force opposes the displacement.""",
|
||||
"input": [
|
||||
{"name": "k", "unit": "newton per meter"},
|
||||
{"name": "x", "unit": "meter"}
|
||||
],
|
||||
"output": {"name": "F", "unit": "newton"},
|
||||
"d4rtCode": "F = -k * x;",
|
||||
"tags": ["physics", "elasticity", "oscillations"]
|
||||
}
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,16 @@
|
|||
[
|
||||
{"name":"Apgar Score","input":[{"name":"HeartRate","values":["Absent","< 100 bpm>","> 100 bpm"]},{"name":"Breathing","values":["Absent","Weak, irregular","Strong, robust cry"]},{"name":"MuscleTone","values":["None","Some","Flexed arms/leg, resists extension"]},{"name":"Reflexes","values":["No response","Grimace on aggressive stimulation","Cry on stimulation"]},{"name":"SkinColor","values":["Blue or pale","Blue extremities, pink body","Pink"]}],"output":{"name":"Result","unit":"string"},"d4rtCode":"var total = indexOf(\"HeartRate\") + indexOf(\"Breathing\") + indexOf(\"MuscleTone\") + indexOf(\"Reflexes\") + indexOf(\"SkinColor\"); late var interpretation; if( total < 4 ) { interpretation = 'Critical condition'; } else if( total < 7 ){ interpretation = 'Needs assistance'; } else { interpretation = 'Normal'; } Result = 'Score: \$total - \$interpretation';","tags":["medical","pediatrics","assessment"]}
|
||||
]
|
||||
{"name":"Apgar Score","description":r"""
|
||||
Newborn health assessment scoring system performed at 1 and 5 minutes after birth.
|
||||
|
||||
The Apgar score sums five categories (0–2 points each):
|
||||
1. Heart rate
|
||||
2. Respiratory effort
|
||||
3. Muscle tone
|
||||
4. Reflex response
|
||||
5. Color
|
||||
|
||||
Total score ranges from 0 to 10. Higher scores indicate better newborn condition.
|
||||
|
||||

|
||||
""","input":[{"name":"HeartRate","values":["Absent","< 100 bpm>","> 100 bpm"]},{"name":"Breathing","values":["Absent","Weak, irregular","Strong, robust cry"]},{"name":"MuscleTone","values":["None","Some","Flexed arms/leg, resists extension"]},{"name":"Reflexes","values":["No response","Grimace on aggressive stimulation","Cry on stimulation"]},{"name":"SkinColor","values":["Blue or pale","Blue extremities, pink body","Pink"]}],"output":{"name":"Result","unit":"string"},"d4rtCode":"var total = indexOf(\"HeartRate\") + indexOf(\"Breathing\") + indexOf(\"MuscleTone\") + indexOf(\"Reflexes\") + indexOf(\"SkinColor\"); late var interpretation; if( total < 4 ) { interpretation = 'Critical condition'; } else if( total < 7 ){ interpretation = 'Needs assistance'; } else { interpretation = 'Normal'; } Result = 'Score: \$total - \$interpretation';","tags":["medical","pediatrics","assessment"]}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,3 +1,26 @@
|
|||
[
|
||||
{"name":"Compare price per mass","description":"Compares two products by their price per mass and returns which is cheaper, including price per kg for each product.","input":[{"name":"price1","unit":"currency"},{"name":"mass1","unit":"kilogram"},{"name":"price2","unit":"currency"},{"name":"mass2","unit":"kilogram"}],"output":{"name":"Result","unit":"string"},"d4rtCode":"var p1 = price1 / mass1; var p2 = price2 / mass2; if (p1 < p2) { Result = 'first product is cheaper at \$\{p1.toStringAsFixed(2)\} currency/kg vs \$\{p2.toStringAsFixed(2)\} currency/kg'; } else if (p2 < p1) { Result = 'second product is cheaper at \$\{p2.toStringAsFixed(2)\} currency/kg vs \$\{p1.toStringAsFixed(2)\} currency/kg'; } else { Result = 'both products have the same price per mass at \$\{p1.toStringAsFixed(2)\} currency/kg'; }","tags":["comparison","shopping","economics"]}
|
||||
// Compare Price per Mass
|
||||
{
|
||||
"name": "Compare price per mass",
|
||||
"description": "Compares two products by their price per mass and returns which is cheaper, including price per kg for each product.",
|
||||
"input": [
|
||||
{"name": "price1", "unit": "currency"},
|
||||
{"name": "mass1", "unit": "kilogram"},
|
||||
{"name": "price2", "unit": "currency"},
|
||||
{"name": "mass2", "unit": "kilogram"}
|
||||
],
|
||||
"output": {"name": "Result", "unit": "string"},
|
||||
"d4rtCode": """
|
||||
var p1 = price1 / mass1;
|
||||
var p2 = price2 / mass2;
|
||||
if (p1 < p2) {
|
||||
Result = 'first product is cheaper at \${p1.toStringAsFixed(2)} currency/kg vs \${p2.toStringAsFixed(2)} currency/kg';
|
||||
} else if (p2 < p1) {
|
||||
Result = 'second product is cheaper at \${p2.toStringAsFixed(2)} currency/kg vs \${p1.toStringAsFixed(2)} currency/kg';
|
||||
} else {
|
||||
Result = 'both products have the same price per mass at \${p1.toStringAsFixed(2)} currency/kg';
|
||||
}
|
||||
""",
|
||||
"tags": ["comparison", "shopping", "economics"]
|
||||
}
|
||||
]
|
||||
|
|
|
|||
83
assets/formulas/networking.d4rt
Normal file
83
assets/formulas/networking.d4rt
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
[
|
||||
// IP Subnet and Broadcast Calculator
|
||||
{
|
||||
"name": "IP Subnet and Broadcast",
|
||||
"description": r"""
|
||||
Calculates the network (subnet) address and broadcast address for an IPv4 address with CIDR notation.
|
||||
|
||||
**Input format:** `ip_address/prefix` where:
|
||||
- `ip_address`: IPv4 address in dotted decimal notation (e.g., `192.168.1.100`)
|
||||
- `prefix`: CIDR prefix length (1-30) or subnet mask in dotted notation (e.g., `24` or `255.255.255.0`)
|
||||
|
||||
**Output:**
|
||||
- `subnet`: Network address in dotted decimal notation
|
||||
- `broadcast`: Broadcast address in dotted decimal notation
|
||||
|
||||
**Examples:**
|
||||
- Input: `192.168.1.100/24` → Subnet: `192.168.1.0`, Broadcast: `192.168.1.255`
|
||||
- Input: `10.0.0.50/8` → Subnet: `10.0.0.0`, Broadcast: `10.255.255.255`
|
||||
- Input: `172.16.5.100/16` → Subnet: `172.16.0.0`, Broadcast: `172.16.255.255`""",
|
||||
"input": [
|
||||
{"name": "ipWithMask", "unit": "scalar"}
|
||||
],
|
||||
"output": {"name": "subnet", "unit": "scalar"},
|
||||
"d4rtCode": """
|
||||
var input = ipWithMask.toString();
|
||||
var slashIndex = input.indexOf('/');
|
||||
if (slashIndex == -1) {
|
||||
subnet = 'error: no / found';
|
||||
broadcast = '';
|
||||
} else {
|
||||
var ipPart = input.substring(0, slashIndex).trim();
|
||||
var maskPart = input.substring(slashIndex + 1).trim();
|
||||
|
||||
// Parse IP address
|
||||
var ipParts = ipPart.split('.');
|
||||
if (ipParts.length != 4) {
|
||||
subnet = 'error: invalid IP';
|
||||
broadcast = '';
|
||||
} else {
|
||||
var octet1 = int.parse(ipParts[0]);
|
||||
var octet2 = int.parse(ipParts[1]);
|
||||
var octet3 = int.parse(ipParts[2]);
|
||||
var octet4 = int.parse(ipParts[3]);
|
||||
|
||||
// Convert IP to 32-bit integer
|
||||
var ipInt = (octet1 << 24) | (octet2 << 16) | (octet3 << 8) | octet4;
|
||||
|
||||
// Parse mask (CIDR prefix or dotted notation)
|
||||
int maskInt;
|
||||
if (maskPart.contains('.')) {
|
||||
var maskParts = maskPart.split('.');
|
||||
var m1 = int.parse(maskParts[0]);
|
||||
var m2 = int.parse(maskParts[1]);
|
||||
var m3 = int.parse(maskParts[2]);
|
||||
var m4 = int.parse(maskParts[3]);
|
||||
maskInt = (m1 << 24) | (m2 << 16) | (m3 << 8) | m4;
|
||||
} else {
|
||||
var prefix = int.parse(maskPart);
|
||||
maskInt = prefix == 0 ? 0 : (-1 << (32 - prefix));
|
||||
}
|
||||
|
||||
// Calculate subnet and broadcast
|
||||
var subnetInt = ipInt & maskInt;
|
||||
var broadcastInt = subnetInt | (~maskInt & 0xFFFFFFFF);
|
||||
|
||||
// Convert back to dotted notation
|
||||
var s1 = (subnetInt >> 24) & 0xFF;
|
||||
var s2 = (subnetInt >> 16) & 0xFF;
|
||||
var s3 = (subnetInt >> 8) & 0xFF;
|
||||
var s4 = subnetInt & 0xFF;
|
||||
subnet = '\$s1.\$s2.\$s3.\$s4';
|
||||
|
||||
var b1 = (broadcastInt >> 24) & 0xFF;
|
||||
var b2 = (broadcastInt >> 16) & 0xFF;
|
||||
var b3 = (broadcastInt >> 8) & 0xFF;
|
||||
var b4 = broadcastInt & 0xFF;
|
||||
broadcast = '\$b1.\$b2.\$b3.\$b4';
|
||||
}
|
||||
}
|
||||
""",
|
||||
"tags": ["networking", "ip", "subnet", "broadcast", "cidr"]
|
||||
}
|
||||
]
|
||||
|
|
@ -1,2 +1,29 @@
|
|||
[
|
||||
// Ideal Gas Law
|
||||
{
|
||||
"name": "Ideal Gas Law",
|
||||
"description": r"""
|
||||
Equation of state for an ideal gas.
|
||||
|
||||
$$PV = nRT$$
|
||||
|
||||
Where:
|
||||
- $P$: Pressure (Pascals)
|
||||
- $V$: Volume (m^3)
|
||||
- $n$: Amount of substance (moles)
|
||||
- $R$: Universal gas constant $8.314\ \mathrm{J/(mol\cdot K)}$
|
||||
- $T$: Temperature (Kelvin)
|
||||
|
||||
This law combines Boyle's, Charles's and Avogadro's laws.
|
||||
|
||||
""",
|
||||
"input": [
|
||||
{"name": "n", "unit": "mole"},
|
||||
{"name": "T", "unit": "kelvin"},
|
||||
{"name": "V", "unit": "cubic meter"}
|
||||
],
|
||||
"output": {"name": "P", "unit": "pascal"},
|
||||
"d4rtCode": "P = (n * 8.314462618 * T) / V;",
|
||||
"tags": ["physics", "thermodynamics", "gas"]
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -32,13 +32,14 @@ RUN chown -R $USER_ID:$GROUP_ID /sdks/flutter
|
|||
USER $USER_ID:$GROUP_ID
|
||||
|
||||
# Pre-cache Flutter artifacts for Linux, Android, and Web to speed up subsequent builds
|
||||
#WORKDIR /dummy_app_only_for_cache
|
||||
#RUN flutter create . && flutter pub get
|
||||
#RUN flutter precache --linux
|
||||
#RUN flutter build linux
|
||||
#RUN flutter precache --web
|
||||
#RUN flutter build web
|
||||
WORKDIR /dummy_app_only_for_cache
|
||||
RUN flutter create . && flutter pub get
|
||||
RUN flutter precache --linux
|
||||
RUN flutter precache --web
|
||||
#RUN flutter precache --android
|
||||
|
||||
#RUN flutter build linux
|
||||
#RUN flutter build web
|
||||
#RUN flutter build apk
|
||||
|
||||
WORKDIR /app
|
||||
|
|
|
|||
705
lib/ai/formula_editor.dart
Normal file
705
lib/ai/formula_editor.dart
Normal file
|
|
@ -0,0 +1,705 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_markdown_plus_latex/flutter_markdown_plus_latex.dart';
|
||||
import 'package:flutter_markdown_plus/flutter_markdown_plus.dart';
|
||||
import 'package:markdown/markdown.dart' as markdown;
|
||||
import '../formula_models.dart';
|
||||
import '../corpus.dart';
|
||||
import 'formula_screen.dart';
|
||||
import 'unit_dropdown.dart';
|
||||
|
||||
/// A screen for editing a Formula's properties including name, description,
|
||||
/// input/output variables, and d4rt code.
|
||||
class FormulaEditor extends StatefulWidget {
|
||||
final Formula formula;
|
||||
final Corpus corpus;
|
||||
|
||||
const FormulaEditor({
|
||||
super.key,
|
||||
required this.formula,
|
||||
required this.corpus,
|
||||
});
|
||||
|
||||
@override
|
||||
State<FormulaEditor> createState() => _FormulaEditorState();
|
||||
}
|
||||
|
||||
class _FormulaEditorState extends State<FormulaEditor> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
late TextEditingController _nameController;
|
||||
late TextEditingController _descriptionController;
|
||||
late TextEditingController _d4rtCodeController;
|
||||
|
||||
// Track input variables
|
||||
final List<_InputVariableRowData> _inputVariables = [];
|
||||
|
||||
// Output variable
|
||||
late _OutputVariableRowData _outputVariable;
|
||||
|
||||
bool _isPreviewVisible = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_nameController = TextEditingController(text: widget.formula.name);
|
||||
_descriptionController = TextEditingController(text: widget.formula.description ?? '');
|
||||
_d4rtCodeController = TextEditingController(text: widget.formula.d4rtCode);
|
||||
|
||||
// Initialize input variables
|
||||
for (final input in widget.formula.input) {
|
||||
_inputVariables.add(_InputVariableRowData(
|
||||
nameController: TextEditingController(text: input.name),
|
||||
unit: input.unit,
|
||||
values: input.values != null ? List.from(input.values!) : null,
|
||||
));
|
||||
}
|
||||
|
||||
// Initialize output variable
|
||||
_outputVariable = _OutputVariableRowData(
|
||||
nameController: TextEditingController(text: widget.formula.output.name),
|
||||
unit: widget.formula.output.unit,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_nameController.dispose();
|
||||
_descriptionController.dispose();
|
||||
_d4rtCodeController.dispose();
|
||||
for (final variable in _inputVariables) {
|
||||
variable.nameController.dispose();
|
||||
}
|
||||
_outputVariable.nameController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _addInputVariable() {
|
||||
setState(() {
|
||||
_inputVariables.add(_InputVariableRowData(
|
||||
nameController: TextEditingController(text: 'var${_inputVariables.length + 1}'),
|
||||
unit: null,
|
||||
values: null,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
void _removeInputVariable(int index) {
|
||||
setState(() {
|
||||
_inputVariables.removeAt(index);
|
||||
});
|
||||
}
|
||||
|
||||
void _showPreview() {
|
||||
setState(() {
|
||||
_isPreviewVisible = true;
|
||||
});
|
||||
}
|
||||
|
||||
void _hidePreview() {
|
||||
setState(() {
|
||||
_isPreviewVisible = false;
|
||||
});
|
||||
}
|
||||
|
||||
void _testFormula() {
|
||||
// Validate the formula before testing
|
||||
if (!_validateFormula()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final formula = _buildFormula();
|
||||
if (formula == null) return;
|
||||
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => FormulaScreen(
|
||||
formula: formula,
|
||||
corpus: widget.corpus,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
bool _validateFormula() {
|
||||
// Validate name
|
||||
if (_nameController.text.trim().isEmpty) {
|
||||
_showErrorDialog('Formula name cannot be empty');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate output name
|
||||
if (_outputVariable.nameController.text.trim().isEmpty) {
|
||||
_showErrorDialog('Output variable name cannot be empty');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate input variable names
|
||||
for (final variable in _inputVariables) {
|
||||
if (variable.nameController.text.trim().isEmpty) {
|
||||
_showErrorDialog('Input variable names cannot be empty');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Validate d4rt code
|
||||
if (_d4rtCodeController.text.trim().isEmpty) {
|
||||
_showErrorDialog('D4RT code cannot be empty');
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Formula? _buildFormula() {
|
||||
try {
|
||||
final input = <VariableSpec>[];
|
||||
for (final variable in _inputVariables) {
|
||||
input.add(VariableSpec(
|
||||
name: variable.nameController.text.trim(),
|
||||
unit: variable.unit,
|
||||
values: variable.values,
|
||||
));
|
||||
}
|
||||
|
||||
final output = VariableSpec(
|
||||
name: _outputVariable.nameController.text.trim(),
|
||||
unit: _outputVariable.unit,
|
||||
);
|
||||
|
||||
return Formula(
|
||||
name: _nameController.text.trim(),
|
||||
description: _descriptionController.text.isEmpty ? null : _descriptionController.text,
|
||||
input: input,
|
||||
output: output,
|
||||
d4rtCode: _d4rtCodeController.text,
|
||||
tags: widget.formula.tags, // Preserve existing tags
|
||||
);
|
||||
} catch (e) {
|
||||
_showErrorDialog('Error building formula: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
void _saveFormula() {
|
||||
if (!_validateFormula()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final formula = _buildFormula();
|
||||
if (formula == null) return;
|
||||
|
||||
// For now, just show a success message
|
||||
// In a real implementation, this would save to database
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Formula "${formula.name}" saved successfully!'),
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showErrorDialog(String message) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: const Text('Error'),
|
||||
content: Text(message),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: const Text('OK'),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Edit Formula'),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.play_arrow),
|
||||
onPressed: _testFormula,
|
||||
tooltip: 'Test Formula',
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.save),
|
||||
onPressed: _saveFormula,
|
||||
tooltip: 'Save',
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Form(
|
||||
key: _formKey,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: ListView(
|
||||
children: [
|
||||
_buildNameSection(),
|
||||
const SizedBox(height: 16),
|
||||
_buildDescriptionSection(),
|
||||
const SizedBox(height: 16),
|
||||
_buildInputVariablesSection(),
|
||||
const SizedBox(height: 16),
|
||||
_buildOutputVariableSection(),
|
||||
const SizedBox(height: 16),
|
||||
_buildD4rtCodeSection(),
|
||||
const SizedBox(height: 32),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildNameSection() {
|
||||
return TextFormField(
|
||||
controller: _nameController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Formula Name',
|
||||
border: OutlineInputBorder(),
|
||||
prefixIcon: Icon(Icons.title),
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.trim().isEmpty) {
|
||||
return 'Name is required';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDescriptionSection() {
|
||||
return Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text(
|
||||
'Description (Markdown)',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (_isPreviewVisible)
|
||||
TextButton.icon(
|
||||
icon: const Icon(Icons.visibility_off),
|
||||
label: const Text('Hide Preview'),
|
||||
onPressed: _hidePreview,
|
||||
)
|
||||
else
|
||||
TextButton.icon(
|
||||
icon: const Icon(Icons.visibility),
|
||||
label: const Text('Preview'),
|
||||
onPressed: _showPreview,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
if (_isPreviewVisible) ...[
|
||||
Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceVariant,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Markdown(
|
||||
data: _descriptionController.text,
|
||||
shrinkWrap: true,
|
||||
builders: {
|
||||
'latex': LatexElementBuilder(),
|
||||
},
|
||||
extensionSet: markdown.ExtensionSet(
|
||||
[LatexBlockSyntax()],
|
||||
[LatexInlineSyntax()],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
TextFormField(
|
||||
controller: _descriptionController,
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'Enter formula description (supports Markdown and LaTeX)',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
maxLines: 5,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildInputVariablesSection() {
|
||||
return Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Input Variables',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
..._inputVariables.asMap().entries.map((entry) {
|
||||
final index = entry.key;
|
||||
final variable = entry.value;
|
||||
return _buildInputVariableRow(index, variable);
|
||||
}).toList(),
|
||||
const SizedBox(height: 8),
|
||||
ElevatedButton.icon(
|
||||
icon: const Icon(Icons.add),
|
||||
label: const Text('Add Input Variable'),
|
||||
onPressed: _addInputVariable,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildInputVariableRow(int index, _InputVariableRowData variable) {
|
||||
return Card(
|
||||
margin: const EdgeInsets.only(bottom: 8),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: TextFormField(
|
||||
controller: variable.nameController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Name',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text('Base Unit', style: TextStyle(fontSize: 12)),
|
||||
DropdownButtonFormField<String?>(
|
||||
value: _getBaseUnit(variable.unit),
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 0),
|
||||
),
|
||||
items: [
|
||||
const DropdownMenuItem<String?>(
|
||||
value: null,
|
||||
child: Text('None', style: TextStyle(fontSize: 14)),
|
||||
),
|
||||
..._getAllBaseUnits().map((baseUnit) {
|
||||
return DropdownMenuItem<String?>(
|
||||
value: baseUnit,
|
||||
child: Text(baseUnit, style: const TextStyle(fontSize: 14)),
|
||||
);
|
||||
}).toList(),
|
||||
],
|
||||
onChanged: (baseUnit) {
|
||||
setState(() {
|
||||
variable.unit = baseUnit;
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text('Derived Unit', style: TextStyle(fontSize: 12)),
|
||||
DropdownButtonFormField<String?>(
|
||||
value: variable.unit,
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 0),
|
||||
),
|
||||
items: [
|
||||
const DropdownMenuItem<String?>(
|
||||
value: null,
|
||||
child: Text('None', style: TextStyle(fontSize: 14)),
|
||||
),
|
||||
..._getDerivedUnits(variable.unit).map((unit) {
|
||||
final unitSpec = widget.corpus.getUnit(unit);
|
||||
return DropdownMenuItem<String?>(
|
||||
value: unit,
|
||||
child: Text('${unitSpec.symbol} - ${unit}',
|
||||
style: const TextStyle(fontSize: 14),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
],
|
||||
onChanged: (unit) {
|
||||
setState(() {
|
||||
variable.unit = unit;
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.delete, color: Colors.red),
|
||||
onPressed: () => _removeInputVariable(index),
|
||||
tooltip: 'Delete variable',
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildOutputVariableSection() {
|
||||
return Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Output Variable',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: TextFormField(
|
||||
controller: _outputVariable.nameController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Name',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text('Base Unit', style: TextStyle(fontSize: 12)),
|
||||
DropdownButtonFormField<String?>(
|
||||
value: _getBaseUnit(_outputVariable.unit),
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 0),
|
||||
),
|
||||
items: [
|
||||
const DropdownMenuItem<String?>(
|
||||
value: null,
|
||||
child: Text('None', style: TextStyle(fontSize: 14)),
|
||||
),
|
||||
..._getAllBaseUnits().map((baseUnit) {
|
||||
return DropdownMenuItem<String?>(
|
||||
value: baseUnit,
|
||||
child: Text(baseUnit, style: const TextStyle(fontSize: 14)),
|
||||
);
|
||||
}).toList(),
|
||||
],
|
||||
onChanged: (baseUnit) {
|
||||
setState(() {
|
||||
_outputVariable.unit = baseUnit;
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text('Derived Unit', style: TextStyle(fontSize: 12)),
|
||||
DropdownButtonFormField<String?>(
|
||||
value: _outputVariable.unit,
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 0),
|
||||
),
|
||||
items: [
|
||||
const DropdownMenuItem<String?>(
|
||||
value: null,
|
||||
child: Text('None', style: TextStyle(fontSize: 14)),
|
||||
),
|
||||
..._getDerivedUnits(_outputVariable.unit).map((unit) {
|
||||
final unitSpec = widget.corpus.getUnit(unit);
|
||||
return DropdownMenuItem<String?>(
|
||||
value: unit,
|
||||
child: Text('${unitSpec.symbol} - ${unit}',
|
||||
style: const TextStyle(fontSize: 14),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
],
|
||||
onChanged: (unit) {
|
||||
setState(() {
|
||||
_outputVariable.unit = unit;
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildD4rtCodeSection() {
|
||||
return Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'D4RT Code',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Theme.of(context).dividerColor),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceVariant,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(4),
|
||||
topRight: Radius.circular(4),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.code, size: 16, color: Theme.of(context).colorScheme.primary),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'Dart Syntax',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
TextFormField(
|
||||
controller: _d4rtCodeController,
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'Enter D4RT/Dart code here',
|
||||
border: InputBorder.none,
|
||||
contentPadding: EdgeInsets.all(12),
|
||||
),
|
||||
maxLines: 10,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'monospace',
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Helper methods for unit management
|
||||
String? _getBaseUnit(String? unit) {
|
||||
if (unit == null) return null;
|
||||
try {
|
||||
return widget.corpus.getUnit(unit).baseUnit;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
List<String> _getAllBaseUnits() {
|
||||
final baseUnits = <String>{};
|
||||
for (final unit in widget.corpus.allUnits()) {
|
||||
baseUnits.add(unit.baseUnit);
|
||||
}
|
||||
return baseUnits.toList()..sort();
|
||||
}
|
||||
|
||||
List<String> _getDerivedUnits(String? baseUnit) {
|
||||
if (baseUnit == null) return [];
|
||||
return widget.corpus.unitsOfSameMagnitude(baseUnit)..sort();
|
||||
}
|
||||
}
|
||||
|
||||
// Data classes to track variable state
|
||||
class _InputVariableRowData {
|
||||
final TextEditingController nameController;
|
||||
String? unit;
|
||||
List<dynamic>? values;
|
||||
|
||||
_InputVariableRowData({
|
||||
required this.nameController,
|
||||
this.unit,
|
||||
this.values,
|
||||
});
|
||||
}
|
||||
|
||||
class _OutputVariableRowData {
|
||||
final TextEditingController nameController;
|
||||
String? unit;
|
||||
|
||||
_OutputVariableRowData({
|
||||
required this.nameController,
|
||||
this.unit,
|
||||
});
|
||||
}
|
||||
|
|
@ -4,6 +4,8 @@ import 'package:d4rt_formulas/formula_models.dart';
|
|||
import '../corpus.dart';
|
||||
import 'formula_screen.dart';
|
||||
import 'package:share_plus/share_plus.dart' as share_plus;
|
||||
import 'formula_editor.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
|
||||
class FormulaList extends StatefulWidget {
|
||||
final Corpus corpus;
|
||||
|
|
@ -74,6 +76,18 @@ class _FormulaListState extends State<FormulaList> {
|
|||
}
|
||||
}
|
||||
|
||||
void _editFormula(Formula formula) {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => FormulaEditor(
|
||||
formula: formula,
|
||||
corpus: widget.corpus,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _copyFormula(Formula formula) async {
|
||||
try {
|
||||
// Get the formula and its dependencies
|
||||
|
|
@ -146,35 +160,45 @@ class _FormulaListState extends State<FormulaList> {
|
|||
subtitle: formula.tags.isNotEmpty
|
||||
? Text('Tags: ${formula.tags.join(', ')}')
|
||||
: null,
|
||||
trailing: PopupMenuButton(
|
||||
icon: Icon(Icons.share),
|
||||
onSelected: (value) {
|
||||
if (value == 'share') {
|
||||
_shareFormula(formula);
|
||||
} else if (value == 'copy') {
|
||||
_copyFormula(formula);
|
||||
}
|
||||
},
|
||||
itemBuilder: (context) => [
|
||||
PopupMenuItem(
|
||||
value: 'share',
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.share),
|
||||
SizedBox(width: 8),
|
||||
Text('Share'),
|
||||
],
|
||||
),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.edit),
|
||||
onPressed: () => _editFormula(formula),
|
||||
tooltip: 'Edit Formula',
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: 'copy',
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.copy),
|
||||
SizedBox(width: 8),
|
||||
Text('Copy to clipboard'),
|
||||
],
|
||||
),
|
||||
PopupMenuButton(
|
||||
icon: const Icon(Icons.share),
|
||||
onSelected: (value) {
|
||||
if (value == 'share') {
|
||||
_shareFormula(formula);
|
||||
} else if (value == 'copy') {
|
||||
_copyFormula(formula);
|
||||
}
|
||||
},
|
||||
itemBuilder: (context) => [
|
||||
PopupMenuItem(
|
||||
value: 'share',
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.share),
|
||||
SizedBox(width: 8),
|
||||
Text('Share'),
|
||||
],
|
||||
),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: 'copy',
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.copy),
|
||||
SizedBox(width: 8),
|
||||
Text('Copy to clipboard'),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import '../formula_evaluator.dart';
|
|||
import '../corpus.dart';
|
||||
import '../error_handler.dart';
|
||||
import 'unit_dropdown.dart';
|
||||
import 'formula_editor.dart';
|
||||
|
||||
class FormulaScreen extends StatefulWidget {
|
||||
final Formula formula;
|
||||
|
|
@ -24,10 +25,25 @@ class D4rtEditingController extends TextEditingController {
|
|||
String? _lastError;
|
||||
String? get lastError => _lastError;
|
||||
FormulaResult? _lastValue;
|
||||
final bool isString;
|
||||
|
||||
D4rtEditingController({super.text});
|
||||
D4rtEditingController({super.text, this.isString = false});
|
||||
|
||||
bool validate() {
|
||||
if( _validateAsNumberExpression(text) ){
|
||||
return true;
|
||||
}
|
||||
if( isString && _validateAsStringExpression(text) ){
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool _validateAsNumberExpression(String text){
|
||||
return _validateAsD4rtExpression(text) && _lastValue is NumberResult;
|
||||
}
|
||||
|
||||
bool _validateAsD4rtExpression(String text){
|
||||
try {
|
||||
_lastValue = null;
|
||||
if( text.trim().isEmpty ){
|
||||
|
|
@ -37,12 +53,26 @@ class D4rtEditingController extends TextEditingController {
|
|||
_lastError = null;
|
||||
return true;
|
||||
} catch (e, s) {
|
||||
errorHandler.notify(e, s);
|
||||
//errorHandler.notify(e, s);
|
||||
_lastError = e.toString();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool _validateAsStringExpression(String text){
|
||||
if( _validateAsD4rtExpression(text) && _lastValue is StringResult ){
|
||||
return true;
|
||||
}
|
||||
if( _validateAsD4rtExpression('"' + text + '"') && _lastValue is StringResult ){
|
||||
return true;
|
||||
}
|
||||
if( _validateAsD4rtExpression("'" + text + "'") && _lastValue is StringResult ){
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
FormulaResult? get d4rtValue => _lastValue;
|
||||
|
||||
@override
|
||||
|
|
@ -79,7 +109,7 @@ class _FormulaScreenState extends State<FormulaScreen> {
|
|||
_selectedValues[input.name] = input.values!.first;
|
||||
} else {
|
||||
// numeric variable -> use D4rtEditingController
|
||||
_inputControllers[input.name] = D4rtEditingController();
|
||||
_inputControllers[input.name] = D4rtEditingController(isString: input.unit == "string");
|
||||
_inputControllers[input.name]!.addListener(_evaluateFormula);
|
||||
}
|
||||
}
|
||||
|
|
@ -172,7 +202,26 @@ class _FormulaScreenState extends State<FormulaScreen> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text(widget.formula.name)),
|
||||
appBar: AppBar(
|
||||
title: Text(widget.formula.name),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.edit),
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => FormulaEditor(
|
||||
formula: widget.formula,
|
||||
corpus: widget.corpus,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
tooltip: 'Edit Formula',
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Form(
|
||||
key: _formKey,
|
||||
child: Padding(
|
||||
|
|
|
|||
|
|
@ -51,11 +51,11 @@ Future<Corpus> createDefaultCorpus() async{
|
|||
|
||||
Future<void> loadFormulas() async {
|
||||
final formulaResources = [
|
||||
"assets/formulas/formulas.d4rt",
|
||||
"assets/formulas/conversions_and_constants.d4rt",
|
||||
"assets/formulas/electromagnetism.d4rt",
|
||||
"assets/formulas/energy_and_power.d4rt",
|
||||
"assets/formulas/fluids_and_pressure.d4rt",
|
||||
"assets/formulas/formulas.d4rt",
|
||||
"assets/formulas/geometry.d4rt",
|
||||
"assets/formulas/gravity.d4rt",
|
||||
"assets/formulas/it-networking.d4rt",
|
||||
|
|
@ -63,9 +63,11 @@ Future<Corpus> createDefaultCorpus() async{
|
|||
"assets/formulas/materials_elasticity.d4rt",
|
||||
"assets/formulas/medical_and_bio.d4rt",
|
||||
"assets/formulas/misc_math.d4rt",
|
||||
"assets/formulas/networking.d4rt",
|
||||
"assets/formulas/optics.d4rt",
|
||||
"assets/formulas/thermodynamics.d4rt",
|
||||
"assets/formulas/trigonometry.d4rt",
|
||||
|
||||
];
|
||||
|
||||
for (final formRes in formulaResources) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue