Download code/scripts/build_dualrate_cache.py from AlexWortega/dualrate-physics: direct link, hf CLI and curl.
- Browser
- Download file 5.61 kB
-
https://huggingface.co/AlexWortega/dualrate-physics/resolve/main/code/scripts/build_dualrate_cache.py
- Command line
-
hf download hf://AlexWortega/dualrate-physics/code/scripts/build_dualrate_cache.py
-
curl -L -o build_dualrate_cache.py https://huggingface.co/AlexWortega/dualrate-physics/resolve/main/code/scripts/build_dualrate_cache.py
5.61 kB
| #!/usr/bin/env python3 | |
| """Build the dual-rate numeric cache directly from packed val tarballs. | |
| Streams scene JSONL files out of data_scenarios_packed/val/<type>.tar.gz | |
| (no raw extraction — disk is tight) and writes one compressed .npz per scene: | |
| states (T, N, 6) float32 [px, py, vx, vy, angle, angvel] | |
| static (N, 8) float32 [is_circle, is_box, log_mass, friction, | |
| elasticity, size0/100, size1/100, 0] | |
| ids (N,) int32 object ids (for obj_{id} text rendering) | |
| gravity (2,) float32 | |
| dt () float32 | |
| header_text str pre-rendered scene header (baseline format) | |
| Scenes are split train/val by seed hash (seed % 10 == 0 -> val). | |
| OOD scenario types are never processed here. | |
| """ | |
| import argparse | |
| import json | |
| import math | |
| import sys | |
| import tarfile | |
| from multiprocessing import Pool | |
| from pathlib import Path | |
| import numpy as np | |
| sys.path.insert(0, str(Path(__file__).parent.parent)) | |
| from src.training.data_loader import _format_header_text # noqa: E402 | |
| OOD_TYPES = {"pong", "bowling", "ramp_roll", "angry_birds", "hourglass", "newtons_cradle"} | |
| def scene_to_arrays(lines): | |
| header = json.loads(lines[0]) | |
| if header.get("type") != "scene_header": | |
| raise ValueError("first line is not scene_header") | |
| frames = [json.loads(ln) for ln in lines[1:]] | |
| ids = [obj["id"] for obj in header["objects"]] | |
| order = {oid: i for i, oid in enumerate(ids)} | |
| n = len(ids) | |
| t = len(frames) | |
| states = np.zeros((t, n, 6), dtype=np.float32) | |
| for fi, frame in enumerate(frames): | |
| for obj in frame["objects"]: | |
| i = order[obj["id"]] | |
| states[fi, i, 0] = obj["position"]["x"] | |
| states[fi, i, 1] = obj["position"]["y"] | |
| states[fi, i, 2] = obj["velocity"]["x"] | |
| states[fi, i, 3] = obj["velocity"]["y"] | |
| states[fi, i, 4] = obj.get("angle", 0.0) | |
| states[fi, i, 5] = obj.get("angular_velocity", 0.0) | |
| static = np.zeros((n, 8), dtype=np.float32) | |
| for obj in header["objects"]: | |
| i = order[obj["id"]] | |
| is_circle = 1.0 if obj.get("type") == "circle" else 0.0 | |
| mat = obj.get("material", {}) | |
| if "radius" in obj: | |
| s0 = s1 = float(obj["radius"]) | |
| else: | |
| s0 = float(obj.get("width", obj.get("size", 0.0)) or 0.0) | |
| s1 = float(obj.get("height", obj.get("size", 0.0)) or 0.0) | |
| static[i] = [ | |
| is_circle, | |
| 1.0 - is_circle, | |
| math.log(max(mat.get("mass", 1.0), 1e-3)), | |
| mat.get("friction", 0.0), | |
| mat.get("elasticity", 0.0), | |
| s0 / 100.0, | |
| s1 / 100.0, | |
| 0.0, | |
| ] | |
| gravity = np.array([header["gravity"]["x"], header["gravity"]["y"]], dtype=np.float32) | |
| dt = np.float32(header["timestep"]) | |
| header_text = _format_header_text(header) | |
| return header, states, static, np.array(ids, dtype=np.int32), gravity, dt, header_text | |
| def process_tarball(args): | |
| tar_path, out_dir, scenes_per_type = args | |
| scenario = Path(tar_path).name.replace(".tar.gz", "") | |
| done = skipped = 0 | |
| with tarfile.open(tar_path, "r:gz") as tf: | |
| for member in tf: | |
| if done >= scenes_per_type: | |
| break | |
| if not member.name.endswith(".jsonl"): | |
| continue | |
| try: | |
| lines = tf.extractfile(member).read().decode().splitlines() | |
| header, states, static, ids, gravity, dt, header_text = scene_to_arrays(lines) | |
| if states.shape[0] < 50: | |
| skipped += 1 | |
| continue | |
| seed = header["seed"] | |
| split = "val" if seed % 10 == 0 else "train" | |
| out_path = Path(out_dir) / split / f"{scenario}_{seed}.npz" | |
| np.savez_compressed( | |
| out_path, | |
| states=states, | |
| static=static, | |
| ids=ids, | |
| gravity=gravity, | |
| dt=dt, | |
| header_text=header_text, | |
| ) | |
| done += 1 | |
| except Exception as e: # noqa: BLE001 — skip malformed scenes, count them | |
| skipped += 1 | |
| if skipped <= 3: | |
| print(f"[{scenario}] skip {member.name}: {e}", flush=True) | |
| print(f"[{scenario}] cached {done}, skipped {skipped}", flush=True) | |
| return scenario, done, skipped | |
| def main(): | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--packed-dir", default="data_scenarios_packed/val") | |
| ap.add_argument("--out", default="data_dualrate/cache") | |
| ap.add_argument("--scenes-per-type", type=int, default=2500) | |
| ap.add_argument("--workers", type=int, default=12) | |
| args = ap.parse_args() | |
| out_dir = Path(args.out) | |
| (out_dir / "train").mkdir(parents=True, exist_ok=True) | |
| (out_dir / "val").mkdir(parents=True, exist_ok=True) | |
| tarballs = sorted( | |
| p for p in Path(args.packed_dir).glob("*.tar.gz") | |
| if p.name.replace(".tar.gz", "") not in OOD_TYPES | |
| ) | |
| print(f"{len(tarballs)} seen-type tarballs, {args.scenes_per_type}/type -> {out_dir}") | |
| jobs = [(str(p), str(out_dir), args.scenes_per_type) for p in tarballs] | |
| with Pool(args.workers) as pool: | |
| results = pool.map(process_tarball, jobs) | |
| total = sum(d for _, d, _ in results) | |
| print(f"TOTAL cached {total} scenes " | |
| f"(train {len(list((out_dir / 'train').glob('*.npz')))}, " | |
| f"val {len(list((out_dir / 'val').glob('*.npz')))})") | |
| if __name__ == "__main__": | |
| main() | |