Features
The complete feature reference. README.md carries a one-or-two-sentence summary of each entry below and links here for the detail; this page is the long form, and the one to keep current when a feature grows.
For the API itself see the Scaladoc — an absolute link, because /api is produced by unidoc after mdoc has run, so a relative one fails mdoc’s link check. For a command-by-command listing of the REPL see the cheat sheet.
-
Expression Parsing: Parses mathematical expressions using a recursive descent parser based on
scala-parser-combinators. Supports standard operators, mathematical functions (sin,cos,tan/tg,asin,acos,atan,exp,ln,log,log(x, b)), implicit multiplication, unary minus in any operand position (3 * -x,2^-x), multi-character variable names (theta,x1,alpha), and built-in constantspi,e, andi(imaginary unit). Logarithms:ln(x)is the natural log;log(x)is decimal (base-10);log(x, b)is the general base-b log computed asln(x)/ln(b)— solog(1000, 10) = 3,log(8, 2) = 3. - Dual Evaluation: Expressions evaluate to either:
- A numeric result (
Double, or a complex value) if all variables are bound - A symbolic result (an AST node) if variables remain unbound
- A numeric result (
-
Complex Numbers: The imaginary unit
iis a built-in constant (2 + 3i,3i), sitting alongsidepiande. Arithmetic is a full field —i*i = -1,(2 + 3i)*(1 - i) = 5 + i, division and powers included — implemented as a_Complex(re, im)value that collapses back to a plain real whenever the imaginary part vanishes, so real math is untouched. The elementary functionsexp,ln,log,sin,cos,tanaccept complex arguments (exp(i*pi) = -1, Euler’s identity), and complex closure means roots and logarithms of negatives return their principal values:(-2)^0.5 = i√2,ln(-1) = iπ,(-8)^(1/3)is the principal complex cube root. Values display as(a + bi)(orbi/iwhen purely imaginary) and round-trip through the parser; a floating-point residual imaginary part rounds away at display, soexp(i*pi)prints-1.0. Genuinely undefined forms (ln(0),0^-1, division by zero) still stay symbolic. -
Variable Binding: Support for binding variables to numeric values, allowing mixed symbolic-numeric evaluation of complex expressions. Multi-character variable names are fully supported.
- Rich AST Representation: Expressions are represented as a type-safe AST with nodes for:
- Numbers and variables
- Binary operations (addition, subtraction, multiplication, division)
- Unary functions (exponential, logarithm, trigonometric, hyperbolic and reciprocal-trigonometric —
sinh/cosh/tanh,asinh/acosh/atanh,sec/csc/cot,sech/csch/coth, withsimplifynormalising the ratio spelling into a single reciprocal node:1/cos(x)→sec(x),cos(x)/sin(x)→cot(x)) - Power operations
- Higher-order operators (derivatives, definite integrals via Simpson’s rule, indefinite integrals via a symbolic rule table with integration by parts, trig-power reduction formulas (including
tan/sec/csc/cotpowers), full rational-function partial fractions (repeated and complex roots at any degree, via square-free factorisation), non-linear u-substitution forf(g(x))·g'(x)forms —∫ x·e^(x²),∫ sin³x·cos x— that the linear chain rule cannot reach, trigonometric/hyperbolic substitution for radicals√(a²−x²)/√(a²+x²)/√(x²−a²)at any half-integer power ((4−x²)^(3/2)included), the Weierstrass half-angle substitutiont = tan(x/2)for rational functions ofsin/cos(∫ 1/(1+sin x),∫ 1/(2+cos x)), the special integral functionsSi/Ci/Ei/li/fresnelS/fresnelCas named answers to the classic non-elementary integrals (∫ sin x/x = Si(x),∫ e^(−x²) = (√π/2)·erf(x)), and a data-driven table of ~64 entries transcribed from the standard references —tan,cot,sec,csc, the hyperbolics, the standalone inverse functions (asin…atanh), product-to-sum (sin(ax)cos(bx)), the generale^(ax)sin(bx)pair, and the symbolic-parameter algebraic/radical forms (1/(a²−v²)→atanh(v/a)/a,1/√(v²+a²)→asinh(v/a),v^a,v·a^v) — table rules are parameterised, so a symbolic constant free of the integration variable is handled too (k^v→k^v/ln(k)), every entry carries the linear-argument chain rule (tan(2x+1)closes), and every entry is verified by a differentiate-back harness, and limits)
-
Matrix Domain: matrix literals parse as
[[1, 2], [3, 4]](a row vector is[[1, 2]]), withtranspose(...),det(...),inv(...), and the ordinary+,-,*,/operators —M := [[1, 2], [3, 4]]thenM * Mworks in the REPL, and:saved sessions restore matrix bindings. Matrices are grids of arbitrary expressions — numbers, variables, functions, even functionals — evaluated element-wise. When every element reduces to a number, the matrix collapses to a dense row-majorArray[Double]value (_MatrixValue), on which sum, product (block-tiled for cache locality, parallel over row blocks above a work threshold), transpose, scalar multiplication, determinant (LU with partial pivoting), and inverse (Gauss–Jordan) run as array kernels; otherwise operations combine element-wise symbolically and stay symbolic until the free variables are bound.det(A)returns a scalar;inv(A)and the reciprocal spelling1 / Areturn the inverse matrix (M / NisM · N⁻¹), staying symbolic whenAis singular or non-square. Small symbolic matrices get a cofactor determinant and adjugate/det inverse. The calculus and structural algorithms (derive,integrate,simplify,expand) distribute element-wise over matrices, matrix sums, and transposes (d/dx [aᵢⱼ] = [daᵢⱼ/dx]); matrix products deliberately stay symbolic under differentiation, as they need the product rule. Scalar functions distribute element-wise over a matrix argument too:sin(A),exp(A),ln(A), etc. reduce to the matrix of per-element results (staying symbolic if an element leaves the function’s real domain). This holds for symbolic matrices as well —exp([[x, 1], [y, 0]])becomes[[exp(x), 2.71828], [exp(y), 1.0]], folding numeric cells and keeping free-variable cells asf(cell)until they are bound. A square dense matrix can be raised to an integer power with the ordinary^operator orpow(A, n):A^0is the identity,A^nmultipliesAby itself (binary exponentiation), and negative powers invert first (A^-n = (A⁻¹)^n); non-square bases, non-integer exponents, and negative powers of a singular matrix stay symbolic. -
Matrix exponential:
expm(A)computes the matrix exponentiale^A = Σ Aᵏ/k!— a different operation fromA^n, which is repeated multiplication. It uses scaling and squaring with a degree-13 Padé approximant, resting on the identitye^A = (e^(A/2ˢ))^(2ˢ):Ais scaled until its norm falls inside the approximant’s accurate radius, the approximant is evaluated there, and the result squared back. The obvious alternative — diagonalise and exponentiate the eigenvalues,V·diag(e^λ)·V⁻¹— is deliberately not used, because it needs an eigenbasis and a defective matrix has none; since a repeated eigenvalue is ordinary rather than exotic, that route would return a confidently wrong answer for a Jordan block instead of refusing, and the closed-formexpm([[λ,1],[0,λ]]) = e^λ·[[1,1],[0,1]]is pinned in the suite for that reason. Nilpotent matrices come out exactly (expm([[0,1],[0,0]])is[[1,1],[0,1]], since the series terminates), and a rotation generator gives back the rotation matrix. Non-square arguments stay symbolic. -
Equations:
10 * x = 2 * x + 1parses as a relation; once all variables are bound, it evaluates totrue/falseusing tolerance-based equality tied to the configured precision (sosin(pi) = 0is true despite floating-point noise). Concrete matrices compare element-wise.derive,simplify,expand, andintegrateapply to both sides. Equations are first-class values:h := x^2 = 4stores the relation, andsolve(h, x)then works.lhs == rhsis an explicit equality check (same semantics, but not accepted bysolve()).solve(lhs = rhs, x)— or equivalentlysolve(h, x)for a named equation — solves for a variable: linear equations exactly (symbolic coefficients included,x = -b/a), quadratics via the discriminant (0, 1, or 2 real roots,±√Δclosed forms when coefficients are symbolic), and transcendental or higher-degree forms numerically (sign-change scan plus bisection over [-100, 100], up to 8 roots). One solution prints asx = 0.125; several as[[x = -2.0, x = 2.0]]. A matrix equation can be solved for a scalar unknown:solve([[x, 2*x]] = [[3, 6]], x)decomposes into the per-cell equations, solves each, and keeps only the value that satisfies every cell (herex = 3); an inconsistent system such as[[x, x]] = [[1, 2]]has no solution and stays symbolic. The unknown may also be a matrix: withAandBbound,solve(A * X = B, X)returns the uniqueX = A⁻¹·Bvia the inverse kernel (singular or non-conformingA→ no solution);solve(X * A = B, X)givesX = B·A⁻¹. Beyond the one-sided forms, a constant term is peeled to the right (solve(A * X + C = B, X)→A·X = B − C) and a matrix on each flank is inverted on both sides (solve(A * X * D = B, X)→X = A⁻¹·B·D⁻¹). Symbolic coefficients yield a symbolic matrix solution (cofactor expansion, ≤ 6×6). General linear matrix equations whereXappears in several terms — the Sylvester equationsolve(A * X + X * B = C, X), the Lyapunov equationsolve(A * X + X * transpose(A) = C, X), scalar coefficientssolve(2 * X = B, X)— are solved by Kronecker vectorization: each terms·L·X·Rcontributess·(Rᵀ ⊗ L)to a dense linear system overvec(X), solved via the inverse kernel and reshaped back (dense coefficients; a singular system — e.g.Aand−Bsharing an eigenvalue — has no solution). -
Logic: the word connectives
and,or,not,implies,xoroperate on the boolean literalstrue/falseand on anything that reduces to a boolean — equations included, sox = 1 and y = 2is a conjunction of two relations. Connectives bind looser than=/==, with precedencenot>and>xor>or>implies(impliesright-associative).and/or/impliesshort-circuit on a decisive left operand (false and Xisfalsewithout evaluatingX), and expressions with free variables stay symbolic until bound. Simplification applies constant folding, double negation, idempotence, complement, and absorption;toCNF/toDNFproduce conjunctive/disjunctive normal forms (De Morgan plus distribution, capped against exponential blow-up), and the REPL’struth <expr>command prints a truth table over an expression’s free variables (up to 16). - Three-valued (Kleene) Logic: the same connectives and the same min–max rule table carry a third truth value,
unknown— the value carrier widens, the operators do not.unknownis a first-class value (bindable, printable, restored by:save) sitting at degree 0.5:not unknownisunknown(its negation fixpoint), while the crisp cases still decide (false and unknownisfalse,true or unknownistrue) — which is exactly why the short-circuits stay valid unchanged. Crisp results collapse back to plain booleans, so the classical truth tables fall out of the graded table as a special case rather than a separate code path. The classical laws that fail atunknown(complementa and not a,a implies a,a xor a) are gated on the expression being free of graded degrees, so simplification never folds them away wrongly;truth3 <expr>prints the 3ⁿ table (up to 10 variables). -
Symmetric Ternary Logic: the same three truth values spelled with the digits
{-1, 0, 1}— the balanced ternary alphabet — instead offalse/unknown/true, related by the affine mapt = (s + 1) / 2. This is an encoding, not a second semantics: the min–max rule table is identical either way, so the Kleene identities restate verbatim (-1 and 0is-1,1 or 0is1,not 0is0). Switched perEnvironment(logic symmetric onat the REPL, persisted by:save), which means a bound variable participates too, not just literals. With the toggle off a bare0stays a plain number so nothing is silently reinterpreted, and ordinary arithmetic is untouched in both modes. Under the encoding the digit0counts as graded, so0 and not 0correctly stays0rather than folding to false by complement. -
Fuzzy Logic: the same connectives over the full
[0, 1]interval.truth(x)turns a scalar degree into a truth value (and is how a graded degree prints, so it round-trips through:save), while membership curves —trimf(x,a,b,c),trapmf(x,a,b,c,d),gaussmf(x,mean,sigma),sigmf(x,a,c)— and the hedgesvery(d) = d²/somewhat(d) = √dmap crisp measurements into degrees. They evaluate to truth values, sovery(trimf(t, 0, 10, 20)) and somewhat(h)composes with no cast at each step. Which t-norm combines degrees is anEnvironmentparameter rather than a separate package: min–max (default), product (a·b/a+b−a·b), or Łukasiewicz (max(0,a+b−1)/min(1,a+b)) — all three agreeing with classical logic on the crisp values, so the boolean and three-valued tiers are untouched by the choice. Only min–max is a lattice, so idempotence and absorption hold for graded degrees there alone and simplification gates them accordingly.defuzz(e, v, lo, hi)collapses a membership curve back to a crisp value by centre of gravity (meanOfMaximaandbisectorare available too), sampling through the same compiled-closure fast path Simpson’s rule uses. -
Special Functions: the factorial family and the gamma function.
fact(n)is exact for integers up to170!and continues analytically past them (fact(0.5)is√pi/2);dfact(n)is the double factorial andmfact(n, k)the multifactorial with stepk.Gamma(z)is the Lanczos approximation with the reflection formula for negative arguments,lgamma(z)its log-space companion that stays finite whereGammaitself overflows, andBeta(a, b)the beta function computed throughlgammaso large arguments survive.GammaandBetaare capitalised deliberately, so the lowercasegammaandbetastay available as ordinary variable names. The analytic tier extends the family with the error functionerf/erfc, the digamma function (which is what makesGammaandfactdifferentiable), the regularised incomplete gamma (gammaP/gammaQ) and incomplete beta (betaI), andGammaover complex arguments. Poles, overflow and out-of-domain arguments stay symbolic rather than returning infinities, and the functions distribute element-wise over a matrix argument like the elementary ones. -
Numeric Sequences:
fib(n)— the library is named after Leonardo Pisano, after all — together withlucas,pellandjacobsthal, all one recurrencex(k) = p·x(k-1) + q·x(k-2)differing only in seeds and coefficients, so each keeps its own name while the recurrence has a single definition (lucas(n)andfib(n, 2, 1)are the same number by construction). Indexing is the standard one —fib(10) = 55, matching OEIS A000045 — and the classic “rabbit” pair is written explicitly asfib(n, 1, 1), which is the same sequence shifted by one. They are numeric, not integer, sequences: a recurrence needs only addition and multiplication, sofib(5, 1.5, -pi)is a legitimate call and exact seeds stay exact. That last point is not decoration — aDoublestops representing Fibonacci numbers exactly atfib(78), so in exact modefib(100)is354224848179261915075rather than an approximation (Binet’s formula is deliberately not used: elegant, but inexact pastn ≈ 70). Alongside them:binom(generalised to a real upper index, sobinom(-1, 3) = -1),catalan, andharmonic, which is rational-valued and shows the exact tier off nicely —harmonic(4)is25/12. Finallytabulate(e, k, lo, hi)evaluates any expression over an integer range into a 1×n matrix:tabulate(binom(4, k), k, 0, 4)is a Pascal row, and because the result is an ordinary matrix,at, tuple assignment and:saveall work on it unchanged. -
Vector Calculus:
grad(f, x, y, z),div(F, x, y, z),curl(F, x, y, z),laplacian(f, x, y, z), plusjacobian(F, ...)andhessian(f, ...). A vector field is simply an n×1 matrix —div([[x^2], [y^3]], x, y)gives2x + 3y^2, andcurl([[-y], [x], [0]], x, y, z)gives[[0], [0], [2]]— so every existing matrix operation applies to a field unchanged. The coordinate tuple is always written out and ordered, never inferred, because it fixes the order of the result’s components. The coordinate-free identities hold (curl(grad f) = 0,div(curl F) = 0,laplacian = div∘grad). Shapes with no meaning stay symbolic rather than being guessed: acurloutside three dimensions (in 2-D the natural object is a scalar curl, a different result type), a component/coordinate count mismatch, a repeated coordinate, or a row vector. Cartesian, cylindrical and spherical coordinates are supported through an optional trailing keyword —laplacian(1/r, r, t, p, spherical)is0, anddiv([[1/r], [0], [0]], r, t, z, cylindrical)is0, both because the operators are written once in terms of the coordinate system’s scale factors rather than once per system. The curvilinear systems are three-dimensional and identify their coordinates by position, not by name (so you may call them anything). Both spherical conventions are available and differ in argument order rather than naming:sphericalis the physics order(r, θ, φ)withθthe polar angle, andsphericalmathsthe mathematics order withθazimuthal andφpolar. Exchanging those two coordinates flips the handedness of the basis, socurl— being a pseudo-vector — carries the corresponding sign; the two conventions agree on the curl of the same physical field. Multivariate integration needs nothing extra — iterated integrals accept limits that depend on the outer variables, sointegral(integral(x*y, y, 0, x), x, 0, 1)is0.125. -
Series Expansions:
taylor(e, v, point, n)expands an expression as a truncated Taylor polynomial about a point, andmaclaurin(e, v, n)is the special case centred on zero. The coefficients come from the memoised higher-order derivative engine, so the k-th term reuses the work of the (k−1)-th, and each is instantiated at the centre by substitution before the whole series is simplified. The centre need not be numeric: expanding about a symbolicayields a genuine polynomial in(x − a)withf⁽ᵏ⁾(a)coefficients. The expansion variable stays free in the result, so binding it evaluates the polynomial and the series composes withderive,samplesand the rest of the library. Order is capped at 20, and an expression whose derivative falls outside the rule table (Gamma,fact— both needing digamma) returns unevaluated rather than a series with an unresolved derivative buried in it. -
Fourier Series:
fourierSeries(e, v, period, n)expands a periodic function over one period centred on zero as a Fourier seriesa0/2 + sum of [ak*cos(kωv) + bk*sin(kωv)]. Unlike the Taylor tier this is a numeric expansion: each coefficient is a definite integral evaluated by the same Simpson path used forintegral(f, x, a, b), so the period must be a concrete positive number. The classic results come out as expected — the sawtoothfourierSeries(x, x, 2*pi, 4)gives2sin(x) - sin(2x) + (2/3)sin(3x) - (1/2)sin(4x), andx^2gives a cosine-only series with constant termpi²/3. Coefficients are deliberately not chopped: a term that vanishes analytically comes back at the integrator’s noise floor rather than exactly zero, on the principle that cleanup is a display concern. Not to be confused withfourier(e, t, w), the Fourier transform. -
Laurent Series:
laurent(e, v, a, m, n)expands about a pole asΣ(k = −m..n) c_k·(v−a)ᵏ— a Laurent series, the expansion Taylor cannot provide at a singular point. The principal-part lengthmis optional: omitted, it is detected from the pole order reported bysingularities. An essential singularity is refused rather than truncated — its principal part is infinite, and the discarded terms grow near the point instead of shrinking, so a truncation would not carry the contract the polynomial tiers do. -
Pade Approximants:
pade(e, v, m, n)builds the[m/n]rational approximant about zero — the quotientP/Qwithdeg P <= m,deg Q <= nandQ(0) = 1whose Maclaurin series matches the function through orderm + n. It is frequently far more accurate than the Taylor polynomial of the same total degree, because a rational function can model a nearby pole that no polynomial can:pade(exp(x), x, 1, 1)is the familiar(2 + x)/(2 − x), andpade(exp(x), x, 2, 2)beatsmaclaurin(exp(x), x, 4)atx = 1. A rational function reproduces itself exactly, andpade(f, x, m, 0)degenerates to the Taylor polynomial. The defining linear system is solved through the dense matrix inverse incore; a singular system (no[m/n]approximant in normal form) leaves the expression symbolic rather than dividing by zero. -
Greek Notation: the special functions accept their Greek spellings —
Γ(z)forGamma(z)andβ(x, y)forBeta(x, y)— and the REPL can insert the glyphs with anALT-\prefix (ALT-\ g,ALT-\ b;ALT-Galso works directly). A prefix was chosen over one chord per symbol becauseALT-Bis alreadybackward-wordin emacs-style line editing, and because a prefix extends to further symbols without re-checking for collisions. Capital Greek Beta is deliberately not accepted: U+0392 is a homoglyph of LatinB, so it would be invisible which of the two had been typed; the visually distinct lowercase β is used instead. Both spellings parse to the same node andtoStringalways emits the ASCII form, so saved scripts stay portable to terminals that cannot render or type these. -
Limits:
limit(expr, var, point)computes lim_{var → point} expr. Supports two-sided and one-sided limits (limit(1/x, x, 0, +)→inf;limit(1/x, x, 0, -)→-inf). Handles indeterminate forms via L’Hôpital’s rule (0/0 and ∞/∞, up to 5 steps), and limits at ±∞ for polynomial/rational functions,exp,ln,atan, and elementary compositions.infis a built-in constant equal to+∞;-inffollows from unary minus. -
Laplace & Fourier Transforms:
laplace(f, t, s)computes the Laplace transform L{f(t)} via a symbolic rule table — constants (c → c/s), powers (t^n → n!/s^(n+1), capped at n ≤ 20), exponentials (e^(ct) → 1/(s−c)),sin(wt) → w/(s²+w²),cos(wt) → s/(s²+w²), linearity, and the first-shift theoreme^(at)·g(t) → G(s−a)applied recursively — solaplace(t*exp(-t), t, s)yields1/(s+1)².fourier(f, t, w)is the unilateral Fourier transform, computed as the Laplace transform evaluated ats = i·w; results are generally complex-valued, riding on the complex-number support (fourier(exp(-2*t), t, w)→1/(2 + i·w)). Shapes outside the table stay symbolic. -
z-transform:
ztrans(x, n, z)computes the one-sided z-transformX(z) = Σ(n≥0) x[n]·z⁻ⁿ, the discrete counterpart oflaplaceand built on the same rule-table machinery: constant, linearity, unit step, geometric (aⁿ → z/(z−a)),exp(c·n), sine and cosine.Z{c} = c·z/(z−1)and notc/z— the Laplace analogueL{c} = c/sdoes not carry over, since a constant sequence iscat every index. Then-multiplied family (n → z/(z−1)²,n² → z(z+1)/(z−1)³,n·aⁿ → a·z/(z−a)²) is not a set of table entries but a single rule,Z{n·x[n]} = −z·dX/dz, so the four cannot drift apart.invztrans(X, z, n)recovers the sequence by decomposingX(z)/z— the division is the technique, since every forward entry carries a factor ofzin its numerator — with eachz/(z−r)^jterm inverting toC(n, j−1)·r^(n−j+1), so distinct and repeated real poles both work:invztrans(z/((z-1)*(z-2)), z, n)is2ⁿ − 1andinvztrans(z/(z-2)^2, z, n)isn·2^(n−1). Three things are deliberately absent rather than approximated: the Kronecker delta (there is no discrete-impulse node), complex poles in the inverse (they invert to a damped oscillation this tier does not build), and the bilateral transform, which would need a region of convergence on every result or the inverse would have to guess between a causal and an anti-causal signal. -
Inverse Laplace Transform:
invlaplace(F, s, t)recovers f(t) from a rational F(s) = N(s)/D(s) with deg N < deg D ≤ 2 — the dual of the forward table. Linearity peels sums and constant factors; the pole structure is read off by completing the square: linear denominators giveb/(s−a) → b·e^(at), distinct real roots split via partial fractions intoA·e^(r₁t) + B·e^(r₂t), a repeated root givese^(at)·(N₁ + (N₀+N₁a)·t), and a complex-conjugate pair givese^(at)·(N₁cos(wt) + …sin(wt)). Soinvlaplace(3/((s-2)^2+9), s, t)returnse^(2t)·sin(3t), andinvlaplace(laplace(f, t, s), s, t)round-trips f. Denominators of degree ≥ 3 are decomposed by square-free factorisation and undetermined coefficients, so repeated poles invert too —1/s^3 → t²/2,1/((s-1)^2(s-2)),1/(s^2+1)^2— while a repeated irreducible quadratic past the second power, symbolic coefficients, and non-rational input stay symbolic. -
Control systems: transfer functions, time and frequency response, stability, state space and discretisation — with no carrier type. A transfer function is an ordinary expression, so
simplify,derive,substitute, the exact tier and the parser all apply to one for free; the price is that the frequency variable is an explicit argument everywhere (poles(g, s), neverg.poles). Interconnection:series(G, H, s),parallel(G, H, s)andfeedback(G, H, s), each returning one rational in lowest terms —feedback(1/(s*(s+2)), 1, s)is1/(s²+2s+1), not the uncancelled quartic whose spurious roots at0and−2would be reported as poles.feedbackis negative feedback, stated because it cannot be inferred: a substantial part of the literature writesG/(1 − G·H), and choosing silently would make every closed-loop answer wrong for half its readers. Time responsestep(G, s, t)andimpulse(G, s, t)are the inverse Laplace transform ofG/sandG;stepis arity-overloaded, sostep(x)remains the Heaviside unit step.poles/zerosreturn_Values rather thanDoubles because a complex pole is the interesting case — an oscillatory mode is a conjugate pair, and reducing to reals would drop exactly the systems the domain is about.isStabledecides from the pole locations rather than a Routh–Hurwitz table (Routh’s degenerate cases each need a repair, and a mishandled one gives a wrong verdict instead of a refusal —routhTableis still there for inspection) and is strict, so marginal stability is not stability.bode/nyquistare one substitutions → iωriding the existing complex closure, andfrequencyResponse/nyquistSweepare their swept forms — the two things a diagram needs that a plainsamplecannot give it. The grid is geometric, because a response is read across decades and a linear grid of 200 points over0.01 .. 100puts 199 in the last decade and none near a corner at0.1. The phase is unwrapped:atan2’s principal value is(−π, π], so a raw sweep of1/(s+1)^3reports+90°where the plant reads−270°— an artefact of the arctangent that every reader plotting a raw sweep inherits. Reported in dB and degrees, because that is what a Bode plot is; the raw pair stays available frombode. Unwrapping cannot tell a genuine half-turn step from a grid too coarse to resolve a fast one, so a sparse sweep across a lightly damped resonance unwraps wrongly — the remedy is points, and the caveat is stated rather than hidden. State space bundles(A, B, C, D)as the1×4matrix rowlu/qr/eigalready return, withcontrollable/observable— the second defined as the first on the dual pair, so they cannot disagree. Discretisationc2d/d2calways names its method (ZohorTustin), never defaults silently, since the two give different discrete poles;c2dExactuses the matrix exponential of[[A, B], [0, 0]]·Tsrather thanA⁻¹(A_d − I)B, which is a correctness matter because the block form is defined for a singularAand any system with an integrator has one. Declined rather than approximated: a non-rationalGsuch as a dead-timeexp(−2s)(padeexists, and reaching for it is the user’s choice), an improperGin a time response (the impulsive term att = 0is not representable), an undeterminable coefficient sign, andd2cby zero-order hold. -
Differential Equations:
ode(rhs, y, t, t0, y0, target)solves the first-order initial-value problemy' = rhs(t, y),y(t₀) = y₀, and returns the solution valuey(target). Linear equationsy' = a(t)·y + b(t)are solved in closed form: constant coefficients directly (y' = y, y(0)=1giveseexactly att = 1;y' = 2y + 3gives the affine-plus-exponential form), and variable coefficients via the integrating factorμ(t) = e^{∫p dt}(y' = -y + t→t − 1 + 2e^{-t};y' = -y/(1+t)→1/(1+t)), reusing the full indefinite-integration engine (so a forcing term liket·eᵗcloses by integration by parts). A freetarget, a free initial condition, or a symbolic coefficient yields a symbolic solution (ode(k*y, y, t, 0, 1, 1)→e^k). Non-linear shapes (y' = sin(y),y' = -y²) and linear ones whose coefficient integral has no closed form (y' = tan(t)·y) are integrated numerically with a fourth-order Runge–Kutta scheme (backward integration whentarget < t₀); a non-evaluable right-hand side or a symbolic target with no closed form stays symbolic. -
Relations and logic:
=builds a solvable equation,==an equality test, and<,>,<=,>=,!=compare — all reducing to booleans that compose withand/or/not/implies, sox > 0 and x < 10is an ordinary expression. Comparisons are exact when both operands are exact and tolerance-based otherwise, with the two agreeing so that precisely one of<,==,>ever holds. -
Inequality solving:
solveaccepts the comparison relations too, and the answer is written in the language itself —solve(x^2 - 4 > 0, x)is(x < -2) or (x > 2), and a conjunction such assolve(2*x > 2 and x < 5, x)solves side by side and recombines. Tiers: constant, linear (with the direction flip on a negative coefficient), quadratic (discriminant sign chart), and rational (critical points are the zeros and the poles). A coefficient whose sign cannot be determined leaves the whole relation symbolic —solve(a*x > b, x)is refused rather than solved for one sign ofa. -
Domain analysis:
domain(e, v),differentiable(e, v)andsingularities(e, v)report where an expression is defined, differentiable, or singular — answered in the language, as comparisons and connectives:domain(ln(x - 2), x)is(x > 2), andsingularities(1/(x-1)^2, x)is a location-over-order matrix. Pole orders come from square-free factorisation, not root counting. The analysis deliberately describes what the library computes, not the analytic extension: exclusion sets that are infinite (tan,Gamma) stay symbolic rather than being truncated, and an optional third argument selects the real or complex reading. -
Base conversion: the literals
0b1011,0o17,0xffand0t1TT(balanced ternary), plustobase(n, b)for any radix 2..36 andbalanced(n). The base lives on the value, soA := 0xFFstill printsFFafter a:save/:loadround-trip while remaining usable as255everywhere; the tag is display-deep only —0xFF + 1is decimal256, since there is no defensible rule for whose base wins in0xFF + 0b1011. -
Statistics: Descriptive statistics over samples (
mean,variance,stddev,covariance,correlation) that stay exact when the sample is written exactly; least squares viaregress(X, y), solved by QR rather than the normal equations so the condition number is never squared; and an inference tier —ttest,confint,chisqtest— sottest(sample, 5) < 0.05reads the way statistics is actually written. -
Probability: Distributions are first-class values —
normal(0, 1),binomial(10, 0.3),poisson(4)and friends bind to names like any other value.pdf,cdf,probandquantileanswer numeric questions about them, with every cumulative distribution a closed form rather than a numeric integral.expectandvariancecarry a linearity rule table, soexpect(2*X + 3, X)is rewritten symbolically to2·E[X] + 3before anything is computed — andexpect(X*Y, X)deliberately stays symbolic, because it needs an independence assumption the language cannot state. -
Precision Control: Configurable display precision for numeric results, kept separate from the exact tier’s working precision — showing five decimals of a value computed to thirty is the normal case, and conflating the two would make raising display precision silently change results.
-
Exact arithmetic mode (
exact on): numeric literals become exact rationals, so0.1 + 0.2is exactly3/10and1/3 * 3is exactly1. Matrices are exact too —detof the 3×3 Hilbert matrix is exactly1/2160where floating point reports4.6E-4, andA * inv(A)is exactly the identity — andfactloses the170!ceiling that aDoubleimposes. Transcendentals are arbitrary-precision, so a user-settable working precision genuinely sharpenssinandexpthemselves:exp(1000)is an exact 435-digit value where floating point overflows to infinity, and the small root ofx² + 10⁸x + 1 = 0gets steadily more accurate as you raise it. Off by default, so the floating-point path is unchanged. -
Normalization:
normalize(e, x)collects like terms into an ascending polynomial in one variable (10x - 2x→8x, whatever the tree shape), andcollect(e, x)extracts the dense coefficient list — the polynomial prerequisite the equation solver, the integration tiers and the transforms are all built on. Non-polynomial forms are left untouched. -
Linear system solver:
solveSystem([[eq₁, eq₂, …]], x, y, …)solves a square system of n linear equations in n unknowns. Coefficient extraction usescollect(the same polynomial prerequisite assolve). Dense path: Gaussian elimination with partial pivoting on Double arrays. Symbolic path: row reduction using_Expressionarithmetic andsimplifyFullywhen any coefficient or constant stays symbolic. Named equation matrices work too:S := [[eq1, eq2]]; solveSystem(S, x, y). Solutions display as[[x = 2.0, y = 1.0]]. -
Clean API: Environment-aware evaluation with no implicit global state. Expressions are immutable and composable.
Environmentis immutable —withBindingreturns a new instance, enabling safe concurrent evaluation. - Performance: Rounding is deferred to display time only (no mid-computation precision loss). Every AST node caches its free-variable set (
freeVars) after the first traversal, makingdependsOnO(1). Definite-integral evaluation (Simpson’s rule) uses a compiledDouble => Doubleclosure when the integrand has no unresolvable symbolic nodes, eliminating per-step allocations.deriveandsimplifyare memoized behind bounded thread-safe caches — repeated derivatives of the same tree (e.g. Simpson’s-rule fallback sampling) andsimplifyFully’s fixpoint passes are paid once.