AgentSquares / test_packing.py
DHDRL's picture
Upload 107 files
6cb442b verified
Raw History Blame Contribute Delete
9.34 kB
"""Run: python test_packing.py (or: python -m pytest -q)"""
from fractions import Fraction as F
import math
from packing import Sq, validate, parse_squares
from bounds import classify, find_conflicts
one, zero = F(1), F(0)
def axis(x, y):
return Sq(F(x), F(y), one, zero)
def five(S):
c, s = math.cos(math.pi / 4), math.sin(math.pi / 4)
return [Sq(0.5, 0.5, 1.0, 0.0), Sq(S - 0.5, 0.5, 1.0, 0.0),
Sq(0.5, S - 0.5, 1.0, 0.0), Sq(S - 0.5, S - 0.5, 1.0, 0.0), Sq(S / 2, S / 2, c, s)]
def test_user_cases():
four = [axis(F(1, 2), F(1, 2)), axis(F(3, 2), F(1, 2)), axis(F(1, 2), F(3, 2)), axis(F(3, 2), F(3, 2))]
r = validate(four, F(2))
assert r["valid"] and r["mode"] == "exact" and r["proof"]
# draft bug: axis(1, ...) passed int 1 -> draft fell into float mode; now stays exact
r = validate([axis(F(1, 2), F(1, 2)), axis(1, F(1, 2))], F(2))
assert not r["valid"] and r["max_overlap"] == 0.5 and r["mode"] == "exact"
q = Sq(F(7, 10), F(7, 10), F(3, 5), F(4, 5))
assert validate([q], F(7, 5))["valid"]
assert not validate([q], F(7, 5) - F(1, 10**9))["valid"]
S = 2 + math.sqrt(2) / 2
r = validate(five(S), S)
assert r["valid"] and not r["proof"] and r["mode"].startswith("float")
assert not validate(five(2.70), 2.70)["valid"]
def test_classify_conflicts():
ok = {"valid": True}
assert classify(2.7, 2.7071, ok)[0] == "improvement"
assert classify(2.7071, 2.7071, ok)[0] == "unchanged"
assert classify(2.7, 2.7071, None)[0] == "unverified"
assert classify(2.8, 2.7071, None)[0] == "worse"
assert classify(None, 2.7071)[0] == "partial"
assert classify(2.7, 2.7071, {"valid": False})[0] == "invalid"
assert classify(7.5, None)[0] == "no_baseline"
assert len(find_conflicts([{"n": 5, "s": 2.70, "source_url": "a"}, {"n": 5, "s": 2.71, "source_url": "b"}])) == 1
assert find_conflicts([{"n": 5, "s": 2.7, "source_url": "a"}, {"n": 6, "s": 9.0, "source_url": "b"}]) == []
def test_edge_cases():
# edge-touching side by side, exact: valid
assert validate([axis(F(1, 2), F(1, 2)), axis(F(3, 2), F(1, 2))], F(2), expected_n=2)["valid"]
# corner-touching diagonal: valid (skipped by dx^2+dy^2>2 prefilter? distance^2 = 2 -> checked, depth 0)
assert validate([axis(F(1, 2), F(1, 2)), axis(F(3, 2), F(3, 2))], F(2))["valid"]
# non-unit direction fails
r = validate([Sq(F(1), F(1), F(3, 5), F(3, 5))], F(2))
assert not r["valid"] and any("unit vector" in i for i in r["issues"])
# expected_n mismatch fails
r = validate([axis(F(1, 2), F(1, 2))], F(2), expected_n=2)
assert not r["valid"] and "expected 2" in r["issues"][0]
# sticking out
assert not validate([axis(F(1, 2), F(1, 2))], F(9, 10))["valid"]
# parse: exact strings and explicit degree unit
sq = parse_squares([{"x": "7/10", "y": "7/10", "c": "3/5", "s": "4/5"}])
assert validate(sq, F(7, 5))["proof"]
sq = parse_squares([{"x": 0.5, "y": 0.5, "theta_deg": 90}])
assert validate(sq, 1.0)["valid"]
try:
parse_squares([{"x": 0, "y": 0, "angle": 3}])
assert False
except ValueError:
pass
# sweep early exit does not hide an overlap after a far square in x-order
sqs = [axis(F(1, 2), F(1, 2)), axis(F(1, 2), F(5, 2)), axis(F(1, 2), F(3, 4))]
assert not validate(sqs, F(3))["valid"]
def test_thresholds():
from bounds import IMPROVEMENT_MATERIAL, VALIDATION_EPS, classify
assert IMPROVEMENT_MATERIAL > VALIDATION_EPS
OK = {"valid": True}
assert classify(2.0 - 5e-9, 2.0, OK)[0] == "unchanged" # inside material band
assert classify(2.0 - 2e-8, 2.0, OK)[0] == "improvement"
assert classify(2.0 - 2e-8, 2.0, None)[0] == "unverified"
def test_display_precision():
from tools import extract_claims
cl, _ = extract_claims("s(5) = 2.70710678…", {"url": "u"})
assert cl[0]["s_uncertainty"] == 1e-8
cl, _ = extract_claims("s(68) ≤ 8.798795237218", {"url": "u"})
assert cl and cl[0]["s"] == 8.798795237218 and cl[0]["s_uncertainty"] == 0
def test_llm_plumbing():
from interfaces import LLMExtractor, MockLLMClient
txt = "12 squares cannot fit below 3.9715. We found s(68) = 8.7987952 here."
m = MockLLMClient([{"claims": [{"n": 12, "s": 3.9715, "kind": "lower_bound", "quote": "cannot fit below 3.9715"},
{"n": 68, "s": 8.7987952, "kind": "upper_bound_packing", "quote": "s(68) = 8.7987952"},
{"n": 29, "s": 5.9, "kind": "upper_bound_packing", "quote": "invented 5.9"}]}])
cl, notes = LLMExtractor(m).extract(txt, {"url": "u"})
assert [(c["n"], c["s"]) for c in cl] == [(68, 8.7987952)], cl
cl, notes = LLMExtractor(MockLLMClient([RuntimeError("down")])).extract("s(68) = 8.7987952", {"url": "u"})
assert cl and any("rule fallback" in x for x in notes)
def test_review_fixes():
from interfaces import LLMExtractor, MockLLMClient, chunk_lines, side_from_quote, RegexGuardExtractor
X = lambda items, text: LLMExtractor(MockLLMClient([{"claims": items}])).extract(text, {"url": "u"})[0]
# 1a. model-returned s is ignored: rounded 5.9 can't sneak in; s comes from the quote
cl = X([{"n": 29, "s": 5.9, "kind": "upper_bound_packing", "quote": "s(29) = 5.93383346"}], "Best: s(29) = 5.93383346 today.")
assert [(c["n"], c["s"]) for c in cl] == [(29, 5.93383346)], cl
# 1b. a quote that stops mid-number is re-read from the source: 5.9 -> 5.93383346, never 5.9
cl = X([{"n": 29, "kind": "upper_bound_packing", "quote": "s(29) = 5.9"}], "s(29) = 5.93383346")
assert [c["s"] for c in cl] == [5.93383346], cl
# 1b'. a quote not present verbatim is dropped
assert X([{"n": 29, "kind": "upper_bound_packing", "quote": "s(29) = 5.90"}], "s(29) = 5.93383346") == []
# 1c. integer s: '3' inside '13' or '2.35' never matches; n itself isn't taken as s
assert side_from_quote("n=13 needs 2.35 more", 13)[0] == "2.35"
assert side_from_quote("4 squares fit in side 2", 4)[0] == "2"
assert side_from_quote("n = 3", 3)[0] is None
# 1d. ambiguous quote (two side values) is rejected
assert side_from_quote("for 29 squares, 5.9338 or 5.9340", 29)[0] is None
# 1e. hedged line rejected even if the model labels it upper_bound_packing
assert X([{"n": 29, "kind": "upper_bound_packing", "quote": "s(29) = 5.90"}], "Our goal: s(29) = 5.90") == []
# 1f. truncation marker outside the quote still sets uncertainty
cl = X([{"n": 5, "kind": "upper_bound_packing", "quote": "s(5) = 2.70710678"}], "s(5) = 2.70710678...")
assert cl and cl[0]["s_uncertainty"] == 1e-8
# 4. chunker splits on lines, never mid-number, with overlap
t = "\n".join(f"row {i}: s(29) = 5.93383346" for i in range(50))
ch = chunk_lines(t, max_chars=200, overlap_lines=2)
assert len(ch) > 1 and all(len(c) <= 200 for c in ch)
assert all(l in t.splitlines() for c in ch for l in c.split("\n"))
assert ch[0].split("\n")[-1] == ch[1].split("\n")[0] or ch[0].split("\n")[-2] == ch[1].split("\n")[0]
assert "2.7071067811" in "".join(chunk_lines("x " * 30 + "2.7071067811", max_chars=40))
# 5. middle arm: guards drop hedged and impossible values
cl, _ = RegexGuardExtractor().extract("n = 5 target 2.6\nn = 5 gives 1.2\nn = 5 gives 2.7071067812", {"url": "u"})
assert [(c["n"], c["s"]) for c in cl if not c.get("rejected")] == [(5, 2.7071067812)]
# 7. float-validated claim just under the bound stays a candidate
from bounds import float_near_bound_margin
assert float_near_bound_margin(1000) > 1e-6
def test_float_near_bound():
import json as _j
from agent import Config, run as _run
from bounds import Bound
from tools import MockTools
from make_scenarios import five as _five, S5 as _S5
pg = "```packing\n" + _j.dumps(_five(_S5)) + "\n```"
T = {"search": [{"url": "u", "date": "day-N"}], "pages": {"u": {"text": pg}}}
r = _run({"n": [5]}, MockTools(T), {5: Bound(5, _S5 + 1.2e-8, "test", "", False)}, 8, Config())
assert not r["improvements"] and [c["n"] for c in r["candidates"]] == [5], r["summary"]
r = _run({"n": [5]}, MockTools(T), {5: Bound(5, _S5 + 1e-4, "test", "", False)}, 8, Config())
assert [c["n"] for c in r["improvements"]] == [5]
def test_n_and_comma():
from interfaces import LLMExtractor, MockLLMClient, n_in_quote
X = lambda items, text: LLMExtractor(MockLLMClient([{"claims": items}])).extract(text, {"url": "u"})[0]
assert X([{"n": 29, "kind": "upper_bound_packing", "quote": "side 2.70710678"}], "side 2.70710678 for something") == []
t = "n = 10: container side of 3,70711"
cl = X([{"n": 10, "kind": "upper_bound_packing", "quote": t}], t)
assert [(c["n"], c["s"]) for c in cl] == [(10, 3.70711)], cl
assert n_in_quote("s(29) = 5.93", 29) and not n_in_quote("2.135 units", 13)
if __name__ == "__main__":
test_user_cases(); test_classify_conflicts(); test_edge_cases()
print("all tests passed")
test_thresholds(); test_display_precision(); test_llm_plumbing()
print("threshold + llm-interface tests passed")
test_review_fixes()
print("review-fix tests passed")
test_float_near_bound()
print("float near-bound tests passed")
test_n_and_comma()
print("n_in_quote + comma-decimal tests passed")