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
25 changes: 22 additions & 3 deletions libs/math/docs/polynomial_arithmetic.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ Separate contexts are required for concurrent calls when the backend owns mutabl
| `half_gcd_basecase_cutoff` | 30 | At or below this coefficient count, recursive half-GCD constructs its transformation iteratively; zero disables this base case. |
| `gcd_half_gcd_cutoff` | 0 | Use half-GCD when the smaller operand reaches this size; zero disables half-GCD. |
| `modular_composition_cached_power_limit` | maximum `size_t` | Cap the number of Brent-Kung baby powers; constructing a composition cache requires a positive limit. |
| `modular_composition_block_size` | 0 | Request a Brent-Kung block size for composition caches; zero selects the square-root heuristic. A positive request is capped by the cached-power limit and maximum outer coefficient count. |
| `distinct_degree_screening_limit` | 0 | Screen initial irreducible-factor degrees before the full Kaltofen-Shoup baby table; zero disables screening. |
| `modular_composition_prepared_byte_limit` | 0 | Bound the additional prepared Fp12 coefficient payload per composition cache; zero disables preparation. |
| `modular_composition_simd_lanes` | 8 | Select four or eight IFMA lanes when supported; zero selects scalar prepared dots. The byte budget still controls whether preparation is enabled. |
Expand Down Expand Up @@ -374,14 +375,27 @@ The faster `compose_mod` overload uses blocked Brent-Kung composition. For block
1, inner, ..., inner^(k - 1) mod B

and the giant step `inner^k mod B`. Each `Fi(inner)` is a linear combination of the baby powers, and the block values
are combined by Horner's rule in the giant step. The default `k = ceil(sqrt(L))`, where `L` is the maximum outer
coefficient count, balances precomputation against giant-step multiplications.
are combined by Horner's rule in the giant step. Let `L` be the positive maximum outer coefficient count and `C` the
positive `modular_composition_cached_power_limit`. With the default `modular_composition_block_size == 0`, the cache
selects `k = min(ceil(sqrt(L)), C)`. A positive request `r` instead selects `k = min(r, L, C)`.

Construct and reuse the precomputation when composing several outer polynomials with the same inner polynomial and
divisor. The one-off overload constructs it internally. `modular_composition_cached_power_limit` caps `k`, reducing
memory from cached powers at the cost of more giant-step multiplications. Zero is rejected by the Brent-Kung APIs;
it does not select Horner composition automatically. `compose_mod_reference` remains available explicitly.

Set `modular_composition_block_size` on the arithmetic options before constructing the context and its caches.
For example, requesting 246 requires both `L >= 246` and a cached-power limit of at least 246 to obtain that width.
The request changes the cache layout while preserving the declared maximum outer coefficient count: an outer
polynomial exceeding `L` is still rejected. The option applies to each newly constructed composition cache, including
those created inside Frobenius and recovery operations; it does not detect how often a particular cache will be used.

For a nonconstant modulus and an outer polynomial with `m` coefficients, blocked evaluation uses
`ceil(m / k) - 1` giant-step modular multiplications. Increasing `k` reduces that count but builds and retains more
baby powers and may require a larger prepared coefficient payload. Choose a block size using complete workloads
that include cache construction, the intended number of reuses, and peak memory. A wider cache can cost more than it
saves when reused only a few times. Its prepared payload remains subject to the separate per-cache byte budget below.

### Prepared coefficient combinations

Supported Fp12 coefficient types can additionally prepare the linear forms used by the block dot products. Set
Expand Down Expand Up @@ -494,7 +508,12 @@ coefficient-field order. These are rough bounds for the current implementation:
| One cached Frobenius map | `O(sqrt(n) * M(n) + n^2)` |
| Frobenius product or weighted sum of `t` terms | `O(log t)` compositions, composition-cache preparations, and modular products for `t > 1` |

