Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
285 changes: 275 additions & 10 deletions CHANGELOG.md

Large diffs are not rendered by default.

398 changes: 380 additions & 18 deletions alkahest-core/src/ball/mod.rs

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions alkahest-core/src/errors/codes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,19 @@ pub const REGISTRY: &[ErrorSpec] = &[
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-SMT — SmtError (P2 item 3: SMT/SAT bridge).
//
// Only the code Rust actually raises is registered here. The rest of the
Expand Down
1,976 changes: 1,976 additions & 0 deletions alkahest-core/src/holonomic/asymptotics.rs

Large diffs are not rendered by default.

68 changes: 68 additions & 0 deletions alkahest-core/src/holonomic/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,22 @@
//! [`boundary::BoundaryStatus::Nonzero`] (the inhomogeneous one does, with the
//! boundary term explicit) or [`boundary::BoundaryStatus::Unknown`] (nothing
//! may be claimed about the sum).
//! - [`modular`] — evaluation of a P-recursive sequence *modulo `p^k`* directly
//! from its recurrence, plus `binomial(a, b) mod p^k`. This is the evidence
//! half of supercongruence work: reduce first and iterate in `ℤ/p^K`, rather
//! than computing `S(N)` over `ℤ` and reducing a number with `Θ(N)` digits.
//! Indices where the leading coefficient is not a unit mod `p` are handled by
//! lifting to a higher working precision — never by dividing anyway.
//! - [`asymptotics`] — the natural *second* question after a certified
//! recurrence: how fast does the sequence grow? Poincaré–Perron reads the
//! growth rate `ρ` and the polynomial exponent `α` in `u(n) ~ C·ρⁿ·n^α`
//! straight off the coefficient polynomials. The connection constant `C` does
//! **not** follow from them — it depends on the initial conditions — so it is
//! fitted from the terms and reported separately as
//! [`asymptotics::ConnectionConstant`], never mixed in with the derived half.
//! Equal-modulus roots, a repeated dominant root, a degenerate leading
//! coefficient and an eventually-zero sequence each get their own
//! [`asymptotics::PerronVerdict`] rather than a confident wrong number.
//!
//! Every certificate this module returns is checked as an *exact* identity
//! in `Q(n)(k)` before it is handed back to the caller — see
Expand All @@ -38,14 +54,26 @@
//! `alkahest.guess_holonomic` on the Python side, where the only mathematical
//! step is an exact nullspace the kernel already provides.

pub mod asymptotics;
pub mod boundary;
pub mod hyperterm;
pub mod modular;
pub mod qfield;
pub mod qzeil;
pub mod zeilberger;

pub use asymptotics::{
asymptotics_from_recurrence, CharacteristicAnalysis, CharacteristicRoot, ConnectionConstant,
PerronVerdict, RecurrenceAsymptotics,
};
pub use boundary::{boundary_status, natural_limits, BoundaryStatus};
pub use hyperterm::{GammaFactor, ProperTerm};
pub use modular::{binomial_mod, ModularEvaluation, ModularRecurrence};
pub use qfield::{PolyK, RatK, Rn};
pub use qzeil::{
q_boundary_status, q_zeilberger, QBoundaryStatus, QCertificate, QHolonomicError, QProperTerm,
QZeilbergerOpts, QZeilbergerReport, QZeilbergerResult,
};
pub use zeilberger::{
boundary_side_condition, boundary_term, zeilberger, zeilberger_search, OrderSearch,
ZeilbergerOpts, ZeilbergerResult, ZeilbergerSearchReport,
Expand All @@ -71,6 +99,20 @@ pub enum HolonomicError {
CertificateVerificationFailed(String),
/// Malformed call (e.g. `n` and `k` not distinct, non-positive bounds).
InvalidInput(String),
/// The modulus is not a prime power this subsystem can work over: the base
/// is composite, the exponent is zero, or `p^k` is past the machine-word
/// backend's ceiling. See [`modular`].
ModulusUnsupported(String),
/// A step of the recurrence does not determine the next term as a `p`-adic
/// integer: the leading coefficient vanishes identically at that index, or
/// the numerator's `p`-adic valuation is below the leading coefficient's.
/// See [`modular`] for how singular indices are handled when they *are*
/// determined.
PAdicallyUndetermined(String),
/// The computation is well posed but past a resource budget — a working
/// precision the machine-word modulus cannot hold, or a `binomial_mod`
/// whose cost is dominated by a pass over `1 … p−1`.
WorkLimitExceeded(String),
}

impl fmt::Display for HolonomicError {
Expand All @@ -88,6 +130,15 @@ impl fmt::Display for HolonomicError {
HolonomicError::InvalidInput(s) => {
write!(f, "holonomic: invalid input: {s}")
}
HolonomicError::ModulusUnsupported(s) => {
write!(f, "holonomic: unsupported modulus: {s}")
}
HolonomicError::PAdicallyUndetermined(s) => {
write!(f, "holonomic: not determined p-adically: {s}")
}
HolonomicError::WorkLimitExceeded(s) => {
write!(f, "holonomic: work limit exceeded: {s}")
}
}
}
}
Expand All @@ -101,6 +152,9 @@ impl crate::errors::AlkahestError for HolonomicError {
HolonomicError::SearchExhausted(_) => "E-HOLO-002",
HolonomicError::CertificateVerificationFailed(_) => "E-HOLO-003",
HolonomicError::InvalidInput(_) => "E-HOLO-004",
HolonomicError::ModulusUnsupported(_) => "E-HOLO-006",
HolonomicError::PAdicallyUndetermined(_) => "E-HOLO-007",
HolonomicError::WorkLimitExceeded(_) => "E-HOLO-008",
}
}

Expand All @@ -121,6 +175,20 @@ impl crate::errors::AlkahestError for HolonomicError {
HolonomicError::InvalidInput(_) => {
"n and k must be distinct symbols; max_order and max_degree must be positive"
}
HolonomicError::ModulusUnsupported(_) => {
"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"
}
HolonomicError::PAdicallyUndetermined(_) => {
"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"
}
HolonomicError::WorkLimitExceeded(_) => {
"lower k, use a smaller prime, or ask for an index the recurrence reaches \
without crossing so many singular steps"
}
})
}
}
Loading
Loading