feat: retire fraction_decrease prose regex — bind dilation from frame scale
The post-CGA / ProblemFrame pivot comes to fruition: geometric dilation for fraction_decrease is built from the bound scale role's GroundedScalar (exact Fraction from kernel slash_fraction / pack numerics), not from a local "decrease to N/M of" regex on evidence spans. - Remove _build_fraction_decrease_payload_and_bind (legacy overfitting path) - Add _fraction_decrease_scale_binding + _versor_binding_from_scale_value - assess_fraction_decrease attaches VersorBindings when runnable - assess_geometric_proposals uses frame scale only for fraction_decrease - compose fallback prefers obligation-complete contracts with bindings - Guard test: contracts source must not reintroduce the prose regex Invariants: construction-boundary CGA only; versor_condition < 1e-6; no new derivation-organ prose parser.
This commit is contained in:
parent
6b4bfbe675
commit
2a3f7ad125
5 changed files with 204 additions and 74 deletions
|
|
@ -164,19 +164,32 @@ def _self_verifies_fraction_decrease(
|
||||||
def _resolve_fraction_decrease_contract(
|
def _resolve_fraction_decrease_contract(
|
||||||
problem_text: str,
|
problem_text: str,
|
||||||
) -> ContractAssessment | None:
|
) -> ContractAssessment | None:
|
||||||
"""Build a geometric ContractAssessment for fraction_decrease when callers
|
"""Build a ContractAssessment for fraction_decrease when callers omit one.
|
||||||
omit an explicit contract (sprint/dev API compatibility).
|
|
||||||
|
|
||||||
Uses ``assess_geometric_proposals`` so VersorBinding payloads come from the
|
Prefers ``assess_contracts`` (obligation-complete + frame-grounded scale
|
||||||
owned CGA construction path (pack signature or legacy N/M helper).
|
VersorBinding). Falls back to ``assess_geometric_proposals`` (same frame
|
||||||
|
scale binding path). Never re-parses "decrease to N/M of" prose.
|
||||||
"""
|
"""
|
||||||
from generate.problem_frame_builder import build_problem_frame
|
from generate.problem_frame_builder import build_problem_frame
|
||||||
from generate.problem_frame_contracts import assess_geometric_proposals
|
from generate.problem_frame_contracts import (
|
||||||
|
assess_contracts,
|
||||||
|
assess_geometric_proposals,
|
||||||
|
)
|
||||||
|
|
||||||
frame = build_problem_frame(problem_text)
|
frame = build_problem_frame(problem_text)
|
||||||
assessments = assess_geometric_proposals(frame)
|
for assessment in assess_contracts(frame):
|
||||||
|
if (
|
||||||
|
assessment.candidate_organ == "fraction_decrease"
|
||||||
|
and assessment.runnable
|
||||||
|
and assessment.bindings
|
||||||
|
):
|
||||||
|
return assessment
|
||||||
return next(
|
return next(
|
||||||
(a for a in assessments if a.candidate_organ == "fraction_decrease"),
|
(
|
||||||
|
a
|
||||||
|
for a in assess_geometric_proposals(frame)
|
||||||
|
if a.candidate_organ == "fraction_decrease" and a.bindings
|
||||||
|
),
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -496,6 +496,12 @@ def assess_fraction_decrease(frame: ProblemFrame) -> ContractAssessment:
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
runnable = not missing and not unresolved
|
runnable = not missing and not unresolved
|
||||||
|
# Geometric dilation binding from frame scale (KernelFacts), not prose.
|
||||||
|
geometric_bindings: list[VersorBinding] = []
|
||||||
|
if runnable:
|
||||||
|
scale_binding = _fraction_decrease_scale_binding(frame)
|
||||||
|
if scale_binding is not None:
|
||||||
|
geometric_bindings.append(scale_binding)
|
||||||
return ContractAssessment(
|
return ContractAssessment(
|
||||||
candidate_organ="fraction_decrease",
|
candidate_organ="fraction_decrease",
|
||||||
missing_bindings=tuple(dict.fromkeys(missing)),
|
missing_bindings=tuple(dict.fromkeys(missing)),
|
||||||
|
|
@ -508,6 +514,7 @@ def assess_fraction_decrease(frame: ProblemFrame) -> ContractAssessment:
|
||||||
+ ", ".join((*dict.fromkeys(missing), *sorted(unresolved)))
|
+ ", ".join((*dict.fromkeys(missing), *sorted(unresolved)))
|
||||||
),
|
),
|
||||||
evidence_spans=evidence_spans,
|
evidence_spans=evidence_spans,
|
||||||
|
bindings=geometric_bindings,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -911,44 +918,101 @@ def _scale_from_geometric_signature(geom: dict) -> float | None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _build_fraction_decrease_payload_and_bind(span_text: str) -> tuple[np.ndarray, str] | None:
|
def _versor_binding_from_scale_value(
|
||||||
"""Construct CGA dilation versor payload and bind text for fraction-decrease evidence.
|
scale_value: object,
|
||||||
|
*,
|
||||||
|
semantic_identity: str,
|
||||||
|
source_span: tuple[int, int],
|
||||||
|
) -> VersorBinding | None:
|
||||||
|
"""Build a dilation VersorBinding from an exact kernel scale (Fraction/float).
|
||||||
|
|
||||||
Prefer pack-driven geometric_signature (via resolve_geometric_signature on
|
This is the post-pivot construction path: scale arrives as a grounded
|
||||||
the span or an embedded slash-fraction token). LEGACY EXCEPTION: if packs
|
scalar fact on ProblemFrame — never re-parsed from "decrease to N/M of"
|
||||||
do not yet ratify the parameterized phrase, extract ``N/M`` from evidence
|
prose. CGA dilation is a pure construction-boundary op on that number.
|
||||||
text via a local regex — migration target is pack senses.jsonl entries
|
|
||||||
with ``geometric_signature: {numerator, denominator}`` (or ``scale``).
|
|
||||||
"""
|
"""
|
||||||
# Pack-first: whole span, then first slash-fraction token.
|
try:
|
||||||
candidates: list[str] = [span_text]
|
k = float(scale_value) # Fraction implements __float__
|
||||||
frac_match = re.search(r"(\d+\s*/\s*\d+)", span_text)
|
except (TypeError, ValueError):
|
||||||
if frac_match:
|
return None
|
||||||
candidates.append(frac_match.group(1).replace(" ", ""))
|
if not np.isfinite(k) or k <= 0.0:
|
||||||
for cand in candidates:
|
return None
|
||||||
signature = resolve_geometric_signature(cand)
|
payload = _dilation_versor_payload(k)
|
||||||
if not signature:
|
return VersorBinding(
|
||||||
continue
|
source_span=source_span,
|
||||||
_, geom = signature
|
semantic_identity=semantic_identity,
|
||||||
scale = _scale_from_geometric_signature(geom)
|
geometric_payload=payload,
|
||||||
if scale is None:
|
versor_error=versor_condition(payload),
|
||||||
continue
|
)
|
||||||
bind_text = cand if cand != span_text else (frac_match.group(1) if frac_match else span_text)
|
|
||||||
return _dilation_versor_payload(scale), bind_text
|
|
||||||
|
|
||||||
# LEGACY EXCEPTION: local prose extract until pack signatures cover
|
|
||||||
# parameterized "decrease to N/M of" phrases (kernel substrate rule).
|
def _fraction_decrease_scale_binding(frame: ProblemFrame) -> VersorBinding | None:
|
||||||
m = re.search(r"decrease to (\d+)/(\d+)\s+of", span_text)
|
"""Resolve fraction-decrease dilation binding from ProblemFrame scale role.
|
||||||
if not m:
|
|
||||||
|
Order of truth (no local prose parse):
|
||||||
|
1. Bound ``decrease_to_fraction`` relation role ``scale`` → GroundedScalar.value
|
||||||
|
2. Optional pack geometric_signature on the scale *surface* (named fractions
|
||||||
|
/ pack-ratified lemmas) when present — still identity = surface token,
|
||||||
|
not a phrase regex.
|
||||||
|
"""
|
||||||
|
relations = [
|
||||||
|
relation
|
||||||
|
for relation in frame.bound_relations
|
||||||
|
if relation.relation_type == "decrease_to_fraction"
|
||||||
|
]
|
||||||
|
if len(relations) != 1:
|
||||||
return None
|
return None
|
||||||
n, d = int(m.group(1)), int(m.group(2))
|
relation = relations[0]
|
||||||
if d == 0:
|
scale_id = _role_target(relation, "scale")
|
||||||
|
if scale_id is None:
|
||||||
return None
|
return None
|
||||||
bind_text = frac_match.group(1) if frac_match else span_text
|
quantities = _quantity_value_by_mention_id(frame)
|
||||||
return _dilation_versor_payload(n / d), bind_text
|
scale_q = quantities.get(scale_id)
|
||||||
|
if scale_q is None:
|
||||||
|
return None
|
||||||
|
mentions = _mention_map(frame)
|
||||||
|
mention = mentions.get(scale_id)
|
||||||
|
surface = getattr(mention, "surface", None) or str(getattr(scale_q, "value", ""))
|
||||||
|
span = getattr(mention, "span", None)
|
||||||
|
if span is None:
|
||||||
|
role_spans = _role_spans(relation, "scale")
|
||||||
|
if not role_spans:
|
||||||
|
return None
|
||||||
|
span = role_spans[0]
|
||||||
|
source_span = (span.start, span.end)
|
||||||
|
|
||||||
|
# Prefer exact kernel scalar (slash_fraction / named fraction already
|
||||||
|
# canonicalized to Fraction on the frame).
|
||||||
|
binding = _versor_binding_from_scale_value(
|
||||||
|
getattr(scale_q, "value", scale_q),
|
||||||
|
semantic_identity=str(surface),
|
||||||
|
source_span=source_span,
|
||||||
|
)
|
||||||
|
if binding is not None:
|
||||||
|
return binding
|
||||||
|
|
||||||
|
# Pack geometric_signature on scale surface (e.g. named fraction lemmas
|
||||||
|
# with senses geometric_signature) — substrate lookup only.
|
||||||
|
signature = resolve_geometric_signature(str(surface))
|
||||||
|
if signature is None:
|
||||||
|
return None
|
||||||
|
_, geom = signature
|
||||||
|
scale = _scale_from_geometric_signature(geom)
|
||||||
|
if scale is None:
|
||||||
|
return None
|
||||||
|
return _versor_binding_from_scale_value(
|
||||||
|
scale,
|
||||||
|
semantic_identity=str(surface),
|
||||||
|
source_span=source_span,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def assess_geometric_proposals(frame: ProblemFrame) -> list[ContractAssessment]:
|
def assess_geometric_proposals(frame: ProblemFrame) -> list[ContractAssessment]:
|
||||||
|
"""Geometric contract assessments from proposals + frame-grounded scale.
|
||||||
|
|
||||||
|
For ``fraction_decrease``, dilation VersorBindings are built from the
|
||||||
|
ProblemFrame scale role (KernelFacts GroundedScalar), not from re-parsing
|
||||||
|
proposal evidence spans with a local "decrease to N/M of" regex.
|
||||||
|
"""
|
||||||
assessments = []
|
assessments = []
|
||||||
|
|
||||||
# Pre-compute reverse mapping from family_id to candidate_organ
|
# Pre-compute reverse mapping from family_id to candidate_organ
|
||||||
|
|
@ -962,12 +1026,18 @@ def assess_geometric_proposals(frame: ProblemFrame) -> list[ContractAssessment]:
|
||||||
if not candidate_organ:
|
if not candidate_organ:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
bindings = []
|
bindings: list[VersorBinding] = []
|
||||||
for span in prop.evidence_spans:
|
|
||||||
signature = resolve_geometric_signature(span.text)
|
if candidate_organ == "fraction_decrease":
|
||||||
payload = None
|
# Frame-first: one binding from bound scale role.
|
||||||
bind_text = span.text
|
scale_binding = _fraction_decrease_scale_binding(frame)
|
||||||
if signature:
|
if scale_binding is not None:
|
||||||
|
bindings.append(scale_binding)
|
||||||
|
else:
|
||||||
|
for span in prop.evidence_spans:
|
||||||
|
signature = resolve_geometric_signature(span.text)
|
||||||
|
if not signature:
|
||||||
|
continue
|
||||||
_, geom = signature
|
_, geom = signature
|
||||||
scale = _scale_from_geometric_signature(geom)
|
scale = _scale_from_geometric_signature(geom)
|
||||||
if scale is not None:
|
if scale is not None:
|
||||||
|
|
@ -976,30 +1046,27 @@ def assess_geometric_proposals(frame: ProblemFrame) -> list[ContractAssessment]:
|
||||||
# Unit payload when signature exists but carries no scale.
|
# Unit payload when signature exists but carries no scale.
|
||||||
payload = np.zeros(32, dtype=np.float64)
|
payload = np.zeros(32, dtype=np.float64)
|
||||||
payload[0] = 1.0
|
payload[0] = 1.0
|
||||||
elif candidate_organ == "fraction_decrease":
|
bindings.append(
|
||||||
frac_result = _build_fraction_decrease_payload_and_bind(span.text)
|
VersorBinding(
|
||||||
if frac_result:
|
source_span=(span.start, span.end),
|
||||||
payload, bind_text = frac_result
|
semantic_identity=span.text,
|
||||||
|
geometric_payload=payload,
|
||||||
if payload is not None:
|
versor_error=versor_condition(payload),
|
||||||
binding = VersorBinding(
|
)
|
||||||
source_span=(span.start, span.end),
|
|
||||||
semantic_identity=bind_text,
|
|
||||||
geometric_payload=payload,
|
|
||||||
versor_error=versor_condition(payload),
|
|
||||||
)
|
)
|
||||||
bindings.append(binding)
|
|
||||||
|
|
||||||
runnable = False
|
runnable = bool(bindings) and all(b.versor_error < 1e-6 for b in bindings)
|
||||||
if bindings and all(b.versor_error < 1e-6 for b in bindings):
|
|
||||||
runnable = True
|
|
||||||
|
|
||||||
assessment = ContractAssessment(
|
assessment = ContractAssessment(
|
||||||
bindings=bindings,
|
bindings=bindings,
|
||||||
candidate_organ=candidate_organ,
|
candidate_organ=candidate_organ,
|
||||||
runnable=runnable,
|
runnable=runnable,
|
||||||
explanation="Assessed via geometric algebra.",
|
explanation=(
|
||||||
evidence_spans=prop.evidence_spans
|
"Assessed via geometric algebra from frame-grounded scale"
|
||||||
|
if candidate_organ == "fraction_decrease"
|
||||||
|
else "Assessed via geometric algebra."
|
||||||
|
),
|
||||||
|
evidence_spans=prop.evidence_spans,
|
||||||
)
|
)
|
||||||
assessments.append(assessment)
|
assessments.append(assessment)
|
||||||
|
|
||||||
|
|
|
||||||
2
plan.md
2
plan.md
|
|
@ -73,7 +73,7 @@ Alignment: AGENTS.md (pre-edit traces, immutability via replace/new, exact recal
|
||||||
- **Same-turn root recog (done):** `resolve_token_depths` + pipeline early wire before graph.
|
- **Same-turn root recog (done):** `resolve_token_depths` + pipeline early wire before graph.
|
||||||
- **Capability obligations (done):** `tests/test_3lang_depth_capability.py` (he/grc exemplars, result fields, construction, dilation).
|
- **Capability obligations (done):** `tests/test_3lang_depth_capability.py` (he/grc exemplars, result fields, construction, dilation).
|
||||||
- **public_demo budget (done):** 60s reference budget; soft case (hard raise opt-in); re-pin lane SHAs.
|
- **public_demo budget (done):** 60s reference budget; soft case (hard raise opt-in); re-pin lane SHAs.
|
||||||
- **Geometric signature (partial):** pack-scale preferred; legacy N/M regex retained with explicit migration note.
|
- **Geometric signature (done 2026-07-08):** fraction_decrease dilation binds from ProblemFrame scale role (KernelFacts GroundedScalar Fraction) — legacy "decrease to N/M of" prose regex **removed** from problem_frame_contracts.
|
||||||
- Rebase/merge per git workflow after review (Forgejo / `tea`).
|
- Rebase/merge per git workflow after review (Forgejo / `tea`).
|
||||||
- (retired) No longer create formal HANDOFF files. Use session-break-summary-<DATETIME>.md on pauses per AGENTS.md.
|
- (retired) No longer create formal HANDOFF files. Use session-break-summary-<DATETIME>.md on pauses per AGENTS.md.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -177,7 +177,7 @@ def test_construction_assess_with_he_root_depth() -> None:
|
||||||
|
|
||||||
|
|
||||||
def test_dilation_payload_from_scale_and_signature() -> None:
|
def test_dilation_payload_from_scale_and_signature() -> None:
|
||||||
"""P4: pack-shaped geometric_signature scale drives dilation versor."""
|
"""Pack-shaped geometric_signature + frame scale drive dilation versor."""
|
||||||
payload = _dilation_versor_payload(0.5)
|
payload = _dilation_versor_payload(0.5)
|
||||||
assert payload.shape == (32,)
|
assert payload.shape == (32,)
|
||||||
assert float(versor_condition(payload)) < 1e-6
|
assert float(versor_condition(payload)) < 1e-6
|
||||||
|
|
@ -188,11 +188,26 @@ def test_dilation_payload_from_scale_and_signature() -> None:
|
||||||
)
|
)
|
||||||
assert _scale_from_geometric_signature({"note": "no scale"}) is None
|
assert _scale_from_geometric_signature({"note": "no scale"}) is None
|
||||||
|
|
||||||
# Legacy extract still works for parameterized decrease phrase.
|
# Post-pivot: frame-grounded scale (KernelFacts Fraction), not prose regex.
|
||||||
from generate.problem_frame_contracts import _build_fraction_decrease_payload_and_bind
|
from generate.problem_frame_contracts import (
|
||||||
|
assess_fraction_decrease,
|
||||||
|
assess_geometric_proposals,
|
||||||
|
)
|
||||||
|
|
||||||
frac = _build_fraction_decrease_payload_and_bind("decrease to 3/4 of the original")
|
text = (
|
||||||
assert frac is not None
|
"In one hour, Addison mountain's temperature will decrease to 3/4 of its temperature. "
|
||||||
payload2, bind = frac
|
"If the current temperature of the mountain is 84 degrees, what will the temperature "
|
||||||
assert float(versor_condition(payload2)) < 1e-6
|
"decrease by?"
|
||||||
assert "3" in bind and "4" in bind
|
)
|
||||||
|
frame = build_problem_frame(text)
|
||||||
|
geom = assess_geometric_proposals(frame)
|
||||||
|
assert len(geom) == 1
|
||||||
|
assert geom[0].runnable
|
||||||
|
assert geom[0].bindings
|
||||||
|
assert geom[0].bindings[0].semantic_identity == "3/4"
|
||||||
|
assert float(versor_condition(geom[0].bindings[0].geometric_payload)) < 1e-6
|
||||||
|
|
||||||
|
obligation = assess_fraction_decrease(frame)
|
||||||
|
assert obligation.runnable
|
||||||
|
assert obligation.bindings
|
||||||
|
assert obligation.bindings[0].semantic_identity == "3/4"
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,13 @@
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from generate.problem_frame_builder import build_problem_frame
|
from generate.problem_frame_builder import build_problem_frame
|
||||||
from generate.problem_frame_contracts import assess_contracts, assess_fraction_decrease, assess_percent_partition
|
from generate.problem_frame_contracts import (
|
||||||
|
assess_contracts,
|
||||||
|
assess_fraction_decrease,
|
||||||
|
assess_geometric_proposals,
|
||||||
|
assess_percent_partition,
|
||||||
|
)
|
||||||
|
from algebra.versor import versor_condition
|
||||||
|
|
||||||
|
|
||||||
FRACTION_DECREASE_CASE = (
|
FRACTION_DECREASE_CASE = (
|
||||||
|
|
@ -59,6 +67,33 @@ def test_fraction_decrease_contract_is_runnable_from_problemframe() -> None:
|
||||||
assert assessment.runnable
|
assert assessment.runnable
|
||||||
assert assessment.missing_bindings == ()
|
assert assessment.missing_bindings == ()
|
||||||
assert assessment.evidence_spans
|
assert assessment.evidence_spans
|
||||||
|
# Pivot revelation: geometric dilation binds from frame scale Fraction(3/4),
|
||||||
|
# not from re-parsing "decrease to N/M of" prose.
|
||||||
|
assert assessment.bindings
|
||||||
|
assert assessment.bindings[0].semantic_identity in {"3/4", "3 / 4"}
|
||||||
|
assert float(versor_condition(assessment.bindings[0].geometric_payload)) < 1e-6
|
||||||
|
|
||||||
|
|
||||||
|
def test_fraction_decrease_geometric_uses_frame_scale_not_prose_regex() -> None:
|
||||||
|
frame = build_problem_frame(FRACTION_DECREASE_CASE)
|
||||||
|
geom = assess_geometric_proposals(frame)
|
||||||
|
assert len(geom) == 1
|
||||||
|
assert geom[0].candidate_organ == "fraction_decrease"
|
||||||
|
assert geom[0].runnable
|
||||||
|
assert geom[0].bindings[0].semantic_identity.replace(" ", "") == "3/4"
|
||||||
|
# Payload is dilation for k=0.75 (same as Fraction(3,4)).
|
||||||
|
from generate.problem_frame_contracts import _dilation_versor_payload
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
expected = _dilation_versor_payload(0.75)
|
||||||
|
assert np.allclose(geom[0].bindings[0].geometric_payload, expected)
|
||||||
|
|
||||||
|
|
||||||
|
def test_no_legacy_decrease_to_fraction_prose_regex_in_contracts() -> None:
|
||||||
|
"""Guard: the overfitting local prose parser must not return."""
|
||||||
|
source = Path("generate/problem_frame_contracts.py").read_text(encoding="utf-8")
|
||||||
|
assert r"decrease to (\d+" not in source
|
||||||
|
assert "_build_fraction_decrease_payload_and_bind" not in source
|
||||||
|
|
||||||
|
|
||||||
def test_fraction_decrease_sibling_is_runnable() -> None:
|
def test_fraction_decrease_sibling_is_runnable() -> None:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue