One refinement step preserves the root and halves the width. For a
squarefree p, iso.refine1 isolates the same real root as iso (a root lies
in iso's interval iff it lies in the refined interval) and its width is exactly
half. The fallback branch of refine1 is never taken: the two half-counts sum
to 1, so exactly one half certifies.
-rootBound p < rootBound p, so the initial interval is nonempty.
The ±rootBound variation gap equals ZPoly.rootCount. The Sturm variation
difference between -rootBound p and rootBound p counts the real roots of p
in (-R, R], which is every real root (Cauchy bound), i.e. ZPoly.rootCount p. This
is a statement about p's roots only — the chain elements' own (possibly larger)
zeros never enter, so there is no ±R-versus-±∞ gap.
The initial interval's width fits the depth budget. For positive-degree
p, 2 · rootBound p ≤ 2 ^ (isolationDepth p − sepPrec p), which is the
depth-sufficiency hypothesis the sturmVisit induction consumes at the top.
Dyadic ≤ is reflexive (routed through toRat, staying in the core
instances that DyadicInterval uses).
sturmVarAt is antitone in the point: the count over (a, b] is a
nonnegative cardinality, so the variation at b is at most the one at a.
Separation caps the count. An interval no wider than 2^(−sepPrec p)
holds at most one real root of a positive-degree squarefree p (two distinct
real roots are more than 4·2^(−sepPrec p) apart by sepPrec_separates'), so
its exact Sturm count is at most 1.
The final assembly step succeeds once its two invariants are witnessed.
The Sturm engine succeeds on nonzero squarefree input. Positive degree
is the real content (isolateSturm?_isSome_of_degree_pos); a nonzero constant
certifies through the empty chain.
The p ≠ 0 hypothesis is necessary because SquareFreeRat 0 is vacuous while
ZPoly.isolateSturm? 0 = none.
The top-level driver succeeds on nonzero squarefree input. A one-liner
over isolateSturm?_isSome: ZPoly.isolateRealRoots? keeps whichever engine's certified
output arrives first, and the Sturm engine always has one.