diff --git a/diophantine_classifier/data/families/ramanujan-nagell.yaml b/diophantine_classifier/data/families/ramanujan-nagell.yaml new file mode 100644 index 0000000..7551816 --- /dev/null +++ b/diophantine_classifier/data/families/ramanujan-nagell.yaml @@ -0,0 +1,26 @@ +# Family: Generalized Ramanujan-Nagell 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: ramanujan-nagell +name: Generalized Ramanujan-Nagell equation +priority: 1 +status: effective +class: expdioph +form: 'x^2 + d = k*b^n (classical: x^2 + 7 = 2^n)' +parents: +- polynomial-exponential +matcher: true +methods: +- hypergeometric method (Beukers) +- Baker + LLL (Petho-de Weger) +notes: 'Classical case: n in {3,4,5,7,15}. At most 2 solutions for general d (Beukers).' +examples: +- x^2 + 7 = 2^n +- x^2 + 11 = 3^n +references: +- key: Nagell1961 + why: the proof that n lies in {3, 4, 5, 7, 15} +- key: Beukers1981 + why: 'sharp bounds: at most two solutions of the generalized equation' +- key: deWeger1989 + why: the Baker + LLL toolkit for concrete instances diff --git a/diophantine_classifier/solvers.py b/diophantine_classifier/solvers.py index b2aa55c..ce5f69e 100644 --- a/diophantine_classifier/solvers.py +++ b/diophantine_classifier/solvers.py @@ -1154,6 +1154,30 @@ def rec(k_left, target, minimum, acc): "permutations give the rest", complete=True) +def _solve_ramanujan_nagell(cls, match): + r""" + The classical Ramanujan-Nagell equation ``x^2 + 7 = 2^n``. + + EXAMPLES:: + + sage: from diophantine_classifier import solve + sage: solve("x^2 + 7 = 2^n").solutions + [(1, 3), (3, 4), (5, 5), (11, 7), (181, 15)] + """ + d, k, base = _zz(match.data, "d"), _zz(match.data, "k"), _zz(match.data, "base") + if (d, k, base) != (7, 1, 2): + raise SolverUnavailable( + "only the classical x^2 + 7 = 2^n is hardwired; general (d, k, b) " + "need a Baker + LLL computation (Petho-de Weger)") + pairs = [(ZZ(x), ZZ(n)) + for x, n in [(1, 3), (3, 4), (5, 5), (11, 7), (181, 15)]] + return SolutionSet( + _normalized(match), pairs, "finite-complete", + "Nagell's theorem: n ∈ {3, 4, 5, 7, 15} (x > 0 shown; -x symmetric)", + complete=True) + + +#: dispatch table: family slug -> solver function SOLVERS = { "univariate": _solve_univariate, "linear": _solve_linear, @@ -1164,6 +1188,7 @@ def rec(k_left, target, minimum, acc): "elliptic-weierstrass": _solve_weierstrass, "thue": _solve_thue, "egyptian-fractions": _solve_egyptian, + "ramanujan-nagell": _solve_ramanujan_nagell, } diff --git a/docs/FAMILIES.md b/docs/FAMILIES.md index 86434cf..1d9c687 100644 --- a/docs/FAMILIES.md +++ b/docs/FAMILIES.md @@ -248,6 +248,17 @@ equations: a miscellany" (Int. J. Number Theory 11, 2015). ## 6. Polynomial–exponential equations +### `ramanujan-nagell` — (Generalized) Ramanujan–Nagell — P1, effective +**Form.** x² + d = k·bⁿ (classical: x² + 7 = 2ⁿ); more generally f(x) = k·bⁿ with +f quadratic. +**Status.** Classical case: exactly n ∈ {3,4,5,7,15} (conjectured Ramanujan 1913, +proved Nagell 1948). Generalized: at most 2 solutions apart from finitely many +explicit exceptional d (Apéry 1960, Beukers 1981 with sharp bounds — hypergeometric +method); fully effective; practical resolution via Baker + LLL (Pethő–de Weger). +**Software.** scripts via PARI/Sage (no standard intrinsic); de Weger's algorithms. +**References.** Ramanujan 1913; Nagell 1948; Apéry 1960; Beukers 1981; +de Weger, *Algorithms for Diophantine Equations* (1989). + ### `power-values` — Power values of polynomials (Schinzel–Tijdeman) — P2, effective in n **Form.** f(x) = c·yⁿ, f fixed polynomial with ≥ 2 distinct roots, n ≥ 2 unknown. **Status.** n is effectively bounded (Schinzel–Tijdeman 1976); for each fixed n it diff --git a/tests/test_classify.py b/tests/test_classify.py index e3d6386..15793a8 100644 --- a/tests/test_classify.py +++ b/tests/test_classify.py @@ -39,6 +39,8 @@ # diagonal / surfaces ("x^4 + y^4 + z^4 = w^4", "", "equal-sums-like-powers"), # Elkies # polynomial-exponential + ("x^2 + 7 = 2^n", "", "ramanujan-nagell"), + ("x^2 + 11 = 3^n", "", "ramanujan-nagell"), ("x^3 - 4 = y^n", "", "power-values"), # unit fractions ("1/x + 1/y + 1/z = 1", "", "egyptian-fractions"), @@ -73,6 +75,12 @@ def test_gen_fermat_regimes(): assert hyperbolic.data["regime"] == "hyperbolic" +def test_explain_smoke(): + text = classify("x^2 + 7 = 2^n").explain() + assert "ramanujan-nagell" in text + assert "Nagell" in text or "1948" in text + + def test_match_lookup_by_slug(): cls = classify("3*x + 5*y = 1") assert cls.match_for("linear").slug == "linear" diff --git a/tests/test_solvers.py b/tests/test_solvers.py index 1b8b095..49eb976 100644 --- a/tests/test_solvers.py +++ b/tests/test_solvers.py @@ -74,6 +74,12 @@ def test_thue(): assert_valid_solutions("x^3 + 2*y^3 = 11", s.solutions) +def test_ramanujan_nagell(): + s = solve("x^2 + 7 = 2^n") + assert (11, 7) in s.solutions and len(s.solutions) == 5 + assert s.complete + + def test_unavailable_carries_hints(): with pytest.raises(SolverUnavailable) as err: solve("y^2 = x^7 + 3")