Exact real roots¶
These operations count and isolate distinct real roots using exact rational arithmetic. Repeated roots count once. Sturm sequences retain the input and its derivative without normalization or squarefree preprocessing; every division, including the terminal zero remainder, has a quotient witness.
The root-count checker verifies polynomial identities and sign variations.
It does not run polynomial division or reconstruct the producer's sequence.
Nonzero constants use a one-element sequence and have no roots. Zero raises
DOMAIN for sequence construction, counting, and isolation.
isolate_real_roots isolates the roots of the squarefree part by Descartes'
rule of signs (see Descartes certificates), on
an interval bounded by a power of two. Output intervals have nonroot dyadic
endpoints, a sign change of the squarefree part, and width below 2**-40;
refine accepts any positive rational target width. It validates the
interval by Descartes' rule on the squarefree part, subdividing where the
bound overcounts, so any interval with exactly one distinct root and nonroot
endpoints is accepted, and narrows it by quadratic interval refinement to at
most half the target width.
To recover all rational roots, including non-dyadic values such as 1/3,
isolation narrows every interval below 1/(2*A*A), where A is the leading
coefficient of the primitive squarefree part. The rational-root theorem bounds
root denominators by A; Legendre's continued-fraction criterion then finds
any such root among the midpoint's convergents. Each candidate is checked by
exact evaluation. Dyadic rational roots met at split points are exact
already. This is rational reconstruction after isolation, without integer
factorization. Rational midpoint roots remain strictly inside the open
interval returned by refine.
count_roots requires increasing nonroot endpoints. sign_on_interval
accepts singleton intervals and returns no sign if any root lies in the closed
interval. Budget exhaustion raises LIMIT and never returns a partial list.
Descartes certificates¶
A DescartesCertificate proves the real roots of an integer polynomial g
without a Sturm sequence. Its leaves partition an interval (low, high) whose
ends are not roots. Each leaf records Descartes' bound for the roots inside
it: the sign variations of \((x+1)^n\,g(\ell + (h-\ell)/(x+1))\), which is at
least the number of roots in the open leaf and has the same parity, so zero
and one are exact counts. The shared leaf endpoints where g vanishes are
listed in exact_roots. Every leaf with a bound of one, and every leaf marked
resolved with a larger bound v, owns the next v brackets: disjoint
intervals inside it, each an open interval where g changes sign or a point
where g is zero. Each holds an odd number of roots, so v of them under a
bound of v hold exactly one root each. A leaf with a bound of two or more and
no brackets is unresolved and leaves its roots uncounted.
check_descartes verifies all of this from g and the certificate alone: the
partition, every leaf's bound recomputed by a Taylor shift, the exact roots,
and two signs per bracket. It does not need g to be squarefree.
descartes_intervals lists the certified roots in increasing order, an exact
root r as the point (r, r). A DescartesRootResult pairs a certificate with
the squarefree part g of a polynomial f, the cofactor gcd(f, f'), the bound
exponent of the certificate's interval and the outcome flags.
descartes_count counts the distinct real roots of f strictly between two
ordered nonroot endpoints from a complete isolation: the exact roots between
them and the brackets inside them, a bracket that contains an endpoint being
decided by the sign of g there against the bracket's end. Its
DescartesCountCertificate holds the isolation, the endpoints and the count,
and check_descartes_count verifies the isolation and recounts. It is the
evidence behind the array API's count_roots; count_roots here keeps its
Sturm certificate.
Example¶
For \(p(x)=(x^2-2)(3x-1)\), count_roots certifies three distinct real roots.
isolate_real_roots returns the rational root \(1/3\) in exact_roots and two
isolating intervals for \(\pm\sqrt{2}\) in intervals:
"""Count real roots and distinguish rational roots from isolating intervals."""
from std.testing import assert_equal, assert_true
from apn_mojo import Rational
from polynomials import Budget, QQ, NoContext, PolynomialRing, DensePoly, count_roots, check_root_count, isolate_real_roots
def main() raises:
var work = Budget()
var ring = PolynomialRing[QQ].create(["x"], NoContext(), work)
var polynomial = DensePoly[QQ].from_sparse(ring.parse("(x^2 - 2)*(3*x - 1)", work), work, variable=Int64(0))
var count = count_roots(polynomial, QQ(Rational(-2)), QQ(Rational(2)), work)
assert_equal(count.count, 3)
assert_true(check_root_count(polynomial, count, work))
var roots = isolate_real_roots(polynomial, work)
assert_equal(len(roots.exact_roots), 1)
assert_equal(roots.exact_roots[0].value, Rational(1, 3))
assert_equal(len(roots.intervals), 2)
print("Distinct real roots:", count.count)
print("Rational root:", roots.exact_roots[0].value)
print("Irrational-root intervals:", len(roots.intervals))
Run from the repository root pixi run mojo run -I build docs/examples/tutorial_roots.mojo
Output
Distinct real roots: 3
Rational root: 1/3
Irrational-root intervals: 2
Types and functions¶
SturmSequence¶
@fieldwise_init
struct SturmSequence(Copyable, Movable, Writable)
An unnormalized signed remainder sequence with exact quotient witnesses.
The final nonzero polynomial is retained, including for repeated roots.
Defined in src/polynomials/certificates.mojo.
| Field | Type | |
|---|---|---|
polynomials |
List[DensePoly[QQ]] |
The input, its derivative, and successive negative remainders. |
quotients |
List[DensePoly[QQ]] |
Quotients proving each recurrence, including the final zero remainder. |
RootCountCertificate¶
@fieldwise_init
struct RootCountCertificate(Copyable, Movable, Writable)
A Sturm sequence proving a distinct-root count on an open interval (T-C1).
Defined in src/polynomials/certificates.mojo.
| Field | Type | |
|---|---|---|
sequence |
SturmSequence |
The sequence and all quotient witnesses. |
a |
QQ |
The lower endpoint, where the input does not vanish. |
b |
QQ |
The upper endpoint, where the input does not vanish. |
count |
Int |
The claimed number of distinct roots strictly between the endpoints. |
check_root_count¶
def check_root_count(f: DensePoly[QQ], certificate: RootCountCertificate, mut budget: Budget) raises PolyError -> Bool
Check all Sturm identities and endpoint variations without polynomial division.
Arguments.
f: The polynomial whose distinct roots are counted.certificate: The sequence, quotient witnesses, endpoints, and count.budget: The shared work allowance.
Returns. Whether the evidence establishes the count on the open interval.
Raises.
PolyError: DOMAIN for incompatible representations, LIMIT or CAPACITY on work limits.
Defined in src/polynomials/certificates.mojo.
RootInterval¶
@fieldwise_init
struct RootInterval(Copyable, Movable, Writable)
An open rational interval containing exactly one distinct real root.
Defined in src/polynomials/roots.mojo.
| Field | Type | |
|---|---|---|
low |
QQ |
The lower endpoint, strictly below the root. |
high |
QQ |
The upper endpoint, strictly above the root. |
RootIsolation¶
@fieldwise_init
struct RootIsolation(Copyable, Movable, Writable)
The distinct real roots as exact rationals and disjoint open intervals.
Defined in src/polynomials/roots.mojo.
| Field | Type | |
|---|---|---|
exact_roots |
List[QQ] |
All rational roots in increasing order, without multiplicities. |
intervals |
List[RootInterval] |
Increasing disjoint intervals for the remaining roots, excluding exact roots. |
sturm_sequence¶
def sturm_sequence(f: DensePoly[QQ], mut budget: Budget) raises PolyError -> SturmSequence
Construct the unnormalized Sturm sequence and every quotient witness.
Arguments.
f: A nonzero rational polynomial; constants give a one-element sequence.budget: The shared work allowance.
Returns. The sequence starting with f and f-prime, including its last nonzero entry.
Raises.
PolyError: DOMAIN for zero or malformed input, LIMIT or CAPACITY on work limits.
Defined in src/polynomials/roots.mojo.
sign_changes¶
def sign_changes(sequence: SturmSequence, a: QQ, mut budget: Budget) raises PolyError -> Int
Count changes of sign at an exact point, omitting zero evaluations.
Arguments.
sequence: The polynomial sequence to evaluate.a: The exact evaluation point.budget: The shared work allowance.
Returns. The number of adjacent opposite nonzero signs.
Raises.
PolyError: LIMIT or CAPACITY on work limits.
Defined in src/polynomials/roots.mojo.
count_roots¶
def count_roots(f: DensePoly[QQ], a: QQ, b: QQ, mut budget: Budget) raises PolyError -> RootCountCertificate
Count distinct real roots in an open interval with nonroot endpoints.
Arguments.
f: A nonzero polynomial, with repeated roots allowed.a: The lower endpoint.b: The upper endpoint.budget: The shared work allowance.
Returns. The count and a complete Sturm certificate.
Raises.
PolyError: DOMAIN for zero, endpoint roots, or a not below b; LIMIT or CAPACITY on work limits.
Defined in src/polynomials/roots.mojo.
isolate_real_roots¶
def isolate_real_roots(f: DensePoly[QQ], mut budget: Budget) raises PolyError -> RootIsolation
Isolate every distinct real root by Descartes' rule of signs.
Arguments.
f: A nonzero rational polynomial; constants have no roots.budget: The shared work allowance.
Returns. Every rational root exactly and sorted disjoint intervals of width below 2**-40 for the others.
Raises.
PolyError: DOMAIN for zero or malformed input, LIMIT or CAPACITY on work limits.
Defined in src/polynomials/roots.mojo.
refine¶
def refine(f: DensePoly[QQ], interval: RootInterval, width: QQ, mut budget: Budget) raises PolyError -> RootInterval
Narrow a validated isolating interval to strictly less than the requested width.
The interval is validated by Descartes' rule of signs on the squarefree part and narrowed by quadratic interval refinement.
Arguments.
f: The polynomial defining the root.interval: An open interval with exactly one root and nonroot endpoints.width: The positive target width.budget: The shared work allowance.
Returns. A narrower open interval containing the same root, including rational roots.
Raises.
PolyError: DOMAIN for nonpositive width or an invalid isolating interval, LIMIT or CAPACITY on work limits.
Defined in src/polynomials/roots.mojo.
sign_on_interval¶
def sign_on_interval(f: DensePoly[QQ], low: QQ, high: QQ, mut budget: Budget) raises PolyError -> Optional[Int]
Determine the strict sign on a closed interval when no root occurs there.
Arguments.
f: The rational polynomial.low: The lower endpoint.high: The upper endpoint, possibly equal to low.budget: The shared work allowance.
Returns. Positive or negative one for a root-free interval, otherwise None.
Raises.
PolyError: DOMAIN for reversed endpoints or malformed input, LIMIT or CAPACITY on work limits.
Defined in src/polynomials/roots.mojo.
DescartesLeaf¶
@fieldwise_init
struct DescartesLeaf(Copyable, Movable)
An open interval of a Descartes certificate with its root bound.
Defined in src/polynomials/descartes.mojo.
| Field | Type | |
|---|---|---|
low |
QQ |
The lower endpoint. |
high |
QQ |
The upper endpoint, above low. |
variations |
Int |
Descartes' bound for the roots of g in (low, high): at least their number, with the same parity; zero and one are exact counts. |
resolved |
Bool |
Whether variations brackets follow for this leaf; always true for bounds below two. Disjoint brackets with sign changes each hold an odd number of roots, so v of them under a bound of v hold exactly one root each. |
DescartesCertificate¶
@fieldwise_init
struct DescartesCertificate(Copyable, Movable)
Leaves partitioning (low, high), the roots of g at their shared ends, and a bracket for every other root.
Defined in src/polynomials/descartes.mojo.
| Field | Type | |
|---|---|---|
low |
QQ |
The lower end of the partitioned interval, not a root of g. |
high |
QQ |
The upper end of the partitioned interval, not a root of g. |
leaves |
List[DescartesLeaf] |
Increasing leaves; each leaf's high is the next leaf's low. |
exact_roots |
List[QQ] |
Increasing: exactly the shared leaf endpoints where g is zero. |
brackets |
List[RootInterval] |
As many per resolved leaf as its bound, increasing and disjoint inside it: an open interval where g changes sign, or a root low == high of g. |
DescartesRootResult¶
@fieldwise_init
struct DescartesRootResult(Copyable, Movable)
The distinct real roots of an exact polynomial with a Descartes certificate and outcome flags.
Defined in src/polynomials/descartes.mojo.
| Field | Type | |
|---|---|---|
outcome |
NumericOutcome |
SUCCESS only when every requested guarantee holds. |
complete |
Bool |
Every leaf is resolved, so every real root is counted. |
accurate |
Bool |
Every bracket meets the requested widths. |
isolated |
Bool |
Every counted root has its own bracket or exact value; equals complete. |
squarefree |
DensePoly[ZZ] |
The primitive positive-leading g = f / gcd(f, f'): the same roots, each simple. |
cofactor |
DensePoly[ZZ] |
The cofactor gcd(f, f'), so that the primitive part of f is g times it. |
bound_exponent |
Int |
Every root lies in (-2^k, 2^k), the certificate's interval. |
certificate |
DescartesCertificate |
The leaves, exact roots and brackets for g. |
check_descartes¶
def check_descartes(g: DensePoly[ZZ], certificate: DescartesCertificate, mut budget: Budget) raises PolyError -> Bool
Verify a Descartes certificate for an integer polynomial without searching.
Checks that the leaves partition the certificate's interval at nonroots and at the listed exact roots, recomputes every leaf's Descartes bound, and checks the brackets of each resolved leaf: disjoint, inside it, and each with a sign change or a root. Unresolved leaves are accepted; they leave a result incomplete, not wrong.
Arguments.
g: The polynomial the certificate is for; squarefree in practice, though the checks do not need it.certificate: The proposed certificate.budget: The shared work allowance.
Returns. Whether every claim of the certificate holds for g.
Raises.
PolyError: LIMIT or CAPACITY on work limits.
Defined in src/polynomials/descartes.mojo.
descartes_intervals¶
def descartes_intervals(certificate: DescartesCertificate, mut budget: Budget) raises PolyError -> List[RootInterval]
List the certified roots of a Descartes certificate in increasing order.
Arguments.
certificate: A certificate, checked or produced by this module.budget: The shared work allowance.
Returns. One interval per root: the point (r, r) for an exact root and the bracket otherwise; roots of unresolved leaves are omitted.
Raises.
PolyError: LIMIT or CAPACITY on work limits.
Defined in src/polynomials/descartes.mojo.
DescartesCountCertificate¶
@fieldwise_init
struct DescartesCountCertificate(Copyable, Movable, Writable)
A complete Descartes isolation proving a distinct-root count on an open interval.
The isolation brackets every real root once; the count is the exact roots strictly between the endpoints and the brackets inside, a bracket holding an endpoint being decided by the squarefree part's sign there.
Defined in src/polynomials/descartes.mojo.
| Field | Type | |
|---|---|---|
isolation |
AlgebraicDescartesCertificate |
The complete isolation of every distinct real root. |
a |
QQ |
The lower endpoint, where the input does not vanish. |
b |
QQ |
The upper endpoint, where the input does not vanish. |
count |
Int |
The claimed number of distinct roots strictly between the endpoints. |
DescartesCountCertificate.write_to¶
def write_to[W: Writer](self, mut writer: W)
Write the endpoints, the count and the isolation.
Parameters.
W: The writer type.
Arguments.
writer: The destination.
descartes_count¶
def descartes_count(f: DensePoly[QQ], a: QQ, b: QQ, mut budget: Budget) raises PolyError -> DescartesCountCertificate
Count the distinct real roots of f strictly between nonroot endpoints by Descartes' rule.
Arguments.
f: The nonzero polynomial.a: The lower endpoint, not a root of f.b: The upper endpoint, above a and not a root of f.budget: The shared work allowance.
Returns. The count with a complete Descartes isolation that check_descartes_count verifies.
Raises.
PolyError: DOMAIN for a zero polynomial or unordered or root endpoints, LIMIT or CAPACITY on work limits.
Defined in src/polynomials/descartes.mojo.
check_descartes_count¶
def check_descartes_count(f: DensePoly[QQ], certificate: DescartesCountCertificate, mut budget: Budget) raises PolyError -> Bool
Check a Descartes root count without searching.
Verifies the isolation with check_algebraic_descartes, requires ordered endpoints where f does not vanish, and recounts.
Arguments.
f: The polynomial whose distinct roots are counted.certificate: The isolation, endpoints and count.budget: The shared work allowance.
Returns. Whether the evidence establishes the count on the open interval.
Raises.
PolyError: DOMAIN for malformed polynomial data, LIMIT or CAPACITY on work limits.
Defined in src/polynomials/descartes.mojo.