83 lines
3.7 KiB
Python
83 lines
3.7 KiB
Python
"""python -m app.simulation.native_codegen INPUT.xml|json --output-dir DIR"""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
from dataclasses import replace
|
|
from hashlib import sha256
|
|
import json
|
|
from pathlib import Path
|
|
import shutil
|
|
import statistics
|
|
import time
|
|
|
|
from app.main import compile_system_xml_network
|
|
from app.simulation.backends import simulation_config
|
|
from .build import build_native
|
|
from .compiler import compile_native_program
|
|
from .input import load_input
|
|
from .runner import execute_native
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("input", type=Path)
|
|
parser.add_argument("--output-dir", type=Path, required=True)
|
|
parser.add_argument("--method", choices=("RK45", "BDF"))
|
|
parser.add_argument("--max-step", type=float)
|
|
parser.add_argument("--rtol", type=float)
|
|
parser.add_argument("--runs", type=int, default=3)
|
|
parser.add_argument("--timeout", type=float, default=300)
|
|
parser.add_argument("--solve-only", action="store_true")
|
|
args = parser.parse_args()
|
|
if args.runs < 1:
|
|
parser.error("--runs must be positive")
|
|
out = args.output_dir.resolve()
|
|
out.mkdir(parents=True, exist_ok=True)
|
|
if (out / "summary.json").exists():
|
|
parser.error("Output directory already contains a completed run; choose a new directory.")
|
|
started = time.perf_counter()
|
|
xml, document = load_input(args.input)
|
|
program = compile_native_program(compile_system_xml_network(document))
|
|
config = simulation_config(document.simulation)
|
|
for name in ("method", "max_step", "rtol"):
|
|
if getattr(args, name) is not None:
|
|
config = replace(config, **{name: getattr(args, name)})
|
|
preparation = time.perf_counter()-started
|
|
(out / "input.xml").write_bytes(xml)
|
|
build = build_native(program)
|
|
package = out / "program"
|
|
package.mkdir(exist_ok=True)
|
|
for name in (*build.manifest["artifacts"], "manifest.json"):
|
|
shutil.copy2(build.executable.parent / name, package / name)
|
|
(out / "model-manifest.json").write_text(json.dumps(build.manifest, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
rows = []
|
|
for i in range(args.runs+1):
|
|
data = execute_native(build, config, document.simulation.sample_step,
|
|
run_dir=out / ("warmup" if i == 0 else f"run-{i}"),
|
|
record_samples=not args.solve_only, timeout=args.timeout)
|
|
row = {k: v for k, v in data.items() if k not in ("series", "final", "finalState")}
|
|
row["run"] = "warmup" if i == 0 else i
|
|
rows.append(row)
|
|
print(json.dumps(row, ensure_ascii=False), flush=True)
|
|
if not data["success"]:
|
|
break
|
|
measured = rows[1:]
|
|
summary = {
|
|
"source": str(args.input.resolve()), "sourceSha256": sha256(args.input.read_bytes()).hexdigest(),
|
|
"xmlSha256": sha256(xml).hexdigest(), "executable": str(package / build.executable.name),
|
|
"cachedExecutable": str(build.executable),
|
|
"buildKey": build.manifest["buildKey"], "buildSeconds": build.seconds,
|
|
"preparationSeconds": preparation, "cacheHit": build.cache_hit,
|
|
"settings": vars(config), "sampleStep": document.simulation.sample_step,
|
|
"recordSamples": not args.solve_only, "runs": rows,
|
|
"success": len(measured) == args.runs and all(row["success"] for row in rows),
|
|
"medianSolveSeconds": statistics.median(row["solveSeconds"] for row in measured) if measured else None,
|
|
}
|
|
(out / "summary.json").write_text(json.dumps(summary, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
print(json.dumps({k: v for k, v in summary.items() if k != "runs"}, ensure_ascii=False))
|
|
return 0 if summary["success"] else 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|