|
| 1 | +# latex-export |
| 2 | + |
| 3 | +Converts [Diff Studio](https://datagrok.ai/help/compute/diff-studio) IVP model files into **LaTeX** and **Markdown** with LaTeX math. Part of the [diff-grok](../../README.md) library. |
| 4 | + |
| 5 | +## Quick start |
| 6 | + |
| 7 | +```typescript |
| 8 | +import {convertIvpToLatex} from 'diff-grok'; |
| 9 | + |
| 10 | +const ivp = `#name: Robertson |
| 11 | +#equations: |
| 12 | + dA/dt = -0.04 * A + 1e4 * B * C |
| 13 | + dB/dt = 0.04 * A - 1e4 * B * C - 3e7 * B**2 |
| 14 | + dC/dt = 3e7 * B**2 |
| 15 | +
|
| 16 | +#inits: |
| 17 | + A = 1 {units: mol/L} |
| 18 | + B = 0 {units: mol/L} |
| 19 | + C = 0 {units: mol/L} |
| 20 | +
|
| 21 | +#argument: t |
| 22 | + start = 0 |
| 23 | + finish = 40 |
| 24 | + step = 0.01`; |
| 25 | + |
| 26 | +// LaTeX output (default) |
| 27 | +const latex = convertIvpToLatex(ivp); |
| 28 | + |
| 29 | +// Markdown output |
| 30 | +const markdown = convertIvpToLatex(ivp, {format: 'markdown'}); |
| 31 | +``` |
| 32 | + |
| 33 | +## API |
| 34 | + |
| 35 | +### `convertIvpToLatex(ivpText, options?)` |
| 36 | + |
| 37 | +Converts the full text of an `.ivp` file to LaTeX or Markdown. |
| 38 | + |
| 39 | +**Parameters:** |
| 40 | + |
| 41 | +| Parameter | Type | Description | |
| 42 | +|---|---|---| |
| 43 | +| `ivpText` | `string` | Raw text content of an `.ivp` file | |
| 44 | +| `options` | `Partial<ConvertOptions>` | Conversion options (all optional) | |
| 45 | + |
| 46 | +**Returns:** `string` — LaTeX or Markdown markup. |
| 47 | + |
| 48 | +### `ConvertOptions` |
| 49 | + |
| 50 | +| Option | Type | Default | Description | |
| 51 | +|---|---|---|---| |
| 52 | +| `format` | `'latex' \| 'markdown'` | `'latex'` | Output format | |
| 53 | +| `includeMetadata` | `boolean` | `true` | Include name, description, and comment | |
| 54 | +| `includeInits` | `boolean` | `true` | Include initial conditions table | |
| 55 | +| `includeParameters` | `boolean` | `true` | Include parameters table | |
| 56 | +| `includeConstants` | `boolean` | `true` | Include constants table | |
| 57 | +| `useCdot` | `boolean` | `true` | Use `\cdot` for multiplication; if `false`, use juxtaposition | |
| 58 | +| `compact` | `boolean` | `false` | Compact mode: no section headings, inline initial conditions | |
| 59 | + |
| 60 | +### Compact mode |
| 61 | + |
| 62 | +When `compact: true`, the output uses text labels instead of section headings — suitable for small models: |
| 63 | + |
| 64 | +- **"Equation:"** / **"Equations:"** followed by the align block and argument range |
| 65 | +- **"initial condition:"** / **"initial conditions:"** with inline `$x(0) = 2$, $y(0) = 0$` |
| 66 | +- **"where"** for expressions, listed inline as `$E_1 = ...$, $E_2 = ...$` |
| 67 | +- Parameters and constants remain as tables |
| 68 | + |
| 69 | +```typescript |
| 70 | +const compact = convertIvpToLatex(ivp, {compact: true}); |
| 71 | +``` |
| 72 | + |
| 73 | +## Examples |
| 74 | + |
| 75 | +The [examples/](examples/) directory contains 13 `.ivp` model files and 13 `.ts` scripts demonstrating `convertIvpToLatex` with different option combinations. Models are based on the [Datagrok model library](https://datagrok.ai/help/compute/models). |
| 76 | + |
| 77 | +Models with up to 3 equations use `compact: true` for concise output; larger models use section headings. |
| 78 | + |
| 79 | +### Running examples |
| 80 | + |
| 81 | +```bash |
| 82 | +# Run a single example |
| 83 | +npx tsx src/latex-export/examples/basic-to-latex.ts |
| 84 | + |
| 85 | +# Run all examples |
| 86 | +for f in src/latex-export/examples/*.ts; do echo "=== $f ==="; npx tsx "$f"; done |
| 87 | +``` |
| 88 | + |
| 89 | +### Example catalog |
| 90 | + |
| 91 | +| Example | Model | Format | Options | |
| 92 | +|---|---|---|---| |
| 93 | +| [basic-to-latex.ts](examples/basic-to-latex.ts) | Simple 1D ODE template | LaTeX | `compact` | |
| 94 | +| [robertson-to-latex-no-meta.ts](examples/robertson-to-latex-no-meta.ts) | Robertson's stiff chemical kinetics (3 species) | LaTeX | `compact`, `includeMetadata: false` | |
| 95 | +| [advanced-to-latex.ts](examples/advanced-to-latex.ts) | 2D ODE template with expressions, constants, and parameters | LaTeX | `compact`, `includeMetadata: false`, `useCdot: false` | |
| 96 | +| [fermentation-to-latex.ts](examples/fermentation-to-latex.ts) | Ethanol fermentation process kinetics | LaTeX | `compact`, `includeParameters: false` | |
| 97 | +| [nimotuzumab-to-latex.ts](examples/nimotuzumab-to-latex.ts) | Population PK of the monoclonal antibody nimotuzumab | LaTeX | `compact`, `includeInits: false`, `useCdot: false` | |
| 98 | +| [chem-react-to-latex-no-cdot.ts](examples/chem-react-to-latex-no-cdot.ts) | Deterministic mass-action kinetics (4 species) | LaTeX | `useCdot: false` | |
| 99 | +| [pollution-to-latex.ts](examples/pollution-to-latex.ts) | Air pollution chemistry (20 species, 25 reactions) | LaTeX | `includeMetadata: false`, `includeInits: false` | |
| 100 | +| [bioreactor-to-latex-selective.ts](examples/bioreactor-to-latex-selective.ts) | Fab-arm exchange bioreactor simulation (13 species) | LaTeX | `includeConstants: false`, `includeParameters: false` | |
| 101 | +| [extended-to-markdown.ts](examples/extended-to-markdown.ts) | 2D ODE system with expressions and annotations | Markdown | `compact` | |
| 102 | +| [pk-to-markdown.ts](examples/pk-to-markdown.ts) | One-compartment pharmacokinetic model with dosing loop | Markdown | `compact` | |
| 103 | +| [energy-n-control-to-markdown.ts](examples/energy-n-control-to-markdown.ts) | Custom JS functions, ternary operators, arrow functions | Markdown | `compact`, `includeConstants: false` | |
| 104 | +| [ga-production-to-markdown.ts](examples/ga-production-to-markdown.ts) | Gluconic acid production by Aspergillus niger (multi-stage) | Markdown | `useCdot: false` | |
| 105 | +| [pk-pd-to-markdown-minimal.ts](examples/pk-pd-to-markdown-minimal.ts) | Two-compartment PK-PD model with dosing loop | Markdown | `includeInits: false`, `includeParameters: false`, `includeConstants: false` | |
| 106 | + |
| 107 | +## What gets converted |
| 108 | + |
| 109 | +### Equations |
| 110 | + |
| 111 | +ODE system from `#equations` block. Derivatives become fractions: |
| 112 | + |
| 113 | +| IVP | LaTeX | |
| 114 | +|---|---| |
| 115 | +| `dy/dt = -k * y` | `\frac{dy}{dt} = -k \cdot y` | |
| 116 | +| `d(FFox)/dt = -E11 + E12` | `\frac{d\,\mathrm{FFox}}{dt} = -\mathrm{E11} + \mathrm{E12}` | |
| 117 | +| `dy1/dt = r1 + r2` | `\frac{dy_{1}}{dt} = r_{1} + r_{2}` | |
| 118 | + |
| 119 | +Equations are rendered inside `align` (LaTeX) or `aligned` (Markdown) environments. |
| 120 | + |
| 121 | +### Expressions |
| 122 | + |
| 123 | +Auxiliary formulas from `#expressions` block, including: |
| 124 | + |
| 125 | +- Regular expressions: `E1 = C1 * exp(-t) + P1` |
| 126 | +- Arrow functions: `func1 = (p, t) => (p < 8) ? ceil(t) : ceil(t**2 / 20)` |
| 127 | +- Ternary operators: `control = (P1 < 3) ? cos(t) : sin(t)` |
| 128 | +- Aliases: `ceil = Math.ceil` |
| 129 | + |
| 130 | +### Identifiers |
| 131 | + |
| 132 | +Automatic recognition and formatting: |
| 133 | + |
| 134 | +| Pattern | Example | LaTeX | |
| 135 | +|---|---|---| |
| 136 | +| Greek letter | `alpha` | `\alpha` | |
| 137 | +| Greek + subscript | `mu2` | `\mu_{2}` | |
| 138 | +| Letter + digits | `x1`, `k25` | `x_{1}`, `k_{25}` | |
| 139 | +| Chemical formula | `CO2`, `N2O5` | `\mathrm{CO}_{2}`, `\mathrm{N_{2}O_{5}}` | |
| 140 | +| Multi-letter name | `FFox`, `MEAthiol` | `\mathrm{FFox}`, `\mathrm{MEAthiol}` | |
| 141 | +| Special constants | `PI`, `Inf` | `\pi`, `\infty` | |
| 142 | + |
| 143 | +### Operators |
| 144 | + |
| 145 | +| IVP | LaTeX | Notes | |
| 146 | +|---|---|---| |
| 147 | +| `a * b` | `a \cdot b` | Configurable via `useCdot` | |
| 148 | +| `a / b` | `\frac{a}{b}` | Inline `/` inside exponents | |
| 149 | +| `x**2` | `x^{2}` | Parentheses added for compound bases | |
| 150 | +| `>=` | `\geq` | | |
| 151 | +| `<=` | `\leq` | | |
| 152 | +| `!=` | `\neq` | | |
| 153 | +| `==` | `=` | | |
| 154 | + |
| 155 | +### Functions |
| 156 | + |
| 157 | +| IVP | LaTeX | |
| 158 | +|---|---| |
| 159 | +| `sin(x)`, `cos(x)`, `tan(x)` | `\sin\!\left(x\right)`, `\cos\!\left(x\right)`, `\tan\!\left(x\right)` | |
| 160 | +| `exp(x)` | `e^{x}` | |
| 161 | +| `sqrt(x)` | `\sqrt{x}` | |
| 162 | +| `pow(a, b)` | `a^{b}` (parentheses around compound base) | |
| 163 | +| `log(x)` | `\ln\!\left(x\right)` | |
| 164 | +| `log10(x)` | `\log_{10}\!\left(x\right)` | |
| 165 | +| `abs(x)` | `\left\lvert x \right\rvert` | |
| 166 | +| `ceil(x)` / `Math.ceil(x)` | `\left\lceil x \right\rceil` | |
| 167 | +| `Math.floor(x)` | `\left\lfloor x \right\rfloor` | |
| 168 | + |
| 169 | +### Scientific notation |
| 170 | + |
| 171 | +| IVP | LaTeX | |
| 172 | +|---|---| |
| 173 | +| `1e4` | `10^{4}` | |
| 174 | +| `3e7` | `3 \times 10^{7}` | |
| 175 | +| `9.2E-2` | `9.2 \times 10^{-2}` | |
| 176 | + |
| 177 | +### Ternary operator |
| 178 | + |
| 179 | +``` |
| 180 | +Fin = t < switchTime ? 0 : 0.025 |
| 181 | +``` |
| 182 | + |
| 183 | +Rendered as a `cases` environment: |
| 184 | + |
| 185 | +```latex |
| 186 | +\mathrm{Fin} = \begin{cases} 0, & \text{if } t < \mathrm{switchTime} \\ 0.025, & \text{otherwise} \end{cases} |
| 187 | +``` |
| 188 | + |
| 189 | +### Tables |
| 190 | + |
| 191 | +Initial conditions, parameters, and constants from `#inits`, `#parameters`, and `#constants` blocks are rendered as tables: |
| 192 | + |
| 193 | +- **LaTeX:** `tabular` with `booktabs` rules (`\toprule`, `\midrule`, `\bottomrule`) |
| 194 | +- **Markdown:** pipe-delimited tables with inline math `$...$` |
| 195 | + |
| 196 | +Tables include name, value, and units (when available). |
| 197 | + |
| 198 | +## Output formats |
| 199 | + |
| 200 | +### LaTeX |
| 201 | + |
| 202 | +Produces a standalone `.tex`-ready document with: |
| 203 | + |
| 204 | +- `\documentclass{article}` preamble |
| 205 | +- `amsmath`, `amssymb`, `booktabs` packages |
| 206 | +- `\section` / `\subsection` structure |
| 207 | +- `align` environments for equations |
| 208 | +- `tabular` environments for data tables |
| 209 | + |
| 210 | +### Markdown |
| 211 | + |
| 212 | +Produces Markdown with: |
| 213 | + |
| 214 | +- `##` / `###` heading structure |
| 215 | +- `$$\begin{aligned}...\end{aligned}$$` display math blocks |
| 216 | +- `$...$` inline math in tables |
| 217 | +- Standard Markdown pipe tables |
| 218 | + |
| 219 | +## Supported IVP blocks |
| 220 | + |
| 221 | +| Block | Converted | Description | |
| 222 | +|---|---|---| |
| 223 | +| `#name` | Yes | Model title | |
| 224 | +| `#description` | Yes | Short description | |
| 225 | +| `#comment` | Yes | Free-form text | |
| 226 | +| `#equations` | Yes | ODE system (primary content) | |
| 227 | +| `#expressions` | Yes | Auxiliary formulas | |
| 228 | +| `#inits` | Yes | Initial conditions | |
| 229 | +| `#parameters` | Yes | Model parameters | |
| 230 | +| `#constants` | Yes | Fixed constants | |
| 231 | +| `#argument` | Yes | Independent variable and range | |
| 232 | +| `#loop` | Yes | Cyclic process (dosing) | |
| 233 | +| `#update` | Yes | Multi-stage process | |
| 234 | +| `#output` | Parsed | Output columns | |
| 235 | +| `#tolerance` | Parsed | Solver tolerance | |
| 236 | +| `#meta.solver` | Parsed | Solver settings | |
| 237 | + |
| 238 | +## Architecture |
| 239 | + |
| 240 | +``` |
| 241 | +index.ts Public API: convertIvpToLatex() |
| 242 | + │ |
| 243 | + ├── parser/ |
| 244 | + │ ├── line-joiner.ts Strip comments, join multi-line formulas |
| 245 | + │ ├── ivp-parser.ts Parse .ivp text → ParsedModel |
| 246 | + │ ├── tokenizer.ts Expression string → token stream |
| 247 | + │ └── ast-parser.ts Tokens → Abstract Syntax Tree |
| 248 | + │ |
| 249 | + ├── transformer/ |
| 250 | + │ ├── identifier.ts Identifiers → Greek, subscripts, \mathrm |
| 251 | + │ ├── functions.ts Function calls → LaTeX |
| 252 | + │ └── operators.ts Operators → LaTeX |
| 253 | + │ |
| 254 | + ├── generator/ |
| 255 | + │ ├── latex-generator.ts AST → LaTeX string |
| 256 | + │ ├── bracket-manager.ts Precedence-based parenthesis logic |
| 257 | + │ └── document-builder.ts Assemble complete document |
| 258 | + │ |
| 259 | + ├── output/ |
| 260 | + │ ├── tex-formatter.ts Wrap in \documentclass preamble |
| 261 | + │ └── md-formatter.ts Wrap math in $...$ / $$...$$ |
| 262 | + │ |
| 263 | + └── types.ts All TypeScript interfaces |
| 264 | +``` |
| 265 | + |
| 266 | +**Data flow:** |
| 267 | + |
| 268 | +``` |
| 269 | +.ivp text → parseIvp() → ParsedModel → buildLatexDocument() / buildMarkdownDocument() → string |
| 270 | + │ |
| 271 | + For each formula: |
| 272 | + expressionToLatex(rhs) |
| 273 | + │ |
| 274 | + tokenize → parse → AST → nodeToLatex |
| 275 | + ├── identifierToLatex |
| 276 | + ├── functionToLatex |
| 277 | + ├── operatorToLatex |
| 278 | + └── needsParentheses |
| 279 | +``` |
| 280 | + |
| 281 | +## Testing |
| 282 | + |
| 283 | +```bash |
| 284 | +npx jest src/tests/latex-export/ # all latex-export tests |
| 285 | +npx jest src/tests/latex-export/parser/ # parser tests only |
| 286 | +npx jest src/tests/latex-export/transformer/ # transformer tests only |
| 287 | +npx jest src/tests/latex-export/generator/ # generator tests only |
| 288 | +npx jest src/tests/latex-export/integration/ # end-to-end with real .ivp files |
| 289 | +``` |
0 commit comments