Skip to content

The supported LaTeX subset

Octant translates a bounded subset of mathematical LaTeX into Chelis Deep. The subset is "mathematical notation as it appears in finance and scientific computing textbooks," not arbitrary LaTeX. This chapter enumerates the constructs that translate, with a worked example or two each, the Chelis shape Octant emits, and the type-inference behaviour that goes with it.

If you hit something not listed here, see Scope and limitations. Most "won't translate" errors come from a known unsupported construct, not a bug, and each has a rewrite recipe.

The Deep snippets below show the structural tags (app, var, lit, def, if, grad) and elide the {span: "..."} metadata that every emitted node actually carries. The metadata is the audit trail; see Provenance and span tracking.

Single-letter ASCII identifiers, Greek letters via \alpha, \sigma, \Phi, \Sigma, \mu, and the constants \pi, e, \infty.

% chelis: x : f32
% chelis: sigma : f32
\sigma x

This translates to (app (var mul) (var sigma) (var x)). Greek letters keep their LaTeX-stripped name (\sigma becomes sigma). The inference pass treats them as ordinary scalars and asks for a % chelis: annotation unless they are bound by a sum, product, or builtin constant.

The constants \pi, e, and \infty parse as identifiers. The inference pass treats them like any other free variable and asks for a % chelis: annotation. The reference pairs that mention e declare it as % chelis: e : f32.

The default scalar type is f32. Add a % chelis: <name> : f64 annotation to widen.

Integers, decimals, and scientific notation in either 1.5 \times 10^{-3} or 1.5e-3 form.

% chelis: r : f32
0.5 r

This translates to (app (var mul) (lit 0.5) (var r)). The \times 10^{...} shape is supported; it routes through the general power lowering and does not get a special-cased numeric literal.

Integer literals infer to i32; decimals to f32. Mixed i32/f32 arithmetic widens to f32 under the unification rules.

Subscripts attach to identifiers via _<single-token> or _{...}.

% chelis: x_i : f32
% chelis: S_t : f32
x_i + S_t

Subscripted identifiers flatten by default: x_i becomes a single named scalar x_i. The % chelis: annotation refers to the flattened name. This matches how textbook math reads x_i, a named scalar indexed by convention. When the index is itself an arithmetic expression (x_{i+1}) or the base is a complex expression ((A B)_{ij}), normalization lowers to a structured index node instead.

Superscripts are exponentiation when the exponent is numeric or evaluates to a number: x^2, e^{rT}, \sigma^2, x^{0.5}. The emitted Deep depends on the exponent shape; see the pow lowering table.

x^T and x^{\top} are special-cased as transpose, not exponentiation; see Transpose.

Binary operators: +, -, *, /, \cdot, \times. Equality = for top-level name = body definitions. Juxtaposition is implicit multiplication.

% chelis: a : f32
% chelis: b : f32
% chelis: c : f32
a b + c

Grouping: (...), [...], \{...\}, and the \left( ... \right) sized variants. The closing form must match the opener. A \left( ... \right] is a parse error.

% chelis: x : f32
% chelis: y : f32
\left( x + y \right)

All binary operators require numeric operands. bool cannot mix with i32, f32, or f64. The = form at top level marks the left side as the definition name and the right side as the body, which is what produces (def name body) in the Deep output.

Comparison operators <, >, \leq, \geq are not supported as free binary operators. They are permitted only inside indicator-condition position (\mathbb{1}_{x = 0}) and the \sum_{i=1}^{n} lower-bound syntax, both as part of larger constructs.

\frac{a}{b} lowers to division:

% chelis: a : f32
% chelis: b : f32
\frac{a}{b}
(app (var div) (var a) (var b))

\sqrt{x} lowers to the sqrt builtin:

% chelis: x : f32
\sqrt{x}
(app (var sqrt) (var x))

\sqrt[n]{x} is the n-th root. It is supported and lowered through the general power fallback (exp (mul (log x) (div 1 n))). There is no dedicated n-th-root primitive.

An empty \frac{}{} or \frac{a}{} is a parse error, as is \sqrt with no following {...}.

Bounded sums and products. The lower bound must use the i = start form, and the upper bound is required.

% chelis: n : i32
% chelis: x : tensor[n, f32]
\sum_{i=1}^{n} x_i
(app (var reduce_sum) (var i) (lit 1) (var n) (var x_i))

The emitted reduce_sum form takes index lower upper body. Octant prefers the direct reduction form for simple subscript bodies. \prod_{i=1}^{n} x_i is the equivalent product, emitting reduce_prod.

Unbounded forms (\sum_i x_i, \sum x) are an explicit error: the lower bound must be derivable. Set-membership iteration \sum_{i \in S} is rejected because \in is not a recognized command. A lower bound with no upper bound is also an error.

Standard math: \sin(x), \cos(x), \tan(x), \exp(x), \log(x), \ln(x), \sqrt{x}, |x| (absolute value), \max(a, b), \min(a, b).

% chelis: x : f32
% chelis: y : f32
\max(x, y)
(app (var max) (var x) (var y))

|x| lowers to (app (var abs) (var x)).

The positive-part shape \max(x, 0) is written directly.

The indicator \mathbb{1}_{cond} and \mathbf{1}_{cond} lower to an if node:

% chelis: x : f32
\mathbb{1}_{x = 0}
(if (app (var eq) (var x) (lit 0)) (lit 1.0) (lit 0.0))

The condition body must be a recognized predicate. = is the comparison Octant accepts in the indicator position.

\sin and the other named functions require the parenthesized argument form (\sin(x)). \sin x (juxtaposition) parses as \sin times x, which is rarely what you want. Use the parenthesized form.

