Skip to content

Fix false-positive in check_primality on degenerate random specialization#525

Closed
ChrisRackauckas-Claude wants to merge 1 commit into
SciML:masterfrom
ChrisRackauckas-Claude:fix-check-primality-degenerate-specialization
Closed

Fix false-positive in check_primality on degenerate random specialization#525
ChrisRackauckas-Claude wants to merge 1 commit into
SciML:masterfrom
ChrisRackauckas-Claude:fix-check-primality-degenerate-specialization

Conversation

@ChrisRackauckas-Claude

Copy link
Copy Markdown
Contributor

Ignore until reviewed by @ChrisRackauckas.

Problem

The master tests / Core (julia pre, ubuntu-latest) lane is red. The only failure is test/bodies/io_projections.jl:55:

IO-projections (+ extra projection): Test Failed at .../test/bodies/io_projections.jl:55
  Expression: !(check_primality(proj))
   Evaluated: !(check_primality(Dict(...)))   # check_primality returned true

The proj ideal for the issue-#132 PK/PD model is not prime, so the test asserts !check_primality(proj). The function returned true, failing the test. This is the prerelease (Julia 1.13.0-rc1) lane only; 1.10/1.11/1.12 pass.

Root cause (a real soundness bug, not version-specific)

check_primality(polys, extra_relations) specializes every non-leader variable of each generator to a random integer (rand(1:100)), then asks whether the characteristic polynomial of a generic multiplication operator on the resulting zero-dimensional quotient is irreducible.

Each generator here is degree 2 in its leader (y1(t)_2, y2(t)_3), with a non-constant leading coefficient in the other variables. When the random point makes a leading coefficient vanish, the generator drops to degree 1. When all generators collapse to degree 1, the quotient ring becomes 1-dimensional, its charpoly is degree 1 (trivially irreducible), and the routine reports the (non-prime) ideal as prime — a false positive.

Reproduced on 1.13.0-rc1: over seeds 0..499, check_primality(proj) returns true for 6 of them (e.g. 30, 169, 305, 393, 488, 496). The 1.13 RNG stream change merely caused the un-seeded global RNG to land on such a draw by the time the test reached line 55; the bug is RNG-dependent and present on every version.

For a bad seed the Groebner basis specializes to [y1(t)_2 - c1, y2(t)_3 - c2] (quotient dim 1, irreducible degree-1 charpoly → true); for a good seed it is [y1(t)_2^2 + ..., y2(t)_3^2 + ...] (quotient dim 4, charpoly factors → false).

Fix

Only accept random specializations that preserve each generator's degree in its leader — i.e. stay on the open set where the leading coefficients do not vanish (precisely the set the ideal is "saturated at the leading coefficient", per the docstring). Resample otherwise, with a bounded retry. extra_relations are evaluated at the same point, unchanged behavior.

Verification (run locally)

Julia 1.13.0-rc1:

  • false-positive rate over seeds 0..499: 6/500 → 0/500
  • io_projections body: 10/10 (was 9 pass / 1 fail)
  • positive case check_primality(proj, [projection_poly]): 50/50 true (unchanged)
  • negative case stress over seeds 0..299: 0/300 true (correct)

Julia 1.12 (no regression):

  • io_projections body: 10/10
  • check_primality_zerodim body: 5/5 (subroutine untouched)

🤖 Generated with Claude Code

check_primality(polys, extra_relations) substitutes the non-leader
variables of each generator with random integers, then tests whether the
characteristic polynomial of a generic multiplication operator on the
resulting zero-dimensional quotient is irreducible. The random
specialization could land on a point where a generator's leading
coefficient (w.r.t. its leader) vanishes, dropping that generator's degree
in its leader. When every generator collapses to degree 1, the quotient
ring becomes 1-dimensional, its charpoly is a degree-1 (hence trivially
irreducible) polynomial, and the routine reports the ideal as prime even
when it is not.

This is a soundness bug: for the io_projections issue-SciML#132 case the ideal
is not prime, yet ~6/500 RNG seeds drove check_primality(proj) to return
true. It surfaced as a master red on the julia-pre (1.13.0-rc1) Core lane,
whose changed global-RNG stream happened to hit such a draw at
test/bodies/io_projections.jl:55 (`@test !check_primality(proj)`).

Fix: only accept random specializations that preserve every generator's
degree in its leader, i.e. that stay on the open set where the leading
coefficients do not vanish (the set the ideal is implicitly saturated at,
per the docstring). Resample otherwise, with a bounded retry. The
extra_relations are evaluated at the same point, matching the previous
behavior.

Verified on Julia 1.13.0-rc1: false-positive rate over seeds 0..499 drops
from 6/500 to 0/500; io_projections body 10/10 (was 9/1); positive case
check_primality(proj, [projection_poly]) 50/50 true. No regression on Julia
1.12: io_projections 10/10, check_primality_zerodim 5/5.

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@pogudingleb

Copy link
Copy Markdown
Collaborator

Thanks!
I added some cleanup in a separate PR (#526 ) as could not edit this one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants