will702's picture
Upload folder using huggingface_hub
eab734a verified
Raw
History Blame Contribute Delete
4.18 kB
"""Constraint residual checks and LaTeX compilation."""
from __future__ import annotations
import shutil
import subprocess
from pathlib import Path
from statement_to_tikz.ir import GeometryIR, SolvedScene
from statement_to_tikz.solve import evaluate_residuals
def residual_report(scene: SolvedScene, tol: float = 1e-4) -> dict:
residuals = evaluate_residuals(scene.ir, scene.coordinates)
max_r = max(residuals) if residuals else 0.0
return {
"max_residual": max_r,
"residuals": residuals,
"within_tol": max_r <= tol,
"tol": tol,
"mode": scene.mode.value,
"message": scene.message,
}
def compile_tex(
tex_path: Path,
*,
engine: str = "pdflatex",
timeout: float = 60.0,
) -> dict:
"""Compile a .tex file in its directory. Returns status dict."""
tex_path = Path(tex_path)
if not tex_path.is_file():
return {"ok": False, "error": f"missing file: {tex_path}"}
latexmk = shutil.which("latexmk")
pdflatex = shutil.which(engine)
cwd = tex_path.parent
if latexmk:
cmd = [
latexmk,
"-pdf",
f"-{engine}",
"-interaction=nonstopmode",
"-halt-on-error",
tex_path.name,
]
elif pdflatex:
cmd = [
engine,
"-interaction=nonstopmode",
"-halt-on-error",
tex_path.name,
]
else:
return {
"ok": False,
"error": f"neither latexmk nor {engine} found on PATH",
"skipped": True,
}
try:
proc = subprocess.run(
cmd,
cwd=cwd,
capture_output=True,
text=True,
timeout=timeout,
check=False,
)
except subprocess.TimeoutExpired:
return {"ok": False, "error": "compile timeout"}
pdf = tex_path.with_suffix(".pdf")
ok = proc.returncode == 0 and pdf.is_file()
return {
"ok": ok,
"returncode": proc.returncode,
"pdf": str(pdf) if pdf.is_file() else None,
"stdout_tail": (proc.stdout or "")[-2000:],
"stderr_tail": (proc.stderr or "")[-1000:],
}
def verify_scene(
scene: SolvedScene,
tex_path: Path | None = None,
*,
tol: float = 1e-4,
compile: bool = True,
) -> dict:
report = residual_report(scene, tol=tol)
report["compile"] = None
if compile and tex_path is not None:
report["compile"] = compile_tex(tex_path)
return report
def check_ir_consistency(ir: GeometryIR) -> list[str]:
"""Return list of soft warnings about IR consistency."""
names = set(ir.point_names())
warnings: list[str] = []
for seg in ir.segments:
for p in (seg.a, seg.b):
if p not in names:
warnings.append(f"segment references unknown point {p!r}")
for c in ir.constraints:
ctype = c.type
fields = c.model_dump()
for key, val in fields.items():
if key in ("type", "value", "degrees"):
continue
if isinstance(val, str) and key not in ("circle",) and len(val) <= 8:
# heuristic: short strings that look like point names
if key in (
"a",
"b",
"a1",
"a2",
"b1",
"b2",
"v1",
"v2",
"vertex",
"point",
"c",
"d",
):
if val not in names:
warnings.append(f"constraint {ctype} unknown point {val!r}")
if key == "points" and isinstance(val, list):
for p in val:
if p not in names:
warnings.append(f"constraint {ctype} unknown point {p!r}")
if key == "circle" and isinstance(val, str):
circ_ids = {ci.id for ci in ir.circles}
if val not in circ_ids:
warnings.append(f"on_circle unknown circle {val!r}")
return warnings