| """End-to-end orchestration: statement → IR → solve → TikZ → verify.""" |
|
|
| from __future__ import annotations |
|
|
| import json |
| from pathlib import Path |
| from typing import Any |
|
|
| from statement_to_tikz.emit_tikz import emit_standalone_tex |
| from statement_to_tikz.formalize import formalize |
| from statement_to_tikz.ir import GeometryIR, SolveMode, SolvedScene |
| from statement_to_tikz.schematic import layout_schematic |
| from statement_to_tikz.solve import DEFAULT_TOL, solve_geometry |
| from statement_to_tikz.verify import check_ir_consistency, verify_scene |
|
|
|
|
| def solve_with_fallback( |
| ir: GeometryIR, |
| *, |
| tol: float = DEFAULT_TOL, |
| force_schematic: bool = False, |
| ) -> SolvedScene: |
| ir = ir.model_copy(deep=True) |
| ir.ensure_labels() |
|
|
| if force_schematic: |
| return layout_schematic(ir) |
|
|
| exact = solve_geometry(ir, tol=tol) |
| if exact.mode == SolveMode.exact: |
| return exact |
|
|
| |
| base = exact.coordinates if exact.coordinates else None |
| schematic = layout_schematic(ir, base=base) |
| schematic.message = ( |
| f"fallback after exact fail ({exact.message}); {schematic.message}" |
| ) |
| return schematic |
|
|
|
|
| def run_pipeline( |
| statement: str | None = None, |
| *, |
| ir: GeometryIR | None = None, |
| out_dir: Path | str | None = None, |
| basename: str = "fig", |
| tol: float = DEFAULT_TOL, |
| compile_pdf: bool = True, |
| allow_offline_formalizer: bool = True, |
| force_schematic: bool = False, |
| formalizer_kwargs: dict[str, Any] | None = None, |
| ) -> dict[str, Any]: |
| """Run full or partial pipeline. Returns paths + report.""" |
| if ir is None: |
| if not statement: |
| raise ValueError("provide statement or ir") |
| ir = formalize( |
| statement, |
| allow_offline=allow_offline_formalizer, |
| **(formalizer_kwargs or {}), |
| ) |
| else: |
| ir = ir.model_copy(deep=True) |
| if statement and not ir.statement: |
| ir.statement = statement |
| ir.ensure_labels() |
|
|
| warnings = check_ir_consistency(ir) |
| scene = solve_with_fallback(ir, tol=tol, force_schematic=force_schematic) |
| tex = emit_standalone_tex(scene) |
|
|
| result: dict[str, Any] = { |
| "mode": scene.mode.value, |
| "max_residual": scene.max_residual, |
| "message": scene.message, |
| "warnings": warnings, |
| "ir": ir.model_dump(), |
| "coordinates": scene.coordinates, |
| "tex": tex, |
| } |
|
|
| if out_dir is not None: |
| out = Path(out_dir) |
| out.mkdir(parents=True, exist_ok=True) |
| tex_path = out / f"{basename}.tex" |
| ir_path = out / f"{basename}.ir.json" |
| report_path = out / f"{basename}.report.json" |
|
|
| tex_path.write_text(tex, encoding="utf-8") |
| ir_path.write_text(ir.model_dump_json(indent=2), encoding="utf-8") |
|
|
| report = verify_scene(scene, tex_path, tol=tol, compile=compile_pdf) |
| report["warnings"] = warnings |
| report["mode"] = scene.mode.value |
| report["message"] = scene.message |
| report["coordinates"] = scene.coordinates |
| report_path.write_text(json.dumps(report, indent=2), encoding="utf-8") |
|
|
| result["paths"] = { |
| "tex": str(tex_path), |
| "ir": str(ir_path), |
| "report": str(report_path), |
| "pdf": (report.get("compile") or {}).get("pdf"), |
| } |
| result["report"] = report |
| else: |
| result["report"] = verify_scene(scene, None, tol=tol, compile=False) |
|
|
| return result |
|
|
|
|
| def load_ir(path: Path | str) -> GeometryIR: |
| data = json.loads(Path(path).read_text(encoding="utf-8")) |
| data.pop("expected_mode", None) |
| ir = GeometryIR.model_validate(data) |
| ir.ensure_labels() |
| return ir |
|
|