The `n^2` term in Brent-Kung and Frobenius maps is the block-formation phase, including when prepared dots are enabled.
The composition and Frobenius bounds above assume automatic square-root blocking and a cached-power limit large
enough to permit it.
For comparable outer and modulus sizes, a chosen block size `k` instead requires `O(k * M(n))` to construct the
cached powers and `O(ceil(n / k) * M(n) + n^2)` per cached composition, with `O(k * n)` coefficients in the baby powers.
Prepared coefficient payloads and scratch require additional work and storage. The `n^2` term in Brent-Kung and
Frobenius maps is the block-formation phase, including when prepared dots are enabled.
Both FFT backends' `multiply_low` operations truncate their inputs but still transform their complete prefix product,
so they have the same rough asymptotic cost as multiplying those prefixes in full. They do not currently use truncated
transforms. The transform-only cost for the small-prime backend is not a complete product-cost estimate: conversion
Expand Down
25 changes: 22 additions & 3 deletions libs/math/docs/polynomial_factorization.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,12 +105,17 @@ Several independent options affect the work in this pipeline:
| `gcd_half_gcd_cutoff` | Recursive half-GCD during square-free decomposition, screening, and splitting | 0: disabled |
| `distinct_degree_screening_limit` | Early degree checks before the full baby table | 0: disabled |
| `modular_composition_cached_power_limit` | Baby powers retained by each Brent-Kung composition cache | Maximum `size_t`: permits the usual square-root block size |
| `modular_composition_block_size` | Explicit Brent-Kung block size, capped by the cached-power limit and maximum outer coefficient count | 0: automatic square-root heuristic |
| `modular_composition_prepared_byte_limit` | Additional prepared Fp12 coefficient combinations | 0: disabled |
| `modular_composition_simd_lanes` | Four- or eight-lane IFMA for eligible prepared combinations | 8; still requires a positive byte budget and build support |

Selecting the small-prime backend does not enable half-GCD or prepared coefficient combinations automatically.
Choose their cutoffs and budgets from representative complete operations, including preparation and memory costs.
Composition budgets apply to each cache, and simultaneous workers need separate arithmetic contexts and generators.
The explicit composition block size controls each Brent-Kung cache, independently of the Kaltofen-Shoup degree-block
size. Larger composition blocks exchange additional preparation and storage for fewer modular products per reuse;
they can make short-lived caches slower. See [composition block selection](@ref math_polynomial_arithmetic) for the
selection rules and outer-size contract.

## Staged factorization

Expand Down Expand Up @@ -139,6 +144,13 @@ is shared with callbacks, so their sampling affects its state for subsequent spl
exceptions propagate to the caller. Reproducibility assumes the same implementation, configuration, and callback
behavior; changing a recovery algorithm can change sampling even with the same initial seed.

At the internal boundary from distinct-degree discovery to complete factorization, a group whose polynomial degree
equals its reported irreducible-factor degree already contains exactly one factor under the distinct-degree
precondition. After the structural and characteristic checks, it is emitted directly without constructing the
Cantor-Zassenhaus splitting exponent or consuming random elements in the dispatch itself. Its callback is invoked
once, its stop/continue result is honored, and exceptions propagate. This shortcut relies on the preceding discovery
stage; it does not establish irreducibility for an arbitrary polynomial supplied as a degree group.

The public factorization APIs collect the factors they report. Internally, complete factorization releases each
distinct-degree group after splitting and any required exact division instead of retaining all processed groups.
Its internal callback path also allows norm recovery to consume irreducible factors without collecting a second
Expand All @@ -164,14 +176,21 @@ Screening reuses the initial `X^Q mod B` and subsequent Frobenius powers, all co
square-free part B. A callback stop avoids constructing the remaining baby table and its block-composition cache.
It still pays for square-free decomposition, the initial power, and the initial Frobenius composition cache. If
screening continues, its powers become the prefix of the full baby table and only missing steps are computed.
Once the table is complete, the blocked search retains the original divisor context and releases the initial
Frobenius context, including its composition cache, before constructing the giant-step composition cache. The baby
table owns its residues, so later steps do not depend on the released cache. The divisor and all powers still refer
to the original square-free part B; this lifetime change does not shrink the modulus or rebuild the baby table.
The blocked search retains its original alignment; its first block may revisit screened degrees, whose factors
have already been removed. This adds GCD/exact-division work relative to the baseline and should be measured on
both rejected and suitable inputs before choosing a limit. Passing a bounded screen does not establish that norm
recovery will succeed.

