From b2671785f69a7c5c5ac7ffd7681e432f3cbf9350 Mon Sep 17 00:00:00 2001 From: Alcides Fonseca Date: Mon, 17 Aug 2026 11:59:57 +0000 Subject: [PATCH 1/2] Add mathematical programming benchmark examples --- examples/benchmarks/mathematical.py | 263 ++++++++++++++++++ examples/mathematical_programming/__init__.py | 1 + .../fixed_egyptian_fractions.py | 16 ++ .../incremented_reciprocal.py | 15 + .../rational_egyptian_fractions.py | 17 ++ .../reptend_fraction.py | 15 + tests/benchmarks/test_mathematical.py | 46 +++ 7 files changed, 373 insertions(+) create mode 100644 examples/benchmarks/mathematical.py create mode 100644 examples/mathematical_programming/__init__.py create mode 100644 examples/mathematical_programming/fixed_egyptian_fractions.py create mode 100644 examples/mathematical_programming/incremented_reciprocal.py create mode 100644 examples/mathematical_programming/rational_egyptian_fractions.py create mode 100644 examples/mathematical_programming/reptend_fraction.py create mode 100644 tests/benchmarks/test_mathematical.py diff --git a/examples/benchmarks/mathematical.py b/examples/benchmarks/mathematical.py new file mode 100644 index 00000000..41c9163d --- /dev/null +++ b/examples/benchmarks/mathematical.py @@ -0,0 +1,263 @@ +"""Benchmarks based on Vaguery's mathematical programming challenges. + +The scoring functions use :class:`fractions.Fraction` deliberately. These +problems are about exact rational arithmetic and floating-point fitness would +make correct programs appear incorrect for sufficiently large inputs. +""" + +from __future__ import annotations + +from abc import ABC +from dataclasses import dataclass +from fractions import Fraction +from typing import Annotated + +from examples.benchmarks.benchmark import Benchmark +from geneticengine.grammar.grammar import Grammar, extract_grammar +from geneticengine.grammar.metahandlers.ints import IntRange +from geneticengine.problems import Problem, SingleObjectiveProblem + + +class IntegerExpression(ABC): + def evaluate(self, values: tuple[int, ...]) -> int: + raise NotImplementedError + + +@dataclass +class IntegerLiteral(IntegerExpression): + value: Annotated[int, IntRange(-100, 100)] + + def evaluate(self, values: tuple[int, ...]) -> int: + return self.value + + +@dataclass +class InputInteger(IntegerExpression): + index: Annotated[int, IntRange(0, 1)] + + def evaluate(self, values: tuple[int, ...]) -> int: + return values[self.index] + + +@dataclass +class Add(IntegerExpression): + left: IntegerExpression + right: IntegerExpression + + def evaluate(self, values: tuple[int, ...]) -> int: + return self.left.evaluate(values) + self.right.evaluate(values) + + +@dataclass +class Subtract(IntegerExpression): + left: IntegerExpression + right: IntegerExpression + + def evaluate(self, values: tuple[int, ...]) -> int: + return self.left.evaluate(values) - self.right.evaluate(values) + + +@dataclass +class Multiply(IntegerExpression): + left: IntegerExpression + right: IntegerExpression + + def evaluate(self, values: tuple[int, ...]) -> int: + return self.left.evaluate(values) * self.right.evaluate(values) + + +@dataclass +class Quotient(IntegerExpression): + left: IntegerExpression + right: IntegerExpression + + def evaluate(self, values: tuple[int, ...]) -> int: + denominator = self.right.evaluate(values) + return 0 if denominator == 0 else self.left.evaluate(values) // denominator + + +@dataclass +class Remainder(IntegerExpression): + left: IntegerExpression + right: IntegerExpression + + def evaluate(self, values: tuple[int, ...]) -> int: + denominator = self.right.evaluate(values) + return 0 if denominator == 0 else self.left.evaluate(values) % denominator + + +@dataclass +class FiveUnitFractions: + terms: tuple[IntegerExpression, IntegerExpression, IntegerExpression, IntegerExpression, IntegerExpression] + + def evaluate(self, values: tuple[int, ...]) -> tuple[int, ...]: + return tuple(term.evaluate(values) for term in self.terms) + + +@dataclass +class IntegerPair: + numerator: IntegerExpression + denominator: IntegerExpression + + def evaluate(self, values: tuple[int, ...]) -> tuple[int, int]: + return self.numerator.evaluate(values), self.denominator.evaluate(values) + + +def fixed_egyptian_fraction(k: int) -> tuple[int, ...]: + """Return five distinct nonzero denominators summing to ``1/k``.""" + return 2 * k, 3 * k, 6 * k, 7 * k, -7 * k + + +def rational_egyptian_fraction(numerator: int, denominator: int) -> tuple[int, ...]: + """Find five signed unit fractions for a small rational target. + + The finite benchmark domain is intentionally small; exhaustive search is + used for the reference oracle so the benchmark remains independently + checkable and does not encode a particular synthesis strategy. + """ + target = Fraction(numerator, denominator) + forbidden = {numerator, denominator} + candidates = [i for i in range(-200, 201) if i and i not in forbidden] + for a in candidates: + for b in candidates: + if b == a: + continue + remainder = target - Fraction(1, a) - Fraction(1, b) + if remainder.numerator not in (-1, 1): + continue + c = remainder.denominator * remainder.numerator + if c in forbidden or c in {a, b} or c not in candidates: + continue + d = next((x for x in candidates if x not in {a, b, c} and -x in candidates), None) + if d is not None and -d not in {a, b, c, d}: + return a, b, c, d, -d + raise ValueError(f"no five-term representation found for {numerator}/{denominator}") + + +def reptend_fraction(prefix: int, repetend: int) -> tuple[int, int]: + prefix_digits = len(str(prefix)) + repetend_digits = len(str(repetend)) + cycle = 10**repetend_digits - 1 + return prefix * cycle + repetend, 10**prefix_digits * cycle + + +def incremented_reciprocal(n: int) -> tuple[int, int]: + """Increment every digit of the repeating decimal expansion of ``1/n``.""" + if n <= 0: + raise ValueError("n must be positive") + remainder = 1 % n + seen: dict[int, int] = {} + digits: list[int] = [] + while remainder and remainder not in seen: + seen[remainder] = len(digits) + remainder *= 10 + digits.append(remainder // n) + remainder %= n + cycle_start = seen.get(remainder, len(digits)) + nonrepeating = digits[:cycle_start] + repeating = digits[cycle_start:] or [0] + shifted_nonrepeating = [((digit + 1) % 10) for digit in nonrepeating] + shifted_repeating = [((digit + 1) % 10) for digit in repeating] + scale = 10 ** len(shifted_nonrepeating) + prefix = 0 + for digit in shifted_nonrepeating: + prefix = 10 * prefix + digit + cycle_value = 0 + for digit in shifted_repeating: + cycle_value = 10 * cycle_value + digit + denominator = scale * (10 ** len(shifted_repeating) - 1) + value = Fraction(prefix, scale) + Fraction(cycle_value, denominator) + return value.numerator, value.denominator + + +def _unit_fraction_error(candidate: tuple[int, ...], target: Fraction, forbidden: set[int]) -> float: + if len(candidate) != 5 or any(value == 0 for value in candidate): + return 1_000_000.0 + error = abs(sum((Fraction(1, value) for value in candidate), Fraction()) - target) + penalty = 0 if len(set(candidate)) == 5 and not forbidden.intersection(candidate) else 1 + return float(error) + penalty + + +def _pair_error(candidate: tuple[int, int], target: Fraction) -> float: + numerator, denominator = candidate + if denominator == 0: + return 1_000_000.0 + return float(abs(Fraction(numerator, denominator) - target)) + + +class _IntegerExpressionBenchmark(Benchmark): + expression_nodes = [IntegerLiteral, InputInteger, Add, Subtract, Multiply, Quotient, Remainder] + + def get_grammar(self) -> Grammar: + return extract_grammar(self.expression_nodes + [self.root_type], self.root_type) + + +class FixedEgyptianFractionBenchmark(_IntegerExpressionBenchmark): + root_type = FiveUnitFractions + + def __init__(self) -> None: + self.problem = SingleObjectiveProblem( + minimize=True, + target=0, + fitness_function=lambda candidate: _unit_fraction_error( + candidate.evaluate((7, 0)), Fraction(1, 7), {7} + ), + ) + + def get_problem(self) -> Problem: + return self.problem + + +class RationalEgyptianFractionBenchmark(_IntegerExpressionBenchmark): + root_type = FiveUnitFractions + + def __init__(self) -> None: + target = Fraction(5, 12) + self.problem = SingleObjectiveProblem( + minimize=True, + target=0, + fitness_function=lambda candidate: _unit_fraction_error( + candidate.evaluate((5, 12)), target, {5, 12} + ), + ) + + def get_problem(self) -> Problem: + return self.problem + + +class ReptendFractionBenchmark(_IntegerExpressionBenchmark): + root_type = IntegerPair + + def __init__(self) -> None: + target = Fraction(*reptend_fraction(539, 4762)) + self.problem = SingleObjectiveProblem( + minimize=True, + target=0, + fitness_function=lambda candidate: _pair_error(candidate.evaluate((539, 4762)), target), + ) + + def get_problem(self) -> Problem: + return self.problem + + +class IncrementedReciprocalBenchmark(_IntegerExpressionBenchmark): + root_type = IntegerPair + + def __init__(self) -> None: + target = Fraction(*incremented_reciprocal(7)) + self.problem = SingleObjectiveProblem( + minimize=True, + target=0, + fitness_function=lambda candidate: _pair_error(candidate.evaluate((7, 0)), target), + ) + + def get_problem(self) -> Problem: + return self.problem + + +BENCHMARKS = [ + FixedEgyptianFractionBenchmark, + RationalEgyptianFractionBenchmark, + ReptendFractionBenchmark, + IncrementedReciprocalBenchmark, +] diff --git a/examples/mathematical_programming/__init__.py b/examples/mathematical_programming/__init__.py new file mode 100644 index 00000000..b2f2afe6 --- /dev/null +++ b/examples/mathematical_programming/__init__.py @@ -0,0 +1 @@ +"""Examples for the mathematical programming challenges.""" diff --git a/examples/mathematical_programming/fixed_egyptian_fractions.py b/examples/mathematical_programming/fixed_egyptian_fractions.py new file mode 100644 index 00000000..3e1f7264 --- /dev/null +++ b/examples/mathematical_programming/fixed_egyptian_fractions.py @@ -0,0 +1,16 @@ +"""Solve the fixed-size Egyptian-fraction challenge for k = 7.""" + +from fractions import Fraction + +from examples.benchmarks.mathematical import fixed_egyptian_fraction + + +def solve(k: int = 7) -> tuple[int, ...]: + terms = fixed_egyptian_fraction(k) + assert Fraction(1, k) == sum((Fraction(1, term) for term in terms), Fraction()) + assert len(set(terms)) == 5 + return terms + + +if __name__ == "__main__": + print(solve()) diff --git a/examples/mathematical_programming/incremented_reciprocal.py b/examples/mathematical_programming/incremented_reciprocal.py new file mode 100644 index 00000000..778b1aa6 --- /dev/null +++ b/examples/mathematical_programming/incremented_reciprocal.py @@ -0,0 +1,15 @@ +"""Solve the digitwise-modulo reciprocal challenge.""" + +from fractions import Fraction + +from examples.benchmarks.mathematical import incremented_reciprocal + + +def solve(n: int = 7) -> tuple[int, int]: + result = incremented_reciprocal(n) + assert Fraction(*result) == Fraction(16, 63) + return result + + +if __name__ == "__main__": + print(solve()) diff --git a/examples/mathematical_programming/rational_egyptian_fractions.py b/examples/mathematical_programming/rational_egyptian_fractions.py new file mode 100644 index 00000000..c0717ee5 --- /dev/null +++ b/examples/mathematical_programming/rational_egyptian_fractions.py @@ -0,0 +1,17 @@ +"""Solve the generalized Egyptian-fraction challenge for 5/12.""" + +from fractions import Fraction + +from examples.benchmarks.mathematical import rational_egyptian_fraction + + +def solve(numerator: int = 5, denominator: int = 12) -> tuple[int, ...]: + terms = rational_egyptian_fraction(numerator, denominator) + assert Fraction(numerator, denominator) == sum((Fraction(1, term) for term in terms), Fraction()) + assert len(set(terms)) == 5 + assert numerator not in terms and denominator not in terms + return terms + + +if __name__ == "__main__": + print(solve()) diff --git a/examples/mathematical_programming/reptend_fraction.py b/examples/mathematical_programming/reptend_fraction.py new file mode 100644 index 00000000..2104064e --- /dev/null +++ b/examples/mathematical_programming/reptend_fraction.py @@ -0,0 +1,15 @@ +"""Solve the repeating-decimal rational-number challenge.""" + +from fractions import Fraction + +from examples.benchmarks.mathematical import reptend_fraction + + +def solve(prefix: int = 539, repetend: int = 4762) -> tuple[int, int]: + result = reptend_fraction(prefix, repetend) + assert Fraction(*result) == Fraction(prefix * 9999 + repetend, 1000 * 9999) + return result + + +if __name__ == "__main__": + print(solve()) diff --git a/tests/benchmarks/test_mathematical.py b/tests/benchmarks/test_mathematical.py new file mode 100644 index 00000000..3db5afdd --- /dev/null +++ b/tests/benchmarks/test_mathematical.py @@ -0,0 +1,46 @@ +from fractions import Fraction + +from examples.benchmarks.mathematical import FixedEgyptianFractionBenchmark +from examples.benchmarks.mathematical import IncrementedReciprocalBenchmark +from examples.benchmarks.mathematical import RationalEgyptianFractionBenchmark +from examples.benchmarks.mathematical import ReptendFractionBenchmark +from examples.benchmarks.mathematical import fixed_egyptian_fraction +from examples.benchmarks.mathematical import incremented_reciprocal +from examples.benchmarks.mathematical import reptend_fraction + + +def test_fixed_egyptian_fraction_is_valid(): + terms = fixed_egyptian_fraction(7) + assert len(set(terms)) == 5 + assert Fraction(1, 7) == sum((Fraction(1, term) for term in terms), Fraction()) + + +def test_reptend_example(): + numerator, denominator = reptend_fraction(539, 4762) + assert Fraction(numerator, denominator) == Fraction(539 * 9999 + 4762, 1000 * 9999) + + +def test_incremented_reciprocal_examples(): + assert incremented_reciprocal(1) == (1, 9) + assert incremented_reciprocal(2) == (11, 18) + # The page prints 16/163, but 0.overline{253968} reduces to 16/63. + assert incremented_reciprocal(7) == (16, 63) + + +def test_benchmarks_expose_zero_fitness_reference_targets(): + fixed = FixedEgyptianFractionBenchmark() + candidate = type("C", (), {"evaluate": staticmethod(lambda _: fixed_egyptian_fraction(7))})() + assert fixed.get_problem().evaluate(candidate).fitness_components == [0] + reptend = ReptendFractionBenchmark() + assert reptend.get_problem().target == [0] + assert incremented_reciprocal(7) == (16, 63) + + +def test_all_benchmark_grammars_are_constructible(): + for benchmark in ( + FixedEgyptianFractionBenchmark(), + RationalEgyptianFractionBenchmark(), + ReptendFractionBenchmark(), + IncrementedReciprocalBenchmark(), + ): + assert benchmark.get_grammar() is not None From 824afa58705ab19072582865684217d7ac3c5d92 Mon Sep 17 00:00:00 2001 From: Alcides Fonseca Date: Mon, 17 Aug 2026 12:10:33 +0000 Subject: [PATCH 2/2] Run mathematical examples from example suite --- .../fixed_egyptian_fractions.py | 16 ++++------------ .../incremented_reciprocal.py | 15 ++++----------- .../rational_egyptian_fractions.py | 17 ++++------------- .../reptend_fraction.py | 15 ++++----------- run_examples.sh | 5 +++++ 5 files changed, 21 insertions(+), 47 deletions(-) diff --git a/examples/mathematical_programming/fixed_egyptian_fractions.py b/examples/mathematical_programming/fixed_egyptian_fractions.py index 3e1f7264..f8f6b923 100644 --- a/examples/mathematical_programming/fixed_egyptian_fractions.py +++ b/examples/mathematical_programming/fixed_egyptian_fractions.py @@ -1,16 +1,8 @@ -"""Solve the fixed-size Egyptian-fraction challenge for k = 7.""" +"""Genetic programming example for fixed-size Egyptian fractions.""" -from fractions import Fraction - -from examples.benchmarks.mathematical import fixed_egyptian_fraction - - -def solve(k: int = 7) -> tuple[int, ...]: - terms = fixed_egyptian_fraction(k) - assert Fraction(1, k) == sum((Fraction(1, term) for term in terms), Fraction()) - assert len(set(terms)) == 5 - return terms +from examples.benchmarks.benchmark import example_run +from examples.benchmarks.mathematical import FixedEgyptianFractionBenchmark if __name__ == "__main__": - print(solve()) + example_run(FixedEgyptianFractionBenchmark()) diff --git a/examples/mathematical_programming/incremented_reciprocal.py b/examples/mathematical_programming/incremented_reciprocal.py index 778b1aa6..9097ed4a 100644 --- a/examples/mathematical_programming/incremented_reciprocal.py +++ b/examples/mathematical_programming/incremented_reciprocal.py @@ -1,15 +1,8 @@ -"""Solve the digitwise-modulo reciprocal challenge.""" +"""Genetic programming example for the incremented reciprocal challenge.""" -from fractions import Fraction - -from examples.benchmarks.mathematical import incremented_reciprocal - - -def solve(n: int = 7) -> tuple[int, int]: - result = incremented_reciprocal(n) - assert Fraction(*result) == Fraction(16, 63) - return result +from examples.benchmarks.benchmark import example_run +from examples.benchmarks.mathematical import IncrementedReciprocalBenchmark if __name__ == "__main__": - print(solve()) + example_run(IncrementedReciprocalBenchmark()) diff --git a/examples/mathematical_programming/rational_egyptian_fractions.py b/examples/mathematical_programming/rational_egyptian_fractions.py index c0717ee5..b17d74e3 100644 --- a/examples/mathematical_programming/rational_egyptian_fractions.py +++ b/examples/mathematical_programming/rational_egyptian_fractions.py @@ -1,17 +1,8 @@ -"""Solve the generalized Egyptian-fraction challenge for 5/12.""" +"""Genetic programming example for rational-target Egyptian fractions.""" -from fractions import Fraction - -from examples.benchmarks.mathematical import rational_egyptian_fraction - - -def solve(numerator: int = 5, denominator: int = 12) -> tuple[int, ...]: - terms = rational_egyptian_fraction(numerator, denominator) - assert Fraction(numerator, denominator) == sum((Fraction(1, term) for term in terms), Fraction()) - assert len(set(terms)) == 5 - assert numerator not in terms and denominator not in terms - return terms +from examples.benchmarks.benchmark import example_run +from examples.benchmarks.mathematical import RationalEgyptianFractionBenchmark if __name__ == "__main__": - print(solve()) + example_run(RationalEgyptianFractionBenchmark()) diff --git a/examples/mathematical_programming/reptend_fraction.py b/examples/mathematical_programming/reptend_fraction.py index 2104064e..9e5aff41 100644 --- a/examples/mathematical_programming/reptend_fraction.py +++ b/examples/mathematical_programming/reptend_fraction.py @@ -1,15 +1,8 @@ -"""Solve the repeating-decimal rational-number challenge.""" +"""Genetic programming example for repeating-decimal rational numbers.""" -from fractions import Fraction - -from examples.benchmarks.mathematical import reptend_fraction - - -def solve(prefix: int = 539, repetend: int = 4762) -> tuple[int, int]: - result = reptend_fraction(prefix, repetend) - assert Fraction(*result) == Fraction(prefix * 9999 + repetend, 1000 * 9999) - return result +from examples.benchmarks.benchmark import example_run +from examples.benchmarks.mathematical import ReptendFractionBenchmark if __name__ == "__main__": - print(solve()) + example_run(ReptendFractionBenchmark()) diff --git a/run_examples.sh b/run_examples.sh index 9347c1e8..6bcaf72c 100755 --- a/run_examples.sh +++ b/run_examples.sh @@ -66,6 +66,11 @@ run_example examples/benchmarks/santafe.py run_example examples/benchmarks/string_match.py run_example examples/benchmarks/vectorialgp.py +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/progsys/Number_IO.py run_example examples/progsys/Median.py