diff --git a/diophantine_classifier/data/families/binary-qf-representation.yaml b/diophantine_classifier/data/families/binary-qf-representation.yaml new file mode 100644 index 0000000..5cd5a16 --- /dev/null +++ b/diophantine_classifier/data/families/binary-qf-representation.yaml @@ -0,0 +1,29 @@ +# Family: Representation by a binary quadratic form +# Schema: see data/families/README.md; prose documentation in docs/FAMILIES.md. +# References are keys into data/references.bib, each with a 'why' annotation. +slug: binary-qf-representation +name: Representation by a binary quadratic form +priority: 1 +status: algorithmic +class: quadratic +form: a*x^2 + b*x*y + c*y^2 = n +parents: +- binary-quadratic +matcher: true +methods: +- reduction theory +- Cornacchia +- class groups +software: + sage: BinaryQF([a,b,c]).solve_integer(n) + pari: qfbsolve, qfbcornacchia +code: + sage: BinaryQF([{a}, {b}, {c}]).solve_integer({n}) + pari: qfbsolve(Qfb({a}, {b}, {c}), {n}, 1) +examples: +- 3*x^2 + 7*y^2 = 19 +references: +- key: Cox1989 + why: 'which n are represented: the class field theory of x^2 + ny^2' +- key: Cornacchia1908 + why: the classical algorithm for representations by definite forms diff --git a/diophantine_classifier/data/families/elliptic-weierstrass.yaml b/diophantine_classifier/data/families/elliptic-weierstrass.yaml new file mode 100644 index 0000000..2af071f --- /dev/null +++ b/diophantine_classifier/data/families/elliptic-weierstrass.yaml @@ -0,0 +1,36 @@ +# Family: Elliptic curve in Weierstrass form +# Schema: see data/families/README.md; prose documentation in docs/FAMILIES.md. +# References are keys into data/references.bib, each with a 'why' annotation. +slug: elliptic-weierstrass +name: Elliptic curve in Weierstrass form +priority: 1 +status: algorithmic +class: genus1 +form: y^2 + a1*x*y + a3*y = x^3 + a2*x^2 + a4*x + a6 +parents: +- genus-one-curve +matcher: true +lmfdb: ec.q +methods: +- Mordell-Weil descent +- elliptic logarithms for integral points +software: + sage: EllipticCurve(...).gens(); .integral_points(); .S_integral_points() + pari: ellrank, ellratpoints + magma: MordellWeilShaInformation; IntegralPoints +code: + sage: | + E = EllipticCurve({ainvs}) + E.gens(); E.integral_points(both_signs=True) + magma: | + E := EllipticCurve({magma_ainvs}); + IntegralPoints(E); +examples: +- y^2 + y = x^3 - x^2 - 10*x - 20 +references: +- key: Cremona1997 + why: descent algorithms (mwrank) for the Mordell-Weil group +- key: StroekerTzanakis1994 + why: the elliptic-logarithm method for integral points +- key: GebelPethoZimmer1994 + why: independent introduction of the elliptic-logarithm method diff --git a/diophantine_classifier/data/families/hyperelliptic.yaml b/diophantine_classifier/data/families/hyperelliptic.yaml new file mode 100644 index 0000000..0d78235 --- /dev/null +++ b/diophantine_classifier/data/families/hyperelliptic.yaml @@ -0,0 +1,29 @@ +# Family: Hyperelliptic curve +# Schema: see data/families/README.md; prose documentation in docs/FAMILIES.md. +# References are keys into data/references.bib, each with a 'why' annotation. +slug: hyperelliptic +name: Hyperelliptic curve +priority: 1 +status: effective +class: curve +form: y^2 = f(x), deg f >= 5 +parents: +- superelliptic +matcher: true +methods: +- Baker (integral points) +- Chabauty-Coleman + MW sieve (rational points) +software: + magma: IntegralPoints (genus 2); Chabauty + pari: hyperellratpoints +code: + magma: | + C := HyperellipticCurve(Polynomial({magma_coeffs})); + J := Jacobian(C); RankBound(J); +examples: +- y^2 = x^7 + 3 +references: +- key: Baker1969 + why: effective bounds for integral points +- key: McCallumPoonen2012 + why: the Chabauty-Coleman method for rational points diff --git a/diophantine_classifier/data/families/legendre.yaml b/diophantine_classifier/data/families/legendre.yaml new file mode 100644 index 0000000..bed3e1d --- /dev/null +++ b/diophantine_classifier/data/families/legendre.yaml @@ -0,0 +1,30 @@ +# Family: Legendre equation (diagonal ternary quadratic) +# Schema: see data/families/README.md; prose documentation in docs/FAMILIES.md. +# References are keys into data/references.bib, each with a 'why' annotation. +slug: legendre +name: Legendre equation (diagonal ternary quadratic) +priority: 1 +status: algorithmic +class: quadratic +form: a*x^2 + b*y^2 + c*z^2 = 0 +parents: +- quadratic-form-zero +- diagonal-form +matcher: true +methods: +- Legendre criterion +- Holzer bounds +- conic parametrization +software: + sage: Conic([a,b,c]).has_rational_point(point=True) + pari: qfsolve + magma: HasRationalPoint +code: + sage: Conic(QQ, [{a}, {b}, {c}]).rational_point() +examples: +- x^2 + y^2 = 2*z^2 +references: +- key: CremonaRusin2003 + why: efficient algorithms for finding a rational point on a conic +- key: Holzer1950 + why: existence of solutions of small height (Holzer's bound) diff --git a/diophantine_classifier/data/families/pell-like.yaml b/diophantine_classifier/data/families/pell-like.yaml new file mode 100644 index 0000000..13bc905 --- /dev/null +++ b/diophantine_classifier/data/families/pell-like.yaml @@ -0,0 +1,25 @@ +# Family: Generalized Pell equation +# Schema: see data/families/README.md; prose documentation in docs/FAMILIES.md. +# References are keys into data/references.bib, each with a 'why' annotation. +slug: pell-like +name: Generalized Pell equation +priority: 1 +status: algorithmic +class: quadratic +form: x^2 - D*y^2 = N +parents: +- binary-qf-representation +matcher: true +methods: +- continued fractions +- automorph orbits +software: + pari: qfbsolve(Qfb(1,0,-D), N, 1) + sage: solve_diophantine +code: + pari: qfbsolve(Qfb(1, 0, -{D}), {N}, 1) +examples: +- x^2 - 61*y^2 = 5 +references: +- key: Matthews2000 + why: exposition of the LMM continued-fraction algorithm for x^2 - Dy^2 = N diff --git a/diophantine_classifier/data/families/thue.yaml b/diophantine_classifier/data/families/thue.yaml new file mode 100644 index 0000000..bfef495 --- /dev/null +++ b/diophantine_classifier/data/families/thue.yaml @@ -0,0 +1,33 @@ +# Family: Thue equation +# Schema: see data/families/README.md; prose documentation in docs/FAMILIES.md. +# References are keys into data/references.bib, each with a 'why' annotation. +slug: thue +name: Thue equation +priority: 1 +status: algorithmic +class: curve +form: F(x, y) = m, F irreducible of degree >= 3 +parents: +- binary-form +matcher: true +methods: +- Baker bounds + LLL (Tzanakis-de Weger +- Bilu-Hanrot) +software: + pari: thue(thueinit(f), m) + sage: gp interface to thue + magma: Thue +code: + pari: thue(thueinit({fx}, 1), {m}) + magma: Solutions(Thue(Polynomial({magma_coeffs})), {m}); +examples: +- x^3 + 2*y^3 = 11 +references: +- key: Baker1968 + why: effective bounds via linear forms in logarithms, making the finiteness algorithmic +- key: Thue1909 + why: the original finiteness theorem +- key: TzanakisDeWeger1989 + why: the first complete practical solution method +- key: BiluHanrot1996 + why: the high-degree algorithm implemented in PARI diff --git a/diophantine_classifier/solvers.py b/diophantine_classifier/solvers.py index f8e52ef..b2aa55c 100644 --- a/diophantine_classifier/solvers.py +++ b/diophantine_classifier/solvers.py @@ -772,6 +772,230 @@ def stream(): complete=True, stream=stream) +def _pell_unit(D): + r""" + Fundamental solution of ``x^2 - D*y^2 = ±1`` by continued fractions. + + OUTPUT: triple ``(x1, y1, norm)`` with ``x1^2 - D*y1^2 = norm ∈ {1, -1}`` + + EXAMPLES:: + + sage: from diophantine_classifier.solvers import _pell_unit + sage: _pell_unit(61) + (29718, 3805, -1) + sage: _pell_unit(3) + (2, 1, 1) + """ + cf = continued_fraction(QuadraticField(D).gen()) + ell = len(cf.period()) + conv = cf.convergent(ell - 1) + x1, y1 = conv.numerator(), conv.denominator() + return x1, y1, x1 ** 2 - D * y1 ** 2 + + +def _signed_orbit_stream(D, fund, unit, include_trivial): + r""" + Stream all solutions of ``x^2 - D*y^2 = ±1`` from the fundamental one. + + INPUT: + + - ``D`` -- the Pell parameter + - ``fund`` -- fundamental solution of the target equation + - ``unit`` -- fundamental solution ``(t, u)`` of the ``+1`` equation + - ``include_trivial`` -- whether ``(±1, 0)`` are solutions (the ``+1`` + case) + + EXAMPLES:: + + sage: from diophantine_classifier.solvers import _signed_orbit_stream + sage: s = _signed_orbit_stream(2, (3, 2), (3, 2), True)() + sage: [next(s) for _ in range(6)] + [(1, 0), (-1, 0), (3, 2), (3, -2), (-3, 2), (-3, -2)] + """ + t, u = unit + + def stream(): + if include_trivial: + yield (ZZ(1), ZZ(0)) + yield (ZZ(-1), ZZ(0)) + x, y = fund + while True: + yield (x, y) + yield (x, -y) + yield (-x, y) + yield (-x, -y) + x, y = t * x + D * u * y, u * x + t * y + return stream + + +def _solve_pell(cls, match): + r""" + Solve ``x^2 - D*y^2 = ±1``: fundamental solution + full enumeration. + + ``solutions[0]`` is the fundamental solution; iteration enumerates all + integer solutions ordered by the power of the fundamental unit, with sign + pattern ``(x, y), (x, -y), (-x, y), (-x, -y)``. + + EXAMPLES:: + + sage: from diophantine_classifier import solve + sage: S = solve("x^2 - 61*y^2 = 1") + sage: S.solutions[0] + (1766319049, 226153980) + sage: S = solve("x^2 - 2*y^2 = -1") # negative Pell + sage: S.first(3) + [(1, 1), (1, -1), (-1, 1)] + sage: solve("x^2 - 3*y^2 = -1").kind # no negative Pell for D = 3 + 'empty' + """ + D = _zz(match.data, "D") + N = _zz(match.data, "N") + if D is None or N not in (1, -1): + raise SolverUnavailable("Pell solver needs concrete D and N = ±1") + x1, y1, norm = _pell_unit(D) + if N == 1: + if norm == 1: + fund = (x1, y1) + else: + fund = (x1 ** 2 + D * y1 ** 2, 2 * x1 * y1) + desc = (f"infinitely many: ±(fundamental)^k for the fundamental " + f"solution {fund}; iteration enumerates them all") + return SolutionSet(_normalized(match), [fund], "infinite", desc, + complete=True, + stream=_signed_orbit_stream(D, fund, fund, True)) + if norm == -1: + unit = (x1 ** 2 + D * y1 ** 2, 2 * x1 * y1) + desc = (f"infinitely many: odd powers of the fundamental unit; " + f"fundamental solution {(x1, y1)}") + return SolutionSet(_normalized(match), [(x1, y1)], "infinite", desc, + complete=True, + stream=_signed_orbit_stream(D, (x1, y1), unit, + False)) + return SolutionSet(_normalized(match), [], "empty", + f"x^2 - {D}y^2 = -1 has no solutions (continued " + "fraction period is even)", complete=True) + + +def _solve_pell_like(cls, match): + r""" + Solve ``x^2 - D*y^2 = N``: orbit representatives + full enumeration. + + PARI's ``qfbsolve`` provides representatives of the finitely many orbits + under the automorph group; iteration walks the orbits outward by + applying the fundamental automorphism in both directions. + + EXAMPLES:: + + sage: from diophantine_classifier import solve + sage: S = solve("x^2 - 2*y^2 = 7") + sage: sols = S.first(8) + sage: all(x^2 - 2*y^2 == 7 for x, y in sols) + True + sage: len(set(sols)) + 8 + """ + D = _zz(match.data, "D") + N = _zz(match.data, "N") + if D is None or N is None: + raise SolverUnavailable("generalized Pell solver needs concrete D, N") + if N in (1, -1): + return _solve_pell(cls, match) + try: + res = pari(f"qfbsolve(Qfb(1,0,{-D}),{N},1)") + reps = [(ZZ(v[0]), ZZ(v[1])) for v in res] + except Exception as err: + raise SolverUnavailable(f"PARI qfbsolve failed: {err}") from None + reps = [s for s in reps if s[0] ** 2 - D * s[1] ** 2 == N] + if not reps: + return SolutionSet(_normalized(match), [], "empty", "no solutions", + complete=True) + x1, y1, norm = _pell_unit(D) + if norm == -1: + t, u = x1 ** 2 + D * y1 ** 2, 2 * x1 * y1 + else: + t, u = x1, y1 + seeds = set() + for x, y in reps: + seeds.update({(x, y), (x, -y), (-x, y), (-x, -y)}) + + def key(s): + return (max(abs(s[0]), abs(s[1])), s) + + def stream(): + seen = set() + level = sorted(seeds, key=key) + while level: + nxt = [] + for s in level: + if s in seen: + continue + seen.add(s) + yield s + x, y = s + nxt.append((t * x + D * u * y, u * x + t * y)) + nxt.append((t * x - D * u * y, -u * x + t * y)) + level = sorted(set(nxt) - seen, key=key) + + desc = (f"{len(reps)} orbit representative(s) under the automorph group " + f"(fundamental automorphism {(t, u)}); iteration enumerates the " + "full orbits") + return SolutionSet(_normalized(match), sorted(reps), "orbits", desc, + complete=True, stream=stream) + + +def _solve_bqf(cls, match): + r""" + Representations by a binary quadratic form. + + Definite forms: the complete (finite) list of representations. + Indefinite forms: a witness via ``BinaryQF.solve_integer``. + + EXAMPLES:: + + sage: from diophantine_classifier import solve + sage: solve("3*x^2 + 7*y^2 = 19").solutions + [(-2, -1), (-2, 1), (2, -1), (2, 1)] + sage: solve("3*x^2 + 7*y^2 = 5").kind + 'empty' + """ + a, b, c, n = (_zz(match.data, k) for k in ("a", "b", "c", "n")) + if None in (a, b, c, n): + raise SolverUnavailable("needs concrete form and n") + disc = b ** 2 - 4 * a * c + if disc < 0 and a > 0 and abs(n) <= MAX_BQF: + if n < 0: + return SolutionSet(_normalized(match), [], "empty", + "positive definite form cannot represent a " + "negative integer", complete=True) + sols = [] + Y = isqrt(4 * a * n // (-disc)) + 1 + for y in range(-Y, Y + 1): + discx = (b * y) ** 2 - 4 * a * (c * y * y - n) + if discx < 0: + continue + s = isqrt(discx) + if s * s != discx: + continue + for sgn in ((s,) if s == 0 else (s, -s)): + num = -b * y + sgn + if num % (2 * a) == 0: + sols.append((ZZ(num // (2 * a)), ZZ(y))) + sols = sorted(set(sols)) + kind = "finite-complete" if sols else "empty" + return SolutionSet(_normalized(match), sols, kind, + "all representations (definite form)", + complete=True) + form = BinaryQF([a, b, c]) + sol = form.solve_integer(n) + if sol is None: + return SolutionSet(_normalized(match), [], "empty", + "no representation", complete=disc < 0) + return SolutionSet(_normalized(match), [tuple(sol)], "witness", + "one representation (BinaryQF.solve_integer); for " + "indefinite forms the full set is a union of " + "automorph orbits", complete=False) + + def _solve_qf_zero(cls, match, gram=None): r""" Nontrivial zero of a quadratic form, or the local obstruction. @@ -810,6 +1034,75 @@ def _solve_qf_zero(cls, match, gram=None): complete=False) +def _solve_legendre(cls, match): + r""" + Legendre equation ``a x^2 + b y^2 + c z^2 = 0`` via ``qfsolve``. + + EXAMPLES:: + + sage: from diophantine_classifier import solve + sage: x, y, z = solve("x^2 + 3*y^2 = 7*z^2").solutions[0] + sage: x^2 + 3*y^2 == 7*z^2 + True + """ + a, b, c = (_zz(match.data, k) for k in ("a", "b", "c")) + if None in (a, b, c): + raise SolverUnavailable("needs concrete coefficients") + return _solve_qf_zero(cls, match, + gram=[[a, 0, 0], [0, b, 0], [0, 0, c]]) + + +def _solve_weierstrass(cls, match): + r""" + Integral points on a Weierstrass model via ``E.integral_points``. + + EXAMPLES:: + + sage: from diophantine_classifier import solve + sage: sorted(solve("y^2 = x^3 - 2").solutions) # unknowns (y, x) + [(-5, 3), (5, 3)] + """ + if not cls.working.is_concrete: + raise SolverUnavailable("needs concrete coefficients") + ainvs = sage_eval(str(match.data["ainvs"])) if "ainvs" in match.data \ + else [0, 0, 0, 0, sage_eval(str(match.data["k"]))] + E = EllipticCurve(QQ, [QQ(t) for t in ainvs]) + pts = E.integral_points(both_signs=True) + sols = [(P[0], P[1]) for P in pts] + return SolutionSet( + _normalized(match), sorted(sols), "finite-complete", + f"all integral points on {E.ainvs()} (rank {E.rank()}); rational " + "points are infinite iff the rank is positive", complete=True) + + +def _solve_thue(cls, match): + r""" + Thue equation via PARI's certified ``thue`` solver. + + EXAMPLES:: + + sage: from diophantine_classifier import solve + sage: solve("x^3 + 2*y^3 = 11").solutions + [(3, -2)] + sage: solve("x^4 - 2*y^4 = 1").solutions + [(-1, 0), (1, 0)] + """ + pe = cls.working + P = pe.poly + x, y = pe.poly_ring.gens() + m = -P.constant_coefficient() + F = P + m + fu = F.subs({y: 1}).univariate_polynomial().change_variable_name("X") + try: + res = pari(f"thue(thueinit({fu},1),{m})") + sols = [(ZZ(v[0]), ZZ(v[1])) for v in res] + except Exception as err: + raise SolverUnavailable(f"PARI thue failed: {err}") from None + sols = [s for s in sols if F.subs({x: s[0], y: s[1]}) == m] + return SolutionSet(_normalized(match), sorted(sols), "finite-complete", + "all solutions (PARI thue, certified)", complete=True) + + def _solve_egyptian(cls, match): r""" Unit fraction equations ``1/x_1 + ... + 1/x_k = a/n``, concrete case. @@ -864,7 +1157,12 @@ def rec(k_left, target, minimum, acc): SOLVERS = { "univariate": _solve_univariate, "linear": _solve_linear, + "pell-like": _solve_pell_like, + "binary-qf-representation": _solve_bqf, "quadratic-form-zero": _solve_qf_zero, + "legendre": _solve_legendre, + "elliptic-weierstrass": _solve_weierstrass, + "thue": _solve_thue, "egyptian-fractions": _solve_egyptian, } diff --git a/docs/FAMILIES.md b/docs/FAMILIES.md index bf60e0c..86434cf 100644 --- a/docs/FAMILIES.md +++ b/docs/FAMILIES.md @@ -71,6 +71,34 @@ unimodular reduction of the quadratic part. **References.** Gauss, *Disquisitiones*; Lagrange 1768; Matthews, "The Diophantine equation ax²+bxy+cy² = N" (J. Théor. Nombres Bordeaux 14, 2002). +### `binary-qf-representation` — Representation by a binary quadratic form — P1, algorithmic +**Form.** ax² + bxy + cy² = n (n ≠ 0). +**Status.** Algorithmic via reduction theory and class-group structure; definite +forms: finitely many representations (Cornacchia); indefinite: finitely many orbits +under the automorph group. +**Software.** Sage: `BinaryQF(a,b,c).solve_integer(n)`; PARI: `qfbsolve`, +`qfbcornacchia`. +**References.** Cox, *Primes of the form x²+ny²*; Cornacchia 1908. + +### `pell-like` — Generalized Pell equation — P1, algorithmic +**Form.** x² − Dy² = N (D > 0 nonsquare). +**Status.** Finitely many classes of solutions, each an orbit under the Pell +automorph; found via continued fractions / LMM algorithm or quadratic-form class +theory. +**Software.** Sage: `solve_diophantine`; PARI: `qfbsolve(Qfb(1,0,-D), N)`. +**References.** Lagrange–Matthews–Mollin; Matthews, "The Diophantine equation +x²−Dy²=N" (2000); Mollin, *Fundamental Number Theory with Applications*. + +### `legendre` — Legendre / diagonal ternary quadratic — P1, algorithmic +**Form.** ax² + by² + cz² = 0 (nontrivial solutions; usually abc squarefree, mixed signs). +**Status.** Solvability by Legendre's criterion / Hasse–Minkowski; when solvable, a +point of provably small height exists (Holzer) and efficient algorithms find it; +all solutions parametrized from one (stereographic projection). +**Software.** Sage: `Conic([a,b,c]).has_rational_point(point=True)`, `qfsolve`; +PARI: `qfsolve`; Magma: `IsLocallySolvable`, `HasRationalPoint`. +**References.** Legendre 1785; Holzer 1950; Cremona–Rusin, "Efficient solution of +rational conics" (Math. Comp. 72, 2003); Simon 2005 (PARI `qfsolve`). + ### `quadratic-form-zero` — Isotropy of a quadratic form — P1, algorithmic **Form.** Q(x₁,…,x_k) = 0, Q a nondegenerate integral quadratic form, k ≥ 3. **Status.** Hasse–Minkowski: solvable iff solvable over ℝ and all ℚ_p (finite @@ -106,6 +134,18 @@ in integers" (Math. Proc. Camb. Phil. Soc. 89, 1981). ## 3. Cubic equations and genus one +### `elliptic-weierstrass` — Elliptic curve, Weierstrass form — P1, algorithmic* +**Form.** y² + a₁xy + a₃y = x³ + a₂x² + a₄x + a₆. +**Status.** Rational points: finitely generated (Mordell); rank computation via +descent is an algorithm *conditional* on Ш finiteness (hence the asterisk) but +succeeds in practice; integral points: finite (Siegel), effective (Baker), computed +by the elliptic-logarithm method once generators are known. +**Software.** Sage: `EllipticCurve.gens()`, `.integral_points()`, `.S_integral_points()` +(mwrank/eclib inside); PARI: `ellrank`, `ellratpoints`; Magma: `MordellWeilShaInformation`, +`IntegralPoints`. +**References.** Mordell 1922; Siegel 1929; Baker 1968; Gebel–Pethő–Zimmer 1994; +Stroeker–Tzanakis 1994; Cremona, *Algorithms for Modular Elliptic Curves*. + ### `cubic-surface` — Cubic surfaces / del Pezzo — P3, research **Form.** F(x, y, z, w) = 0 cubic (e.g. diagonal ax³+by³+cz³+dw³ = 0). **Status.** Rational points conjecturally dense once one exists (unirationality); @@ -127,6 +167,28 @@ transformation step for this part of the classifier. **References.** Evertse–Győry, *Unit Equations in Diophantine Number Theory*; Cremona–Stoll, "On the reduction theory of binary forms" (J. reine angew. Math. 565, 2003). +### `thue` — Thue equation — P1, algorithmic +**Form.** F(x, y) = m, F irreducible of degree ≥ 3. +**Status.** Finite (Thue 1909, via Diophantine approximation — ineffective); +effective via Baker 1968; practical algorithms Tzanakis–de Weger 1989, +Bilu–Hanrot 1996 (used by PARI). Fully automated today. +**Software.** PARI/Sage: `thueinit` + `thue` (rigorous with flag 1, may need GRH +certification for large fields); Magma: `Thue`. +**References.** Thue 1909; Baker 1968; Bilu–Hanrot, "Solving Thue equations of high +degree" (J. Number Theory 60, 1996). + +### `hyperelliptic` — Hyperelliptic curves — P1, effective (integral) / ineffective (rational) +**Form.** y² = f(x), f squarefree, deg f ≥ 5. +**Status.** Integral points: finite, effective (Baker); practical via Baker + LLL +or via unit equations. Rational points: finite for genus ≥ 2 (Faltings, +ineffective); in practice Chabauty–Coleman + Mordell–Weil sieve resolves most +instances of moderate genus/rank, quadratic Chabauty extends the range. +**Software.** Magma: `IntegralPoints` (genus 2), `Chabauty`, `MordellWeilSieve`; +Sage: `monsky_washnitzer`/Coleman integration (partial), `rational_points(bound)`; +PARI: `hyperellratpoints`. +**References.** Baker 1969; Faltings 1983; Chabauty 1941, Coleman 1985; +McCallum–Poonen survey 2012; Balakrishnan–Dogra–Müller–Tuitman–Vonk 2019. + ### `superelliptic` — Superelliptic curves — P1, effective (integral) **Form.** yᵐ = f(x), m ≥ 2, deg f ≥ 2 (genus ≥ 1 cases). **Status.** Integral points finite and effective (Baker); reduction to Thue diff --git a/tests/test_classify.py b/tests/test_classify.py index b45e5b1..e3d6386 100644 --- a/tests/test_classify.py +++ b/tests/test_classify.py @@ -14,12 +14,20 @@ ("12*x - 21*y + 30*z = 9", "", "linear"), ("x^2 - 5*x + 6 = 0", "", "univariate"), # quadratic, two variables + ("x^2 - 61*y^2 = 5", "", "pell-like"), ("2*x^2 + 3*x*y - 5*y^2 + x - 7 = 0", "", "binary-quadratic"), + ("3*x^2 + 7*y^2 = 19", "", "binary-qf-representation"), # quadratic, more variables + ("x^2 + 3*y^2 = 7*z^2", "", "legendre"), ("x^2 - 3*y^2 + 5*z^2 - 7*w^2 = 0", "", "quadratic-form-zero"), ("x^2 + x*y + y^2 + z^2 = 14", "", "quadratic-form-representation"), ("x^2 + y^2 - z^2 + 3*x - 7 = 0", "", "quadric"), + # genus one + ("y^2 + y = x^3 - x^2 - 10*x - 20", "", "elliptic-weierstrass"), # higher-genus curves and binary forms + ("x^3 + 2*y^3 = 11", "", "thue"), + ("x^4 - 2*y^4 = 1", "", "thue"), + ("y^2 = x^7 + 3", "", "hyperelliptic"), ("y^3 = x^4 + 2", "", "superelliptic"), ("x^3*y + y^3*z + z^3*x = 0", "", "general-curve"), # Klein quartic ("x^2*y^2 = x^3 + 1", "", "genus-one-curve"), @@ -73,6 +81,19 @@ def test_match_lookup_by_slug(): assert cls.data_for("no-such-family") is None +def test_code_is_filled_from_each_match_not_the_primary(): + """An ancestor's template gets the ancestor's own data.""" + cls = classify("x^2 + 3*y^2 = 7*z^2") + assert cls.slug == "legendre" + code = cls.code() + assert "[1, 3, -7]" in code["sage (legendre)"] + # quadratic-form-zero wants a Gram matrix, which the Legendre match's + # a/b/c cannot supply; it was matched too, so it fills from its own data + gram = code["sage (quadratic-form-zero)"] + assert "{gram}" not in gram + assert "[[1, 0, 0], [0, 3, 0], [0, 0, -7]]" in gram + + def test_repeated_factor_preserves_input(): text = "(x + y)^2 = 0" cls = classify(text, domain="QQ") @@ -160,6 +181,21 @@ def test_conditions_serialize_structurally(): json.dumps(d) +def test_multi_parent_lineage_has_no_false_edge(): + """legendre specializes both quadratic-form-zero and diagonal-form; + those two are siblings, and no output may put an arrow between them.""" + cls = classify("x^2 + 3*y^2 = 7*z^2") + assert cls.slug == "legendre" + paths = cls.as_dict()["lineage_paths"] + assert {path[1] for path in paths} == {"quadratic-form-zero", + "diagonal-form"} + for line in _lineage_lines(cls): + assert "quadratic-form-zero → diagonal-form" not in line + assert "diagonal-form → quadratic-form-zero" not in line + + +# --- conditional identities never reach the matchers (brief 3.3) --------- + def test_conditional_identity_does_not_enter_polynomial_matchers(): cls = classify("x/x = 1") assert cls.parsed.is_conditional_identity diff --git a/tests/test_iterators.py b/tests/test_iterators.py index cf8e823..a74dde2 100644 --- a/tests/test_iterators.py +++ b/tests/test_iterators.py @@ -5,6 +5,13 @@ from diophantine_classifier import solve +def test_pell_like_stream(): + s = solve("x^2 - 2*y^2 = 7") + sols = s.first(12) + assert len(set(sols)) == 12 + assert all(x ** 2 - 2 * y ** 2 == 7 for x, y in sols) + + def test_linear_stream(): s = solve("3*x + 5*y = 1") sols = s.first(9) @@ -18,3 +25,6 @@ def test_egyptian_complete(): assert s.complete +def test_finite_iteration_matches_list(): + s = solve("x^3 + 2*y^3 = 11") + assert list(iter(s)) == s.solutions diff --git a/tests/test_solvers.py b/tests/test_solvers.py index 6f75753..1b8b095 100644 --- a/tests/test_solvers.py +++ b/tests/test_solvers.py @@ -25,6 +25,32 @@ def test_solver_does_not_emit_denominator_pole(): assert_valid_solutions("1/(x - 1) = 1/(y - 1)", S.first(30)) +def test_pell_like(): + s = solve("x^2 - 2*y^2 = 7") + assert s.solutions + for x, y in s.solutions: + assert x ** 2 - 2 * y ** 2 == 7 + assert s.complete # orbit representatives + automorph action + + +def test_bqf(): + s = solve("3*x^2 + 7*y^2 = 19") + assert s.solutions == [(-2, -1), (-2, 1), (2, -1), (2, 1)] + assert s.complete + + +def test_legendre(): + s = solve("x^2 + y^2 = 2*z^2") + ((x, y, z),) = s.solutions + assert x ** 2 + y ** 2 == 2 * z ** 2 + assert (x, y, z) != (0, 0, 0) + + +def test_legendre_obstruction(): + s = solve("x^2 + y^2 = 3*z^2") + assert s.kind == "empty" and s.complete + + def test_linear(): s = solve("3*x + 5*y = 1") ((x, y),) = s.solutions @@ -41,6 +67,19 @@ def test_univariate(): assert [t[0] for t in s.solutions] == [2, 3] +def test_thue(): + s = solve("x^3 + 2*y^3 = 11") + assert (3, -2) in s.solutions + assert s.complete + assert_valid_solutions("x^3 + 2*y^3 = 11", s.solutions) + + +def test_unavailable_carries_hints(): + with pytest.raises(SolverUnavailable) as err: + solve("y^2 = x^7 + 3") + assert "magma" in str(err.value).lower() or "Chabauty" in str(err.value) + + def test_linear_over_qq_is_not_integer_gcd_problem(): S = solve("2*x + 4*y = 1", domain="QQ") assert S.kind != "empty"