3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2026-03-14 17:19:59 +00:00
z3/.github/skills/explain/scripts/explain.py
Angelica Moreira d349b93d1d Add Copilot skill architecture with 10 skills, 2 agents, and shared infra
Introduce .github/skills/ with solve, prove, optimize, simplify,
encode, explain, benchmark, memory-safety, static-analysis, and
deeptest skills. Each skill follows a SKILL.md + scripts/ pattern
with Python scripts backed by a shared SQLite logging library
(z3db.py). Two orchestrator agents (z3-solver, z3-verifier) route
requests to the appropriate skills.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-11 17:41:29 +00:00

128 lines
3.3 KiB
Python

#!/usr/bin/env python3
"""
explain.py: interpret Z3 output in a readable form.
Usage:
python explain.py --file output.txt
echo "sat\n(model ...)" | python explain.py --stdin
"""
import argparse
import re
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent / "shared"))
from z3db import Z3DB, parse_model, parse_stats, parse_unsat_core, setup_logging
def detect_type(text: str) -> str:
if "(define-fun" in text:
return "model"
if "(error" in text:
return "error"
if re.search(r':\S+\s+[\d.]+', text):
return "stats"
first = text.strip().split("\n")[0].strip()
if first == "unsat":
return "core"
return "unknown"
def explain_model(text: str):
model = parse_model(text)
if not model:
print("no model found in output")
return
print("satisfying assignment:")
for name, val in model.items():
# show hex for large integers (likely bitvectors)
try:
n = int(val)
if abs(n) > 255:
print(f" {name} = {val} (0x{n:x})")
else:
print(f" {name} = {val}")
except ValueError:
print(f" {name} = {val}")
def explain_core(text: str):
core = parse_unsat_core(text)
if core:
print(f"conflicting assertions ({len(core)}):")
for label in core:
print(f" {label}")
else:
print("unsat (no named assertions for core extraction)")
def explain_stats(text: str):
stats = parse_stats(text)
if not stats:
print("no statistics found")
return
print("performance breakdown:")
for k in sorted(stats):
print(f" :{k} {stats[k]}")
if "time" in stats:
print(f"\ntotal time: {stats['time']}s")
if "memory" in stats:
print(f"peak memory: {stats['memory']} MB")
def explain_error(text: str):
errors = re.findall(r'\(error\s+"([^"]+)"\)', text)
if errors:
print(f"Z3 reported {len(errors)} error(s):")
for e in errors:
print(f" {e}")
else:
print("error in output but could not parse message")
def main():
parser = argparse.ArgumentParser(prog="explain")
parser.add_argument("--file")
parser.add_argument("--stdin", action="store_true")
parser.add_argument("--type", choices=["model", "core", "stats", "error", "auto"],
default="auto")
parser.add_argument("--db", default=None)
parser.add_argument("--debug", action="store_true")
args = parser.parse_args()
setup_logging(args.debug)
if args.file:
text = Path(args.file).read_text()
elif args.stdin:
text = sys.stdin.read()
else:
parser.error("provide --file or --stdin")
return
output_type = args.type if args.type != "auto" else detect_type(text)
db = Z3DB(args.db)
run_id = db.start_run("explain", text[:200])
if output_type == "model":
explain_model(text)
elif output_type == "core":
explain_core(text)
elif output_type == "stats":
explain_stats(text)
elif output_type == "error":
explain_error(text)
else:
print("could not determine output type")
print("raw output:")
print(text[:500])
db.finish_run(run_id, "success", 0, 0)
db.close()
if __name__ == "__main__":
main()