//! Central registry of all stable diagnostic codes for alkahest-core.
//!
//! Every code returned by an `AlkahestError::code()` implementation must appear
//! in `REGISTRY`. Tests below assert no duplicates and ascending order within
//! each prefix.
/// Root cause of an error — informs remediation style, not type dispatch.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Cause {
UserInput,
Domain,
Unsupported,
Resource,
Internal,
}
pub struct ErrorSpec {
pub code: &'static str,
pub class: &'static str,
pub cause: Cause,
pub remediation: Option<&'static str>,
}
pub const REGISTRY: &[ErrorSpec] = &[
// E-POLY — ConversionError
ErrorSpec { code: "E-POLY-001", class: "ConversionError", cause: Cause::UserInput, remediation: Some("remove the unsupported symbol, or declare it as a parameter") },
ErrorSpec { code: "E-POLY-002", class: "ConversionError", cause: Cause::UserInput, remediation: Some("all coefficients must be rational integers; rationalize or substitute") },
ErrorSpec { code: "E-POLY-003", class: "ConversionError", cause: Cause::UserInput, remediation: Some("only non-negative integer exponents are supported in poly_normal") },
ErrorSpec { code: "E-POLY-004", class: "ConversionError", cause: Cause::UserInput, remediation: Some("reduce the degree (e.g. substitute t = x^k), or keep the expression symbolic") },
ErrorSpec { code: "E-POLY-005", class: "ConversionError", cause: Cause::UserInput, remediation: Some("substitute a concrete integer for the exponent before calling poly_normal") },
ErrorSpec { code: "E-POLY-006", class: "ConversionError", cause: Cause::Unsupported, remediation: Some("only polynomial expressions are supported; remove transcendental functions") },
ErrorSpec { code: "E-POLY-007", class: "ConversionError", cause: Cause::Domain, remediation: Some("ensure the denominator is non-zero before converting") },
ErrorSpec { code: "E-POLY-008", class: "FactorError", cause: Cause::UserInput, remediation: Some("factorization is only defined for non-zero polynomials") },
ErrorSpec { code: "E-POLY-009", class: "FactorError", cause: Cause::UserInput, remediation: Some("use a prime modulus p ≥ 2 that fits in a machine word (FLINT nmod); ℤ/nℤ for composite n is not a field") },
ErrorSpec { code: "E-POLY-010", class: "FactorError", cause: Cause::Internal, remediation: Some("report the polynomial as a minimal failing example") },
// E-DIFF — DiffError (symbolic + forward-mode)
ErrorSpec { code: "E-DIFF-001", class: "DiffError", cause: Cause::Unsupported, remediation: Some("register the function in PrimitiveRegistry, or use diff_forward with a custom rule") },
ErrorSpec { code: "E-DIFF-002", class: "DiffError", cause: Cause::UserInput, remediation: Some("symbolic exponents require the chain rule; use diff_forward for non-integer powers") },
ErrorSpec { code: "E-DIFF-003", class: "DiffError", cause: Cause::Unsupported, remediation: Some("register the function in PrimitiveRegistry with diff_forward implemented") },
ErrorSpec { code: "E-DIFF-004", class: "DiffError", cause: Cause::UserInput, remediation: Some("substitute concrete values first; diff_forward requires integer exponents") },
// E-SIMPLIFY — AssumptionError
ErrorSpec { code: "E-SIMPLIFY-001", class: "AssumptionError", cause: Cause::Domain, remediation: Some("remove the conflicting refinement or create a separate AssumptionContext") },
// E-SERIES — SeriesError (V2-15 truncated expansions)
ErrorSpec { code: "E-SERIES-001", class: "SeriesError", cause: Cause::Unsupported, remediation: Some("ensure all functions are registered primitives with differentiation rules") },
ErrorSpec { code: "E-SERIES-002", class: "SeriesError", cause: Cause::UserInput, remediation: Some("pass order >= 1 (exclusive truncation degree in x)") },
// A `series` call that ran past its work ceiling (or an active budget) before
// reaching the requested order. Refusing is the point: coefficients are formed by
// repeated differentiation without re-simplifying, so a nested radical grows by a
// constant factor per coefficient, and returning the prefix under the requested
// `O(h^order)` label would understate the remainder rather than admit the miss.
// Carried out of band on `SeriesError::InvalidOrder` (exhaustive public enum) —
// see `calculus::series::take_series_refusal`.
ErrorSpec { code: "E-SERIES-003", class: "SeriesRefusal", cause: Cause::Resource, remediation: Some("ask for a lower order, raise the budget, or rewrite the expression so its repeated derivatives close") },
// A coefficient came out as an indeterminate form rather than a number: `0/0` for a
// removable singularity the engine could not cancel, `1/0` for a branch point,
// `log(0)`, `exp(0^-1)` for an essential one. Returning the series anyway is the
// silent-wrong-answer shape this registry exists to prevent — it reports success,
// cannot be evaluated, and turns into NaN the moment anyone tries. Carried out of
// band on `SeriesError::InvalidOrder` alongside E-SERIES-003; see
// `calculus::series::take_series_refusal` and `SeriesRefusal::cause`.
ErrorSpec { code: "E-SERIES-004", class: "SeriesRefusal", cause: Cause::Domain, remediation: Some("the expansion point is a branch point, an essential singularity, or a removable one this engine cannot cancel: cancel the singular factor by hand, expand about a nearby regular point, or use `limit` for the single value you need") },
// `calculus::puiseux` — the fractional-exponent expansion `series` refuses with
// E-SERIES-004. Same subsystem, same Python exception class: this is the series
// engine widened, not a new one, and reusing the prefix keeps a caller's
// `except SeriesError` covering both.
//
// 005: the expression has no Puiseux expansion at the point at all — a logarithm
// of something vanishing (`√x·log x` needs a Puiseux-log/transseries type that
// does not exist here), an essential singularity, or a ramification index finer
// than the returned expansion could be *checked* at.
ErrorSpec { code: "E-SERIES-005", class: "PuiseuxError", cause: Cause::Domain, remediation: Some("a logarithm of a vanishing quantity (`sqrt(x)*log x`) needs a Puiseux-log/transseries type this engine does not have, and an essential singularity has no truncation exponent at all: factor the log term out by hand, or expand about a nearby regular point. A ramification index past the verifiable range is the third reading — a Python `Fraction` exponent reaches the kernel through `f64` and is not the fraction you wrote; build it with `pow_expr(pool.rational(p, q))`") },
// 006: an expansion was computed and then **withheld** because the verifier could
// not confirm it. Distinct from 005 on purpose — 005 says "there is nothing to
// return", 006 says "there was something and it is not trustworthy". Returning it
// with a caveat is not an option: a caller cannot tell a checked expansion from an
// unchecked one once it is in their hands.
ErrorSpec { code: "E-SERIES-006", class: "PuiseuxError", cause: Cause::Unsupported, remediation: Some("ask for a lower order, or expand about a point where the function is real and its heads have numeric kernels; an unverified expansion is withheld rather than returned") },
// E-INT — IntegrationError
ErrorSpec { code: "E-INT-001", class: "IntegrationError", cause: Cause::Unsupported, remediation: Some("use a numeric integrator for arbitrary functions") },
ErrorSpec { code: "E-INT-002", class: "IntegrationError", cause: Cause::Domain, remediation: None },
ErrorSpec { code: "E-INT-003", class: "IntegrationError", cause: Cause::Unsupported, remediation: Some("v1.1 supports sqrt(P(x)) only; higher-degree radicals planned for v2.0") },
ErrorSpec { code: "E-INT-004", class: "IntegrationError", cause: Cause::Domain, remediation: Some("no elementary antiderivative exists; use a numeric integrator or elliptic-integral library") },
// E-MAT — MatrixError
ErrorSpec { code: "E-MAT-001", class: "MatrixError", cause: Cause::UserInput, remediation: Some("check that row/column counts agree") },
ErrorSpec { code: "E-MAT-002", class: "MatrixError", cause: Cause::UserInput, remediation: Some("use pseudo-inverse for rectangular matrices") },
ErrorSpec { code: "E-MAT-003", class: "MatrixError", cause: Cause::Domain, remediation: Some("check for linear dependence in the rows/columns") },
ErrorSpec { code: "E-MAT-004", class: "MatrixError", cause: Cause::Unsupported, remediation: Some("rewrite the entries into a form whose determinant's vanishing is decidable, or substitute concrete values") },
// V2-17 — EigenError
ErrorSpec { code: "E-EIGEN-001", class: "EigenError", cause: Cause::UserInput, remediation: Some("pass a square n×n matrix") },
ErrorSpec { code: "E-EIGEN-002", class: "EigenError", cause: Cause::UserInput, remediation: Some("ensure det(λI−M) is a ℤ-polynomial in the fresh λ variable") },
ErrorSpec { code: "E-EIGEN-003", class: "EigenError", cause: Cause::Internal, remediation: Some("report the polynomial as a minimal failing example") },
ErrorSpec { code: "E-EIGEN-004", class: "EigenError", cause: Cause::Unsupported, remediation: Some("irreducible characteristic factors of degree > 2 require a future algebraic-number extension") },
ErrorSpec { code: "E-EIGEN-005", class: "EigenError", cause: Cause::Domain, remediation: Some("the matrix is defective or the eigenbasis is incomplete") },
ErrorSpec { code: "E-EIGEN-006", class: "EigenError", cause: Cause::Unsupported, remediation: Some("nullspace elimination failed; try a purely rational or ℚ(i) spectrum") },
ErrorSpec { code: "E-EIGEN-007", class: "EigenError", cause: Cause::Domain, remediation: Some("eigenvector matrix is singular; check multiplicities") },
ErrorSpec { code: "E-EIGEN-008", class: "SpectrumRefusal", cause: Cause::Domain, remediation: Some("substitute concrete numbers for any symbolic entries, or use real_roots / numeric eigenvalues for this matrix") },
// E-LINALG — LinearAlgebraError (symbolic LA coverage)
ErrorSpec { code: "E-LINALG-001", class: "LinearAlgebraError", cause: Cause::UserInput, remediation: Some("pass a square n×n matrix") },
ErrorSpec { code: "E-LINALG-002", class: "LinearAlgebraError", cause: Cause::Unsupported, remediation: Some("nullspace elimination failed; try rational entries") },
ErrorSpec { code: "E-LINALG-003", class: "LinearAlgebraError", cause: Cause::Domain, remediation: Some("Cholesky requires symmetric positive definite input") },
ErrorSpec { code: "E-LINALG-004", class: "LinearAlgebraError", cause: Cause::UserInput, remediation: Some("ensure det(λI−M) is a polynomial in λ") },
ErrorSpec { code: "E-LINALG-005", class: "LinearAlgebraError", cause: Cause::Internal, remediation: None },
ErrorSpec { code: "E-LINALG-006", class: "LinearAlgebraError", cause: Cause::Unsupported, remediation: Some("irreducible factor of degree > 2 in minimal polynomial") },
ErrorSpec { code: "E-LINALG-007", class: "LinearAlgebraError", cause: Cause::Unsupported, remediation: Some("use rational entries for Smith-based decompositions") },
ErrorSpec { code: "E-LINALG-008", class: "LinearAlgebraError", cause: Cause::Domain, remediation: Some("similarity transform matrix is singular") },
ErrorSpec { code: "E-LINALG-009", class: "LinearAlgebraError", cause: Cause::UserInput, remediation: Some("matrix entries must be rational constants") },
// Zero-testing over a transcendental extension is not decidable in general, so
// elimination can reach an entry it can neither prove zero nor prove non-zero.
// Refusing is the point: treating "unknown" as "non-zero" is what produced a
// confident wrong rank (and a false inconsistency signature) before this code existed.
// Raised through `EigenError` too (`eigenvects`), which shares the elimination it
// refuses in; the class below names where the code is defined, not every route.
ErrorSpec { code: "E-LINALG-010", class: "LinearAlgebraError", cause: Cause::Unsupported, remediation: Some("rewrite the entry into a form whose vanishing is decidable, or substitute concrete values for the parameters") },
// `matrix_exponential` refusing rather than returning an e^A it cannot stand behind:
// an eigenvalue list nothing confirmed is the spectrum, an eigenvalue gap that is
// neither provably zero nor settled non-zero (the confluent and generic branches
// disagree and only one is right), or a candidate that failed the standing
// scaling-and-squaring check. Raised through `LinearAlgebraError::UnsupportedField`;
// the class below names where the code is defined, not the carrier variant.
ErrorSpec { code: "E-LINALG-011", class: "MatrixExpRefusal", cause: Cause::Unsupported, remediation: Some("substitute concrete numbers for any symbolic entries, or state the assumption that makes the eigenvalue gap non-zero") },
// E-ODE — OdeError
ErrorSpec { code: "E-ODE-001", class: "OdeError", cause: Cause::UserInput, remediation: Some("number of state variables must equal number of RHS expressions") },
ErrorSpec { code: "E-ODE-002", class: "OdeError", cause: Cause::UserInput, remediation: Some("use lower_to_first_order() before passing to a solver") },
ErrorSpec { code: "E-ODE-003", class: "OdeError", cause: Cause::Unsupported, remediation: Some("check differentiability of all functions in the system") },
ErrorSpec { code: "E-ODE-010", class: "DsolveError", cause: Cause::Unsupported, remediation: None },
ErrorSpec { code: "E-ODE-011", class: "DsolveError", cause: Cause::UserInput, remediation: None },
ErrorSpec { code: "E-ODE-012", class: "DsolveError", cause: Cause::UserInput, remediation: None },
ErrorSpec { code: "E-ODE-013", class: "DsolveError", cause: Cause::Unsupported, remediation: Some("the ODE class was recognised but the quadrature it needs is not elementary for this integrator") },
ErrorSpec { code: "E-ODE-014", class: "DsolveError", cause: Cause::Unsupported, remediation: Some("supply a particular solution; no general-Riccati closed form is attempted") },
ErrorSpec { code: "E-ODE-020", class: "NumericOdeError", cause: Cause::UserInput, remediation: None },
ErrorSpec { code: "E-ODE-021", class: "NumericOdeError", cause: Cause::Resource, remediation: None },
ErrorSpec { code: "E-ODE-022", class: "NumericOdeError", cause: Cause::Resource, remediation: None },
ErrorSpec { code: "E-ODE-023", class: "NumericOdeError", cause: Cause::UserInput, remediation: None },
ErrorSpec { code: "E-ODE-024", class: "NumericOdeError", cause: Cause::Unsupported, remediation: None },
ErrorSpec { code: "E-ODE-025", class: "NumericOdeError", cause: Cause::UserInput, remediation: None },
ErrorSpec { code: "E-ODE-026", class: "NumericOdeError", cause: Cause::UserInput, remediation: None },
ErrorSpec { code: "E-ODE-030", class: "DsolveSystemError", cause: Cause::UserInput, remediation: Some("each right-hand side must be affine in the state variables") },
ErrorSpec { code: "E-ODE-031", class: "DsolveSystemError", cause: Cause::Unsupported, remediation: Some("y' = A(t)y needs a Magnus/Peano series, not e^{At}") },
ErrorSpec { code: "E-ODE-032", class: "DsolveSystemError", cause: Cause::Unsupported, remediation: Some("substitute concrete values for the symbolic entries of A, or use a 2x2/3x3 or triangular system") },
ErrorSpec { code: "E-ODE-033", class: "DsolveSystemError", cause: Cause::Unsupported, remediation: Some("the forcing term has no elementary antiderivative against the fundamental matrix") },
ErrorSpec { code: "E-ODE-034", class: "DsolveSystemError", cause: Cause::UserInput, remediation: None },
// E-ODE-040..045 — `ode::series_solve::SeriesError` (Frobenius / power-series
// solutions). These sat on 020..025 until 3.10, colliding head-on with the
// `NumericOdeError` block above: `E-ODE-021` was simultaneously "the adaptive
// step size fell below the floor" and "the point is irregular singular", which
// is precisely the branch a caller is told these codes exist to support. The
// series side moved because it had never surfaced — `series_solve` raised an
// uncoded `ValueError` — so no caller could be reading it.
//
// 041/042 are declines about *this* implementation's reach; 040 and 043 are
// facts about the equation; 044 is the withheld-answer code (a candidate
// series that failed the exact-residual gate is never returned with a caveat).
ErrorSpec { code: "E-ODE-040", class: "SeriesOdeError", cause: Cause::Unsupported, remediation: Some("ensure p, q, r are analytic at x0 with rational Taylor coefficients") },
ErrorSpec { code: "E-ODE-041", class: "SeriesOdeError", cause: Cause::Domain, remediation: Some("no Frobenius series exists at an irregular singular point; expand about an ordinary or regular-singular point instead") },
ErrorSpec { code: "E-ODE-042", class: "SeriesOdeError", cause: Cause::Unsupported, remediation: Some("the indicial equation has irrational roots, outside the rational-recurrence path this solver uses") },
ErrorSpec { code: "E-ODE-043", class: "SeriesOdeError", cause: Cause::UserInput, remediation: Some("supply a non-zero coefficient for y''; p = 0 is not a second-order ODE") },
ErrorSpec { code: "E-ODE-044", class: "SeriesOdeError", cause: Cause::Internal, remediation: Some("the candidate series did not satisfy the ODE exactly, so it is withheld rather than returned; report the equation as a minimal failing example") },
ErrorSpec { code: "E-ODE-045", class: "SeriesOdeError", cause: Cause::Unsupported, remediation: Some("the logarithmic second solution is intractable for this equation; the first solution is available on its own") },
// E-DAE — DaeError
ErrorSpec { code: "E-DAE-001", class: "DaeError", cause: Cause::Unsupported, remediation: Some("ensure all functions are differentiable before calling pantelides()") },
ErrorSpec { code: "E-DAE-002", class: "DaeError", cause: Cause::UserInput, remediation: Some("DAE index exceeds depth-10 limit; reformulate the model") },
ErrorSpec { code: "E-DAE-003", class: "DaeError", cause: Cause::UserInput, remediation: Some("check constraint count against variable count") },
// E-DIFFALG — DiffAlgError (V2-13 differential algebra / Rosenfeld–Gröbner)
ErrorSpec { code: "E-DIFFALG-001", class: "DiffAlgError", cause: Cause::Unsupported, remediation: Some("ensure the DAE is polynomial in its state and derivative symbols") },
ErrorSpec { code: "E-DIFFALG-002", class: "DiffAlgError", cause: Cause::UserInput, remediation: Some("declare all jet variables; remove transcendental functions") },
ErrorSpec { code: "E-DIFFALG-003", class: "DiffAlgError", cause: Cause::UserInput, remediation: None },
ErrorSpec { code: "E-SOLVE-001", class: "SolverError", cause: Cause::UserInput, remediation: Some("ensure all equations are polynomial or rational in the declared variables") },
ErrorSpec { code: "E-SOLVE-002", class: "SolverError", cause: Cause::Unsupported, remediation: Some("only degree ≤ 2 univariate solving is implemented; Gröbner basis is still returned") },
ErrorSpec { code: "E-SOLVE-003", class: "SolverError", cause: Cause::UserInput, remediation: Some("provide one equation per variable") },
// `triangularize` extracts one polynomial per main variable, so two basis
// generators sharing a main variable lose one of them and the chain describes
// a larger variety than the input system. Splitting on the initials
// (Lazard–Kalkbrener) is what would decompose those ideals; refusing is the
// point until it exists. Travels inside `SolverError::NotPolynomial` — see
// `solver::regular_chains::TriangularizeRefusal`.
ErrorSpec { code: "E-SOLVE-004", class: "TriangularizeRefusal", cause: Cause::Unsupported, remediation: Some("this ideal needs a splitting triangular decomposition (Lazard–Kalkbrener on the initials), which is not implemented; use GroebnerBasis::compute or primary_decomposition instead") },
// A rational equation whose denominator is identically zero (`1/(x − x)`)
// denotes no function, so it has no solution set. Clearing it would
// multiply through by zero and turn every point into a "solution". Like
// `E-SOLVE-004`, it travels inside `SolverError::NotPolynomial` — see
// `solver::UndefinedEquation`.
ErrorSpec { code: "E-SOLVE-005", class: "UndefinedEquation", cause: Cause::UserInput, remediation: Some("a denominator simplifies to zero, so the equation denotes no function; check the equation for a subtraction that cancels") },
ErrorSpec { code: "E-SOLVE-010", class: "SolverError", cause: Cause::Resource, remediation: Some("check GPU availability; pass device_id=None to fall back to CPU") },
ErrorSpec { code: "E-SOLVE-011", class: "SolverError", cause: Cause::Resource, remediation: Some("CRT reconstruction failed; try adding more equations or use CPU path") },
// E-IDEAL — PrimaryDecompositionError (ideal/primary.rs) and IdealRefusal
ErrorSpec { code: "E-IDEAL-001", class: "PrimaryDecompositionError", cause: Cause::UserInput, remediation: Some("pass at least one generator") },
ErrorSpec { code: "E-IDEAL-002", class: "PrimaryDecompositionError", cause: Cause::UserInput, remediation: Some("all generators must be polynomials in the same variable list") },
ErrorSpec { code: "E-IDEAL-003", class: "PrimaryDecompositionError", cause: Cause::Resource, remediation: Some("the saturation split recursed past its depth limit; simplify the generating set") },
ErrorSpec { code: "E-IDEAL-004", class: "PrimaryDecompositionError", cause: Cause::Internal, remediation: Some("report the generating set as a minimal failing example") },
// √I over an arbitrary ideal needs Gianni–Trager–Zacharias (or a
// characteristic-set method). Only monomial, principal and zero-dimensional
// ideals are certified; outside those, returning the input unchanged would be
// asserting √I = I with no justification, so the routine refuses instead.
ErrorSpec { code: "E-IDEAL-005", class: "IdealRefusal", cause: Cause::Unsupported, remediation: Some("radical is certified for monomial, principal and zero-dimensional ideals; intersect the associated primes of a primary decomposition if one is available") },
ErrorSpec { code: "E-IDEAL-006", class: "IdealRefusal", cause: Cause::Unsupported, remediation: Some("primary decomposition is certified for monomial and principal ideals, for saturation/CRT splits of them, and for shape-position zero-dimensional ideals; no general algorithm is implemented") },
// E-HOMOTOPY — HomotopyError (V2-14 numerical algebraic geometry)
ErrorSpec { code: "E-HOMOTOPY-002", class: "HomotopyError", cause: Cause::Unsupported, remediation: Some("raise HomotopyOpts.max_bezout_paths or use mixed-volume continuation for deficient systems") },
ErrorSpec { code: "E-HOMOTOPY-003", class: "HomotopyError", cause: Cause::Resource, remediation: Some("try HomotopyOpts.gamma_angle_seed or rescale equations") },
ErrorSpec { code: "E-HOMOTOPY-004", class: "HomotopyError", cause: Cause::Resource, remediation: Some("adjust predictor step or increase max_tracker_steps") },
// E-JIT — JitError
ErrorSpec { code: "E-JIT-001", class: "JitError", cause: Cause::Unsupported, remediation: Some("use eval_expr or simplify the expression before JIT") },
ErrorSpec { code: "E-JIT-002", class: "JitError", cause: Cause::Resource, remediation: Some("check LLVM 21 installation; run with RUST_LOG=debug for details") },
ErrorSpec { code: "E-JIT-003", class: "JitError", cause: Cause::Resource, remediation: Some("ensure LLVM_SYS_211_PREFIX is set correctly") },
ErrorSpec { code: "E-JIT-004", class: "JitError", cause: Cause::UserInput, remediation: None },
// The expression has a free symbol the input list does not bind, so the compiled
// function would be NaN everywhere. Carried out of band on `JitError::UnsupportedNode`
// (exhaustive public enum) — see `jit::take_unbound_symbols`.
ErrorSpec { code: "E-JIT-005", class: "UnboundSymbolsError", cause: Cause::UserInput, remediation: Some("add every free symbol of the expression to the input list, or substitute a value for each parameter before compiling") },
// E-CAD — CadError (V2-9 QE / cylindrical decomposition)
ErrorSpec {
code: "E-CAD-001",
class: "CadError",
cause: Cause::Unsupported,
// Two distinct causes now share this code, and the remediation has to
// cover both: outside the supported fragment (≤ 2 variables, ≤ 2
// quantifiers, polynomial atoms), or inside it but undecidable by the
// sample points available — a non-strict atom whose only solutions sit
// at an irrational boundary. The second is a *refusal to guess*: before
// 3.8 that case silently answered, which produced false universals.
remediation: Some(
"keep to polynomial atoms in at most two real variables with at most two quantifiers; if the sentence is already in that fragment, its solutions may lie only at an irrational boundary point, which cannot be tested exactly — substitute concrete values, use a strict inequality, or hand it to an SMT solver via alkahest.smt",
),
},
// E-CUDA — CudaError
ErrorSpec { code: "E-CUDA-001", class: "CudaError", cause: Cause::Resource, remediation: Some("rebuild LLVM with nvptx64 in LLVM_TARGETS_TO_BUILD") },
ErrorSpec { code: "E-CUDA-002", class: "CudaError", cause: Cause::Unsupported, remediation: Some("inspect PTX; verify every primitive has CUDA lowering") },
ErrorSpec { code: "E-CUDA-003", class: "CudaError", cause: Cause::Resource, remediation: Some("run nvidia-smi; retry with CUDA_LAUNCH_BLOCKING=1") },
ErrorSpec { code: "E-CUDA-004", class: "CudaError", cause: Cause::Unsupported, remediation: Some("V1.0 stub; track feature request") },
ErrorSpec { code: "E-CUDA-005", class: "CudaError", cause: Cause::Resource, remediation: Some("install nvidia-cuda-toolkit or set ALKAHEST_LIBDEVICE_PATH") },
ErrorSpec { code: "E-CUDA-006", class: "CudaError", cause: Cause::Resource, remediation: Some("check grid/block dimensions; rerun with compute-sanitizer") },
// E-POOL — ExprPool construction (E-POOL-001, cross-pool arguments, is raised
// only by the Python bindings)
ErrorSpec { code: "E-POOL-002", class: "PoolError", cause: Cause::UserInput, remediation: Some("a built-in function name has a fixed number of arguments; use a different name for a user-defined function of another arity") },
// E-IO — IoError (formerly PoolPersistError with E-POOL-* codes)
ErrorSpec { code: "E-IO-001", class: "IoError", cause: Cause::Resource, remediation: None },
ErrorSpec { code: "E-IO-002", class: "IoError", cause: Cause::UserInput, remediation: Some("file is not an alkahest pool; check the path or regenerate with ExprPool::checkpoint()") },
ErrorSpec { code: "E-IO-003", class: "IoError", cause: Cause::UserInput, remediation: Some("run `alkahest migrate-pool` to upgrade the file, or regenerate from source") },
ErrorSpec { code: "E-IO-004", class: "IoError", cause: Cause::Resource, remediation: Some("file was truncated (likely a crash during checkpoint); rerun from source and checkpoint again") },
ErrorSpec { code: "E-IO-005", class: "IoError", cause: Cause::UserInput, remediation: None },
ErrorSpec { code: "E-IO-006", class: "IoError", cause: Cause::UserInput, remediation: None },
ErrorSpec { code: "E-IO-007", class: "IoError", cause: Cause::UserInput, remediation: None },
ErrorSpec { code: "E-IO-008", class: "IoError", cause: Cause::UserInput, remediation: None },
ErrorSpec { code: "E-IO-009", class: "IoError", cause: Cause::UserInput, remediation: None },
// E-MOD — ModularError (V2-1 Modular/CRT framework)
ErrorSpec { code: "E-MOD-001", class: "ModularError", cause: Cause::UserInput, remediation: Some("use a prime modulus p ≥ 2, e.g. 101, 1009, 32749") },
ErrorSpec { code: "E-MOD-002", class: "ModularError", cause: Cause::UserInput, remediation: Some("ensure all images share the same variable ordering and modulus") },
ErrorSpec { code: "E-MOD-003", class: "ModularError", cause: Cause::UserInput, remediation: Some("provide at least one (MultiPolyFp, prime) pair") },
ErrorSpec { code: "E-MOD-004", class: "ModularError", cause: Cause::Unsupported, remediation: Some("provide more modular images so the prime product M exceeds 2 * max_coeff²") },
// E-LAT — LatticeError (V2-6 LLL)
ErrorSpec { code: "E-LAT-001", class: "LatticeError", cause: Cause::UserInput, remediation: Some("pass a non-empty matrix of integer rows, all of equal length") },
ErrorSpec { code: "E-LAT-002", class: "LatticeError", cause: Cause::UserInput, remediation: Some("every row must lie in ℤ^m for fixed ambient dimension m") },
ErrorSpec { code: "E-LAT-003", class: "LatticeError", cause: Cause::UserInput, remediation: Some("pick δ strictly between ¼ and 1; the default δ = ¾ is standard") },
ErrorSpec { code: "E-LAT-004", class: "LatticeError", cause: Cause::Unsupported, remediation: Some("check for rank deficiency; try a smaller basis or report a minimal reproducer") },
ErrorSpec { code: "E-LAT-005", class: "LatticeGeometryError", cause: Cause::UserInput, remediation: Some("a Gram matrix has one row and one column per basis vector") },
ErrorSpec { code: "E-LAT-006", class: "LatticeGeometryError", cause: Cause::UserInput, remediation: Some("supply the full symmetric matrix G[i][j] = <b_i, b_j>") },
ErrorSpec { code: "E-LAT-007", class: "LatticeGeometryError", cause: Cause::UserInput, remediation: Some("basis rows must be linearly independent and a Gram matrix positive definite") },
ErrorSpec { code: "E-LAT-008", class: "LatticeGeometryError", cause: Cause::Unsupported, remediation: Some("exact SVP/CVP is exponential in the rank; project to a sublattice or use LLL instead of enumeration") },
ErrorSpec { code: "E-LAT-009", class: "LatticeGeometryError", cause: Cause::Resource, remediation: Some("raise the enumeration node budget, lower the norm bound, or reduce the basis first") },
ErrorSpec { code: "E-LAT-010", class: "LatticeGeometryError", cause: Cause::Unsupported, remediation: Some("scale the lattice to an integral Gram matrix, or ask for the minimum rather than a theta series") },
ErrorSpec { code: "E-LAT-011", class: "LatticeGeometryError", cause: Cause::UserInput, remediation: Some("supply a vector with one entry per ambient coordinate") },
ErrorSpec { code: "E-LAT-012", class: "LatticeGeometryError", cause: Cause::UserInput, remediation: Some("check the documented parameter range for this lattice constructor") },
ErrorSpec { code: "E-LAT-013", class: "LatticeGeometryError", cause: Cause::UserInput, remediation: Some("build the lattice from a basis if you need ambient coordinates") },
ErrorSpec { code: "E-LAT-014", class: "LatticeGeometryError", cause: Cause::Internal, remediation: Some("this is a bug: please report it with the lattice that produced it") },
// E-LOGIC — first-order formulas (V3-3)
ErrorSpec { code: "E-LOGIC-001", class: "LogicError", cause: Cause::UserInput, remediation: Some("pass a predicate or quantified Expr; use pool.gt/… or And/Or/Not") },
// E-PSLQ — PslqError (V2-6 augmented-lattice relation heuristic)
ErrorSpec { code: "E-PSLQ-001", class: "PslqError", cause: Cause::UserInput, remediation: Some("pass at least two constants that might admit a linear dependence") },
ErrorSpec { code: "E-PSLQ-002", class: "PslqError", cause: Cause::UserInput, remediation: Some("literals must not truncate to zero — use higher precision or decimal strings") },
ErrorSpec { code: "E-PSLQ-003", class: "PslqError", cause: Cause::UserInput, remediation: Some("allocate at least 64 MPFR bits; ≈664 bits ≈ 200 decimal digits") },
// E-RSOLVE — RsolveError (V2-18 difference equations / rsolve)
ErrorSpec { code: "E-RSOLVE-001", class: "RsolveError", cause: Cause::UserInput, remediation: Some("write the recurrence as a sum of pool.func(seq, [n ± integer]) shifts plus a polynomial in n, then call rsolve(equation, n, seq_name)") },
ErrorSpec { code: "E-RSOLVE-002", class: "RsolveError", cause: Cause::UserInput, remediation: Some("each addend may contain at most one sequence application; avoid products like n*f(n)") },
ErrorSpec { code: "E-RSOLVE-003", class: "RsolveError", cause: Cause::UserInput, remediation: Some("clear denominators; the rhs must be a polynomial in the recurrence index") },
ErrorSpec { code: "E-RSOLVE-004", class: "RsolveError", cause: Cause::Unsupported, remediation: Some("order > 2 non-homogeneous systems and some characteristic factorizations are not implemented yet") },
ErrorSpec { code: "E-RSOLVE-005", class: "RsolveError", cause: Cause::UserInput, remediation: Some("pass exactly order-many initial samples as a dict n → Expr value") },
// E-DIOPH — DiophantineError (V2-19 linear / Pell / sum-of-squares)
ErrorSpec { code: "E-DIOPH-001", class: "DiophantineError", cause: Cause::UserInput, remediation: Some("pass one polynomial equation = 0 in two integer symbols") },
ErrorSpec { code: "E-DIOPH-002", class: "DiophantineError", cause: Cause::UserInput, remediation: Some("clear denominators; coefficients must be rational integers") },
ErrorSpec { code: "E-DIOPH-003", class: "DiophantineError", cause: Cause::Unsupported, remediation: Some("supported: linear 2-variable, x²+y²=n, x²−D·y²=N including unit Pell (no xy term); very large integers may be unsupported") },
ErrorSpec { code: "E-DIOPH-004", class: "DiophantineError", cause: Cause::Domain, remediation: Some("check gcd divisibility (linear) or solvability over ℤ (quadratic)") },
// E-PROD — ProductError (V2-22 symbolic discrete products)
ErrorSpec { code: "E-PROD-001", class: "ProductError", cause: Cause::Unsupported, remediation: Some("reduce the term to ℚ(k) with only ℤ-linear factors in k (no extra symbols in k)") },
ErrorSpec { code: "E-PROD-002", class: "ProductError", cause: Cause::Internal, remediation: Some("report the polynomial as a minimal failing example") },
ErrorSpec { code: "E-PROD-003", class: "ProductError", cause: Cause::Unsupported, remediation: Some("ℤ-irreducible factors of degree ≥ 2 in k require a Gamma extension beyond this path") },
ErrorSpec { code: "E-PROD-004", class: "ProductError", cause: Cause::UserInput, remediation: Some("check that lo/hi share the ExprPool with the summation index symbol") },
// E-NT — NumberTheoryError (V3-1)
ErrorSpec { code: "E-NT-001", class: "NumberTheoryError", cause: Cause::UserInput, remediation: Some("pass decimal strings parsable into fmpz") },
ErrorSpec { code: "E-NT-002", class: "NumberTheoryError", cause: Cause::Domain, remediation: Some("check positivity / parity constraints on arguments") },
ErrorSpec { code: "E-NT-003", class: "NumberTheoryError", cause: Cause::Domain, remediation: Some("adjust residue, base, or root degree until a modular solution exists") },
ErrorSpec { code: "E-NT-004", class: "NumberTheoryError", cause: Cause::Domain, remediation: Some("use prime moduli for discrete_log/nthroot_mod as documented") },
ErrorSpec { code: "E-NT-005", class: "NumberTheoryError", cause: Cause::Unsupported, remediation: Some("use quadratic roots or gcd(k,p−1)=1; general radicals require more machinery") },
ErrorSpec { code: "E-NT-006", class: "ArithmeticError", cause: Cause::Resource, remediation: Some("the arithmetic-function argument is past this crate's work cap; p(n), B_n, E_n and H_n all grow superpolynomially in the number of digits, so the cap is a refusal to sit in FLINT for an unbounded time rather than a statement that the value does not exist") },
// E-PARSE — expression parser (V2-21)
ErrorSpec { code: "E-PARSE-001", class: "ParseError", cause: Cause::UserInput, remediation: Some("only ASCII arithmetic expressions are supported") },
ErrorSpec { code: "E-PARSE-002", class: "ParseError", cause: Cause::UserInput, remediation: Some("check parentheses and operator placement") },
ErrorSpec { code: "E-PARSE-003", class: "ParseError", cause: Cause::UserInput, remediation: Some("use a known function: sin, cos, tan, sinh, cosh, tanh, asin, acos, atan, atan2, exp, log, sqrt, abs, sign, floor, ceil, round, erf, erfc, gamma, lambert_w, digamma, bessel_j0, bessel_j1") },
ErrorSpec { code: "E-PARSE-004", class: "ParseError", cause: Cause::Resource, remediation: Some("flatten the expression — deeply nested parentheses, prefix signs or function calls exceed the parser's recursion budget") },
ErrorSpec { code: "E-EVAL-001", class: "EvalError", cause: Cause::UserInput, remediation: Some("bind every free symbol before evaluation") },
ErrorSpec { code: "E-EVAL-002", class: "EvalError", cause: Cause::UserInput, remediation: Some("use mode='f64' or 'complex' for float literals") },
ErrorSpec { code: "E-EVAL-003", class: "EvalError", cause: Cause::UserInput, remediation: Some("only integer exponents are supported in exact mode") },
ErrorSpec { code: "E-EVAL-004", class: "EvalError", cause: Cause::Domain, remediation: Some("0 to a negative power is undefined") },
ErrorSpec { code: "E-EVAL-005", class: "EvalError", cause: Cause::Unsupported, remediation: Some("register the function or use a supported evaluation mode") },
ErrorSpec { code: "E-EVAL-006", class: "EvalError", cause: Cause::Unsupported, remediation: Some("this expression form is not evaluable in the requested mode") },
ErrorSpec { code: "E-EVAL-007", class: "EvalError", cause: Cause::UserInput, remediation: Some("check predicate arity") },
ErrorSpec { code: "E-EVAL-008", class: "EvalError", cause: Cause::Domain, remediation: Some("predicate truth value is not uniform over the input domain") },
ErrorSpec { code: "E-EVAL-009", class: "EvalError", cause: Cause::Domain, remediation: Some("result is not finite") },
ErrorSpec { code: "E-EVAL-010", class: "EvalError", cause: Cause::Domain, remediation: Some("interval evaluation failed or branch is indeterminate") },
ErrorSpec { code: "E-EVAL-011", class: "EvalError", cause: Cause::Domain, remediation: Some("principal Arg/log branch cut — expression stays unevaluated at this point") },
ErrorSpec { code: "E-EVAL-012", class: "EvalError", cause: Cause::Resource, remediation: Some("the exponent is an exact integer whose power the requested representation cannot hold — reduce the exponent, or evaluate a base of 0 or ±1 where the answer is the parity alone") },
ErrorSpec { code: "E-RESIDUE-001", class: "ResidueError", cause: Cause::UserInput, remediation: Some("input must be a rational function of the variable over ℚ") },
ErrorSpec { code: "E-RESIDUE-002", class: "ResidueError", cause: Cause::Domain, remediation: Some("denominator must be non-zero") },
ErrorSpec { code: "E-RESIDUE-003", class: "ResidueError", cause: Cause::Unsupported, remediation: Some("pole order exceeds supported bound; essential singularities are out of scope") },
ErrorSpec { code: "E-RESIDUE-004", class: "ResidueError", cause: Cause::Domain, remediation: Some("division by zero during Laurent coefficient extraction") },
// E-BUDGET — BudgetError (P1 search plumbing item 4: budgets/cancellation/determinism)
ErrorSpec { code: "E-BUDGET-001", class: "BudgetError", cause: Cause::Resource, remediation: Some("raise Budget(wall_ms=...), or accept a heuristic/numeric result for this candidate instead of an exact one") },
// Also the code for a *growth* refusal: one step asking `budget::check_growth`
// to spend more units at once than `max_steps` (or, with no budget active,
// `budget::DEFAULT_MAX_GROWTH_UNITS`) allows. Same currency, same remedy — and
// the reason it needs no code of its own.
ErrorSpec { code: "E-BUDGET-002", class: "BudgetError", cause: Cause::Resource, remediation: Some("raise Budget(max_steps=...), or accept a partial/heuristic result for this candidate instead of an exact one") },
ErrorSpec { code: "E-BUDGET-003", class: "BudgetError", cause: Cause::Resource, remediation: Some("call alkahest.clear_cancel() (Python) or budget::clear_cancel() (Rust) before starting the next candidate") },
// E-BUDGET-004/005 — BudgetTrip. Carried on `BudgetTrip`, not `BudgetError`:
// the latter is an exhaustive public enum, so a `Memory` variant would be a
// major semver break. 005 fires with no budget active — it is the guard that
// turns GMP's `abort()` under a `ulimit -v` into a catchable refusal.
ErrorSpec { code: "E-BUDGET-004", class: "BudgetError", cause: Cause::Resource, remediation: Some("raise Budget(max_bytes=...), or ask for a smaller problem (fewer unknowns, a lower order/degree) — the exact coefficients, not the shape of the system, are what grew") },
ErrorSpec { code: "E-BUDGET-005", class: "BudgetError", cause: Cause::Resource, remediation: Some("raise the process address-space limit (ulimit -v, or the container/cgroup memory limit), or ask for a smaller problem — this refusal replaces the uncatchable abort that would otherwise follow") },
// E-BUDGET-006 — BudgetTrip::Oversized: a single result larger than physical
// memory, or than GMP can represent as one integer. Fires with no budget.
ErrorSpec { code: "E-BUDGET-006", class: "BudgetError", cause: Cause::Resource, remediation: Some("ask for a smaller result (a smaller exponent, degree or shape) — no limit setting can make this one fit; this refusal replaces the uncatchable abort that would otherwise follow") },
// E-DEPTH — DepthLimitError (expression nesting ceiling; see kernel::depth).
// Resource, not UserInput: the expression is well-formed, we decline to
// recurse over it because a native stack overflow would kill the process.
ErrorSpec { code: "E-DEPTH-001", class: "DepthLimitError", cause: Cause::Resource, remediation: Some("rebuild the expression with less nesting (a balanced n-ary Add is shallow where a chain of binary ones is not), or process it in smaller pieces") },
// E-DOMAIN — reserved; DomainError is Python-only pending Rust implementation
// E-SOS — SosError (P1 item 8: positivity certificates / Positivstellensatz)
ErrorSpec { code: "E-SOS-001", class: "SosError", cause: Cause::UserInput, remediation: Some("positivity certificates are for polynomials in the listed variables; expand or clear denominators first, and pass every symbol that occurs as a variable") },
ErrorSpec { code: "E-SOS-002", class: "SosError", cause: Cause::Unsupported, remediation: Some("record this as unknown, not as a closed branch: read the 'what the search actually did' trace in the message first — a line marked NOT SEARCHED is a size ceiling that fired, not a search that came up empty — then raise basis_degree (unconstrained) or level (constrained); this is not a proof that no SOS decomposition exists, and still less that the inequality is false — alkahest.decide is the complete (and far more expensive) fallback") },
ErrorSpec { code: "E-SOS-003", class: "SosError", cause: Cause::UserInput, remediation: Some("the witness point in the message satisfies the constraints and makes the target negative; the claim is false as stated") },
ErrorSpec { code: "E-SOS-004", class: "SosError", cause: Cause::UserInput, remediation: Some("pass at least one variable, and keep basis_degree/level within the supported range") },
ErrorSpec { code: "E-SOS-005", class: "SosError", cause: Cause::Internal, remediation: Some("internal: report the target and constraints as a minimal failing example") },
// E-HOLO — HolonomicError (P1 item 7: creative telescoping / Zeilberger's algorithm)
ErrorSpec { code: "E-HOLO-001", class: "HolonomicError", cause: Cause::UserInput, remediation: Some("rewrite the term as R(n,k)*z**k*w**n*prod(gamma(a*n + b*k + c)**e) with integer a, b and rational c; supported function heads are gamma, factorial, binomial, pochhammer") },
ErrorSpec { code: "E-HOLO-002", class: "HolonomicError", cause: Cause::Resource, remediation: Some("raise max_order and/or max_degree in ZeilbergerOpts; if the term genuinely has no such recurrence within reach, Zeilberger's algorithm does not apply") },
ErrorSpec { code: "E-HOLO-003", class: "HolonomicError", cause: Cause::Internal, remediation: Some("internal: report the term as a minimal failing example") },
ErrorSpec { code: "E-HOLO-004", class: "HolonomicError", cause: Cause::UserInput, remediation: Some("n and k must be distinct symbols; max_order and max_degree must be positive") },
// E-HOLO-005 is Python-only (`python/alkahest/_guess_holonomic.py`): a fit
// the supplied terms cannot support. Registering a code no Rust
// `AlkahestError` impl returns would fail `scripts/check_error_codes.py`.
ErrorSpec { code: "E-HOLO-006", class: "HolonomicError", cause: Cause::UserInput, remediation: Some("the modulus must be p**k with p prime, k >= 1 and p**k < 2**62; for a composite modulus, evaluate at each prime power and recombine by CRT") },
ErrorSpec { code: "E-HOLO-007", class: "HolonomicError", cause: Cause::UserInput, remediation: Some("no modulus repairs this: the recurrence itself leaves Z_p at that index. Supply more initial terms so the evaluation starts past it, use a recurrence whose leading coefficient does not vanish there, or accept that the sequence is not p-integral and rescale it") },
ErrorSpec { code: "E-HOLO-008", class: "HolonomicError", cause: Cause::Resource, remediation: Some("lower k, use a smaller prime, or ask for an index the recurrence reaches without crossing so many singular steps") },
// E-HOLO-02x — QHolonomicError (M4b: q-analogue creative telescoping). A
// separate block so a caller can tell which of the two engines refused.
ErrorSpec { code: "E-HOLO-020", class: "HolonomicError", cause: Cause::UserInput, remediation: Some("write the summand with qbinomial(N, K), qpochhammer(u, d, v), powers of q with a degree-2 exponent in n and k, and rational functions of q, q**n and q**k; a bare n or k outside an exponent is not q-hypergeometric") },
ErrorSpec { code: "E-HOLO-021", class: "HolonomicError", cause: Cause::Resource, remediation: Some("raise max_order and/or max_degree; if the sum genuinely satisfies no such q-recurrence, q-Zeilberger does not apply") },
ErrorSpec { code: "E-HOLO-022", class: "HolonomicError", cause: Cause::Internal, remediation: Some("internal: report the term as a minimal failing example") },
ErrorSpec { code: "E-HOLO-023", class: "HolonomicError", cause: Cause::UserInput, remediation: Some("q, n and k must be three distinct symbols; max_order and max_degree must be at least 1; a q-Pochhammer base step must be at least 1") },
ErrorSpec { code: "E-HOLO-024", class: "HolonomicError", cause: Cause::Unsupported, remediation: Some("the term is q-hypergeometric in shape but its shift quotient is not a rational function of q**n and q**k — e.g. (q; q**2)_k shifted in k. No algorithm in this family applies; close the branch") },
// E-HOLO-04x — Telescoping2dError (M4: double-sum / Apagodu-Zeilberger
// creative telescoping). A separate block so a caller can tell which of
// the three engines refused.
ErrorSpec { code: "E-HOLO-040", class: "HolonomicError", cause: Cause::UserInput, remediation: Some("rewrite the term as R(n,j,k)*z1**j*z2**k*w**n*prod(gamma(a*n+b*j+c*k+d)**e) with integer a, b, c; supported function heads are gamma, factorial, binomial, pochhammer") },
ErrorSpec { code: "E-HOLO-041", class: "HolonomicError", cause: Cause::Resource, remediation: Some("raise max_order, max_a_degree and/or max_cert_degree in Telescoping2dOpts; if the term genuinely has no such double-sum certificate within reach — or needs a certificate denominator this module's fixed-denominator ansatz cannot represent — this method does not apply") },
ErrorSpec { code: "E-HOLO-042", class: "HolonomicError", cause: Cause::UserInput, remediation: Some("n, j and k must be three distinct symbols") },
// E-HOLO-06x — DiffTelescopingError (continuous creative telescoping /
// Almkvist-Zeilberger, the differential twin of the classical engine). Its
// own sub-block for the same reason the q- and multi-sum engines have one:
// a caller can tell which engine refused, and the refusals differ in kind
// (061 in particular closes the branch rather than suggesting new bounds).
ErrorSpec { code: "E-HOLO-060", class: "HolonomicError", cause: Cause::UserInput, remediation: Some("write the integrand as R(n,x)*w**n*exp(eta(x))*prod(B(x)**(a*n + b)) with integer a and rational b; supported function heads are exp and sqrt, and a sum of two hyperexponential terms is not itself hyperexponential") },
ErrorSpec { code: "E-HOLO-061", class: "HolonomicError", cause: Cause::Unsupported, remediation: Some("the x-side is fine but F(n+1,x)/F(n,x) is not rational in x - e.g. exp(n*x), whose ratio is exp(x). No algorithm in this family applies; close the branch") },
ErrorSpec { code: "E-HOLO-062", class: "HolonomicError", cause: Cause::Resource, remediation: Some("raise max_order, max_degree and/or max_den_power in AzOpts; if the integrand genuinely has no such certificate within reach - or needs a certificate denominator this module's D**kappa * B ansatz cannot represent - this method does not apply") },
ErrorSpec { code: "E-HOLO-063", class: "HolonomicError", cause: Cause::Internal, remediation: Some("internal: report the integrand as a minimal failing example") },
ErrorSpec { code: "E-HOLO-064", class: "HolonomicError", cause: Cause::UserInput, remediation: Some("n and x must be distinct symbols; the max_order, max_degree and max_den_power bounds must be within the documented ranges") },
// E-SMT — SmtError (P2 item 3: SMT/SAT bridge).
//
// Only the code Rust actually raises is registered here. The rest of the
// family lives entirely in `python/alkahest/smt.py`, which drives the solver
// process: E-SMT-001 (no solver binary on PATH), E-SMT-003 (a model value —
// a `root-obj` algebraic number — that cannot be lifted exactly), and
// E-SMT-004 (a model that failed exact back-substitution). Registering a
// code no Rust `AlkahestError` impl returns would fail
// `scripts/check_error_codes.py`; `E-BATCH-001` in `python/alkahest/_batch.py`
// is the same precedent.
ErrorSpec { code: "E-SMT-002", class: "SmtError", cause: Cause::Unsupported, remediation: Some("check alkahest.smt.supported(formula) before exporting; the fragment is polynomial (in)equalities over Int/Real symbols plus boolean structure and quantifiers, and float literals are refused because they are not the exact question they look like") },
// Codes raised by alkahest-core that were never registered here. The registry
// is the contract `scripts/check_error_codes.py` enforces, so an unregistered
// code is an undocumented one. Remediation text is taken from each type's own
// `AlkahestError::remediation` impl rather than re-worded, so the two cannot
// disagree about what a caller should do.
// E-ASYMPT — AsymptoticError
ErrorSpec { code: "E-ASYMPT-001", class: "AsymptoticError", cause: Cause::UserInput, remediation: Some("pass n_terms >= 1") },
ErrorSpec { code: "E-ASYMPT-002", class: "AsymptoticError", cause: Cause::UserInput, remediation: Some("the function may not be analytic/Laurent-expandable at infinity; try a simpler form or fewer terms") },
ErrorSpec { code: "E-ASYMPT-003", class: "AsymptoticError", cause: Cause::UserInput, remediation: Some("ensure all functions are registered primitives with differentiation rules") },
ErrorSpec { code: "E-ASYMPT-004", class: "AsymptoticError", cause: Cause::UserInput, remediation: Some("the expansion could not be numerically verified at large x; the function may have an oscillatory or non-power-scale tail") },
ErrorSpec { code: "E-ASYMPT-005", class: "AsymptoticError", cause: Cause::Unsupported, remediation: Some("exp/log scale hierarchies and Gamma/Stirling asymptotics are out of scope for asymptotic_expand; power-scale (rational/algebraic) and single log/exp peels are supported. For the asymptotics of a *sum* use experimental.euler_maclaurin, which also reaches Stirling via the sum of log k") },
// E-FPS — FpsError
ErrorSpec { code: "E-FPS-001", class: "FpsError", cause: Cause::Domain, remediation: Some("q(0) = 0 makes p/q singular at the origin: divide out the common factor of x, or expand about a point where q does not vanish") },
ErrorSpec { code: "E-FPS-002", class: "FpsError", cause: Cause::Domain, remediation: Some("the expression has a pole at x = 0, so its expansion is a Laurent series and not a formal power series; multiply by the polar factor first, or use series/puiseux_series") },
ErrorSpec { code: "E-FPS-003", class: "FpsError", cause: Cause::UserInput, remediation: Some("Fps carries exact rational coefficients only; substitute rationals for the symbolic parameters") },
ErrorSpec { code: "E-FPS-004", class: "FpsError", cause: Cause::Domain, remediation: Some("composition, exp and reversion need f(0) = 0; subtract the constant term, e.g. exp(f) = exp(f(0))*exp(f - f(0))") },
ErrorSpec { code: "E-FPS-005", class: "FpsError", cause: Cause::UserInput, remediation: Some("log and the default n-th root need f(0) = 1 to pick a branch; factor the constant term out, e.g. log(f) = log(f(0)) + log(f/f(0))") },
ErrorSpec { code: "E-FPS-006", class: "FpsError", cause: Cause::Domain, remediation: Some("the multiplicative inverse needs f(0) != 0; when f vanishes to order k, divide x^k out first") },
ErrorSpec { code: "E-FPS-007", class: "FpsError", cause: Cause::UserInput, remediation: Some("the underlying Taylor expansion failed; ensure every function has a differentiation rule and the expansion point is regular") },
// E-TRANSFORM — the integral/sequence transform tables
// (`transform::laplace`, `transform::fourier`, `transform::ztransform`).
//
// One prefix for the whole `transform` module, three disjoint numeric
// blocks: 00x Laplace, 01x Fourier, 10x Z. This is the arrangement E-ODE-*
// already uses for `OdeError`/`DsolveError`/`NumericOdeError` — one Python
// class per *prefix*, with the number saying which engine and which failure.
//
// The blocks are split the same way inside: a table miss (001/002/011/101/102)
// is a fact about *this implementation* and a wider table would close it,
// while 004 and 013 are facts about *the mathematics* — the hypothesis the
// rule rests on is refuted, and no table can be widened to cover it. That is
// the branch these codes exist for: the first says "try again later or rewrite
// the input", the second says "there is nothing to find".
ErrorSpec { code: "E-TRANSFORM-001", class: "TransformError", cause: Cause::Unsupported, remediation: Some("laplace_transform is table-based: reduce f(t) to constants, t^n, exp(a*t), sin/cos/sinh/cosh of b*t, Heaviside/Dirac shifts, and products of those with exp(a*t) or t^n") },
ErrorSpec { code: "E-TRANSFORM-002", class: "TransformError", cause: Cause::Unsupported, remediation: Some("inverse_laplace_transform inverts a proper rational F(s) whose denominator factors into poles of degree <= 2 (times an optional delay exp(-a*s)); an improper part would invert to derivatives of the Dirac delta and is declined rather than fabricated") },
ErrorSpec { code: "E-TRANSFORM-003", class: "TransformError", cause: Cause::UserInput, remediation: Some("pass distinct symbols for the time and frequency variables") },
// Not a table gap: the unilateral transform integrates over t >= 0 only, so a
// step/impulse edge at a < 0 is invisible to it and exp(+a*s) on the inverse is
// the transform of nothing causal. Emitting the shifted answer anyway is a
// clean, plausible, wrong function — L{theta(t+1)} = 1/s, not e^s/s.
ErrorSpec { code: "E-TRANSFORM-004", class: "TransformError", cause: Cause::Domain, remediation: Some("the unilateral transform sees t >= 0 only, so no wider table fixes this; shift the edge to a >= 0, or use a bilateral transform") },
ErrorSpec { code: "E-TRANSFORM-011", class: "TransformError", cause: Cause::Unsupported, remediation: Some("fourier_transform is table-based: reduce f(x) to Gaussians, one- and two-sided exponentials, Lorentzians, rect/sinc, Dirac deltas, constants and polynomials, and modulations of those") },
ErrorSpec { code: "E-TRANSFORM-012", class: "TransformError", cause: Cause::UserInput, remediation: Some("pass distinct symbols for the space and frequency variables") },
// The table entry's own decay hypothesis, refuted rather than unproven: at a
// non-positive rate the defining integral diverges, and a negative Lorentzian
// amplitude has a transform of the opposite sign to the tabulated one.
ErrorSpec { code: "E-TRANSFORM-013", class: "TransformError", cause: Cause::Domain, remediation: Some("the rate named in the message is not positive, so the defining integral does not converge; supply a positive rate, or leave it symbolic and read the hypothesis off side_conditions") },
ErrorSpec { code: "E-TRANSFORM-101", class: "TransformError", cause: Cause::Unsupported, remediation: Some("z_transform is table-based: reduce a[n] to constants, n, n^2, a^n, sin/cos of omega*n, and products of those with a^n or n") },
ErrorSpec { code: "E-TRANSFORM-102", class: "TransformError", cause: Cause::Unsupported, remediation: Some("inverse_z_transform inverts X(z) whose partial-fraction terms are A*z^p/(z - a)^k with k <= 2; a constant term would invert to the Kronecker delta, for which there is no primitive here") },
ErrorSpec { code: "E-TRANSFORM-103", class: "TransformError", cause: Cause::UserInput, remediation: Some("pass distinct symbols for the index and frequency variables") },
// E-INTERP — SparseInterpError
ErrorSpec { code: "E-INTERP-001", class: "SparseInterpError", cause: Cause::UserInput, remediation: None },
ErrorSpec { code: "E-INTERP-002", class: "SparseInterpError", cause: Cause::UserInput, remediation: None },
ErrorSpec { code: "E-INTERP-003", class: "SparseInterpError", cause: Cause::UserInput, remediation: None },
ErrorSpec { code: "E-INTERP-004", class: "SparseInterpError", cause: Cause::Domain, remediation: None },
ErrorSpec { code: "E-INTERP-010", class: "SparseGcdError", cause: Cause::Unsupported, remediation: None },
ErrorSpec { code: "E-INTERP-011", class: "SparseGcdError", cause: Cause::UserInput, remediation: None },
ErrorSpec { code: "E-INTERP-012", class: "SparseGcdError", cause: Cause::Internal, remediation: None },
// E-LIMIT — LimitError
ErrorSpec { code: "E-LIMIT-001", class: "LimitError", cause: Cause::UserInput, remediation: Some("increase truncation order indirectly by simplifying the expression, or rewrite using standard limits") },
ErrorSpec { code: "E-LIMIT-002", class: "LimitError", cause: Cause::UserInput, remediation: Some("ensure primitives have differentiation rules, or simplify before taking the limit") },
ErrorSpec { code: "E-LIMIT-003", class: "LimitError", cause: Cause::UserInput, remediation: Some("use LimitDirection::Plus or Minus matching the desired one-sided approach") },
ErrorSpec { code: "E-LIMIT-004", class: "LimitError", cause: Cause::Resource, remediation: Some("try manual algebra (quotient form, cancellations) or split into simpler sub-expressions") },
ErrorSpec { code: "E-LIMIT-005", class: "LimitError", cause: Cause::Unsupported, remediation: Some("limit could not be computed — try manual algebra, or the expression may involve oscillation or non-comparable growth not yet handled") },
ErrorSpec { code: "E-LIMIT-006", class: "LimitError", cause: Cause::UserInput, remediation: Some("state the sign of the parameter — `Assumptions.refine(pool.gt(k, pool.integer(0)))`, or declare it with `Domain.Positive` — and retry inside `alkahest.context(assumptions=…)`") },
// E-NFM — NormalFormError
ErrorSpec { code: "E-NFM-001", class: "NormalFormError", cause: Cause::UserInput, remediation: None },
ErrorSpec { code: "E-NFM-002", class: "NormalFormError", cause: Cause::Unsupported, remediation: None },
// E-REC — LinearRecurrenceError
ErrorSpec { code: "E-REC-001", class: "LinearRecurrenceError", cause: Cause::Unsupported, remediation: None },
ErrorSpec { code: "E-REC-002", class: "LinearRecurrenceError", cause: Cause::UserInput, remediation: None },
// E-RES — ResultantError
ErrorSpec { code: "E-RES-001", class: "ResultantError", cause: Cause::UserInput, remediation: None },
ErrorSpec { code: "E-RES-003", class: "ResultantError", cause: Cause::UserInput, remediation: None },
// E-ROOT — RealRootError
ErrorSpec { code: "E-ROOT-001", class: "RealRootError", cause: Cause::UserInput, remediation: None },
ErrorSpec { code: "E-ROOT-002", class: "RealRootError", cause: Cause::Domain, remediation: None },
// E-SUM — SumError
ErrorSpec { code: "E-SUM-001", class: "SumError", cause: Cause::UserInput, remediation: None },
ErrorSpec { code: "E-SUM-002", class: "SumError", cause: Cause::UserInput, remediation: None },
ErrorSpec { code: "E-SUM-003", class: "SumError", cause: Cause::UserInput, remediation: None },
// E-PARAMGB — ParamGroebnerError (M9, Gröbner bases over Q(params))
ErrorSpec { code: "E-PARAMGB-001", class: "ParamGroebnerError", cause: Cause::UserInput, remediation: Some("pass at least one polynomial") },
ErrorSpec { code: "E-PARAMGB-002", class: "ParamGroebnerError", cause: Cause::UserInput, remediation: Some("build every generator against the same variable and parameter lists") },
ErrorSpec { code: "E-PARAMGB-003", class: "ParamGroebnerError", cause: Cause::UserInput, remediation: Some("supply exactly one value per parameter, in the parameter list's order") },
// Not a malfunction: the basis is a *generic* one, and this is it saying so
// at a point where it does not apply, rather than specialising anyway.
ErrorSpec { code: "E-PARAMGB-004", class: "ParamGroebnerError", cause: Cause::Domain, remediation: Some("compute the basis directly over ℚ at that parameter point, or move the vanishing factors into the generators and recompute") },
// E-PROB — ProbError (probability distributions and expectations)
//
// 001 is the only one of these that is a *user* error. The rest are the
// module declining to answer, and they are kept apart because "there is no
// closed form", "this integrator could not find one", "the quantity does
// not exist" and "one was found and is not trustworthy" call for four
// different next steps, and collapsing them would tell a caller to go
// looking for a better integrator when the integral diverges.
ErrorSpec { code: "E-PROB-001", class: "ProbError", cause: Cause::UserInput, remediation: Some("pass a parameter inside the distribution's constraint (σ > 0, a < b, 0 ≤ p ≤ 1, n a literal non-negative integer), or leave it symbolic — a symbolic parameter is carried as a side condition on `constraints` rather than decided") },
ErrorSpec { code: "E-PROB-002", class: "ProbError", cause: Cause::Unsupported, remediation: Some("reduce the query to a supported shape: a polynomial f for an expectation over a countably infinite discrete support, an affine combination for `expectation_affine`, a moment order within MAX_MOMENT_ORDER") },
ErrorSpec { code: "E-PROB-003", class: "ProbError", cause: Cause::Unsupported, remediation: Some("the named integral has no antiderivative this integrator found; integrate it numerically, or ask for a moment/CDF that is in the closed-form table") },
// Distinct from 003: no integrator would help, because the closed form
// needs a function this library does not implement (the incomplete gamma,
// the incomplete beta, erf⁻¹, ₁F₁) — or, for the log-normal characteristic
// function, one that does not exist in closed form at all.
ErrorSpec { code: "E-PROB-004", class: "ProbError", cause: Cause::Unsupported, remediation: Some("use an integer shape parameter where that collapses the special function to a finite sum (Erlang, integer-parameter Beta), or integrate numerically") },
// The prime-directive code: a closed form *was* computed and is being
// withheld because the numeric gate could not confirm it against
// quadrature of its own defining integral. Never downgraded to a warning —
// a caller cannot tell a checked value from an unchecked one once it is in
// their hands.
ErrorSpec { code: "E-PROB-005", class: "ProbError", cause: Cause::Internal, remediation: Some("an unconfirmed closed form is withheld rather than returned; report the distribution and query as a minimal failing example") },
ErrorSpec { code: "E-PROB-006", class: "ProbError", cause: Cause::Domain, remediation: Some("the integral defining this expectation does not converge — there is no value to return; restrict f so that f(x)·p(x) is integrable over the support") },
// E-VALIDATED — ValidatedError
ErrorSpec { code: "E-VALIDATED-001", class: "ValidatedError", cause: Cause::Unsupported, remediation: None },
ErrorSpec { code: "E-VALIDATED-002", class: "ValidatedError", cause: Cause::UserInput, remediation: None },
ErrorSpec { code: "E-VALIDATED-003", class: "ValidatedError", cause: Cause::Domain, remediation: None },
ErrorSpec { code: "E-VALIDATED-004", class: "ValidatedError", cause: Cause::Domain, remediation: None },
ErrorSpec { code: "E-VALIDATED-005", class: "ValidatedError", cause: Cause::UserInput, remediation: None },
// E-VEC — VectorError (vector calculus over orthogonal curvilinear charts)
ErrorSpec { code: "E-VEC-001", class: "VectorError", cause: Cause::Unsupported, remediation: Some("the field contains a function with no differentiation rule; register it in PrimitiveRegistry or rewrite the component") },
ErrorSpec { code: "E-VEC-002", class: "VectorError", cause: Cause::UserInput, remediation: Some("pass three distinct coordinate symbols, e.g. Coordinates::cylindrical(rho, phi, z)") },
ErrorSpec { code: "E-VEC-003", class: "VectorError", cause: Cause::UserInput, remediation: Some("build each coordinate with `pool.symbol(..)` before constructing Coordinates") },
// Every grad/div/curl/laplacian formula in `vector` assumes the chart is
// orthogonal. On a skew chart they still evaluate — to a number that looks
// like a divergence and is not one — so an *undecided* inner product is a
// refusal rather than an assumption.
ErrorSpec { code: "E-VEC-004", class: "VectorError", cause: Cause::Domain, remediation: Some("these formulas hold only for orthogonal charts; supply an orthogonal embedding, or work in Cartesian coordinates and transform the result by hand") },
ErrorSpec { code: "E-VEC-005", class: "VectorError", cause: Cause::Domain, remediation: Some("restrict the chart to a region where the scale factor is non-zero, or re-parametrise; a chart that collapses a direction has no unique orthonormal frame there") },
// E-QUAT — QuaternionError (Hamilton quaternions and the rotation operator)
ErrorSpec { code: "E-QUAT-001", class: "QuaternionError", cause: Cause::Domain, remediation: Some("use a non-zero quaternion; if the components are symbolic, substitute concrete values or declare the parameters so the norm can be decided") },
// The identity rotation has no axis — *every* unit vector is one. Returning
// the conventional (0,0,1) is a stated answer to a question with no answer.
ErrorSpec { code: "E-QUAT-002", class: "QuaternionError", cause: Cause::Domain, remediation: Some("the rotation is the identity — report the angle as 0 and pick whatever axis your convention prefers, explicitly, at the call site") },
ErrorSpec { code: "E-QUAT-003", class: "QuaternionError", cause: Cause::UserInput, remediation: Some("pass a 3×3 matrix of numbers with RᵀR = I and det R = +1; a reflection (det = −1) is not a rotation and has no quaternion") },
// E-GFQ — FiniteFieldError (linear algebra over GF(q), q = p^k)
//
// 001/002 guard the field itself. A composite modulus gives a ring with zero
// divisors, where Gaussian elimination can meet a pivot that is non-zero and
// still not invertible — every "rank" reported over it is a guess, so the
// field is refused rather than the answer caveated.
ErrorSpec { code: "E-GFQ-001", class: "FiniteFieldError", cause: Cause::UserInput, remediation: Some("GF(q) needs a prime characteristic: pass a prime p, or factor the modulus and work over each prime factor separately. Z/nZ for composite n is a ring with zero divisors and has no well-defined rank or nullspace") },
ErrorSpec { code: "E-GFQ-002", class: "FiniteFieldError", cause: Cause::Unsupported, remediation: Some("the nmod/fq_nmod backend is word-sized: use a prime below 2^64. A multi-precision prime field would need FLINT's fmpz_mod_mat, which is not wrapped") },
ErrorSpec { code: "E-GFQ-003", class: "FiniteFieldError", cause: Cause::UserInput, remediation: Some("the extension degree must lie in 1..=64; degree 1 is the prime field itself") },
ErrorSpec { code: "E-GFQ-004", class: "FiniteFieldError", cause: Cause::UserInput, remediation: Some("supply a polynomial of degree >= 1 that is irreducible over GF(p); coefficients ascend, so x^3+x+1 over GF(2) is [1, 1, 0, 1]. A reducible modulus would make the quotient a ring with zero divisors") },
ErrorSpec { code: "E-GFQ-005", class: "FiniteFieldError", cause: Cause::UserInput, remediation: Some("an element of GF(p^k) has at most k coefficients over the prime subfield; reduce it modulo the defining polynomial at the call site if that is what was meant") },
ErrorSpec { code: "E-GFQ-006", class: "FiniteFieldError", cause: Cause::UserInput, remediation: Some("rebuild both operands over the same FiniteField; two fields of the same order but different defining polynomials are isomorphic, not interchangeable — an element's coordinates mean different things in each") },
ErrorSpec { code: "E-GFQ-007", class: "FiniteFieldError", cause: Cause::UserInput, remediation: Some("check the shapes: A*B needs A.ncols == B.nrows, add and sub need identical shapes, and solve needs A.nrows == B.nrows") },
ErrorSpec { code: "E-GFQ-008", class: "FiniteFieldError", cause: Cause::UserInput, remediation: Some("determinant, charpoly and inverse are defined only for square matrices; for a rectangular one use rank, rref or nullspace") },
ErrorSpec { code: "E-GFQ-009", class: "FiniteFieldError", cause: Cause::Domain, remediation: Some("the matrix is singular over this field: use nullspace to see the kernel, or solve, which returns a particular solution whenever one exists") },
ErrorSpec { code: "E-GFQ-010", class: "FiniteFieldError", cause: Cause::Domain, remediation: Some("the right-hand side is outside the column space, so no solution exists; check that rank([A|b]) == rank(A)") },
ErrorSpec { code: "E-GFQ-011", class: "FiniteFieldError", cause: Cause::UserInput, remediation: Some("indices are zero-based and must satisfy i < nrows and j < ncols") },
ErrorSpec { code: "E-GFQ-012", class: "FiniteFieldError", cause: Cause::Resource, remediation: Some("the requested shape exceeds what the FLINT backend can allocate; a dense matrix over GF(q) costs at least one machine word per entry") },
// E-GRP — GroupError (permutation groups: orbits, Schreier–Sims, membership)
ErrorSpec { code: "E-GRP-001", class: "GroupError", cause: Cause::UserInput, remediation: Some("supply each of 0..n exactly once as an image; points are 0-based, so a cycle copied from GAP or the ATLAS needs `Permutation::from_cycles_one_based`") },
// Degrees are never widened by padding with fixed points: S_3 inside S_5 is a
// different subgroup of a different symmetric group from S_3 itself, and which
// one the caller meant is not recoverable from the arguments.
ErrorSpec { code: "E-GRP-002", class: "GroupError", cause: Cause::UserInput, remediation: Some("embed the smaller permutation with `Permutation::extend_degree(n)` before composing, or rebuild both at the common degree") },
ErrorSpec { code: "E-GRP-003", class: "GroupError", cause: Cause::UserInput, remediation: Some("points are 0-based: the valid range is 0..degree") },
// A group of order 10^20 has a perfectly computable order and no listable
// element set. Refusing the list keeps those two facts apart.
ErrorSpec { code: "E-GRP-004", class: "GroupError", cause: Cause::Resource, remediation: Some("use `order()`, `contains()` or `random_element()` instead of listing the elements, or raise the cap with `elements_with_cap` if the list really fits in memory") },
ErrorSpec { code: "E-GRP-005", class: "GroupError", cause: Cause::Resource, remediation: Some("orbits and orbit representatives are available at any degree; only the stabilizer chain (and therefore `order`, `contains` and `elements`) is capped") },
// Below its stated minimum, a standard family's natural degree-n action is not
// faithful, so the group returned would not be the group the name promises.
ErrorSpec { code: "E-GRP-006", class: "GroupError", cause: Cause::Unsupported, remediation: Some("these constructors build the natural degree-n action; below the stated minimum that action is not faithful, so build the group by hand on the point set you actually mean") },
// E-FFLD — FunctionFieldError (divisors, Pic⁰ and Riemann–Roch on curves)
//
// The whole point of this prefix is the boundary it marks. The implemented
// class is the *imaginary* hyperelliptic model `y² = a(x)` with `a`
// squarefree of odd degree and ℚ-rational places, because that is exactly
// what the Mumford/Cantor machinery in `integrate::algebraic` — which this
// layer reuses rather than reimplements — is scoped to. Everything outside
// it is a typed refusal, never a guess.
//
// Three of these are *not* refusals and must be kept apart by any caller
// branching on the code: E-FFLD-007 is a **verdict** (the class has
// infinite order, and no wider search will change that), E-FFLD-006 is the
// matching **undecided** (neither torsion nor non-torsion was established),
// and E-FFLD-011 reports an answer that was computed and then **withheld**
// because it failed its own consistency check.
ErrorSpec { code: "E-FFLD-001", class: "FunctionFieldError", cause: Cause::Unsupported, remediation: Some("supply c₂·y² + c₁(x)·y + c₀(x) with c₂ a non-zero rational constant, whose discriminant c₁² − 4c₂c₀ is non-constant; higher-degree plane curves are not modelled") },
ErrorSpec { code: "E-FFLD-002", class: "FunctionFieldError", cause: Cause::Unsupported, remediation: Some("move the model to odd degree by sending a rational root of a(x) to infinity, or use genus() alone, which is model-independent") },
ErrorSpec { code: "E-FFLD-003", class: "FunctionFieldError", cause: Cause::Unsupported, remediation: Some("restrict to divisors supported on rational places, or work with the divisor class (Mumford form), which represents conjugate places implicitly") },
ErrorSpec { code: "E-FFLD-004", class: "FunctionFieldError", cause: Cause::UserInput, remediation: Some("check the sign and the model: the place must satisfy y² = a(x) in the *normalised* coordinates reported by FunctionField::curve()") },
ErrorSpec { code: "E-FFLD-005", class: "FunctionFieldError", cause: Cause::UserInput, remediation: Some("replace D by D − deg(D)·∞ before asking for its class") },
ErrorSpec { code: "E-FFLD-006", class: "FunctionFieldError", cause: Cause::Unsupported, remediation: Some("record the result as undecided — never as non-torsion; a larger prime search or a different model may settle it") },
ErrorSpec { code: "E-FFLD-007", class: "FunctionFieldError", cause: Cause::Domain, remediation: Some("this is a verdict: no multiple of the divisor is principal, so stop looking for one") },
ErrorSpec { code: "E-FFLD-008", class: "FunctionFieldError", cause: Cause::UserInput, remediation: Some("pass a non-zero function; div(0) is not defined") },
ErrorSpec { code: "E-FFLD-009", class: "FunctionFieldError", cause: Cause::UserInput, remediation: Some("rebuild both operands over the same FunctionField") },
ErrorSpec { code: "E-FFLD-010", class: "FunctionFieldError", cause: Cause::Resource, remediation: Some("reduce the multiplicities or the degree of the divisor; the Cantor and linear-algebra steps here are bounded by a machine word") },
ErrorSpec { code: "E-FFLD-011", class: "FunctionFieldError", cause: Cause::Internal, remediation: Some("report this as a bug with the curve and divisor that produced it; the answer was withheld rather than returned wrong") },
// E-STAB — StabilizerError (binary symplectic form, Pauli and stabilizer
// codes, CSS codes, and the classical matrix groups over GF(q))
//
// The prefix exists to keep three different kinds of "no" apart. E-STAB-004,
// -005 and -006 are *structural*: the generators offered do not define a
// stabilizer group at all, and no amount of extra work will make them.
// E-STAB-007 and -008 are about the *question*: a [[n, 0]] code has no
// minimum distance to report, and above the search cap the distance is not
// unknown-for-now but unreached — the answer on offer instead is a
// `Distance::UpperBound`, a different variant, so a caller cannot store a
// bound in a field it reads as a distance. E-STAB-013 is the one that must
// never fire: a result computed, failed against its own invariant, and
// withheld.
//
// Note that `StabilizerError::FiniteField` does *not* have a code here. It
// carries a `FiniteFieldError` out of the GF(q) layer unchanged and
// delegates `.code()` to it, so a shape or singularity refusal still
// reports its own `E-GFQ-NNN` rather than being relabelled at the boundary.
ErrorSpec { code: "E-STAB-001", class: "StabilizerError", cause: Cause::Unsupported, remediation: Some("the Pauli and stabilizer surface is GF(2) only; qudit stabilizer codes are a different theory, not a widening of this one. Only MatrixGroup is defined over general GF(q)") },
ErrorSpec { code: "E-STAB-002", class: "StabilizerError", cause: Cause::UserInput, remediation: Some("symplectic vectors have length 2n in (x | z) layout, and Sp membership needs a square matrix of even size; check which half of the vector you filled") },
ErrorSpec { code: "E-STAB-003", class: "StabilizerError", cause: Cause::UserInput, remediation: Some("qubit counts are not padded with identities automatically; rebuild both operators on the same number of qubits") },
ErrorSpec { code: "E-STAB-004", class: "StabilizerError", cause: Cause::Domain, remediation: Some("a stabilizer group is abelian: the two named generators anticommute, so they share no +1 eigenspace. Check the (x | z) layout first — a (z | x) transcription commutes for a different code") },
ErrorSpec { code: "E-STAB-005", class: "StabilizerError", cause: Cause::Domain, remediation: Some("the CSS condition H_X · H_Zᵀ = 0 fails at the named entry; the X-type generator from that row of H_X anticommutes with the Z-type one from that row of H_Z") },
ErrorSpec { code: "E-STAB-006", class: "StabilizerError", cause: Cause::Domain, remediation: Some("the named generators multiply to −I, so the stabilized subspace is {0}; negate one of them, or drop the dependent generator") },
ErrorSpec { code: "E-STAB-007", class: "StabilizerError", cause: Cause::Domain, remediation: Some("an [[n, 0]] code has no logical qubits, so N(S) \\ S is empty and there is no minimum distance to report") },
ErrorSpec { code: "E-STAB-008", class: "StabilizerError", cause: Cause::Resource, remediation: Some("minimum distance is NP-hard and the only algorithm here is exhaustive; raise the cap with minimum_distance_with_cap if 2^(n+k) really is affordable, or take distance_upper_bound() and report it as a bound") },
ErrorSpec { code: "E-STAB-009", class: "StabilizerError", cause: Cause::Resource, remediation: Some("reduce the qubit count; every symplectic vector here is dense and the centralizer computation is cubic in n") },
ErrorSpec { code: "E-STAB-010", class: "StabilizerError", cause: Cause::UserInput, remediation: Some("Pauli bits must be 0 or 1, letters must be I, X, Y or Z, and a stabilizer generator must be Hermitian — parse a string such as \"-XZZXI\" or use PauliOperator::hermitian, which picks the phase for you") },
ErrorSpec { code: "E-STAB-011", class: "StabilizerError", cause: Cause::Resource, remediation: Some("use order(), which is a closed-form product and has no size limit, instead of listing the elements; enumeration scans all q^(d²) matrices") },
ErrorSpec { code: "E-STAB-012", class: "StabilizerError", cause: Cause::UserInput, remediation: Some("rebuild both operands over the same FiniteField; two GF(q) of the same order with different defining polynomials are not interchangeable") },
ErrorSpec { code: "E-STAB-013", class: "StabilizerError", cause: Cause::Internal, remediation: Some("report this as a bug with the generators or check matrices that produced it; the answer was withheld rather than returned wrong") },
// E-CODE — CodingError
//
// Classical linear codes, weight enumerators and the Delsarte linear
// programme. Two of these guard the only two ways this subsystem could
// return a confident wrong answer. E-CODE-004 refuses a truncated
// codeword enumeration, because the minimum weight of *some* of the
// codewords is an upper bound on d wearing d's name. E-CODE-007 withholds
// an LP bound whose dual certificate did not pass its own exact
// feasibility check — an upper bound that is too small is worse than no
// bound, since it "rules out" codes that exist.
ErrorSpec { code: "E-CODE-001", class: "CodingError", cause: Cause::UserInput, remediation: Some("give the code a positive length; a generator or parity-check matrix needs at least one column") },
ErrorSpec { code: "E-CODE-002", class: "CodingError", cause: Cause::UserInput, remediation: Some("pass a minimum distance in 1..=n; d = 0 bounds nothing and d > n describes an empty code") },
ErrorSpec { code: "E-CODE-003", class: "CodingError", cause: Cause::Domain, remediation: Some("read the embedded E-GFQ code: the finite-field backend refused the elimination, usually for a field mismatch or a shape that does not conform") },
ErrorSpec { code: "E-CODE-004", class: "CodingError", cause: Cause::Resource, remediation: Some("reduce k, or accept that q^k codewords cannot be enumerated; there is no partial answer, because a truncated search reports an upper bound on d as if it were d") },
ErrorSpec { code: "E-CODE-005", class: "CodingError", cause: Cause::Resource, remediation: Some("reduce n below MAX_LP_LENGTH; the Delsarte programme is dense and exact, so its cost is superlinear in the length") },
ErrorSpec { code: "E-CODE-006", class: "CodingError", cause: Cause::UserInput, remediation: Some("check the weight distribution: a linear code has A_0 = 1, no negative multiplicity, and a MacWilliams sum divisible by |C| at every index") },
ErrorSpec { code: "E-CODE-007", class: "CodingError", cause: Cause::Internal, remediation: Some("report this as a bug with n, d and q; the uncertified bound was withheld rather than returned, because an upper bound that is too small rules out codes that exist") },
ErrorSpec { code: "E-CODE-008", class: "CodingError", cause: Cause::UserInput, remediation: Some("use an alphabet size of at least 2; for the enumeration paths the field must also be small enough to list element by element") },
// E-NUMF — NumberFieldError (algebraic number fields Q[x]/(f) on FLINT's
// `nf`/`nf_elem`, and the cyclotomic fields Q(zeta_n))
//
// E-NUMF-003 is the one this module exists for. Q[x]/(f) is a field only
// when f is irreducible; for a reducible f it is a ring with zero divisors,
// in which `inverse` has no answer for some non-zero elements and `norm`
// stops being multiplicative. The polynomial is checked, never assumed.
ErrorSpec { code: "E-NUMF-001", class: "NumberFieldError", cause: Cause::UserInput, remediation: Some("supply at least one non-zero coefficient; coefficients ascend in degree, so x^2 - 2 is [-2, 0, 1]") },
ErrorSpec { code: "E-NUMF-002", class: "NumberFieldError", cause: Cause::UserInput, remediation: Some("the defining polynomial must have degree in 1..=1024; degree 0 would make Q[x]/(f) the zero ring rather than a field, and degree 1 is Q itself") },
ErrorSpec { code: "E-NUMF-003", class: "NumberFieldError", cause: Cause::UserInput, remediation: Some("pass an irreducible polynomial — the error names a proper factor. Factor f and build one field per irreducible factor: Q[x]/(f) for reducible f has zero divisors, so inverses and norms there would be wrong rather than merely unavailable") },
ErrorSpec { code: "E-NUMF-004", class: "NumberFieldError", cause: Cause::UserInput, remediation: Some("write each coefficient as a decimal integer or a fraction \"p/q\", without spaces") },
ErrorSpec { code: "E-NUMF-005", class: "NumberFieldError", cause: Cause::Domain, remediation: Some("zero has no inverse; test with is_zero() before dividing. In a genuine number field zero is the only non-invertible element, which is why the defining polynomial is checked") },
ErrorSpec { code: "E-NUMF-006", class: "NumberFieldError", cause: Cause::UserInput, remediation: Some("rebuild both operands over one NumberField; two fields with different canonical defining polynomials are treated as different even when isomorphic, because an element's coordinates mean different things in each") },
ErrorSpec { code: "E-NUMF-007", class: "NumberFieldError", cause: Cause::UserInput, remediation: Some("an element of a degree-d field has at most d coordinates in the power basis 1, a, a^2, ...; reduce modulo the defining polynomial at the call site if that is what was meant") },
ErrorSpec { code: "E-NUMF-008", class: "NumberFieldError", cause: Cause::UserInput, remediation: Some("Q(zeta_n) needs n >= 1 with phi(n) <= 1024; note phi(n) is the degree, so n = 2048 is allowed (degree 1024) while n = 3^7 is not") },
// E-THETA — ThetaError (Riemann theta, modular and Weierstrass functions)
//
// This prefix guards a surface whose failure mode is uniquely hard to spot
// downstream: a theta value is a number nobody can sanity-check by eye, so
// a wrong *error bound* would travel further than a wrong value. Three of
// these are therefore about the bound rather than about the input.
//
// E-THETA-010 is the load-bearing one. It fires when a value was computed
// and then **withheld** because its enclosure was too wide to be worth
// reading. It is reachable in two quite different situations and the
// `achieved_bits` field is what tells them apart: `Some(n)` with `n` far
// below the request means more precision may help, while a value that is
// exactly zero — `j(rho)`, `theta_1(0, tau)` — has no relative accuracy at
// any precision and will refuse forever. That is not a defect: relative
// accuracy of zero is not a thing, and `Precision::Bits` plus
// `ComplexBall::contains_zero` is the right question to ask there.
//
// E-THETA-002 exists because `arb_struct` and `acb_struct` cross the FFI
// boundary by value inside arrays, so a size disagreement with the
// installed FLINT is silent memory corruption rather than a link error. It
// is raised by a run-time probe that re-derives the sizes from FLINT itself.
ErrorSpec { code: "E-THETA-001", class: "ThetaError", cause: Cause::Unsupported, remediation: Some("build against FLINT >= 3.1 for the modular functions and >= 3.4 for genus-g Riemann theta; `riemann_theta_available()` and `arb_backend_available()` report what this build has") },
ErrorSpec { code: "E-THETA-002", class: "ThetaError", cause: Cause::Internal, remediation: Some("the installed FLINT's ball struct layout differs from the one this binary was compiled against; rebuild alkahest against the FLINT it will run with, and report the measured sizes as a bug") },
ErrorSpec { code: "E-THETA-003", class: "ThetaError", cause: Cause::UserInput, remediation: Some("ask for a working precision between 2 and 1048576 bits") },
ErrorSpec { code: "E-THETA-004", class: "ThetaError", cause: Cause::Resource, remediation: Some("theta returns 4^g values, so the output alone is exponential in the genus; genus 1 and 2 are the tested regime") },
ErrorSpec { code: "E-THETA-005", class: "ThetaError", cause: Cause::UserInput, remediation: Some("the argument z needs exactly g entries and the period matrix g*g (or g(g+1)/2 for the upper triangle)") },
ErrorSpec { code: "E-THETA-006", class: "ThetaError", cause: Cause::UserInput, remediation: Some("build the period matrix with SiegelMatrix::from_upper_triangle, which fills the lower half by copying; symmetry is checked as ball identity, so two separately-computed enclosures of one number are two different inputs") },
ErrorSpec { code: "E-THETA-007", class: "ThetaError", cause: Cause::Domain, remediation: Some("this means `not proved`, never `proved false`: raise the working precision, or tighten the input balls until Im(tau) is certainly positive definite") },
ErrorSpec { code: "E-THETA-008", class: "ThetaError", cause: Cause::UserInput, remediation: Some("characteristics run over 0..4^g; build the index with theta_characteristic_index rather than by hand") },
ErrorSpec { code: "E-THETA-009", class: "ThetaError", cause: Cause::Domain, remediation: Some("the modular functions need Im(tau) certainly > 0; as with E-THETA-007 a refusal means `not proved`") },
ErrorSpec { code: "E-THETA-010", class: "ThetaError", cause: Cause::Resource, remediation: Some("read `achieved_bits`: a value far below the request may come good at higher precision, but a quantity that is exactly zero has no relative accuracy at any precision — use Precision::Bits and ComplexBall::contains_zero there") },
ErrorSpec { code: "E-THETA-011", class: "ThetaError", cause: Cause::Domain, remediation: Some("the result could not be bounded at all; check for a pole (Weierstrass p at a lattice point) or an input ball that was already indeterminate") },
ErrorSpec { code: "E-THETA-012", class: "ThetaError", cause: Cause::Resource, remediation: Some("the midpoint or radius has an exponent outside the range that moves between FLINT and MPFR without rounding; rescale the problem") },
// E-MATGRP — MatGroupError (matrix groups over GF(q) from generators).
//
// This is a `#[non_exhaustive]` enum that *wraps* FiniteFieldError and
// GroupError through delegating variants, so `E-GFQ-*` and `E-GRP-*` reach
// a caller of `matgroup` with their own codes. No code below is a duplicate
// of one of those.
//
// E-MATGRP-007 is the one to read twice. Schreier-Sims that runs out of
// budget must *refuse*, because a chain missing a level reports the product
// of the orbits it did build: a proper divisor of |G|, indistinguishable
// from a correct answer for a smaller group. Returning a partial chain is
// the exact confident-wrong-answer shape this registry exists to prevent.
// The same reasoning makes E-MATGRP-006 a refusal rather than a truncated
// orbit.
ErrorSpec { code: "E-MATGRP-001", class: "MatGroupError", cause: Cause::UserInput, remediation: Some("group elements are d x d matrices; pass a square matrix") },
ErrorSpec { code: "E-MATGRP-002", class: "MatGroupError", cause: Cause::UserInput, remediation: Some("build the matrix at the group's degree; vectors are 1 x d row vectors, because the action is v -> v*M") },
ErrorSpec { code: "E-MATGRP-003", class: "MatGroupError", cause: Cause::UserInput, remediation: Some("use the same FiniteField object the group was built with; two isomorphic fields with different defining polynomials are different fields here") },
ErrorSpec { code: "E-MATGRP-004", class: "MatGroupError", cause: Cause::Domain, remediation: Some("check the determinant of every generator; a singular matrix generates no group") },
ErrorSpec { code: "E-MATGRP-005", class: "MatGroupError", cause: Cause::Resource, remediation: Some("these algorithms store explicit transversals and are built for small degrees; large-degree matrix groups need constructive recognition, which is not implemented") },
ErrorSpec { code: "E-MATGRP-006", class: "MatGroupError", cause: Cause::Resource, remediation: Some("an orbit on vectors is bounded by q^d - 1, so this caps q^d and not |G|; use the projective action, whose orbits are (q-1) times smaller") },
ErrorSpec { code: "E-MATGRP-007", class: "MatGroupError", cause: Cause::Resource, remediation: Some("raise the budget with MatGroup::with_budget if the group really is this large; no order is reported, because an incomplete chain reports a proper divisor of the true one") },
ErrorSpec { code: "E-MATGRP-008", class: "MatGroupError", cause: Cause::Resource, remediation: Some("use order(), contains() or random_element() instead of listing the elements, or raise the cap with elements_with_cap") },
ErrorSpec { code: "E-MATGRP-009", class: "MatGroupError", cause: Cause::Resource, remediation: Some("order, membership and the orbit of a given vector never enumerate GF(q); the projective point list, the classical constructors and the centre do") },
ErrorSpec { code: "E-MATGRP-010", class: "MatGroupError", cause: Cause::Unsupported, remediation: Some("build the group from explicit generators with MatGroup::new; the constructors here cover GL, SL, Sp and Singer cycles only") },
ErrorSpec { code: "E-MATGRP-011", class: "MatGroupError", cause: Cause::Resource, remediation: Some("a large centralizing algebra means a very reducible module; split it first, or take the centre of a smaller group") },
ErrorSpec { code: "E-MATGRP-012", class: "MatGroupError", cause: Cause::Domain, remediation: Some("pass a non-zero row vector; the zero vector spans no line and is fixed by every matrix") },
ErrorSpec { code: "E-MATGRP-013", class: "MatGroupError", cause: Cause::Internal, remediation: Some("this is a bug: please report it with the generators that produced it") },
// E-FPGRP — FpGroupError (finitely presented groups, Todd-Coxeter,
// Reidemeister-Schreier, low-degree group cohomology).
//
// 004 and 005 are the two codes this whole subsystem is arranged around, and
// they must never be collapsed. 004 says coset enumeration hit its cap — the
// word problem is undecidable, so it is *not* a claim that the group is
// infinite, nor that it is finite. 005 says the group **is** infinite,
// because its abelianisation has an infinite cyclic factor, which is a
// terminating Smith normal form. An agent told it can branch on a stable code
// would, if these shared one, read "I could not decide" as "no such order
// exists". The (2,3,7) triangle group is infinite with a trivial
// abelianisation and lands on 004.
ErrorSpec { code: "E-FPGRP-001", class: "FpGroupError", cause: Cause::UserInput, remediation: Some("word letters are signed and 1-based: +k is the k-th generator, -k its inverse, and 0 is not a letter; build words with FreeGroup::word or FreeGroup::parse, which check against the rank") },
ErrorSpec { code: "E-FPGRP-002", class: "FpGroupError", cause: Cause::UserInput, remediation: Some("generator names must be non-empty, pairwise distinct, free of the reserved characters * ^ ( ) + - and not start with a digit; the rank is capped at MAX_FREE_RANK") },
ErrorSpec { code: "E-FPGRP-003", class: "FpGroupError", cause: Cause::UserInput, remediation: Some("the word syntax is a product of powers of generator names and parenthesised sub-words, e.g. a^2, (a*b)^5, a*b^-1*a^-1*b; 1 is the identity") },
ErrorSpec { code: "E-FPGRP-004", class: "FpGroupError", cause: Cause::Resource, remediation: Some("raise max_cosets if the group may simply be large, or call abelian_invariants(), which always terminates, to look for an infinite cyclic factor; this refusal is NOT evidence either way about whether the group is finite") },
ErrorSpec { code: "E-FPGRP-005", class: "FpGroupError", cause: Cause::Domain, remediation: Some("the group is proved infinite, so no order exists: ask for abelian_invariants() instead of order(), or pass to a finite-index subgroup or a finite quotient") },
ErrorSpec { code: "E-FPGRP-006", class: "FpGroupError", cause: Cause::Resource, remediation: Some("lower the coset cap or reduce the number of generators; the table costs 2 * rank machine words per coset") },
ErrorSpec { code: "E-FPGRP-007", class: "FpGroupError", cause: Cause::UserInput, remediation: Some("cosets are 0-based and coset 0 is the subgroup H itself; the valid range is 0..index()") },
ErrorSpec { code: "E-FPGRP-008", class: "FpGroupError", cause: Cause::Unsupported, remediation: Some("only H^0, H^1 and H^2 are implemented; H^2 is the one that classifies extensions, and higher degrees need a resolution this module does not build") },
ErrorSpec { code: "E-FPGRP-009", class: "FpGroupError", cause: Cause::Resource, remediation: Some("use a smaller group or module: the degree-(d+1) cochain group has rank(M) * |G|^(d+1) generators, so H^2 is cubic in |G| and is capped at MAX_COCHAIN_DIMENSION") },
ErrorSpec { code: "E-FPGRP-010", class: "FpGroupError", cause: Cause::UserInput, remediation: Some("module invariants are non-negative (0 for a Z summand, d >= 1 for Z/d), and there must be one square rank-by-rank action matrix per group generator") },
ErrorSpec { code: "E-FPGRP-011", class: "FpGroupError", cause: Cause::Domain, remediation: Some("check that every relator's matrix product is the identity on M and that each generator's matrix maps the module's relation lattice into itself; GModule::trivial cannot fail either check") },
ErrorSpec { code: "E-FPGRP-012", class: "FpGroupError", cause: Cause::Internal, remediation: Some("this is a bug in the fp-group subsystem: report it with the presentation that produced it") },
ErrorSpec { code: "E-FPGRP-013", class: "FpGroupError", cause: Cause::Resource, remediation: Some("exponents nest, so the cap is on the length of the resulting word and not on any one exponent; reduce the exponent or the length of the base word") },
ErrorSpec { code: "E-FPGRP-014", class: "FpGroupError", cause: Cause::Resource, remediation: Some("the table is |G|^2 words and |G|^2 transversal traces; use permutation_group(), whose degree is |G| rather than |G|^2, or take a smaller quotient") },
// E-CHAR — CharacterError (conjugacy classes and character tables)
//
// This prefix guards a surface where a wrong answer is unusually hard to
// notice: a character table is a small grid of algebraic numbers that looks
// plausible whatever it says, and downstream code branches on it rather
// than eyeballing it. So the load-bearing code here is E-CHAR-007, which is
// a table that was **computed, failed its own orthogonality check, and
// withheld** — not an input error at all. The orthogonality relations are
// implied by the group axioms, so a violation is a defect in alkahest, and
// returning the table with a caveat is not an option: a caller cannot tell
// a checked table from an unchecked one once it is in their hands.
//
// E-CHAR-001 and E-CHAR-003 are the two ceilings, and they are ceilings on
// different things. 001 is on |G|, because conjugacy classes here are the
// orbits of G acting on itself and every element is held in memory. 003 is
// on phi(exp G), the degree of the one cyclotomic field every value lives
// in — a limit on the arithmetic rather than on the group. The two are
// independent: a group of small exponent passes 003 at any order and meets
// 001 instead, while the cyclic group of order 1000 is refused by 003
// despite being small.
//
// A failure raised inside the permutation-group layer, the cyclotomic
// field or the GF(p) matrix layer keeps its own E-GRP / E-NUMF / E-GFQ code
// rather than being relabelled: `CharacterError`'s three delegating
// variants forward `code()` and `remediation()` unchanged.
ErrorSpec { code: "E-CHAR-001", class: "CharacterError", cause: Cause::Resource, remediation: Some("conjugacy classes here are found by conjugating every element, so the whole group is held in memory; use `order()` and the stabilizer chain instead, or raise the cap with `ConjugacyClasses::of_with_cap` if |G| images arrays really fit. There is no partial class partition on offer — half the classes of a group is a wrong answer, not a coarse one") },
ErrorSpec { code: "E-CHAR-002", class: "CharacterError", cause: Cause::Resource, remediation: Some("the hard ceiling bounds memory, not patience: work with a smaller group, or with the subgroup or quotient whose table is actually wanted") },
ErrorSpec { code: "E-CHAR-003", class: "CharacterError", cause: Cause::Resource, remediation: Some("every value lives in Q(zeta_e) for e = exp G, and the orthogonality checks multiply there O(r^3) times, so the cap is on phi(exp G) and not on |G| at all; a group of small exponent passes this ceiling at any order and meets the |G| cap instead, while a cyclic group of order 1000 (phi = 400) is refused here despite being small") },
ErrorSpec { code: "E-CHAR-004", class: "CharacterError", cause: Cause::Internal, remediation: Some("Dirichlet guarantees a prime p = 1 mod exp G above |G| exists, so this is a search-bound miss rather than a mathematical obstruction; report the group as a bug") },
ErrorSpec { code: "E-CHAR-005", class: "CharacterError", cause: Cause::UserInput, remediation: Some("test with `PermutationGroup::contains` first; points are 0-based here and composition is left-to-right, so a generator transcribed from GAP or the ATLAS needs `Permutation::from_cycles_one_based`") },
ErrorSpec { code: "E-CHAR-006", class: "CharacterError", cause: Cause::UserInput, remediation: Some("class and character indices run over 0..len(), with class 0 always the identity class and row 0 always the trivial character") },
ErrorSpec { code: "E-CHAR-007", class: "CharacterError", cause: Cause::Internal, remediation: Some("this is a defect in alkahest, not in the input: row and column orthogonality, sum of squared degrees = |G|, and the eigenvalue-multiplicity checksums are all implied by the group axioms, so a violation means the table is wrong and it is withheld rather than returned. Report the generators as a minimal failing example") },
ErrorSpec { code: "E-CHAR-008", class: "CharacterError", cause: Cause::Internal, remediation: Some("with p prime to |G| and p = 1 mod exp G the class algebra is split semisimple over GF(p), so the class multiplication matrices must split the space into one line per class; report the generators as a minimal failing example") },
ErrorSpec { code: "E-CHAR-009", class: "CharacterError", cause: Cause::Internal, remediation: Some("report the generators as a minimal failing example") },
];
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashSet;
#[test]
fn no_duplicate_codes() {
let mut seen = HashSet::new();
for spec in REGISTRY {
assert!(
seen.insert(spec.code),
"duplicate error code in REGISTRY: {}",
spec.code
);
}
}
#[test]
fn codes_ascending_within_prefix() {
let mut by_prefix: std::collections::BTreeMap<&str, Vec<u32>> =
std::collections::BTreeMap::new();
for spec in REGISTRY {
if let Some(pos) = spec.code.rfind('-') {
let prefix = &spec.code[..pos];
if let Ok(num) = spec.code[pos + 1..].parse::<u32>() {
by_prefix.entry(prefix).or_default().push(num);
}
}
}
for (prefix, nums) in &by_prefix {
let mut sorted = nums.clone();
sorted.sort_unstable();
assert_eq!(
nums, &sorted,
"codes under prefix {prefix} are not in ascending order"
);
}
}
}