| from __future__ import annotations |
| from dataclasses import dataclass |
| from typing import Any, Dict, List, Optional, Tuple |
| from patch_proof import Patch |
|
|
| @dataclass |
| class CexDelta: |
| add_assumption: str |
| required_change: str |
| edge: Optional[Tuple[int, int]] = None |
| why_breaks: str = "" |
|
|
| def delta_to_patch(delta_item: Dict[str, Any]) -> Patch: |
| """ |
| counterexample_delta の1要素から Patch を組み立てる。 |
| MVP: add_edge のみ対応。 |
| """ |
| req = delta_item.get("required_change") |
| edge = delta_item.get("edge") |
|
|
| if req == "add_edge" and isinstance(edge, list) and len(edge) == 2: |
| a, b = edge |
| if isinstance(a, int) and isinstance(b, int): |
| return Patch(add_edges=[(a, b)], remove_edges=[]) |
| return Patch(add_edges=[], remove_edges=[]) |
|
|
| def explain_counterexample_delta( |
| formula: str, |
| counterexample: Dict[str, Any], |
| repair_suggestions: List[Dict[str, Any]], |
| ) -> List[Dict[str, Any]]: |
| """ |
| 与えられた反例モデルが、提案された仮定を入れると「どこが壊れるか」を説明する。 |
| ここでは MVP として modal logic のフレーム性質の delta を扱う: |
| - reflexive: 全 world に self-loop を要求 |
| - transitive: (a->b and b->c) implies (a->c) |
| - symmetric: (a->b) implies (b->a) |
| - euclidean: (a->b and a->c) implies (b->c) |
| |
| NOTE: |
| - これは “反例を修正して仮定を満たす最小変更” を提示するだけ。 |
| - それにより formula が必ず valid になることまで証明するものではない(Phase 9 で別途検証)。 |
| """ |
| |
| worlds = int(counterexample.get("n_worlds") or counterexample.get("worlds") or 0) |
| edges = counterexample.get("edges", []) or [] |
| |
| edge_set = set() |
| for e in edges: |
| if isinstance(e, (list, tuple)) and len(e) >= 2: |
| edge_set.add((int(e[0]), int(e[1]))) |
|
|
| out: List[CexDelta] = [] |
|
|
| |
| def add(delta: CexDelta): |
| out.append(delta) |
|
|
| for s in (repair_suggestions or []): |
| adds = s.get("add") or [] |
| for a in adds: |
| if a == "assume:reflexive": |
| |
| missing = [(i, i) for i in range(worlds) if (i, i) not in edge_set] |
| if missing: |
| |
| e = missing[0] |
| add(CexDelta( |
| add_assumption=a, |
| required_change="add_edge", |
| edge=e, |
| why_breaks=( |
| "Reflexivity requires wRw. Adding a self-loop forces □ to quantify over the current world; " |
| "many counterexamples to □A→A rely on having no wRw so □A can be vacuously true while A is false." |
| ) |
| )) |
| else: |
| add(CexDelta( |
| add_assumption=a, |
| required_change="note", |
| why_breaks="Counterexample already satisfies reflexivity; this repair would not break it via reflexivity delta." |
| )) |
|
|
| elif a == "assume:symmetric": |
| |
| missing_rev = [(b, a2) for (a2, b) in edge_set if (b, a2) not in edge_set] |
| if missing_rev: |
| e = missing_rev[0] |
| add(CexDelta( |
| add_assumption=a, |
| required_change="add_edge", |
| edge=e, |
| why_breaks="Symmetry requires that every edge has a reverse edge; counterexamples often use one-way reachability." |
| )) |
| else: |
| add(CexDelta(add_assumption=a, required_change="note", why_breaks="Already symmetric.")) |
|
|
| elif a == "assume:transitive": |
| |
| needed = [] |
| for x, y in list(edge_set): |
| for y2, z in list(edge_set): |
| if y2 == y and (x, z) not in edge_set: |
| needed.append((x, z)) |
| if needed: |
| e = needed[0] |
| add(CexDelta( |
| add_assumption=a, |
| required_change="add_edge", |
| edge=e, |
| why_breaks="Transitivity forces reachability shortcuts; counterexamples that rely on missing shortcuts can collapse." |
| )) |
| else: |
| add(CexDelta(add_assumption=a, required_change="note", why_breaks="Already transitive.")) |
|
|
| elif a == "assume:euclidean": |
| |
| needed = [] |
| for a0, b in list(edge_set): |
| for a1, c in list(edge_set): |
| if a1 == a0 and (b, c) not in edge_set: |
| needed.append((b, c)) |
| if needed: |
| e = needed[0] |
| add(CexDelta( |
| add_assumption=a, |
| required_change="add_edge", |
| edge=e, |
| why_breaks="Euclidean property forces edges between successors; counterexamples using branching without closure may break." |
| )) |
| else: |
| add(CexDelta(add_assumption=a, required_change="note", why_breaks="Already euclidean.")) |
|
|
| else: |
| |
| add(CexDelta( |
| add_assumption=a, |
| required_change="note", |
| why_breaks="No structural delta rule implemented for this assumption yet." |
| )) |
|
|
| |
| return [ |
| { |
| "add_assumption": d.add_assumption, |
| "required_change": d.required_change, |
| "edge": list(d.edge) if d.edge else None, |
| "why_breaks": d.why_breaks, |
| } |
| for d in out |
| ] |