Expectation \mathbb{E}[X], conditional expectation \mathbb{E}[X | F], probability \mathbb{P}(A), and conditional probability \mathbb{P}(A | B).

% chelis: x : f32
% chelis: f : f32
\mathbb{E}[x | f]
(app (var cond_expectation) (var x) (var f))

The unconditional \mathbb{E}[X] form lowers to expectation. Octant does not compute expectations. The surrounding context must define expectation, cond_expectation, or cond_probability, typically via an import:

% chelis: import Shoals.MonteCarlo (expectation, cond_expectation)

The | inside \mathbb{E}[...] and \mathbb{P}(...) is a conditional separator, not absolute value. Octant's parser keeps a context stack so that \mathbb{E}[|x| | F] parses correctly: the inner |x| is absolute value, the outer | is the separator.

Joint probability \mathbb{P}(A, B) is not supported. Rewrite as \mathbb{P}(A \cap B) if your target context defines cap, or split it.

Partial derivatives \frac{\partial f}{\partial x} and ordinary derivatives \frac{df}{dx} lower to a gradient node:

% chelis: x : f32
% chelis: f : f32
\frac{\partial f}{\partial x}
(grad {wrt: x} (var f))

The Chelis compiler is responsible for verifying that f is pure and differentiable. Octant emits the gradient form and defers the check.

Integrals are not supported. \int_0^1 f(x) dx raises an error: Octant requires a known closed form. Hand-expand the integral to its closed-form expression and translate that.

\begin{equation} with an optional \label{...} propagates the label into every emitted span identifier:

% chelis: c : f32
% chelis: s : f32
% chelis: k : f32
\begin{equation}\label{eq:bs}
c = s + k
\end{equation}
(def {span: "eq:bs_001"} c
(app {span: "eq:bs_002"} (var add) (var s) (var k)))

Every span identifier inside the labelled equation is prefixed with the label (eq:bs_001, eq:bs_002), so octant explain --target eq:bs_002 resolves to the right LaTeX byte range. The label also flows into the spans manifest's latex.label field. \begin{equation*} is treated the same as \begin{equation}.

Other environments (tabular, aligned, matrix) are not math environments and produce a clean error. \begin{align} parses the shell but its & row separators are not handled; split it into multiple \begin{equation} blocks.

The % chelis: magic comments declare the types of free identifiers and bring imported symbols into scope. The supported forms:

LaTeXEffect
% chelis: r : f64declares r of type f64
% chelis: x, y : f32declares both x and y of type f32
% chelis: theta : tensor[k, f64]a tensor with one named dim k, element f64
% chelis: import Shoals.MonteCarlo (expectation, dW)brings expectation and dW into scope

The recognized primitive types are f32, f64, i32, i64, and bool. Tensor types take a comma-separated list [<dim>..., <prim>] where each dim is an integer literal, an identifier, or _ for a wildcard, and the element type is the last entry of the bracket list.

Imports add the listed names to scope as customer-supplied symbols. The emitter records the import header in the output program.

An unknown primitive errors. Stray colons error. The % chelis: prefix is required: % r : f64 is treated as a generic LaTeX comment and ignored.

Bin{Pow, x, y} is lowered at Deep emission, not at type inference. The exponent shape selects the emitted form:

LaTeX exponentEmitted Deep
x^2(app (var mul) x x)
x^3(app (var mul) x (app (var mul) x x))
x^4(app (var mul) (app (var mul) x x) (app (var mul) x x))
x^{0.5}(app (var sqrt) x)
x^{-1}(app (var div) (lit 1.0) x)
x^{-2}(app (var div) (lit 1.0) (app (var mul) x x))
anything else(app (var exp) (app (var mul) (app (var log) x) y))

The negative-integer rows match either a literal negative integer or the synthesized (sub 0 n) form that unary-negation lowering produces, so x^{-1} reaches the cheap form regardless of how it renders.

The fallback uses the standard real-valued power identity. There is no pow primitive in the output vocabulary. Every emission goes through add, sub, mul, div, sqrt, exp, and log. A customer reading the emitted Deep sees only that small set.

The synthesized (lit 1.0) literals in the inverse-power cases inherit the span of the parent power node. They have no source representation but still point back into the LaTeX, which is what keeps the audit chain intact.

x^T, x^{\top}, and A^T are special-cased in normalization and lower to a transpose node rather than a power:

% chelis: a : f32
a^T
(app (var transpose) (var a))

The T and \top forms are equivalent. Anything else in the exponent position goes through the regular power path. Only the bare T and \top are transpose. The emitted transpose reference assumes a customer-supplied definition; Octant emits the structural form so downstream linear-algebra modules can resolve it.

The following LaTeX names map into Nautilus modules. The normal-distribution family lives in Nautilus.Distributions; the rest in Nautilus.Special.

LaTeXChelis target
\Phi(x)Nautilus.Distributions.normal_cdf(x)
\phi(x)Nautilus.Distributions.normal_pdf(x)
\Phi^{-1}(x)Nautilus.Distributions.normal_inv_cdf(x)
\Gamma(x)Nautilus.Special.gamma(x)
\text{erf}(x)Nautilus.Special.erf(x)
\text{erfc}(x)Nautilus.Special.erfc(x)
\text{Beta}(a, b)Nautilus.Special.beta(a, b)

By default Octant emits these in stub mode: it inlines a local identity stub def for each Nautilus reference and rewrites call sites to a bare name. The --emit-nautilus access opt-out reverts to the access-chain form. See Provenance and span tracking for the contract.

% chelis: x : f32
\Phi(x)

\log and \ln both map to log (natural log).