alkahest
Python API

Error handling API

Structured exception hierarchy with stable diagnostic codes.

Base class

exception
exception AlkahestError(
    message: str,
    code: str='E-UNKNOWN',
    remediation: str | None=None,
    span: tuple[int, int] | None=None
)

Base class for all alkahest errors.

Inherits the native base class registered by the extension. That is load-bearing rather than cosmetic: the Rust engines raise the native classes, the pure-Python subsystems (ansatz, crosscheck, smt, the batch helpers) raise the wrappers below, and before this the two hierarchies were disjoint. except alkahest.AlkahestError — the documented way to catch anything this library raises — therefore caught the Rust half and silently missed the Python half.

Subclasses keep their keyword constructors: only the message is forwarded to the native base, whose __init__ is BaseException's and takes positional arguments only.

Attributes
codestr

Stable diagnostic code, e.g. "E-POLY-001".

remediationstr | None

Human-readable fix suggestion.

spantuple[int, int] | None

Source byte span (start, end), or None.

Properties
code

Stable diagnostic code string, e.g. "E-POLY-001".

message

Human-readable description.

remediation

Suggested remediation, or None.

span

Character offset range (start, end) in the source expression, or None.

Refusals versus verdicts

Some codes are refusals: Alkahest could not establish the answer, and the only alternative to saying so was a confident wrong one. They mean undecided, never false, and an unattended loop that records one as a negative result closes a branch it never explored.

Refusals: E-CAD-001, E-LINALG-010, E-MAT-004, E-SOS-002, E-ANSATZ-003, E-SMT-003, E-INT-001, E-BUDGET-001..003, E-ASYMPT-004, E-ODE-011, E-ODE-044, E-EVAL-012.

