Operators¶
Sutra's operators are not primitive runtime instructions. They are syntactic sugar over composed function calls, and every one has a root definition โ the chain of more-primitive functions it beta-reduces to. The compiler's function-expansion pass inlines each operator down to a tensor-op leaf (+, *, @ matmul, polynomial, axis-projector). Once inlined, the simplifier and (future) fusion pass can fold long operator chains into single cached matrices.
The principle: basically all functions beta-reduce to their components, except recursive ones. Each operator below has a function-expansion form that names what it actually computes. If that form references another non-primitive function, that function in turn has its own expansion, and the chain bottoms out at the substrate primitives the runtime exposes (dot, tanh, @, axis-indexed read/write, Haar rotation, LLM embedding).
Status legend:
- In Sutra โ the body is written in Sutra's standard library and gets inlined into user code.
- Intrinsic โ declared in the standard library without a body; the codegen routes the call to a runtime method.
- Blocked โ the Sutra-level body is sketched but a primitive surface (matrix literals,
@matmul as an operator, indexed axis write, etc.) needs to land before the body can replace the runtime method. The runtime method works today; only the compile-time inlining is blocked. - Disabled โ declared but the codegen rejects calls with
CodegenNotSupported.
Logical operators¶
Sutra's logic is fuzzy โ every value lives on the truth axis as a number in [-1, +1] (Kleene Kโ: +1 true, 0 unknown, -1 false). Logical operators are Lagrange polynomials over this scheme: exact on the three-valued grid, smooth everywhere, differentiable for gradient flow.
Each connective accepts multiple spellings; all forms produce identical AST and lower to the same stdlib polynomial body. The keyword forms (not, and, or, nand, xor, xnor, iff) are case-insensitive AND contextual โ they only become operators in expression positions, so user identifiers like Iff, Nand, or XorTable keep parsing as plain names.
| Connective | Symbolic | Keyword (case-insensitive) | Stdlib body |
|---|---|---|---|
| NOT | !a, ~a |
not a |
0 - a |
| AND | a && b, a & b |
a and b |
(a + b + ab โ aยฒ โ bยฒ + aยฒbยฒ) / 2 |
| NAND | โ | a nand b |
!(a && b) |
| OR | a \|\| b, a \| b |
a or b |
(a + b โ ab + aยฒ + bยฒ โ aยฒbยฒ) / 2 |
| XOR | โ | a xor b |
0 - a * b |
| XNOR | โ | a xnor b, a iff b |
a * b |
| EQ | a == b |
โ | cosine similarity on truth axis (intrinsic) |
| NEQ | a != b |
โ | !(a == b) |
| LT | a < b |
โ | b > a |
| GT | a > b |
โ | smooth-tanh on real-axis difference (intrinsic) |
| LE | a <= b |
โ | a < b (collapses to LT under tanh-scheme) |
| GE | a >= b |
โ | a > b (same collapse) |
!a โ negation¶
Polynomial form: NOT(v) = -v. Exact on {-1, 0, +1}, fixed point at 0 (unknown stays unknown). Spellings: !a, ~a, not a / NOT a. Status: in Sutra (stdlib/logic.su).
a && b โ conjunction (Kleene Kโ AND)¶
function fuzzy logical_and(fuzzy a, fuzzy b) {
return (a + b + a * b - a * a - b * b + a * a * b * b) * 0.5;
}
Lagrange polynomial: a โง b = (a + b + ab โ aยฒ โ bยฒ + aยฒbยฒ) / 2. Spellings: a && b, a & b, a and b / AND / And. Status: in Sutra (stdlib/logic.su).
a nand b โ NAND¶
Composition over AND and NOT. Functionally complete on its own ({NAND} alone can express any Boolean function). Status: in Sutra (stdlib/logic.su).
a || b โ disjunction (Kleene Kโ OR)¶
function fuzzy logical_or(fuzzy a, fuzzy b) {
return (a + b - a * b + a * a + b * b - a * a * b * b) * 0.5;
}
Dual polynomial to AND: a โจ b = (a + b โ ab + aยฒ + bยฒ โ aยฒbยฒ) / 2. Spellings: a || b, a | b, a or b / OR / Or. Status: in Sutra (stdlib/logic.su).
a xor b โ exclusive OR¶
Closed form: XOR(a, b) = -aยทb. Equivalent to (a && !b) || (!a && b) after polynomial folding. The simplifier discovers this collapse on its own when XOR is written compositionally; shipping the closed form here saves the simplifier the work. Status: in Sutra (stdlib/logic.su).
a xnor b โ biconditional / IFF¶
NOT(XOR), which simplifies to the bare product. Spellings: a xnor b, a iff b. Status: in Sutra (stdlib/logic.su).
a == b โ fuzzy equality (cosine on truth axis)¶
// Target Sutra body (currently intrinsic):
function fuzzy eq(vector a, vector b) {
scalar na = sqrt(dot(a, a));
scalar nb = sqrt(dot(b, b));
scalar cos = dot(a, b) / (na * nb + finfo_tiny);
return make_truth(cos);
}
Cosine similarity placed on the truth axis. Status: intrinsic (blocked on dot, sqrt, make_truth, finfo_tiny having Sutra-level surfaces).
a != b โ fuzzy inequality¶
Pure composition over == and !. Status: in Sutra (stdlib/similarity.su).
a > b โ strict greater-than (smooth sign)¶
// Target Sutra body (currently intrinsic):
function fuzzy gt(complex a, complex b) {
vector diff = a - b;
vector diff_r = real_projector @ diff;
vector signed = tanh(100.0 * diff_r);
return truth_from_real @ signed;
}
Differentiable smooth-sign on real-axis difference. Status: intrinsic (blocked on axis-projector matrix literals, tanh as a Sutra surface, @ matmul as an operator).
a < b โ strict less-than¶
> with sides swapped. Status: in Sutra.
a >= b and a <= b โ non-strict comparison¶
function fuzzy ge(complex a, complex b) { return a > b; }
function fuzzy le(complex a, complex b) { return a < b; }
On the differentiable-tanh scheme >= collapses to > (both give tanh(0) = 0 on exact ties). Programs that need strict-vs-tie distinction compose with ==. Status: in Sutra.
Chained comparisons (Python-style)¶
Sutra recognizes Python-style comparison chains and reduces them to named operations at parse time. Each pattern lowers through a dedicated codegen path so the user's intent is visible in the AST and the emitted polynomial form stays clean.
| Source | Reduction |
|---|---|
a == b == c |
Equals(a, b, c) |
a < b < c |
hasOrder(a, b, c) |
a > b > c |
hasOrder(c, b, a) (args reversed โ reduction is always-ascending) |
a <= b <= c |
hasOrderOrEqual(a, b, c) |
a >= b >= c |
hasOrderOrEqual(c, b, a) |
a == b > c == d > e |
hasOrder(e, Equals(c, d), Equals(a, b)) (equality groups inlined as nested Equals(...) args; codegen rejects nested form for now, but the parser-level shape is locked in) |
a != b == c > d |
(a != b) && (b == c) && (c > d) (any chain with !=, or otherwise mixed โ AND-chain fallback) |
Reductions:
Equals(a, b, c, ...)โ fuzzy AND of_VSA.eq(a, b)between each adjacent pair.hasOrder(a, b, c, ...)โ fuzzy AND of_VSA.gt(b, a)between each adjacent pair (sincea < bisb > aon the runtime).hasOrderOrEqual(...)โ currently identical tohasOrderbecause the K3-tanh<=collapses to<(both producetanh(0) = 0on exact ties); when a real non-strict semantics lands the body switches.- For chains mixing
==with uniform-direction ordering (e.g.a == b > c == d > e), the parser buildshasOrder(orhasOrderOrEqual) with each equality group inlined as a nestedEquals(...)call. The example reduces tohasOrder(e, Equals(c, d), Equals(a, b)). Note the "weird programming concept" this introduces:Equalsreturns a fuzzy truth value at top level, but as a positional arg insidehasOrderit represents the equality group itself โ same call name, different role by context. The codegen rejects the nested form today (the group-expansion semantics โ chain-AND with cross-group ordering plus internal group equality โ isn't wired yet). The parser-level reduction is locked in so a future codegen pass can implement it without breaking source.
This is parser-level sugar โ the +, -, *, etc. arithmetic operators continue to bind tighter than comparisons (so a + 1 == b + 2 == c works as expected).
Arithmetic operators¶
Arithmetic on Sutra's number axis (real / imag / char-flag in synthetic[]). The primitive operations +, -, *, / are runtime substrate operations; ^ and the transcendentals decompose to them.
a + b, a - b, a * b, a / b¶
Element-wise vector arithmetic on the substrate. These are runtime primitives โ tensor-op leaves the rest of the language is built on. The codegen lowers them directly to torch.add / torch.mul / etc.
Augmented assignment forms (+=, -=, *=, /=) desugar to x = x op y. Per Sutra's no-memory-points principle, the assignment is just naming the right-hand side; there's no cell being mutated.
a ^ b โ exponent (planned)¶
The ^ operator is reserved as exponentiation on the number axis (not bitwise XOR โ Sutra has no bits to flip). Surface declared 2026-04-29 from the transcendentals chat. Target expansion:
function Number operator ^(Number a, Number b) {
return Math.Pow(a, b);
}
function Number Pow(Number a, Number b) {
return exp(a * log(b));
}
Each step of the chain is a function-expansion: ^ reduces to Pow, Pow reduces to exp and log. The chain bottoms out at exp and log โ currently disabled (see Transcendental functions below).
Status: not yet implemented. Lexer needs ^ token; parser needs an infix-binary production at the appropriate precedence (above *, right-associative is the math convention); codegen routes through whatever exponentiation tier the math-approximation work picks.
The leaf VSA primitives โ what everything beta-reduces to¶
The operations below are the bottom of the function-expansion chain. The rest of this page lists higher-level operations (bind, unbind, bundle, similarity, argmax_cosine, etc.) โ those are convenience names for common compositions that ultimately reduce to calls into the Tensor namespace defined here. The point is transparency: when you ask "what does bind actually do at the lowest level?" the answer is MatrixMul(RotationFor(role), filler), and you can read that chain in the stdlib source.
Both PascalCase and snake_case spellings are accepted; both bare-name and namespaced forms work:
vector x = Tensor.MatrixMul(M, v); // preferred
vector x = Tensor.matmul(M, v); // also works
vector x = MatrixMul(M, v); // bare-name shortcut via stdlib_loader
| Leaf primitive | Sutra form | Runtime |
|---|---|---|
| Matrix multiplication | Tensor.MatrixMul(a, b) / matmul(a, b) |
np.matmul / torch.matmul |
| Tensor / Kronecker product | Tensor.TensorProduct(a, b) / tensor_product(a, b) |
np.kron / torch.kron |
| Outer product | Tensor.Outer(a, b) / outer(a, b) |
np.outer / torch.outer |
| Dot product (scalar result) | Tensor.Dot(a, b) / dot(a, b) |
np.dot / torch.dot |
| Transpose | Tensor.Transpose(M) / transpose(M) |
np.transpose / torch.transpose |
How the higher-level VSA names reduce:
bind(role, filler) -> MatrixMul(RotationFor(role), filler)
unbind(role, record) -> MatrixMul(Transpose(RotationFor(role)), record)
bundle(a, b, c, ...) -> Normalize(a + b + c + ...)
similarity(a, b) -> Dot(a, b) / (Norm(a) * Norm(b))
argmax_cosine(q, [a, b, c]) -> a/b/c with the largest similarity(q, .)
There's nothing else underneath. Every Sutra program is some chain of these five leaf operations plus element-wise +, -, *, / and the polynomial logical-connective forms.
Worked example: examples/tensor_ops.su calls each of the five primitives directly.
Vector / VSA primitives โ what reduces to the leaves above¶
The hyperdimensional-computing core. Every operation is a tensor op on the substrate.
bind(role, filler) โ role-rotation binding¶
// Target Sutra body (currently a runtime method):
function vector bind(vector role, vector filler) {
matrix Q = rotation_for_role(role);
return Q @ filler;
}
Role-seeded Haar-random orthogonal rotation applied to the filler. Block-diagonal: Haar in the semantic block, identity in the synthetic block โ bind acts only on semantic content. The rotation is cached in _VSA.prewarm_rotation_cache() at module init, so repeat calls with the same role are one matmul. Status: blocked (target body in stdlib/vectors.su; runtime method works today).
unbind(role, bound) โ inverse of bind¶
function vector unbind(vector role, vector bound) {
matrix Q = rotation_for_role(role);
return transpose(Q) @ bound;
}
Same cached rotation matrix, transposed. Status: blocked.
bundle(v1, v2, ...) โ normalized superposition¶
function vector bundle(vector... args) {
vector s = zero_vector();
foreach (v in args) {
s = s + v;
}
return s / (norm(s) + finfo_tiny);
}
Variadic sum + L2-normalize. The fused bundle_of_binds((r1,f1), (r2,f2), ...) codegen peephole is a compile-time optimization on top of this spec definition โ it collapses N sequential binds + an N-arg bundle into one batched einsum. Status: blocked (variadic param surface needs work).
similarity(a, b) โ cosine similarity (scalar)¶
function scalar similarity(vector a, vector b) {
scalar na = sqrt(dot(a, a));
scalar nb = sqrt(dot(b, b));
return dot(a, b) / (na * nb + finfo_tiny);
}
Cosine without truth-axis placement. Same dot/norm pipeline as ==, but returns the raw scalar. Status: intrinsic.
argmax_cosine(query, candidates) โ best-match retrieval¶
function vector argmax_cosine(vector query, vector[] candidates) {
matrix C = stack(candidates);
vector scores = (C @ query) / (row_norms(C) * norm(query) + tiny);
int best = argmax(scores);
return candidates[best];
}
Stacked-candidate matmul + argmax. Status: blocked.
select(scores, options) โ softmax-weighted superposition¶
function vector select(scalar[] scores, vector[] options) {
scalar[] s = scores - max(scores);
scalar[] w = exp(s);
w = w / sum(w);
return sum(w[i] * options[i] for i);
}
Softmax over scores, weighted sum of options. The runtime never does a host-side branch โ every option contributes weighted by its softmax score. Status: blocked.
permute(v, key) and friends¶
Element-wise multiply with a deterministic ยฑ1 mask. Status: blocked on sign_flip_mask primitive.
Memory operators¶
zero_vector()¶
The neutral vector โ zero in semantic block, zero in synthetic block. Status: intrinsic.
hashmap_get(map, key) and hashmap_set(map, key, value)¶
VSA-style associative memory: a hashmap is a bundle of bind(key, value) pairs; get is an unbind + cleanup. Target Sutra forms in stdlib/memory.su. Status: blocked.
Embedding¶
embed(string)¶
The LLM-substrate intrinsic: a string goes in, a (mean-centered, normalized) vector comes out. Cached at compile time via _VSA.embed_batch and _VSA.populate_sutradb. The runtime never pays a network round-trip on the hot path โ every literal string is pre-embedded at module init.
intrinsic function vector embed(string name);
// `basis_vector` is an alias used in role-allocation contexts:
function vector basis_vector(string name) {
return embed(name);
}
Status: intrinsic (embed); blocked (basis_vector, on the inliner consuming it).
Defuzzification¶
defuzzy(v) โ polarize along the truth axis¶
Iterates cosine equality with true ten times. Inputs with truth = 0 stay at 0 (unknown is a fixed point of equality-with-true under the eps-guarded ==); inputs with truth โ 0 sharpen toward ยฑ1. Ten iterations is the spec definition โ one pass mathematically suffices for well-separated inputs, but the unrolled form is what the fusion pass targets. Status: in Sutra.
is_true(v) and defuzzify(v)¶
defuzzify is the same operation as defuzzy โ both names are accepted. is_true(v) is defuzzify(v) followed by a positive-axis projection. Both keep the value fuzzy and differentiable; neither binarizes.
Transcendental functions (currently disabled)¶
intrinsic function scalar log(scalar x);
intrinsic function scalar sqrt(scalar x);
intrinsic function scalar exp(scalar x);
intrinsic function scalar sin(scalar x);
intrinsic function scalar cos(scalar x);
intrinsic function scalar tan(scalar x);
intrinsic function scalar pow(scalar x, scalar y);
Status: disabled. Codegen rejects calls with CodegenNotSupported. A 2026-04-29 Taylor-with-frexp implementation was withdrawn 2026-04-30 because it ran as host Python scalar arithmetic (substrate-purity violation). Future direction: eigenrotation-as-modulus for substrate-pure trig (Emma 2026-04-30 hunch), with exp / log building on top.
If a substrate-pure log and exp(E) land, the rest of the chain falls into place automatically โ Pow(a, b) = exp(a * log(b)) makes ^ work, and sin / cos can compose from eigenrotation. That's the unlock.
How to read the chain¶
Take ^ as the worked example Emma cites:
a ^ b
โ Math.Pow(a, b) // operator desugar
โ exp(a * log(b)) // Pow's expansion
โ exp(<scalar>) // multiplication is primitive
... // exp lowers to substrate ops
Each arrow is the inliner replacing a function call with its body. The chain stops at substrate primitives โ operations the runtime exposes as torch tensor ops without further decomposition. For a long chain of operator applications, the inliner produces a long flat sequence of tensor ops; the simplifier and fusion pass then fold linear sub-chains into cached matrices. By the time the runtime executes, what was a stack of nested operators in the source is one matmul on CUDA.
Related reading¶
- Logical operations โ deeper coverage of the Kleene Kโ scheme and the polynomial gates.
- Numeric math โ how integers, floats, complex numbers live on the substrate.
- Memory โ bind / unbind / bundle in detail.
- Compilation โ the function-expansion / inlining / fusion pipeline.