diff --git a/Makefile b/Makefile index 98d84c2..39fe39a 100644 --- a/Makefile +++ b/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: diff --git a/TODO.md b/TODO.md index 92aa2ec..5a4f019 100644 --- a/TODO.md +++ b/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 diff --git a/assets/formula-element-format.md b/assets/formula-element-format.md new file mode 100644 index 0000000..3eb8710 --- /dev/null +++ b/assets/formula-element-format.md @@ -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 +![Description](https://upload.wikimedia.org/wikipedia/commons/...) +``` + +**Example:** +```dart +"description": r""" +Newton's law of universal gravitation. + +$$F = G\frac{m_1m_2}{r^2}$$ + +![Gravitation Diagram](https://upload.wikimedia.org/wikipedia/commons/thumb/3/33/NewtonsLawOfUniversalGravitation.svg/1200px-NewtonsLawOfUniversalGravitation.svg.png)""", +``` + +### 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}$) + +![Newton's Second Law](https://upload.wikimedia.org/wikipedia/commons/thumb/7/73/Newtonslawsofmotion.jpg/800px-Newtonslawsofmotion.jpg)""", + "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. diff --git a/assets/formulas/conversions_and_constants.d4rt b/assets/formulas/conversions_and_constants.d4rt index 0d4f101..1353e9e 100644 --- a/assets/formulas/conversions_and_constants.d4rt +++ b/assets/formulas/conversions_and_constants.d4rt @@ -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). + +![Temperature scales (Wikipedia)](https://upload.wikimedia.org/wikipedia/commons/thumb/3/39/Comparison_of_temperature_scales.svg/960px-Comparison_of_temperature_scales.svg.png)""", + "input": [ + {"name": "Input", "unit": "Kelvin"} + ], + "output": {"name": "Output", "unit": "Kelvin"}, + "d4rtCode": "Output = Input;", + "tags": ["converter", "temperature"] + } ] diff --git a/assets/formulas/electromagnetism.d4rt b/assets/formulas/electromagnetism.d4rt index 49c6af7..b5ba790 100644 --- a/assets/formulas/electromagnetism.d4rt +++ b/assets/formulas/electromagnetism.d4rt @@ -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"] + } ] diff --git a/assets/formulas/energy_and_power.d4rt b/assets/formulas/energy_and_power.d4rt index 0d4f101..ea0e46c 100644 --- a/assets/formulas/energy_and_power.d4rt +++ b/assets/formulas/energy_and_power.d4rt @@ -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) + +![Kinetic energy (Wikipedia)](https://upload.wikimedia.org/wikipedia/commons/thumb/4/44/Kinetic_energy.svg/1200px-Kinetic_energy.svg.png)""", + "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 + +![Work (diagram) (Wikipedia)](https://upload.wikimedia.org/wikipedia/commons/thumb/e/e7/Work.svg/800px-Work.svg.png)""", + "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) + +![Power (diagram) (Wikipedia)](https://upload.wikimedia.org/wikipedia/commons/thumb/9/90/Power_equation.svg/800px-Power_equation.svg.png)""", + "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. + +![Einstein formula (Wikipedia)](https://upload.wikimedia.org/wikipedia/commons/thumb/8/86/Einstein_light_beam.svg/800px-Einstein_light_beam.svg.png)""", + "input": [ + {"name": "m", "unit": "kilogram"} + ], + "output": {"name": "E", "unit": "joule"}, + "d4rtCode": "E = m * pow(299792458, 2);", + "tags": ["physics", "relativity", "energy"] + } ] diff --git a/assets/formulas/fluids_and_pressure.d4rt b/assets/formulas/fluids_and_pressure.d4rt index 5e66ef0..f35aa6d 100644 --- a/assets/formulas/fluids_and_pressure.d4rt +++ b/assets/formulas/fluids_and_pressure.d4rt @@ -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. -![Density illustration (Wikipedia)](https://upload.wikimedia.org/wikipedia/commons/thumb/2/2d/Density_of_solids.svg/800px-Density_of_solids.svg.png) -""","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""" +![Density illustration (Wikipedia)](https://upload.wikimedia.org/wikipedia/commons/thumb/2/2d/Density_of_solids.svg/800px-Density_of_solids.svg.png)""", + "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. -![Pressure (diagram) (Wikipedia)](https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Pressure_in_fluid.svg/800px-Pressure_in_fluid.svg.png) -""","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""" +![Pressure (diagram) (Wikipedia)](https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Pressure_in_fluid.svg/800px-Pressure_in_fluid.svg.png)""", + "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. -![Archimedes principle (Wikipedia)](https://upload.wikimedia.org/wikipedia/commons/thumb/7/72/Archimedes-principle.svg/960px-Archimedes-principle.svg.png) - -""","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"]} +![Archimedes principle (Wikipedia)](https://upload.wikimedia.org/wikipedia/commons/thumb/7/72/Archimedes-principle.svg/960px-Archimedes-principle.svg.png)""", + "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"] + } ] diff --git a/assets/formulas/formulas.d4rt b/assets/formulas/formulas.d4rt index 9357404..fe60480 100644 --- a/assets/formulas/formulas.d4rt +++ b/assets/formulas/formulas.d4rt @@ -1,53 +1,24 @@ [ -{ - "name": "Temperature converter", - "description": "Example of simple formula, just unit conversion", - "input": [ - {"name": "Input", "unit": "Kelvin" } - ], - "output": {"name": "Output", "unit": "Kelvin" }, - "d4rtCode": "Output = Input;", - "tags": ["converter", "temperature" ] -}, - - // Kinetic Energy - { - "name": "Kinetic Energy", - "description": r''' -Energy possessed by a moving object - -$$KE = \frac{1}{2}mv^2$$ - -Where: -- $m$: Mass of object -- $v$: Velocity of object - -![Kinetic Energy](https://upload.wikimedia.org/wikipedia/commons/thumb/4/44/Kinetic_energy.svg/1200px-Kinetic_energy.svg.png)''', - "input": [ - {"name": "m", "unit": "kilogram"}, // Mass - {"name": "v", "unit": "meters per second"} // Velocity - ], - "output": {"name": "KE", "unit": "joule"}, // Energy in joules - "d4rtCode": "KE = 0.5 * m * pow(v, 2);", - "tags": ["physics", "energy", "mechanics"] - }, - // Projectile Motion Range { "name": "Projectile Range", - "description": r"""Calculates horizontal distance of projectile motion - $$R = \frac{v^2 \sin(2\theta)}{g}$$ - Where: - - $v$: Initial velocity - - $\theta$: Launch angle - - $g$: Gravitational acceleration - ![Projectile Motion](https://upload.wikimedia.org/wikipedia/commons/thumb/5/52/Projectile_motion_diagram.png/800px-Projectile_motion_diagram.png)""", + "description": r""" +Calculates horizontal distance of projectile motion + +$$R = \frac{v^2 \sin(2\theta)}{g}$$ + +Where: +- $v$: Initial velocity +- $\theta$: Launch angle +- $g$: Gravitational acceleration + +![Projectile Motion](https://upload.wikimedia.org/wikipedia/commons/thumb/5/52/Projectile_motion_diagram.png/800px-Projectile_motion_diagram.png)""", "input": [ - {"name": "v", "unit": "meters per second"}, // Initial velocity - {"name": "a", "unit": "degree"} // Launch angle + {"name": "v", "unit": "meters per second"}, + {"name": "a", "unit": "degree"} ], - "output": {"name": "R", "unit": "meter"}, // Horizontal distance + "output": {"name": "R", "unit": "meter"}, "d4rtCode": """ var radians = a * (pi / 180); R = (pow(v, 2) * sin(2 * radians)) / 9.80665; @@ -55,9 +26,10 @@ Where: "tags": ["physics", "kinematics", "projectile"] }, + // Newton's Second Law { "name": "Newton's Second Law", - "description": r''' + "description": r""" Force equals mass times acceleration $$F = m \cdot a$$ @@ -66,142 +38,20 @@ Where: - $m$: Mass of object ($\mathrm{kg}$) - $a$: Acceleration ($\mathrm{m/s^2}$) -![Newton's Second Law](https://upload.wikimedia.org/wikipedia/commons/thumb/7/73/Newtonslawsofmotion.jpg/800px-Newtonslawsofmotion.jpg)''', +![Newton's Second Law](https://upload.wikimedia.org/wikipedia/commons/thumb/7/73/Newtonslawsofmotion.jpg/800px-Newtonslawsofmotion.jpg)""", "input": [ - {"name": "m", "unit": "kilogram"}, // Mass - {"name": "a", "unit": "meters per square second"} // Acceleration + {"name": "m", "unit": "kilogram"}, + {"name": "a", "unit": "meters per square second"} ], - "output": {"name": "F", "unit": "newton"}, // Force in newtons + "output": {"name": "F", "unit": "newton"}, "d4rtCode": "F = m * a;", "tags": ["physics", "mechanics", "newton"] }, - // Apgar Score - { - "name": "Apgar Score", - "description": "Newborn health assessment scoring system\n\nScores 0-2 for:\n1. Heart rate\n2. Breathing\n3. Muscle tone\n4. Reflexes\n5. Skin color\nTotal score 0-10", - "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": "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"] - } - , - - // Einstein's Mass-Energy Equivalence - { - "name": "Mass-Energy Equivalence", - "description": r''' -Einstein's famous equation showing the relationship between mass and energy - -$$E = mc^2$$ - -Where: -- $E$: Energy (Joules) -- $m$: Mass (kilograms) -- $c$: Speed of light $299,792,458$ $\mathrm{m/s}$ - -This equation shows that mass can be converted to energy and vice versa.''', - "input": [ - {"name": "m", "unit": "kilogram"} // Mass - ], - "output": {"name": "E", "unit": "joule"}, // Energy - "d4rtCode": "E = m * pow(299792458, 2);", - "tags": ["physics", "relativity", "energy"] - }, - - // Ohm's Law - { - "name": "Ohm's Law", - "description": r''' -Relationship between voltage, current, and resistance in electrical circuits - -$$V = IR$$ - -Where: -- $V$: Voltage (Volts) -- $I$: Current (Amperes) -- $R$: Resistance (Ohms) - -This fundamental law describes how current flows through resistive materials.''', - "input": [ - {"name": "I", "unit": "ampere"}, // Current - {"name": "R", "unit": "ohm"} // Resistance - ], - "output": {"name": "V", "unit": "volt"}, // Voltage - "d4rtCode": "V = I * R;", - "tags": ["physics", "electricity", "electronics"] - }, - - // 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"}, // Spring constant - {"name": "x", "unit": "meter"} // Displacement - ], - "output": {"name": "F", "unit": "newton"}, // Force - "d4rtCode": "F = -k * x;", - "tags": ["physics", "elasticity", "oscillations"] - }, - // Centripetal Force { "name": "Centripetal Force", - "description": r''' + "description": r""" Force required to keep an object moving in circular motion $$F = \frac{mv^2}{r}$$ @@ -212,13 +62,13 @@ Where: - $v$: Velocity (m/s) - $r$: Radius of circular path (meters) -This force acts toward the center of the circle.''', +This force acts toward the center of the circle.""", "input": [ - {"name": "m", "unit": "kilogram"}, // Mass - {"name": "v", "unit": "meters per second"}, // Velocity - {"name": "r", "unit": "meter"} // Radius + {"name": "m", "unit": "kilogram"}, + {"name": "v", "unit": "meters per second"}, + {"name": "r", "unit": "meter"} ], - "output": {"name": "F", "unit": "newton"}, // Force + "output": {"name": "F", "unit": "newton"}, "d4rtCode": "F = (m * pow(v, 2)) / r;", "tags": ["physics", "circular motion", "centripetal"] }, @@ -226,7 +76,7 @@ This force acts toward the center of the circle.''', // Wave Equation { "name": "Wave Equation", - "description": r''' + "description": r""" Relationship between wave speed, frequency, and wavelength $$v = f\lambda$$ @@ -236,12 +86,12 @@ Where: - $f$: Frequency (Hertz) - $\lambda$: Wavelength (meters) -This applies to all types of waves including sound and light.''', +This applies to all types of waves including sound and light.""", "input": [ - {"name": "f", "unit": "hertz"}, // Frequency - {"name": "lambda", "unit": "meter"} // Wavelength + {"name": "f", "unit": "hertz"}, + {"name": "lambda", "unit": "meter"} ], - "output": {"name": "v", "unit": "meters per second"}, // Wave speed + "output": {"name": "v", "unit": "meters per second"}, "d4rtCode": "v = f * lambda;", "tags": ["physics", "waves", "frequency"] }, @@ -249,7 +99,7 @@ This applies to all types of waves including sound and light.''', // Pythagorean Theorem { "name": "Pythagorean Theorem", - "description": r''' + "description": r""" Fundamental relation in Euclidean geometry among the three sides of a right triangle $$a^2 + b^2 = c^2$$ @@ -258,12 +108,12 @@ Where: - $a$, $b$: Legs of the right triangle - $c$: Hypotenuse of the right triangle -The square of the hypotenuse is equal to the sum of squares of the other two sides.''', +The square of the hypotenuse is equal to the sum of squares of the other two sides.""", "input": [ - {"name": "a", "unit": "meter"}, // First leg - {"name": "b", "unit": "meter"} // Second leg + {"name": "a", "unit": "meter"}, + {"name": "b", "unit": "meter"} ], - "output": {"name": "c", "unit": "meter"}, // Hypotenuse + "output": {"name": "c", "unit": "meter"}, "d4rtCode": "c = sqrt(pow(a, 2) + pow(b, 2));", "tags": ["trigonometry", "geometry", "pythagorean"] }, @@ -271,7 +121,7 @@ The square of the hypotenuse is equal to the sum of squares of the other two sid // Sine Rule { "name": "Sine Rule", - "description": r''' + "description": r""" Relationship between the sides and angles of any triangle $$\frac{a}{\sin A} = \frac{b}{\sin B} = \frac{c}{\sin C}$$ @@ -280,13 +130,13 @@ Where: - $a$, $b$, $c$: Sides of the triangle - $A$, $B$, $C$: Angles opposite to sides $a$, $b$, $c$ respectively -This rule is useful for solving triangles when certain combinations of angles and sides are known.''', +This rule is useful for solving triangles when certain combinations of angles and sides are known.""", "input": [ - {"name": "a", "unit": "meter"}, // Side a - {"name": "A", "unit": "degree"}, // Angle A in degrees - {"name": "B", "unit": "degree"} // Angle B in degrees + {"name": "a", "unit": "meter"}, + {"name": "A", "unit": "degree"}, + {"name": "B", "unit": "degree"} ], - "output": {"name": "b", "unit": "meter"}, // Side b + "output": {"name": "b", "unit": "meter"}, "d4rtCode": """ var angleARad = A * (pi / 180); var angleBRad = B * (pi / 180); @@ -298,7 +148,7 @@ This rule is useful for solving triangles when certain combinations of angles an // Cosine Rule { "name": "Cosine Rule", - "description": r''' + "description": r""" Generalization of the Pythagorean theorem for any triangle $$c^2 = a^2 + b^2 - 2ab\cos(C)$$ @@ -307,13 +157,13 @@ Where: - $a$, $b$, $c$: Sides of the triangle - $C$: Angle opposite to side $c$ -This rule relates all three sides of a triangle to one of its angles.''', +This rule relates all three sides of a triangle to one of its angles.""", "input": [ - {"name": "a", "unit": "meter"}, // Side a - {"name": "b", "unit": "meter"}, // Side b - {"name": "C", "unit": "degree"} // Angle C in degrees + {"name": "a", "unit": "meter"}, + {"name": "b", "unit": "meter"}, + {"name": "C", "unit": "degree"} ], - "output": {"name": "c", "unit": "meter"}, // Side c + "output": {"name": "c", "unit": "meter"}, "d4rtCode": """ var angleCRad = C * (pi / 180); c = sqrt(pow(a, 2) + pow(b, 2) - 2*a*b*cos(angleCRad)); @@ -324,7 +174,7 @@ This rule relates all three sides of a triangle to one of its angles.''', // Trigonometric Identity { "name": "Trigonometric Identity", - "description": r''' + "description": r""" Fundamental Pythagorean identity in trigonometry $$\sin^2(\theta) + \cos^2(\theta) = 1$$ @@ -332,11 +182,11 @@ $$\sin^2(\theta) + \cos^2(\theta) = 1$$ Where: - $\theta$: Any angle in radians or degrees -This identity is derived from the Pythagorean theorem applied to the unit circle.''', +This identity is derived from the Pythagorean theorem applied to the unit circle.""", "input": [ - {"name": "theta", "unit": "degree"} // Angle in degrees + {"name": "theta", "unit": "degree"} ], - "output": {"name": "result", "unit": "scalar"}, // Result (should be 1) + "output": {"name": "result", "unit": "scalar"}, "d4rtCode": """ var thetaRad = theta * (pi / 180); result = pow(sin(thetaRad), 2) + pow(cos(thetaRad), 2); @@ -347,7 +197,7 @@ This identity is derived from the Pythagorean theorem applied to the unit circle // Gravitational Potential Energy { "name": "Gravitational Potential Energy", - "description": r''' + "description": r""" Energy possessed by an object due to its position in a gravitational field $$PE = mgh$$ @@ -357,13 +207,13 @@ Where: - $g$: Gravitational acceleration ($9.81\ \mathrm{m/s^2}$ on Earth) - $h$: Height above reference point (meters) -This energy increases with height and can be converted to kinetic energy.''', +This energy increases with height and can be converted to kinetic energy.""", "input": [ - {"name": "m", "unit": "kilogram"}, // Mass - {"name": "h", "unit": "meter"}, // Height - {"name": "g", "unit": "meters per square second"} // Gravitational acceleration + {"name": "m", "unit": "kilogram"}, + {"name": "h", "unit": "meter"}, + {"name": "g", "unit": "meters per square second"} ], - "output": {"name": "PE", "unit": "joule"}, // Potential energy + "output": {"name": "PE", "unit": "joule"}, "d4rtCode": "PE = m * g * h;", "tags": ["physics", "energy", "mechanics", "gravity"] }, @@ -371,7 +221,7 @@ This energy increases with height and can be converted to kinetic energy.''', // Linear Momentum { "name": "Linear Momentum", - "description": r''' + "description": r""" Quantity of motion possessed by a moving object $$p = mv$$ @@ -381,334 +231,20 @@ Where: - $m$: Mass (kilograms) - $v$: Velocity (m/s) -Momentum is conserved in isolated systems.''', +Momentum is conserved in isolated systems.""", "input": [ - {"name": "m", "unit": "kilogram"}, // Mass - {"name": "v", "unit": "meters per second"} // Velocity + {"name": "m", "unit": "kilogram"}, + {"name": "v", "unit": "meters per second"} ], - "output": {"name": "p", "unit": "kilogram meter per second"}, // Momentum + "output": {"name": "p", "unit": "kilogram meter per second"}, "d4rtCode": "p = m * v;", "tags": ["physics", "mechanics", "momentum"] }, - // Density - { - "name": "Density", - "description": r''' -Mass per unit volume of a substance - -$$\rho = \frac{m}{V}$$ - -Where: -- $\rho$: Density ($\mathrm{kg/m^3}$) -- $m$: Mass (kilograms) -- $V$: Volume (cubic meters) - -Density is an intrinsic property of materials.''', - "input": [ - {"name": "m", "unit": "kilogram"}, // Mass - {"name": "V", "unit": "cubic meter"} // Volume - ], - "output": {"name": "rho", "unit": "kilogram per cubic meter"}, // Density - "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}$$ - -Where: -- $P$: Pressure (Pascals) -- $F$: Force (Newtons) -- $A$: Area (square meters) - -Pressure is transmitted equally in all directions in fluids.''', - "input": [ - {"name": "F", "unit": "newton"}, // Force - {"name": "A", "unit": "square meter"} // Area - ], - "output": {"name": "P", "unit": "pascal"}, // Pressure - "d4rtCode": "P = F / A;", - "tags": ["physics", "mechanics", "fluid"] - }, - - // Work - { - "name": "Work", - "description": r''' -Energy transferred when a force moves an object - -$$W = Fd\cos(\theta)$$ - -Where: -- $W$: Work (Joules) -- $F$: Force (Newtons) -- $d$: Displacement (meters) -- $\theta$: Angle between force and displacement - -Maximum work occurs when force and displacement are parallel.''', - "input": [ - {"name": "F", "unit": "newton"}, // Force - {"name": "d", "unit": "meter"}, // Displacement - {"name": "theta", "unit": "degree"} // Angle - ], - "output": {"name": "W", "unit": "joule"}, // Work - "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) - -Power measures how quickly energy is used or transferred.''', - "input": [ - {"name": "W", "unit": "joule"}, // Work or Energy - {"name": "t", "unit": "second"} // Time - ], - "output": {"name": "P", "unit": "watt"}, // Power - "d4rtCode": "P = W / t;", - "tags": ["physics", "energy", "mechanics"] - }, - - // Coulomb's Law - { - "name": "Coulomb's Law", - "description": r''' -Force between two electrically charged particles - -$$F = k_e\frac{q_1q_2}{r^2}$$ - -Where: -- $F$: Electrostatic force (Newtons) -- $k_e$: Coulomb's constant $8.988\times 10^9\ \mathrm{N\cdot m^2/C^2}$ -- $q_1, q_2$: Electric charges (Coulombs) -- $r$: Distance between charges (meters) - -Like charges repel, opposite charges attract.''', - "input": [ - {"name": "q1", "unit": "coulomb"}, // Charge 1 - {"name": "q2", "unit": "coulomb"}, // Charge 2 - {"name": "r", "unit": "meter"} // Distance - ], - "output": {"name": "F", "unit": "newton"}, // Force - "d4rtCode": "F = (8.9875517923e9 * q1 * q2) / pow(r, 2);", - "tags": ["physics", "electricity", "electrostatics"] - }, - - // Electric Power - { - "name": "Electric Power", - "description": r''' -Rate at which electrical energy is transferred - -$$P = VI$$ - -Where: -- $P$: Power (Watts) -- $V$: Voltage (Volts) -- $I$: Current (Amperes) - -This formula is fundamental in electrical circuit analysis.''', - "input": [ - {"name": "V", "unit": "volt"}, // Voltage - {"name": "I", "unit": "ampere"} // Current - ], - "output": {"name": "P", "unit": "watt"}, // Power - "d4rtCode": "P = V * I;", - "tags": ["physics", "electricity", "electronics"] - }, - - // Ideal Gas Law - { - "name": "Ideal Gas Law", - "description": r''' -Equation of state for an ideal gas - -$$PV = nRT$$ - -Where: -- $P$: Pressure (Pascals) -- $V$: Volume (cubic meters) -- $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"}, // Amount of substance - {"name": "T", "unit": "kelvin"}, // Temperature - {"name": "V", "unit": "cubic meter"} // Volume - ], - "output": {"name": "P", "unit": "pascal"}, // Pressure - "d4rtCode": "P = (n * 8.314462618 * T) / V;", - "tags": ["physics", "thermodynamics", "gas"] - }, - - // Snell's Law - { - "name": "Snell's Law", - "description": r''' -Law describing refraction of light at interface between media - -$$n_1\sin(\theta_1) = n_2\sin(\theta_2)$$ - -Where: -- $n_1, n_2$: Refractive indices of the two media -- $\theta_1$: Angle of incidence -- $\theta_2$: Angle of refraction - -This law explains how light bends when passing between materials.''', - "input": [ - {"name": "n1", "unit": "scalar"}, // Refractive index 1 - {"name": "n2", "unit": "scalar"}, // Refractive index 2 - {"name": "theta1", "unit": "degree"} // Angle of incidence - ], - "output": {"name": "theta2", "unit": "degree"}, // Angle of refraction - "d4rtCode": """ - var theta1Rad = theta1 * (pi / 180); - var sinTheta2 = (n1 * sin(theta1Rad)) / n2; - theta2 = asin(sinTheta2) * (180 / pi); - """, - "tags": ["physics", "optics", "light"] - }, - - - // Area of Circle - { - "name": "Area of Circle", - "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 - ], - "output": {"name": "A", "unit": "square meter"}, // Area - "d4rtCode": "A = pi * pow(r, 2);", - "tags": ["geometry", "circle", "area"] - }, - - // Circumference of Circle - { - "name": "Circumference of Circle", - "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 - ], - "output": {"name": "C", "unit": "meter"}, // Circumference - "d4rtCode": "C = 2 * pi * r;", - "tags": ["geometry", "circle", "perimeter"] - }, - - // Area of Triangle - { - "name": "Area of 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 - ], - "output": {"name": "A", "unit": "square meter"}, // Area - "d4rtCode": "A = 0.5 * b * h;", - "tags": ["geometry", "triangle", "area"] - }, - - // Area of Rectangle - { - "name": "Area of Rectangle", - "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 - ], - "output": {"name": "A", "unit": "square meter"}, // Area - "d4rtCode": "A = l * w;", - "tags": ["geometry", "rectangle", "area"] - }, - - // Area of Trapezoid - { - "name": "Area of Trapezoid", - "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 - ], - "output": {"name": "A", "unit": "square meter"}, // Area - "d4rtCode": "A = 0.5 * (a + b) * h;", - "tags": ["geometry", "trapezoid", "area"] - }, - // Volume of Sphere { "name": "Volume of Sphere", - "description": r''' + "description": r""" Volume enclosed by a sphere $$V = \frac{4}{3}\pi r^3$$ @@ -718,11 +254,11 @@ Where: - $r$: Radius (meters) - $\pi$: Pi ($\approx 3.14159$) -The volume is proportional to the cube of the radius.''', +The volume is proportional to the cube of the radius.""", "input": [ - {"name": "r", "unit": "meter"} // Radius + {"name": "r", "unit": "meter"} ], - "output": {"name": "V", "unit": "cubic meter"}, // Volume + "output": {"name": "V", "unit": "cubic meter"}, "d4rtCode": "V = (4.0/3.0) * pi * pow(r, 3);", "tags": ["geometry", "sphere", "volume"] }, @@ -730,7 +266,7 @@ The volume is proportional to the cube of the radius.''', // Surface Area of Sphere { "name": "Surface Area of Sphere", - "description": r''' + "description": r""" Total surface area of a sphere $$A = 4\pi r^2$$ @@ -740,11 +276,11 @@ Where: - $r$: Radius (meters) - $\pi$: Pi ($\approx 3.14159$) -The surface area is four times the area of a circle with the same radius.''', +The surface area is four times the area of a circle with the same radius.""", "input": [ - {"name": "r", "unit": "meter"} // Radius + {"name": "r", "unit": "meter"} ], - "output": {"name": "A", "unit": "square meter"}, // Surface area + "output": {"name": "A", "unit": "square meter"}, "d4rtCode": "A = 4 * pi * pow(r, 2);", "tags": ["geometry", "sphere", "surface area"] }, @@ -752,7 +288,7 @@ The surface area is four times the area of a circle with the same radius.''', // Volume of Cylinder { "name": "Volume of Cylinder", - "description": r''' + "description": r""" Volume enclosed by a cylinder $$V = \pi r^2 h$$ @@ -763,12 +299,12 @@ Where: - $h$: Height (meters) - $\pi$: Pi ($\approx 3.14159$) -The volume is the area of the base times the height.''', +The volume is the area of the base times the height.""", "input": [ - {"name": "r", "unit": "meter"}, // Radius - {"name": "h", "unit": "meter"} // Height + {"name": "r", "unit": "meter"}, + {"name": "h", "unit": "meter"} ], - "output": {"name": "V", "unit": "cubic meter"}, // Volume + "output": {"name": "V", "unit": "cubic meter"}, "d4rtCode": "V = pi * pow(r, 2) * h;", "tags": ["geometry", "cylinder", "volume"] }, @@ -776,7 +312,7 @@ The volume is the area of the base times the height.''', // Surface Area of Cylinder { "name": "Surface Area of Cylinder", - "description": r''' + "description": r""" Total surface area of a cylinder (including top and bottom) $$A = 2\pi r(r + h)$$ @@ -787,12 +323,12 @@ Where: - $h$: Height (meters) - $\pi$: Pi ($\approx 3.14159$) -This includes the lateral surface plus the two circular ends.''', +This includes the lateral surface plus the two circular ends.""", "input": [ - {"name": "r", "unit": "meter"}, // Radius - {"name": "h", "unit": "meter"} // Height + {"name": "r", "unit": "meter"}, + {"name": "h", "unit": "meter"} ], - "output": {"name": "A", "unit": "square meter"}, // Surface area + "output": {"name": "A", "unit": "square meter"}, "d4rtCode": "A = 2 * pi * r * (r + h);", "tags": ["geometry", "cylinder", "surface area"] }, @@ -800,7 +336,7 @@ This includes the lateral surface plus the two circular ends.''', // Volume of Cone { "name": "Volume of Cone", - "description": r''' + "description": r""" Volume enclosed by a cone $$V = \frac{1}{3}\pi r^2 h$$ @@ -811,12 +347,12 @@ Where: - $h$: Height (meters) - $\pi$: Pi ($\approx 3.14159$) -The volume is one-third the volume of a cylinder with the same base and height.''', +The volume is one-third the volume of a cylinder with the same base and height.""", "input": [ - {"name": "r", "unit": "meter"}, // Radius - {"name": "h", "unit": "meter"} // Height + {"name": "r", "unit": "meter"}, + {"name": "h", "unit": "meter"} ], - "output": {"name": "V", "unit": "cubic meter"}, // Volume + "output": {"name": "V", "unit": "cubic meter"}, "d4rtCode": "V = (1.0/3.0) * pi * pow(r, 2) * h;", "tags": ["geometry", "cone", "volume"] }, @@ -824,7 +360,7 @@ The volume is one-third the volume of a cylinder with the same base and height.' // Volume of Cube { "name": "Volume of Cube", - "description": r''' + "description": r""" Volume enclosed by a cube $$V = s^3$$ @@ -833,11 +369,11 @@ Where: - $V$: Volume (cubic meters) - $s$: Side length (meters) -The volume is the cube of the side length.''', +The volume is the cube of the side length.""", "input": [ - {"name": "s", "unit": "meter"} // Side length + {"name": "s", "unit": "meter"} ], - "output": {"name": "V", "unit": "cubic meter"}, // Volume + "output": {"name": "V", "unit": "cubic meter"}, "d4rtCode": "V = pow(s, 3);", "tags": ["geometry", "cube", "volume"] }, @@ -845,7 +381,7 @@ The volume is the cube of the side length.''', // Surface Area of Cube { "name": "Surface Area of Cube", - "description": r''' + "description": r""" Total surface area of a cube $$A = 6s^2$$ @@ -854,11 +390,11 @@ Where: - $A$: Total surface area (square meters) - $s$: Side length (meters) -The surface area is six times the area of one face.''', +The surface area is six times the area of one face.""", "input": [ - {"name": "s", "unit": "meter"} // Side length + {"name": "s", "unit": "meter"} ], - "output": {"name": "A", "unit": "square meter"}, // Surface area + "output": {"name": "A", "unit": "square meter"}, "d4rtCode": "A = 6 * pow(s, 2);", "tags": ["geometry", "cube", "surface area"] }, @@ -866,7 +402,7 @@ The surface area is six times the area of one face.''', // Perimeter of Rectangle { "name": "Perimeter of Rectangle", - "description": r''' + "description": r""" Total distance around a rectangle $$P = 2(l + w)$$ @@ -876,12 +412,12 @@ Where: - $l$: Length (meters) - $w$: Width (meters) -The perimeter is twice the sum of length and width.''', +The perimeter is twice the sum 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": "P", "unit": "meter"}, // Perimeter + "output": {"name": "P", "unit": "meter"}, "d4rtCode": "P = 2 * (l + w);", "tags": ["geometry", "rectangle", "perimeter"] }, @@ -889,7 +425,7 @@ The perimeter is twice the sum of length and width.''', // Perimeter of Triangle { "name": "Perimeter of Triangle", - "description": r''' + "description": r""" Total distance around a triangle $$P = a + b + c$$ @@ -898,91 +434,14 @@ Where: - $P$: Perimeter (meters) - $a, b, c$: Side lengths (meters) -The perimeter is the sum of all three sides.''', +The perimeter is the sum of all three sides.""", "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": "P", "unit": "meter"}, // Perimeter + "output": {"name": "P", "unit": "meter"}, "d4rtCode": "P = a + b + c;", "tags": ["geometry", "triangle", "perimeter"] - }, - - // Area of Regular Polygon - { - "name": "Area of Regular Polygon", - "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 - ], - "output": {"name": "A", "unit": "square meter"}, // Area - "d4rtCode": "A = 0.25 * n * pow(s, 2) * (cos(pi/n) / sin(pi/n));", - "tags": ["geometry", "polygon", "area"] - }, - - // Sum of Interior Angles of Polygon - { - "name": "Sum of Interior Angles", - "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 - ], - "output": {"name": "S", "unit": "degree"}, // Sum of angles - "d4rtCode": "S = (n - 2) * 180;", - "tags": ["geometry", "polygon", "angles"] - }, - - // Heron's Formula (Area of Triangle) - { - "name": "Heron's Formula", - "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 - ], - "output": {"name": "A", "unit": "square meter"}, // Area - "d4rtCode": """ - if( a + b < c || a+c < b || b+c < a ){ - signal( "There is not a valid triangle with those longitudes" ); - } - var s = (a + b + c) / 2; - A = sqrt(s * (s - a) * (s - b) * (s - c)); - """, - "tags": ["geometry", "triangle", "area"] } ] diff --git a/assets/formulas/geometry.d4rt b/assets/formulas/geometry.d4rt index b26d10c..4376ba4 100644 --- a/assets/formulas/geometry.d4rt +++ b/assets/formulas/geometry.d4rt @@ -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" ); diff --git a/assets/formulas/materials_elasticity.d4rt b/assets/formulas/materials_elasticity.d4rt index 48e1ad2..25df21d 100644 --- a/assets/formulas/materials_elasticity.d4rt +++ b/assets/formulas/materials_elasticity.d4rt @@ -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"] + } ] diff --git a/assets/formulas/medical_and_bio.d4rt b/assets/formulas/medical_and_bio.d4rt index 6a8bac0..5136b6c 100644 --- a/assets/formulas/medical_and_bio.d4rt +++ b/assets/formulas/medical_and_bio.d4rt @@ -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. + +![Apgar score (illustration) (Wikipedia)](https://upload.wikimedia.org/wikipedia/commons/thumb/8/80/Apgar_scale.svg/800px-Apgar_scale.svg.png) +""","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"]} +] diff --git a/assets/formulas/misc_math.d4rt b/assets/formulas/misc_math.d4rt index 97a8d3e..8c5c957 100644 --- a/assets/formulas/misc_math.d4rt +++ b/assets/formulas/misc_math.d4rt @@ -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"] + } ] diff --git a/assets/formulas/networking.d4rt b/assets/formulas/networking.d4rt new file mode 100644 index 0000000..ceda27e --- /dev/null +++ b/assets/formulas/networking.d4rt @@ -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"] + } +] diff --git a/assets/formulas/thermodynamics.d4rt b/assets/formulas/thermodynamics.d4rt index 0d4f101..7248302 100644 --- a/assets/formulas/thermodynamics.d4rt +++ b/assets/formulas/thermodynamics.d4rt @@ -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"] + } ] diff --git a/docker/Dockerfile b/docker/Dockerfile index 5e58621..24dff8d 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -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 diff --git a/lib/ai/formula_editor.dart b/lib/ai/formula_editor.dart new file mode 100644 index 0000000..ff7805e --- /dev/null +++ b/lib/ai/formula_editor.dart @@ -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 createState() => _FormulaEditorState(); +} + +class _FormulaEditorState extends State { + final _formKey = GlobalKey(); + 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 = []; + 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: [ + 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( + value: _getBaseUnit(variable.unit), + decoration: const InputDecoration( + border: OutlineInputBorder(), + contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 0), + ), + items: [ + const DropdownMenuItem( + value: null, + child: Text('None', style: TextStyle(fontSize: 14)), + ), + ..._getAllBaseUnits().map((baseUnit) { + return DropdownMenuItem( + 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( + value: variable.unit, + decoration: const InputDecoration( + border: OutlineInputBorder(), + contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 0), + ), + items: [ + const DropdownMenuItem( + value: null, + child: Text('None', style: TextStyle(fontSize: 14)), + ), + ..._getDerivedUnits(variable.unit).map((unit) { + final unitSpec = widget.corpus.getUnit(unit); + return DropdownMenuItem( + 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( + value: _getBaseUnit(_outputVariable.unit), + decoration: const InputDecoration( + border: OutlineInputBorder(), + contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 0), + ), + items: [ + const DropdownMenuItem( + value: null, + child: Text('None', style: TextStyle(fontSize: 14)), + ), + ..._getAllBaseUnits().map((baseUnit) { + return DropdownMenuItem( + 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( + value: _outputVariable.unit, + decoration: const InputDecoration( + border: OutlineInputBorder(), + contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 0), + ), + items: [ + const DropdownMenuItem( + value: null, + child: Text('None', style: TextStyle(fontSize: 14)), + ), + ..._getDerivedUnits(_outputVariable.unit).map((unit) { + final unitSpec = widget.corpus.getUnit(unit); + return DropdownMenuItem( + 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 _getAllBaseUnits() { + final baseUnits = {}; + for (final unit in widget.corpus.allUnits()) { + baseUnits.add(unit.baseUnit); + } + return baseUnits.toList()..sort(); + } + + List _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? values; + + _InputVariableRowData({ + required this.nameController, + this.unit, + this.values, + }); +} + +class _OutputVariableRowData { + final TextEditingController nameController; + String? unit; + + _OutputVariableRowData({ + required this.nameController, + this.unit, + }); +} diff --git a/lib/ai/formula_list.dart b/lib/ai/formula_list.dart index b45f7e0..a9f6675 100644 --- a/lib/ai/formula_list.dart +++ b/lib/ai/formula_list.dart @@ -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; @@ -55,13 +57,13 @@ class _FormulaListState extends State { try { // Get the formula and its dependencies final dependencies = widget.corpus.withDependencies(formula); - + // Convert each dependency to its string literal representation final literals = dependencies.map((element) => element.toStringLiteral()).toList(); - + // Create an array string literal containing all the elements final exportString = '[${literals.join(', ')}]'; - + // Share the string await share_plus.SharePlus.instance.share( share_plus.ShareParams( @@ -74,6 +76,18 @@ class _FormulaListState extends State { } } + 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 { 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'), + ], + ), + ), + ], ), ], ), diff --git a/lib/ai/formula_screen.dart b/lib/ai/formula_screen.dart index 4ec9f0c..dc1089f 100644 --- a/lib/ai/formula_screen.dart +++ b/lib/ai/formula_screen.dart @@ -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 { _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 { @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( diff --git a/lib/defaults/default_corpus.dart b/lib/defaults/default_corpus.dart index 2b78476..10057f1 100644 --- a/lib/defaults/default_corpus.dart +++ b/lib/defaults/default_corpus.dart @@ -51,11 +51,11 @@ Future createDefaultCorpus() async{ Future 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 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) {