""" Validates all .fold target files against origami theorems. Run directly: python -m env.targets.validator """ import json import os import sys from pathlib import Path from ..graph import CreaseGraph from ..verifier import check_kawasaki_at_vertex, check_maekawa_at_vertex, check_blb_at_vertex def build_graph_from_fold(fold_data: dict) -> CreaseGraph: """ Reconstruct a CreaseGraph from a FOLD JSON dict. Used to validate target files. """ graph = CreaseGraph() verts = fold_data['vertices_coords'] edges = fold_data['edges_vertices'] assignments = fold_data['edges_assignment'] # Map file vertex indices to graph vertex IDs vert_map = {} for i, (x, y) in enumerate(verts): vid = graph.add_vertex(float(x), float(y)) vert_map[i] = vid # Add edges (boundary edges from init may already exist, add_edge handles dedup) for i, (v1_idx, v2_idx) in enumerate(edges): v1_id = vert_map[v1_idx] v2_id = vert_map[v2_idx] assignment = assignments[i] graph.add_edge(v1_id, v2_id, assignment) return graph def validate_target(fold_path: str) -> dict: """ Validate a single .fold target file. Returns {'file': str, 'valid': bool, 'issues': list[str], 'interior_vertices': int} """ with open(fold_path) as f: fold_data = json.load(f) issues = [] # Basic structure checks required = ['vertices_coords', 'edges_vertices', 'edges_assignment', 'edges_foldAngle'] for field in required: if field not in fold_data: issues.append(f"Missing field: {field}") if issues: return {'file': os.path.basename(fold_path), 'valid': False, 'issues': issues, 'interior_vertices': -1} n_edges = len(fold_data['edges_vertices']) if len(fold_data['edges_assignment']) != n_edges: issues.append("edges_assignment length mismatch") if len(fold_data['edges_foldAngle']) != n_edges: issues.append("edges_foldAngle length mismatch") # Build graph and check theorems graph = build_graph_from_fold(fold_data) interior = graph.interior_vertices() for v_id in interior: ok, alt_sum = check_kawasaki_at_vertex(v_id, graph) if not ok: issues.append(f"Kawasaki violated at vertex {v_id} (alt_sum={alt_sum:.6f})") if not check_maekawa_at_vertex(v_id, graph): issues.append(f"Maekawa violated at vertex {v_id}") blb_violations = check_blb_at_vertex(v_id, graph) if blb_violations: issues.append(f"BLB violated at vertex {v_id}: {blb_violations}") return { 'file': os.path.basename(fold_path), 'valid': len(issues) == 0, 'issues': issues, 'interior_vertices': len(interior), } def validate_all(targets_dir: str = None) -> bool: """Validate all .fold files in the targets directory. Returns True if all pass.""" if targets_dir is None: targets_dir = Path(__file__).parent all_pass = True fold_files = sorted(Path(targets_dir).glob('*.fold')) if not fold_files: print("No .fold files found") return False for fold_path in fold_files: result = validate_target(str(fold_path)) status = "OK" if result['valid'] else "FAIL" n_interior = result['interior_vertices'] print(f" [{status}] {result['file']} — {n_interior} interior vertices") if result['issues']: for issue in result['issues']: print(f" ! {issue}") if not result['valid']: all_pass = False return all_pass if __name__ == '__main__': print("Validating targets...") ok = validate_all() sys.exit(0 if ok else 1)