Verdicts: E-INT-004 (proven non-elementary), E-MAT-003 (proven singular), E-EVAL-009 (undefined at this point), E-TRANSFORM-004 and E-TRANSFORM-013 (the rule's own hypothesis is refuted — no transform of this shape exists), E-ODE-041 (an irregular singular point has no Frobenius series).

Exception subclasses

exception
exception ConversionError(
    message: str,
    remediation: str | None=None,
    span: tuple[int, int] | None=None
)

Expression could not be converted to the requested type (e.g. not polynomial).

exception
exception DomainError(
    message: str,
    remediation: str | None=None,
    span: tuple[int, int] | None=None
)

A side condition was violated (e.g. division by a known-zero expression).

exception
exception LogicError(
    message: str,
    remediation: str | None=None,
    span: tuple[int, int] | None=None
)

A propositional-logic operation refused (E-LOGIC-*).

Raised by simplify_logic(), to_cnf(), bdd(), equivalent() and friends:

  • E-LOGIC-002 — the input is not a propositional formula over the atoms the operation accepts (a quantifier, a non-Boolean term, or a propositional counterexample over relations that may be dependent).
  • E-LOGIC-003 — a size ceiling was reached (BDD nodes, normal-form terms, SAT decisions, truth-table width); nothing was truncated.
  • E-LOGIC-004 — exact=True was demanded and a minimum could not be certified.
  • E-LOGIC-005 — a malformed argument (form, method, order).
  • E-LOGIC-006 — an internal self-check failed; the result was withheld.
exception
exception DiffError(
    message: str,
    remediation: str | None=None,
    span: tuple[int, int] | None=None
)

Differentiation failed (e.g. unknown function).

exception
exception IntegrationError(
    message: str,
    remediation: str | None=None,
    span: tuple[int, int] | None=None
)

Symbolic integration failed.

exception
exception MatrixError(
    message: str,
    remediation: str | None=None,
    span: tuple[int, int] | None=None
)

Matrix operation failed (dimension mismatch, singular, etc.).

exception
exception LinearAlgebraError(
    message: str,
    remediation: str | None=None,
    span: tuple[int, int] | None=None
)

Symbolic linear algebra operation failed (nullspace, decompositions, normal forms).

exception
exception EigenError(
    message: str,
    remediation: str | None=None,
    span: tuple[int, int] | None=None
)

Eigen-decomposition failed (unsplit characteristic polynomial, defective matrix, etc.).

exception
exception CadError(
    message: str,
    remediation: str | None=None,
    span: tuple[int, int] | None=None
)

Cylindrical algebraic decomposition / real QE failed.

exception
exception AnsatzError(
    message: str,
    code: str='E-ANSATZ-001',
    remediation: str | None=None,
    span: tuple[int, int] | None=None
)

An ansatz family could not be built, or could not be fitted.

Raised by ansatz (P2 item 1 — conjecture generation). .code distinguishes the causes, and note that only the first two are "you called it wrong"; the others are results:

  • E-ANSATZ-001 — a coefficient symbol name collides with a symbol already interned in the pool, so the fit would silently solve for the wrong thing.
  • E-ANSATZ-002 — the requested family exceeds max_terms. C(n+d, d) grows fast; refusing beats materialising it.
  • E-ANSATZ-003 — the constraints are inconsistent: no member of this family satisfies them. For a search loop this is a positive result — a closed branch — not a malfunction.
  • E-ANSATZ-004 — the residual is nonlinear in the unknowns and the escalation path (solve()) needs a groebner build, which this one is not.
exception
exception CrossCheckError(
    message: str,
    code: str='E-XCHECK-001',
    remediation: str | None=None,
    span: tuple[int, int] | None=None
)

A cross-CAS differential check could not be posed, so it was refused.

Raised by crosscheck (P2 item 2). A refusal here always beats a guess: a divergence is only informative if both systems were asked the same question, and a best-effort translation manufactures false divergences, which are worse than no signal at all.

  • E-XCHECK-001 — the expression contains a node (or an active assumption) with no faithful mapping into the oracle's language.
  • E-XCHECK-002 — no oracle is installed. Never reported as agreement; see crosscheck.oracles().
  • E-XCHECK-003 — the operation has no defined comparison rung. A caller error, raised before any oracle is consulted.
  • E-XCHECK-004 — the oracle itself declined: it has no implementation for the operation, raised, or returned an unevaluated form. An environmental outcome rather than a caller error, and not a divergence — comparing against a refusal would fabricate one.
exception
exception SmtError(
    message: str,
    code: str='E-SMT-001',
    remediation: str | None=None,
    span: tuple[int, int] | None=None
)

An SMT-LIB export, solver invocation, or model lift failed.

Raised by smt (P2 item 3).

  • E-SMT-001 — no solver binary was found. Reported as a refusal rather than a silent fallback to satisfiable(), which decides only the propositional and single-real-variable fragments and would answer None here — looking like the solver had run and found nothing.
  • E-SMT-002 — the formula is outside the supported SMT-LIB fragment; check smt.supported() first.
  • E-SMT-003 — the model contains a value (an root-obj algebraic number) that cannot yet be lifted exactly. Refused rather than truncated to a float: a float witness recorded as an exact one is the silent-error shape this whole subsystem exists to avoid.
  • E-SMT-004 — a returned model failed back-substitution. The bridge or the solver is broken; this must never be downgraded to a warning.
exception
exception OdeError(
    message: str,
    code: str='E-ODE-001',
    remediation: str | None=None,
    span: tuple[int, int] | None=None
)

An ODE routine did not return a solution (E-ODE-*).

One class for the whole prefix, because the prefix is the subsystem: the number says which engine declined and why.

  • E-ODE-001 … 003 — construction or lowering (ODE).
  • E-ODE-010 … 014 — dsolve(). 013 is a recognised ODE class whose quadrature did not close, and 014 a Riccati equation with no particular solution to seed it — both strictly more informative than 010 ("outside the implemented classes"), and 011 is a candidate that failed substitution verification and is withheld rather than returned.
  • E-ODE-020 … 026 — the numeric integrators (ode_integrate_rk4() and friends).
  • E-ODE-030 … 034 — dsolve_system().
  • E-ODE-040 … 045 — series_solve() (Frobenius). 041 is a fact about the equation — an irregular singular point has no Frobenius series, so no wider implementation would find one — whereas 042 (irrational indicial roots) is a limit of this solver's rational recurrence. These sat on 020 … 025 before 3.10, where they collided with the numeric block above; they had never reached Python at all, so the renumbering broke nothing.
exception
exception TransformError(
    message: str,
    code: str='E-TRANSFORM-001',
    remediation: str | None=None,
    span: tuple[int, int] | None=None
)

An integral or sequence transform did not return a value (E-TRANSFORM-*).

Raised by laplace_transform(), fourier_transform(), z_transform() and their inverses. One class for the whole transform module, the way OdeError is one class for the whole of E-ODE-*: the number says which table and which failure, and a caller who wants only one of them filters on the code.

Laplace (00x), Fourier (01x), Z (10x):

  • E-TRANSFORM-001 / 011 / 101 — no forward rule matched. A fact about this implementation: these are table-driven, and a wider table would close it.
  • E-TRANSFORM-002 / 102 — the inverse table does not reach this form. Same kind of fact.
  • E-TRANSFORM-003 / 012 / 103 — the two variables passed are the same symbol.
  • E-TRANSFORM-004 — the unilateral hypothesis is refuted. The Laplace integral runs over t >= 0, so a Heaviside/Dirac edge at a < 0 is invisible to it (L{theta(t+1)} = 1/s, not e**s/s), and an advance factor e**(+a*s) on the inverse is the transform of no causal function. Not a gap: there is nothing to find.
  • E-TRANSFORM-013 — the Fourier table entry's decay hypothesis is refuted. At a non-positive rate the defining integral diverges, and a negative Lorentzian amplitude transforms to the opposite sign and the opposite direction of growth from the tabulated form.

The 004/013 split from the rest is the point of the prefix. A table miss says "rewrite the input, or wait for a wider table"; a refuted hypothesis says "no implementation will ever answer this". A symbolic parameter is never reported as either — it cannot be decided, so it is carried as a side condition instead.

exception
exception AsymptoticError(
    message: str,
    code: str='E-ASYMPT-001',
    remediation: str | None=None,
    span: tuple[int, int] | None=None
)

An asymptotic expansion at infinity was not produced (E-ASYMPT-*).

Raised by asymptotic_expand().

  • E-ASYMPT-001 — n_terms below 1.
  • E-ASYMPT-002 — the x = 1/t series did not close.
  • E-ASYMPT-003 — a derivative needed for the expansion failed.
  • E-ASYMPT-004 — an expansion was computed and then withheld, because the numeric o()-gate could not confirm a single term of it at large x. Never downgraded to a warning.
  • E-ASYMPT-005 — the scale is outside the implemented rules (exp/log hierarchies, Gamma/Stirling); power scales and a single log/exp peel are what this covers.
exception
exception FpsError(
    message: str,
    code: str='E-FPS-001',
    remediation: str | None=None,
    span: tuple[int, int] | None=None
)

A formal-power-series operation refused (E-FPS-*).

Raised by Fps.

E-FPS-001 and 002 are the same mathematical fact reached two ways: the object has a pole at the origin, so it is a Laurent series and not a formal power series. 004/005/006 are the constant-term hypotheses the operations need to be well defined at all — f(0) = 0 for composition/exp/reversion, f(0) = 1 for log and the default n-th root, f(0) != 0 for the inverse — and each carries the rewrite that removes the obstruction in its .remediation. 003 and 007 are the implementation's limits: exact rational coefficients only, and an underlying Taylor expansion that has to close.

exception
exception ProbabilityError(
    message: str,
    code: str='E-PROB-001',
    remediation: str | None=None,
    span: tuple[int, int] | None=None
)

A probability query did not return a value (E-PROB-*).

Raised by the experimental distribution surface. The six codes are kept apart because they call for four different next steps, and collapsing them would send a caller looking for a better integrator when the integral simply diverges:

  • E-PROB-001 — a parameter is a number that violates the distribution's own constraint (sigma <= 0, a >= b, p outside [0, 1], a non-literal n for Binomial). A symbolic parameter is never reported here: it cannot be decided, so it is carried as a side condition on constraints() instead.
  • E-PROB-002 — the query is outside the supported shape: a non-polynomial f over a countably infinite discrete support, a non-affine argument to expectation_affine(), a moment order past the range the verifier can still fail on.
  • E-PROB-003 — the reduction integral was built and the symbolic integrator declined it. The message names the integral.
  • E-PROB-004 — there is no closed form inside this library's primitive set: the Gamma CDF at a non-integer shape needs the incomplete gamma, the Beta CDF needs the incomplete beta, the normal quantile needs erf**-1, the Beta characteristic function needs 1F1 — and the log-normal characteristic function has no closed form at all.
  • E-PROB-005 — a closed form was computed and then withheld, because quadrature of its own defining integral could not confirm it. Never downgraded to a warning: a caller cannot tell a checked value from an unchecked one once it is in their hands.
  • E-PROB-006 — the defining integral does not converge, so the quantity does not exist. The formal symbolic answer for a divergent expectation is typically a clean, plausible, wrong number, which is why the convergence check runs before the symbolic work rather than after.
function
VectorError

Code prefix E-VEC-*. Vector calculus over an orthogonal chart (experimental.Coordinates). E-VEC-004 is the one to read twice: every grad/div/curl/laplacian formula in the module is derived for an orthogonal frame, and on a skew chart they still evaluate — to an expression that looks like a divergence and is not one. So the tangent inner products must be proven to vanish, and an undecided one is a refusal rather than an assumption. E-VEC-005 is a scale factor that is zero, or whose non-vanishing could not be established; every operator divides by it.

Reached as alkahest.experimental.VectorError; it is not on the top-level namespace.

exception
exception UnitError

Code prefix E-UNIT-*. Physical units and dimensional analysis (units). E-UNIT-001 is an operation that needs equal dimensions — a sum, a conversion — and got two different ones; the message names both. E-UNIT-002 is a malformed or unknown unit string. E-UNIT-003 refuses an affine temperature (degC/degF) anywhere but on its own in a conversion: 2 · 20 °C has no meaning. E-UNIT-004 is a function argument or symbolic exponent that is not a pure number (an angle must be in rad). E-UNIT-005 is a symbol or function with no known unit; E-UNIT-006 a sum whose terms share a dimension but not a unit (its value depends on which number is in which unit); E-UNIT-007 a fractional power whose scale has no exact rational root.

exception
exception TensorError

Code prefix E-TENSOR-*. Tensor calculus on a coordinate chart (tensor). E-TENSOR-001 a metric that is not n x n for n distinct coordinate symbols; 002 a metric that is not symmetric; 003 a determinant that is identically zero; 004 a floating-point entry (the layer is exact); 005 a node the component field cannot hold (Piecewise, a predicate); 006 a partial derivative that is not available; 007 a denominator that vanishes identically; 008 the Weyl tensor below dimension 3; 009 an abstract-index error (an index three times, twice up or twice down, mismatched free indices); 010 an internal invariant, refused rather than answered.

exception
exception ControlsError(
    message: str,
    code: str='E-CTRL-001',
    remediation: str | None=None,
    span: tuple[int, int] | None=None
)

A controls-layer operation refused (E-CTRL-001 … E-CTRL-006).

001 malformed input; 002 an improper transfer function where a proper one is needed; 003 a numeric routine on data with free parameters; 004 the requested object does not exist (singular Lyapunov operator, no stabilising Riccati solution, infinite DC gain); 005 out of the exact engine's reach — use the numeric route; 006 numpy is required and missing.

exception
exception NcError

Code prefix E-NC-*. Noncommutative algebra. E-NC-001: word rewriting did not reach a normal form within max_steps (the rules may not terminate). E-NC-002: an expansion past the term or power cap. E-NC-003: a rule whose left side is not a single word with coefficient

  1. E-NC-004: a malformed ladder-mode list. E-NC-005: a γ slot that is not 0–3, 5 or a commutative label. E-NC-006: a γ trace past 14 slots. E-NC-007: a vacuum expectation over a factor that is not a listed ladder operator. E-NC-008: a malformed Lie-algebra table (not distinct noncommutative generators, bad or inconsistent structure constants, failed Jacobi identity). E-NC-009: a malformed sequence-wildcard pattern in make_rule(..., sequence=[...]).
function
QuaternionError

Code prefix E-QUAT-*. Quaternion algebra and rotations (experimental.Quaternion). E-QUAT-001 is a zero or undecided norm. E-QUAT-002 refuses the axis of the identity rotation, which does not exist — every unit vector is one, and the conventional (0, 0, 1) is a stated answer to a question with no answer. E-QUAT-003 refuses a matrix that could not be checked to be a proper rotation, including any symbolic matrix, because Shepperd's branch selection is a comparison between entries and there is none to make on a symbol.

Reached as alkahest.experimental.QuaternionError.

exception
exception DaeError(
    message: str,
    remediation: str | None=None,
    span: tuple[int, int] | None=None
)

DAE structural analysis failed.

exception
exception SolverError(
    message: str,
    remediation: str | None=None,
    span: tuple[int, int] | None=None
)

Polynomial system solver failed.

Note radical and primary_decomposition refuse rather than return an ideal they cannot certify: E-IDEAL-005 (no certified radical) and E-IDEAL-006 (no certified primary decomposition). Both currently arrive as a plain ValueError whose message states the reason; the stable code is available to Rust callers from alkahest_cas::ideal::take_ideal_refusal().

exception
exception LinprogError

Code prefix E-LP-*. Exact linear / mixed-integer programming (linprog(), milp(), linprog_expr()):

  • E-LP-001 — malformed shapes (row length, right-hand-side count, bounds, integrality, sense)
  • E-LP-002 — a float or irrational coefficient; refused, never rounded
  • E-LP-003 — not affine in the decision variables
  • E-LP-004 — a strict inequality or a non-relation constraint
  • E-LP-005 — branch and bound reached max_nodes. Not an answer: the exception carries incumbent (best integer point, unproven), incumbent_fun, bound (best open bound) and nodes
  • E-LP-006 — a certificate failed its own exact check; the result was withheld
  • E-LP-007 — an unsupported parametric programme (linprog_parametric(), linprog_expr() with parameter): the parameter multiplies a variable in a constraint, enters non-linearly, moves both the objective and the right-hand side, or is combined with integer variables

A budget trip during either solver raises BudgetExceededError with the same incumbent / incumbent_fun / bound attributes.

exception
exception JitError(
    message: str,
    remediation: str | None=None,
    span: tuple[int, int] | None=None
)

JIT compilation failed.

Note to_stablehlo() raises the base AlkahestError with code E-STABLEHLO-001 (no exact StableHLO lowering for a function, named in the message), -002 (a non-numeric node such as O(x)), -003 (a free symbol not in the input list), -004 (an infinite/NaN constant) or -005 (an invalid MLIR function name).

exception
exception CudaError(
    message: str,
    remediation: str | None=None,
    span: tuple[int, int] | None=None
)

NVPTX / CUDA compilation or launch failed.

exception
exception SparseInterpError(
    message: str,
    remediation: str | None=None,
    span: tuple[int, int] | None=None
)

Sparse modular polynomial interpolation failed.

exception
exception SparseGcdError(
    message: str,
    remediation: str | None=None,
    span: tuple[int, int] | None=None
)

Sparse modular GCD computation failed.

exception
exception ParseError(
    message: str,
    remediation: str | None=None,
    span: tuple[int, int] | None=None,
    code: str='E-PARSE-001'
)

Malformed expression text, with the source span (.span) of the fault.

E-PARSE-001 lexical (an unexpected character, a malformed or out-of-range number literal), E-PARSE-002 syntactic (including division by the literal 0), E-PARSE-003 unknown function, E-PARSE-004 nesting deeper than the parser's limit (the same ceiling as E-DEPTH-001). Every malformed input to parse() raises this — never RecursionError, ValueError or ZeroDivisionError.

exception
exception PoolError(
    message: str,
    remediation: str | None=None,
    span: tuple[int, int] | None=None
)

Pool was closed, mismatched, or otherwise invalid.

exception
exception CertificateUnavailableError(
    message: str,
    remediation: str | None=None,
    span: tuple[int, int] | None=None
)

A Lean certificate was required but the emitter withheld one.

Raised by require_certificate() and by any derivation-producing call made under with alkahest.context(require_certificate=True). The computation itself succeeded — what is missing is the machine-checkable evidence, so this is a policy failure, not a mathematical one.

.remediation names the blocking rewrite rules where they can be identified. certificate_coverage() tabulates which shapes certify.

exception
exception BudgetExceededError(
    message: str,
    remediation: str | None=None,
    span: tuple[int, int] | None=None
)

A Budget was exceeded, or cancellation was requested.

Raised by heavy engines (integrate() today) at a cooperative checkpoint inside the Rust kernel — see alkahest_core::budget and _budget. A fine, expected answer for a fan-out search loop, not a crash: .code distinguishes the three causes:

  • E-BUDGET-001 — the active budget's wall-clock limit elapsed.
  • E-BUDGET-002 — the active budget's step counter exceeded max_steps.
  • E-BUDGET-003 — request_cancel() was called and not yet cleared.

Catching errors by subsystem

Match on the base class and filter by code prefix:

Python
import alkahest

try:
    result = alkahest.integrate(expr, x)
except alkahest.AlkahestError as e:
    if e.code.startswith("E-INT-"):
        # integration failed
        print(f"Integration failed ({e.code}): {e.remediation}")
    else:
        raise

For the full error taxonomy (cause classification, refusal vs verdict, rules for adding codes) see the Error handling <../errors.html>_ chapter of the user guide, whose source is docs/mdbook/src/errors.md.

More exception classes

exception
exception AssumptionError(
    message: str,
    remediation: str | None=None,
    span: tuple[int, int] | None=None
)

An explicit simplification assumption contradicted the current context.

exception
exception DepthLimitError(
    message: str,
    remediation: str | None=None,
    span: tuple[int, int] | None=None
)

An expression was too deeply nested to walk by recursion.

Alkahest processes most expressions by structural recursion, and a native stack overflow is a SIGSEGV rather than an exception — it would kill the interpreter outright, with no traceback for a caller to log. Past a measured ceiling the operation therefore declines instead, which is something except Exception can actually catch.

Depth is nesting, not size: pool.add([t1, ..., t100000]) has depth 2 and is fine. So does the same sum written with repeated + — Add and Mul splice nested same-operator children when they are built, so a left-associated chain of + is the very same flat, depth-2 node. What still nests is everything else: Pow towers, function chains such as sin(sin(sin(...))), and Piecewise nesting.

Which operations raise this is per operation. The simplification entry points (simplify() and its variants) do not walk by recursion — their traversals are trampolined or iterative — and accept input of any depth. They still raise here for an input that would reach the one recursive pass on that path: the assumption-gated e-graph, which runs when the expression contains a Domain.Positive or Domain.NonZero symbol, or when an explicit Assumptions context is supplied. Everything else — printing, diff, symbolic_grad, subs, the evaluators, the emitters — recurses, and raises past the ceiling.

Rebuilding the expression with less nesting, or splitting the work into subexpressions, is the fix.

exception
exception DiophantineError(
    message: str,
    remediation: str | None=None,
    span: tuple[int, int] | None=None
)

Integer Diophantine solving failed (linear / quadratic patterns).

exception
exception DivisionByZeroError(DomainError, ZeroDivisionError)

Division by an exact zero (E-DOMAIN-001).

Raised by simplify() when a denominator simplifies to 0 — e.g. simplify(1/(x + (-1)*x)) — and by Expr division by a literal zero. It is a DomainError and a ZeroDivisionError, so both except ak.DomainError and except ZeroDivisionError catch it.

exception
exception FactorError(
    message: str,
    remediation: str | None=None,
    span: tuple[int, int] | None=None
)

Polynomial factorization failed.

exception
exception HolonomicError(
    message: str,
    remediation: str | None=None,
    span: tuple[int, int] | None=None
)

Creative telescoping refused: input outside the proper hypergeometric class (E-HOLO-001), bounded search exhausted (E-HOLO-002), a candidate certificate failed exact verification (E-HOLO-003), the call was malformed (E-HOLO-004), or a guessed recurrence is not supported by the terms supplied (E-HOLO-005).

M6 added three more, all from modular / p-adic evaluation (ModularRecurrence, binomial_mod()): the modulus is not a prime power the backend supports (E-HOLO-006), a step of the recurrence does not determine its next term as a p-adic integer (E-HOLO-007), or the working precision the singular steps demand is past a machine-word modulus (E-HOLO-008).

A refusal here is informative, not a failure: it says the term is not one Zeilberger's algorithm decides at the requested bounds, so a loop can close that branch instead of re-attempting it.

E-HOLO-005 is the one exception to "close that branch". It comes from guess_holonomic() and means the data could not answer — too few terms to test every candidate in bounds, or a fit with no surplus equations to confirm it. Recording it as "this sequence has no recurrence" closes a branch that was never explored. E-HOLO-008 is the same shape: a resource ceiling, not a mathematical verdict.

exception
exception HomotopyError(
    message: str,
    remediation: str | None=None,
    span: tuple[int, int] | None=None
)

Numerical polynomial continuation failed (V2-14 homotopy solver).

The native class is registered by the extension as PyHomotopyError; this is the Python-side wrapper used for isinstance checks, and its absence is what scripts/check_error_codes.py was reporting.

  • E-HOMOTOPY-002 — the start system has too few paths for the target.
  • E-HOMOTOPY-003 — path tracking failed for a random gamma.
  • E-HOMOTOPY-004 — the tracker exhausted its step budget.
exception
exception IoError(
    message: str,
    remediation: str | None=None,
    span: tuple[int, int] | None=None
)

Checkpoint / restore I/O error (formerly PoolPersistError, E-POOL-* codes).

exception
exception LatticeError(
    message: str,
    remediation: str | None=None,
    span: tuple[int, int] | None=None
)

LLL lattice reduction failed (structure, Lovász parameter, or iteration limit).

exception
exception LimitError(
    message: str,
    remediation: str | None=None,
    span: tuple[int, int] | None=None
)

Symbolic limit computation failed.

exception
exception LinearRecurrenceError(
    message: str,
    remediation: str | None=None,
    span: tuple[int, int] | None=None
)

Linear recurrence solving failed.

exception
exception ModularError(
    message: str,
    remediation: str | None=None,
    span: tuple[int, int] | None=None
)

A modular / CRT reconstruction step failed (E-MOD-001 … E-MOD-004).

As with HomotopyError, the native class exists as PyModularError and this wrapper is what makes it catchable by name from Python.

exception
exception NumberTheoryError(
    message: str,
    remediation: str | None=None,
    span: tuple[int, int] | None=None
)

Integer number-theory primitive failed (parity, modulus, or unsolvable congruence).

exception
exception ParamGroebnerError(
    message: str,
    remediation: str | None=None,
    span: tuple[int, int] | None=None
)

A Gröbner basis over Q(params) refused or was called wrongly (M9).

Raised by ParametricGroebnerBasis and by GroebnerBasis.compute(..., params=[...]). The native class exists as PyParamGroebnerError; this wrapper is what makes it catchable by name.

  • E-PARAMGB-001 — no generators were supplied.
  • E-PARAMGB-002 — the generators disagree on the variable/parameter lists.
  • E-PARAMGB-003 — a specialisation was given the wrong number of values.
  • E-PARAMGB-004 — a result, not a malfunction. The requested parameter point is on the degeneracy locus: one of the conditions the basis assumed vanishes there, so the generic basis says nothing about it. Read ParametricGroebnerBasis.conditions() to see which hypersurfaces those are, or compute over ℚ directly at that point.
exception
exception ProductError(
    message: str,
    remediation: str | None=None,
    span: tuple[int, int] | None=None
)

Symbolic discrete product failed (unsupported term or factorisation).

exception
exception PslqError(
    message: str,
    remediation: str | None=None,
    span: tuple[int, int] | None=None,
    code: str='E-PSLQ-001'
)

Integer-relation heuristic failed (input, coefficient bound, or lattice step).

E-PSLQ-004 is raised when the supplied constants carry less precision than the relation found needs, and E-PSLQ-005 when the constants are exact rationals and the relation is simply false for them — see guess_relation().

exception
exception RealRootError(
    message: str,
    remediation: str | None=None,
    span: tuple[int, int] | None=None
)

Real root isolation failed (conversion to univariate polynomial, etc.).

exception
exception ResultantError(
    message: str,
    remediation: str | None=None,
    span: tuple[int, int] | None=None
)

Polynomial resultant or subresultant PRS computation failed.

exception
exception RsolveError(
    message: str,
    remediation: str | None=None,
    span: tuple[int, int] | None=None
)

Difference equation / rsolve failed.

exception
exception SeriesError(
    message: str,
    remediation: str | None=None,
    span: tuple[int, int] | None=None
)

Symbolic series expansion failed.

exception
exception SosError(
    message: str,
    remediation: str | None=None,
    span: tuple[int, int] | None=None
)

A positivity question was not answered with a certificate.

The three outcomes are deliberately distinct, because conflating them loses information a search loop needs:

  • E-SOS-003 — the target is definitely negative somewhere; the message carries a witness point. The claim is false.
  • E-SOS-002 — no certificate of the searched shape at this degree. This is not a proof that none exists; raise the degree, or fall back to the complete (and far more expensive) decide().
  • E-SOS-001 / E-SOS-004 — the input was not a polynomial in the given variables, or the call was malformed.
exception
exception SumError(
    message: str,
    remediation: str | None=None,
    span: tuple[int, int] | None=None
)

Symbolic summation failed (not hypergeometric or not Gosper-summable).

exception
exception ValidatedError(
    message: str,
    remediation: str | None=None,
    span: tuple[int, int] | None=None
)

A rigorous bound could not be established, so none is returned.

Every variant is a refusal, never a guess: an unsupported primitive (E-VALIDATED-001), a free symbol with no interval (E-VALIDATED-002), a singularity or branch cut inside the box (E-VALIDATED-003), an enclosure that overflowed to infinity (E-VALIDATED-004), or a malformed request (E-VALIDATED-005). verified_ode() adds a step-size underflow near a blow-up or non-Lipschitz point (E-VALIDATED-006), an enclosure grown too wide to say anything (E-VALIDATED-007) and a step limit, typically on a stiff problem (E-VALIDATED-008).

Note that running out of budget is not an error — that returns a wide but still rigorous enclosure with budget_exhausted=True, because a loose true bound is useful and a tight false one is not.