Intrinsics
The few functions and constants the compiler knows without a library — everything else is defined in Hilbert.
An intrinsic is a name every .hb document may use without opening anything. The bar for being one is
strict: an exact ONNX operator on one side and an exact .NET method on the other, so that both
executors compute the same numbers. The compiler knows the names of the intrinsics, not any
identity about them: sin(x)² + cos(x)² = 1 is a law in Trigonometry, and their
derivatives are laws in Calculus.
All intrinsics are elementwise on tensors unless stated otherwise.
Constants
| Name | Spelling | Value |
|---|---|---|
| π | π or pi |
3.141592653589793 |
| e | e |
2.718281828459045 |
Neither name may be used for a parameter.
Elementwise functions
| Function | Meaning | ONNX operator |
|---|---|---|
sin(x), cos(x), tan(x) |
trigonometric functions (radians) | Sin, Cos, Tan |
asin(x), acos(x), atan(x) |
inverse trigonometric functions | Asin, Acos, Atan |
sinh(x), cosh(x), tanh(x) |
hyperbolic functions | Sinh, Cosh, Tanh |
exp(x) |
exponential | Exp |
ln(x) |
natural logarithm | Log |
sqrt(x), √x |
square root | Sqrt |
abs(x) |
absolute value | Abs |
sign(x) |
−1, 0 or 1 | Sign |
floor(x), ceil(x) |
round down, round up | Floor, Ceil |
round(x) |
round half to even | Round |
min(x, y), max(x, y) |
elementwise minimum and maximum of two values (with broadcasting) | Min, Max |
Of these, Calculus has derivative laws for sin, cos, exp, ln and √ only. A
formula that differentiates through any other function in this table is not reduced.
sin · cos · tan
Intuition Position on a circle turning at a constant rate: cos is the horizontal coordinate, sin the vertical one, and tan their ratio, the slope of the radius. Anything that repeats (a season, a rotation, a wave) is built from them.
Use it when you model periodic signals, rotate points, encode a cyclic feature such as hour of day as the pair sin/cos so 23:00 sits next to 00:00, or invert a distribution: cauchyQuantile uses tan. Arguments are radians. tan has poles at odd multiples of \(\pi/2\) and returns huge values near them.
signal-processinggeometrycyclic-features
asin · acos · atan
Intuition The way back from a ratio to an angle. asin and acos accept only \([-1, 1]\), and atan accepts any real and returns an angle in \((-\pi/2, \pi/2)\).
Use it when you recover an angle from coordinates or a dot product (the angle between two vectors is acos of their cosine similarity), or evaluate a CDF built on an angle: cauchyCdf uses atan. There is no two-argument atan2, so atan(y / x) cannot tell opposite quadrants apart. Clamp a computed cosine into \([-1, 1]\) with min and max before acos, because rounding just outside the range gives NaN.
geometrysimilarity-measurementnumerical-stability
sinh · cosh · tanh
Intuition The circle functions’ counterparts on a hyperbola, made from \(e^{x}\) and \(e^{-x}\). tanh is the useful one in practice: an S-curve from −1 to 1 that is almost linear near 0 and flattens for large inputs.
Use it when you need a bounded, zero-centred squashing: recurrent cells (stateStep, candidateOf) and the sigmoidal kernel use tanh. cosh describes a hanging cable, and sinh/cosh appear in closed-form solutions of some diffusion problems. sinh and cosh overflow for $$ |
x | $$ above about 710. In a deep stack of tanh, saturated units pass back almost no gradient. |
nonlinear-modellingsequence-modellingbounded-outputs
exp · ln
Intuition exp turns adding into multiplying and ln turns multiplying back into adding. Growth at a rate proportional to size is exp. A product of many small probabilities becomes a sum of logs that does not underflow.
Use it when you model compounding or decay, map an unbounded score to a positive rate (exp), turn a positive quantity into an unbounded one (ln), or work with likelihoods on the log scale. ln is the natural log: for base 10 write ln(x) / ln(10). ln(0) is \(-\infty\) and a negative argument is NaN; the Prelude guards with positive(v), which floors at \(10^{-300}\). exp overflows above about 709.
growth-modellinglikelihood-evaluationnumerical-stability
sqrt · √
Intuition The way back from squared units to the original units: variance to standard deviation, a sum of squares to a length. Noise that adds up independently grows like \(\sqrt{n}\), not \(n\).
Use it when you report a spread or a distance in the data’s own units, scale a random-walk step by \(\sqrt{\Delta t}\), or compute a standard error \(s / \sqrt{n}\). A negative argument gives NaN, and a difference of two nearly equal squares can round slightly below zero, so wrap it as sqrt(max(v, 0)).
spreaddistance-measurementnumerical-stability
abs · sign
Intuition Every number is a size times a direction: abs keeps the size and sign keeps the direction (−1, 0 or 1), so sign(x) · abs(x) = x.
Use it when you measure an error without letting positives and negatives cancel (mean absolute error, L1 distance), extend a formula from \(x \ge 0\) to all reals by symmetry (erf is sign(x) times a function of \(x^2\), and laplaceCdf is one half plus sign(x − m) times a function of abs(x − m)), or test closeness with abs(a − b) < tol. Both have a kink or a jump at 0 and no derivative law, so do not differentiate through them.
error-analysissymmetryrobust-statistics
floor · ceil
Intuition Snap a real number to the whole number below or above it. They are the bridge from a continuous formula to a count: “how many whole steps fit” is a floor, and “how many are needed to reach it” is a ceil.
Use it when you evaluate a discrete CDF at a real point (poissonCdf, geometricCdf use floor), invert a closed-form quantile to a whole count (geometricQuantile uses ceil), compute a middle position (median), or bin continuous values into buckets. Their derivative is zero almost everywhere, so they stop any gradient.
discretisationcount-databinning
round
Intuition The nearest whole number, with ties such as 0.5 and 2.5 going to the even neighbour (0 and 2). This is banker’s rounding: over many values, ties round up and down equally often, so it adds no systematic upward bias.
Use it when you report or bucket values to whole units, or turn a continuous estimate into an integer choice. If you expect “0.5 rounds up”, write floor(x + 0.5) instead. ONNX Round and .NET’s default Math.Round both round half to even, which is why this is the rule both executors can agree on.
discretisationreporting
min · max
Intuition An entry-by-entry comparison of two values that keeps the smaller or larger one, with broadcasting. With one side a constant, they become a floor, a ceiling or a switch.
Use it when you clamp into a range with min(max(x, lo), hi), write a ReLU as max(z, 0), keep a divisor or a log argument away from zero (positive(v) is max(v, 1e-300)), or cap a reward or a step size. To collapse a whole tensor or axis to its extreme value, use the reductions minimum and maximum instead.
clampingnumerical-stabilitynonlinear-modelling
erf is intentionally not an intrinsic — .NET has no exact counterpart to ONNX’s Erf, so the two
executors would disagree in the seventh digit. It is defined in Hilbert in
Distributions, where both run the same arithmetic.
Operators
| Operator | Meaning |
|---|---|
x + y, x − y, x · y, x / y |
elementwise arithmetic, broadcasting |
x ^ y, x², x³ |
elementwise power |
−x |
negation |
x < y, x > y, x ≤ y, x ≥ y, x = y, x ≠ y |
comparisons, giving 1 or 0 elementwise |
Comparisons
Intuition Hilbert has no if. A comparison is a number, 1 or 0, so a condition becomes a factor: (x ≥ 0) · f(x) is \(f(x)\) where the test holds and 0 elsewhere. This is the Iverson bracket \([P]\) used throughout the reference.
Use it when you branch without a branch: piecewise formulas (geometricCdf starts with (x ≥ 0) ·), counting (einsum("i->", x > t) counts values above t), masking pairs in a matrix, or guarding a divisor (safe(d) is d + (d = 0)). Both branches are always computed, so a guarded branch that divides by zero still yields NaN or infinity; guard the divisor itself, not only the result.
maskingpiecewise-functionsevent-counting
Contraction
einsum
einsum(equation, t₁, …, tₘ) · equation a string such as "ij,jk->ik" · t tensors, one per
comma-separated group → tensor whose axes are the letters after ->
Einstein summation: multiplies the operands where their indices agree and sums every index that does
not appear in the output. This is the only contraction in the language; the dot product, matrix
product, transpose, sums and means of the Prelude are all written with it. Lowers to ONNX Einsum.
See Einstein’s summation and einsum in practice.
fn productOf(a, b) ↦ einsum("ij,jk->ik", a, b) -- [[1,2],[3,4]]·[[5,6],[7,8]] = [[19,22],[43,50]]
Intuition Name every axis with a letter; letters shared between operands are multiplied together, and letters missing after -> are summed away. One notation then covers the dot product ("i,i->"), matrix product, transpose ("ij->ji"), outer product ("i,j->ij") and totals ("i->"), and the equation string documents the shapes.
Use it when you need any sum of products over tensors, which is most of linear algebra and statistics. Before writing one, check whether the Prelude already names it (dot, mul, outer, total, …). Cost is the product of the extents of all distinct letters, so "ij,jk->ik" on n×n inputs is \(O(n^3)\). It cannot take a minimum or maximum; use minimum and maximum.
tensor-algebralinear-systemsdata-summary
Reductions
minimum
minimum(x) · x tensor → scalar minimum(x, axis) · axis whole number → tensor with that axis removed
The smallest entry of x, over every axis or along one. A minimum is not a sum of products, so it
needs its own operator (ONNX ReduceMin).
Intuition A reduction that keeps the extreme instead of adding: it collapses a tensor, or one axis of it, to the smallest value present. Since einsum can only sum products, anything “best”, “worst” or “tightest” needs this operator.
Use it when you need the low end of a range (min–max scaling, a lower bound, the cheapest option, the worst case of a set of scenarios) or the per-row minimum with an axis. Do not confuse it with min(x, y), which compares two values entry by entry and keeps the shape. It picks a value, not its position.
data-summaryfeature-scalingrisk-measurement
maximum
maximum(x) · x tensor → scalar maximum(x, axis) · axis whole number → tensor with that axis removed
The largest entry of x, over every axis or along one (ONNX ReduceMax).
Intuition The counterpart of minimum: it collapses a tensor, or one axis, to its largest entry. It is how a sum-of-products language expresses “the best” or “the biggest”.
Use it when you need a peak, an upper bound, a scale factor (the Prelude’s inverse seeds Newton–Schulz with maxima of absolute row and column sums), the greedy value \(\max_a Q(s, a)\) along the action axis, or the top of a min–max range. For an entrywise cap or a ReLU, use max(x, 0) instead, which keeps the shape.
data-summaryfeature-scalingdecision-making
Positions and extents
triangle
triangle(x, k) · x matrix · k diagonal offset → matrix of the same shape
Keeps the entries on and above diagonal k and zeroes the rest (ONNX Trilu, upper). It is the one
intrinsic that knows where a value sits, and the Prelude builds every positional idea from it:
triangle(ones, 0) − triangle(ones, 1) is the identity matrix, and summing the upper triangle of ones
down its columns gives the positions 1, 2, …, n.
Intuition Contractions know what to multiply but never where a value sits; triangle is the one source of position. A 0/1 upper triangle is the relation “column ≥ row”, and from it come the identity, index vectors, one-hot columns and “from step t onward” masks.
Use it when you need order: running totals and discounting over time (as in Returns’ cumulate), causal masks, triangular factors, or indices for selecting a row or column. \(k = 0\) keeps the diagonal, \(k = 1\) excludes it. It keeps the upper part only; for a lower triangle, transpose before and after. Masks built from it are n×n, so memory grows as \(n^2\).
maskingtime-serieslinear-systems
rows
rows(x) · x tensor → scalar
The extent of the first axis — the number of observations in a data matrix, the length of a vector.
It is computed by the graph when a tensor arrives, not folded at compile time:
mean(x) ≔ total(x) / count(x) with count(x) ≔ rows(x).
Intuition “How many?” as a value the formula can use. Because it is read from the tensor when it arrives, one compiled function works for any sample size rather than one fixed at compile time.
Use it when you average, normalise by \(n\) or \(n - 1\), or need a loop count that follows the data — the Prelude’s Cholesky, QR and conjugate-gradient loops run rows(a) turns. For the number of features of a data matrix use columns.
data-summaryshape-handling
columns
columns(x) · x matrix → scalar
The extent of the second axis — the number of features in a data matrix.
Intuition The width of a table: with observations in rows, it counts the variables measured on each one.
Use it when a formula depends on the dimension \(p\) rather than the sample size — degrees of freedom in a regression (\(n - p\)), a penalty that grows with model size, an average over features. Its argument must have a second axis; for a vector’s length use rows.
shape-handlingmodel-selection
Words
startsWith
startsWith(w, prefix) · w text · prefix text → 1 or 0
Whether a word begins with prefix. Used in guards over text data.
Intuition A yes/no question about text answered as a number, so it can sit in a guard or multiply a value like any other 0/1 mask.
Use it when categories are encoded in names: codes with a shared prefix ("ERR", a region code), identifiers grouped by namespace, or rows to include in a sum. For a match anywhere in the word, use contains.
text-filteringmasking
contains
contains(w, part) · w text · part text → 1 or 0
Whether a word contains part.
Intuition A substring test turned into 1 or 0, so text can drive a guard or a mask the same way a comparison does.
Use it when a keyword may appear anywhere in a word — tags, product names, free-text labels — and you want to branch on it or count matching rows. It is looser than startsWith, so prefer that when the position is known, to avoid accidental matches inside longer words.
text-filteringmasking
Calculus symbols
∂ u / ∂ x and ∫ u d x are part of the syntax, not functions. The compiler has no rules for them
of its own: derivatives are taken by the laws of Calculus, and a doorway that still
holds a ∂ or ∫ after reduction is not emitted.