Splitting and callback work run while the active Frobenius precomputation remains alive, which can increase peak
memory. Splitting copies the emitted group because distinct-degree refinement or screening still needs its original
polynomial after the callback returns. No default screening bound is selected from the behavior of one candidate.
Splitting and callback work run while the current discovery phase's precomputation remains alive: the initial
Frobenius cache during screening, or the completed baby table and giant-step cache during the blocked search. Their
storage can therefore overlap a callback's own recovery caches even though the two discovery composition caches no
longer overlap each other during the transition. Splitting copies the emitted group because distinct-degree
refinement or screening still needs its original polynomial after the callback returns. No default screening bound
is selected from the behavior of one candidate.

## Preconditions and current restrictions

Expand Down
14 changes: 14 additions & 0 deletions libs/math/docs/polynomial_recovery.md
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,20 @@ phases. Optional prepared coefficient payloads obey `modular_composition_prepare
cached powers, backend scratch, and concurrent operations require additional memory; this option is not a total
memory limit. Measure seed construction, degree-dependent work, and complete recovery together when assessing speed.

`modular_composition_block_size` applies to the composition caches constructed by these recovery paths as well as
those used in factor discovery. Its default of zero keeps the automatic square-root heuristic. A positive value
requests a width capped by the cached-power limit and each cache's maximum outer coefficient count, as described in
the [composition guide](@ref math_polynomial_arithmetic). A larger width spends more time and memory on preparation
to reduce giant-step products during composition. Recovery chains can have different degrees and reuse counts from
discovery, so a width that helps a long discovery chain can slow a short recovery chain. Assess the complete recovery
with its actual factor pattern, including preparation and peak memory, before selecting a width.

During composite norm recovery, factor discovery releases its initial Frobenius cache after the baby table is
complete and before allocating its giant-step cache. This reduces overlapping discovery storage, but the active
discovery precomputation still remains alive while the factor callback performs norm recovery. Its storage must be
counted alongside the recovery caches when estimating peak memory. See
[staged factorization](@ref math_polynomial_factorization) for screening and callback lifetimes.

Let `Q = p^e` be the coefficient-field order, `d` the irreducible divisor degree, and `s` the two-adicity of `Q^d - 1`.
The current implementations have the rough bounds shown below.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ namespace nil::crypto3::math::polynomial_arithmetic {
// Prepared BN254 combinations may use four or eight IFMA lanes when the build supports them.
// Zero retains the scalar prepared kernel. The byte budget above remains the opt-in switch.
std::size_t modular_composition_simd_lanes = 8;

// Explicit Brent-Kung block size for reusable composition caches. Zero selects the square-root heuristic.
// A positive request is capped by the cached-power limit and maximum outer coefficient count. Larger blocks
// trade more cache construction and storage for fewer giant-step products when the cache is reused.
std::size_t modular_composition_block_size = 0;
};

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,16 @@ namespace nil::crypto3::math {
"equal-degree factorization requires the factor degree to divide the polynomial degree");
}

// A group of exactly one irreducible factor needs no splitting exponent or random trials.
// Preserve the characteristic restriction of the splitting context before emitting it.
if (group.polynomial.size() - 1 == group.irreducible_factor_degree) {
if (algebra::fields::field_characteristic<typename value_type::field_type>() == 2) {
throw std::invalid_argument(
"Cantor-Zassenhaus splitting for characteristic two is not implemented");
}
return factor_callback(std::move(group.polynomial));
}

cantor_zassenhaus_context<Backend> split_context(group.irreducible_factor_degree);
return cantor_zassenhaus_split_all<Backend>(std::move(group.polynomial), split_context, arithmetic_context,
generator, std::forward<FactorCallback>(factor_callback));
Expand Down
Loading
Loading