|
|
from __future__ import annotations |
|
|
|
|
|
from dataclasses import dataclass |
|
|
from typing import Any, Dict, List, Optional, Set, Tuple, Callable |
|
|
|
|
|
|
|
|
@dataclass |
|
|
class Patch: |
|
|
add_edges: List[Tuple[int, int]] |
|
|
remove_edges: List[Tuple[int, int]] |
|
|
|
|
|
|
|
|
def _edge_set_from_cex(cex: Dict[str, Any]) -> Set[Tuple[int, int]]: |
|
|
edges = cex.get("edges", []) or [] |
|
|
out: Set[Tuple[int, int]] = set() |
|
|
for e in edges: |
|
|
if isinstance(e, (list, tuple)) and len(e) == 2: |
|
|
a, b = e |
|
|
if isinstance(a, int) and isinstance(b, int): |
|
|
out.add((a, b)) |
|
|
return out |
|
|
|
|
|
|
|
|
def apply_patch_to_counterexample(cex: Dict[str, Any], patch: Patch) -> Dict[str, Any]: |
|
|
""" |
|
|
反例モデルに対して edge の add/remove を適用した新しいモデルを返す。 |
|
|
valuation 等はそのまま残す(Phase 11 の目的は構造破壊の確認)。 |
|
|
""" |
|
|
worlds = int(cex.get("n_worlds") or cex.get("worlds") or 0) |
|
|
es = _edge_set_from_cex(cex) |
|
|
|
|
|
for a, b in patch.remove_edges: |
|
|
es.discard((a, b)) |
|
|
for a, b in patch.add_edges: |
|
|
|
|
|
if 0 <= a < worlds and 0 <= b < worlds: |
|
|
es.add((a, b)) |
|
|
|
|
|
new_cex = dict(cex) |
|
|
new_cex["edges"] = [[a, b] for (a, b) in sorted(es)] |
|
|
return new_cex |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def check_assumption_satisfied(cex: Dict[str, Any], assumption: str) -> bool: |
|
|
worlds = int(cex.get("n_worlds") or cex.get("worlds") or 0) |
|
|
es = _edge_set_from_cex(cex) |
|
|
|
|
|
if assumption == "assume:reflexive": |
|
|
return all((i, i) in es for i in range(worlds)) |
|
|
|
|
|
if assumption == "assume:symmetric": |
|
|
return all((b, a) in es for (a, b) in es) |
|
|
|
|
|
if assumption == "assume:transitive": |
|
|
|
|
|
for (x, y) in es: |
|
|
for (y2, z) in es: |
|
|
if y2 == y and (x, z) not in es: |
|
|
return False |
|
|
return True |
|
|
|
|
|
if assumption == "assume:euclidean": |
|
|
|
|
|
for (a, b) in es: |
|
|
for (a2, c) in es: |
|
|
if a2 == a and (b, c) not in es: |
|
|
return False |
|
|
return True |
|
|
|
|
|
|
|
|
return False |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def recheck_formula_with_model_search( |
|
|
model_checker_fn: Callable[[str, Dict[str, Any]], bool], |
|
|
formula: str, |
|
|
assumptions: List[str], |
|
|
patched_cex: Dict[str, Any], |
|
|
) -> Dict[str, Any]: |
|
|
""" |
|
|
Check if patched_cex is STILL a counterexample. |
|
|
model_checker_fn(formula, model) returns True if model satisfies formula (VALID), False if COUNTEREXAMPLE. |
|
|
""" |
|
|
try: |
|
|
|
|
|
is_valid_on_model = model_checker_fn(formula, patched_cex) |
|
|
|
|
|
if is_valid_on_model: |
|
|
return { |
|
|
"still_counterexample": False, |
|
|
"note": "Formula holds on the patched model (counterexample destroyed)." |
|
|
} |
|
|
else: |
|
|
return { |
|
|
"still_counterexample": True, |
|
|
"note": "Formula still fails on the patched model." |
|
|
} |
|
|
except Exception as e: |
|
|
return {"still_counterexample": None, "note": f"Error during recheck: {e}"} |
|
|
|
|
|
def model_search_wrapper( |
|
|
model_search_fn: Callable, |
|
|
formula: str, |
|
|
assumptions: List[str] |
|
|
) -> Dict[str, Any]: |
|
|
""" |
|
|
engineの model_search を呼ぶための薄いラッパ。 |
|
|
返り値形式を統一: |
|
|
{ "valid": bool, "counterexample": dict|None, "note": str } |
|
|
""" |
|
|
|
|
|
|
|
|
res = model_search_fn(formula=formula, assumptions=assumptions) |
|
|
|
|
|
|
|
|
is_valid = (res.status == "valid") |
|
|
return { |
|
|
"valid": is_valid, |
|
|
"counterexample": res.counterexample, |
|
|
"note": f"Status: {res.status}. Audit: {res.audit[-1] if res.audit else ''}" |
|
|
} |