Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions examples/benchmarks/benchmark_suite.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from examples.benchmarks.mario_level import MarioBenchmark
from examples.benchmarks.lambda_calculus import LambdaCalculusBenchmark
from examples.benchmarks.median import MedianBenchmark
from examples.benchmarks.pell import PellsEquationBenchmark


banknote = get_banknote()
Expand All @@ -40,6 +41,7 @@
MarioBenchmark(),
LambdaCalculusBenchmark(),
MedianBenchmark(),
PellsEquationBenchmark(),
]


Expand Down
57 changes: 57 additions & 0 deletions examples/benchmarks/pell.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
"""A benchmark for the positive solutions of Pell's equation.

The default instance is ``x² - 2y² = 1``. Pell solutions are conventionally
positive; allowing arbitrary signed integers would make minimizing ``x + y``
unbounded below because the negation of every solution is also a solution.
"""

from __future__ import annotations

from dataclasses import dataclass
from typing import Annotated

from examples.benchmarks.benchmark import Benchmark, example_run
from geneticengine.grammar.grammar import Grammar, extract_grammar
from geneticengine.grammar.metahandlers.ints import IntRange
from geneticengine.problems import Problem, SingleObjectiveProblem


@dataclass
class PellPair:
x: Annotated[int, IntRange(1, 100)]
y: Annotated[int, IntRange(1, 100)]

def evaluate(self, d: int) -> tuple[int, int]:
return self.x, self.y


def pell_fitness(candidate: PellPair, d: int = 2) -> float:
"""Penalize equation violations, then minimize the positive coordinate sum."""
x, y = candidate.evaluate(d)
residual = x * x - d * y * y - 1
return abs(residual) * 1_000_000 + x + y


class PellsEquationBenchmark(Benchmark):
"""Find the smallest positive solution to ``x² - 2y² = 1``."""

def __init__(self, d: int = 2) -> None:
if d <= 0 or int(d**0.5) ** 2 == d:
raise ValueError("d must be positive and nonsquare")
self.d = d
self.problem = SingleObjectiveProblem(
minimize=True,
target=5 if d == 2 else None,
fitness_function=lambda candidate: pell_fitness(candidate, d),
)
self.grammar = extract_grammar([PellPair], PellPair)

def get_problem(self) -> Problem:
return self.problem

def get_grammar(self) -> Grammar:
return self.grammar


if __name__ == "__main__":
example_run(PellsEquationBenchmark())
8 changes: 8 additions & 0 deletions examples/mathematical_programming/pells_equation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"""Genetic programming example for Pell's equation."""

from examples.benchmarks.benchmark import example_run
from examples.benchmarks.pell import PellsEquationBenchmark


if __name__ == "__main__":
example_run(PellsEquationBenchmark())
1 change: 1 addition & 0 deletions run_examples.sh
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ run_example examples/mathematical_programming/fixed_egyptian_fractions.py
run_example examples/mathematical_programming/rational_egyptian_fractions.py
run_example examples/mathematical_programming/reptend_fraction.py
run_example examples/mathematical_programming/incremented_reciprocal.py
run_example examples/mathematical_programming/pells_equation.py


run_example examples/progsys/Number_IO.py
Expand Down
10 changes: 10 additions & 0 deletions tests/benchmarks/test_mathematical.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
from examples.benchmarks.mathematical import IncrementedReciprocalBenchmark
from examples.benchmarks.mathematical import RationalEgyptianFractionBenchmark
from examples.benchmarks.mathematical import ReptendFractionBenchmark
from examples.benchmarks.pell import PellsEquationBenchmark
from examples.benchmarks.pell import PellPair
from examples.benchmarks.pell import pell_fitness
from examples.benchmarks.mathematical import fixed_egyptian_fraction
from examples.benchmarks.mathematical import incremented_reciprocal
from examples.benchmarks.mathematical import reptend_fraction
Expand Down Expand Up @@ -42,5 +45,12 @@ def test_all_benchmark_grammars_are_constructible():
RationalEgyptianFractionBenchmark(),
ReptendFractionBenchmark(),
IncrementedReciprocalBenchmark(),
PellsEquationBenchmark(),
):
assert benchmark.get_grammar() is not None


def test_pells_equation_minimizes_the_smallest_positive_solution():
assert pell_fitness(PellPair(3, 2)) == 5
assert pell_fitness(PellPair(1, 1)) > pell_fitness(PellPair(3, 2))
assert PellsEquationBenchmark().get_problem().target == [5]
Loading