Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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.

20 changes: 18 additions & 2 deletions alkahest-core/src/diff/diff_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,22 @@ pub fn diff(expr: ExprId, var: ExprId, pool: &ExprPool) -> Result<DerivedExpr<Ex
// Core recursive differentiation (no simplification)
// ---------------------------------------------------------------------------

/// The primitive registry used for derivative dispatch.
///
/// A singleton built with `dispatch_registry`, for two reasons. It was
/// previously `default_registry()` called *inside* the recursive walk, so every
/// `Func` node rebuilt all 41 primitives — and `default_registry` additionally
/// probes each one's capability bundle, work this path never reads: it only
/// calls `diff_forward` and treats `None` as "unknown function". Adding a ball
/// kernel for `gamma` in 3.9.0 made that probe measurably more expensive and
/// surfaced as a ~14% regression on `test_series_sin_order12`, which
/// differentiates.
fn diff_registry() -> &'static crate::primitive::PrimitiveRegistry {
static REGISTRY: std::sync::OnceLock<crate::primitive::PrimitiveRegistry> =
std::sync::OnceLock::new();
REGISTRY.get_or_init(crate::primitive::PrimitiveRegistry::dispatch_registry)
}

#[inline]
fn diff_poly_try_univariate_fastpath(
expr: ExprId,
Expand Down Expand Up @@ -359,7 +375,7 @@ fn diff_raw(
}
other => {
// Fall back to PrimitiveRegistry for V1-12 primitives
let reg = crate::primitive::PrimitiveRegistry::default_registry();
let reg = diff_registry();
if let Some(d) = reg.diff_forward(other, &[f], var, pool) {
log.push(RewriteStep::simple("diff_primitive_registry", expr, d));
d
Expand All @@ -383,7 +399,7 @@ fn diff_raw(
let da = diff_raw(a, var, pool, memo)?;
log = log.merge(da.log);
}
let reg = crate::primitive::PrimitiveRegistry::default_registry();
let reg = diff_registry();
if let Some(d) = reg.diff_forward(&name, &args, var, pool) {
log.push(RewriteStep::simple("diff_primitive_registry", expr, d));
memo.insert(expr, d);
Expand Down
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
Loading
Loading