File size: 18,009 Bytes
e488ce7 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 | #!/usr/bin/env python3
"""Unit test for H3 Indecision Oracle, the experimental x0-jitter oracle.
python tests/test_indecision_oracle.py
Synthetic, no GPU, no comfy, no models. Writes fake X0 Tap dumps into
a temp dir.
What it checks:
1. PARITY with the desk study. The map math in motion.py is a port of
benchmarks/scripts/indecision/x0_jitter.py in ComfyUI-ModelCatalog; if
that file is reachable, every ported function is asserted bit-identical
to the original on synthetic tensors (jitter_map, detrend_phase,
normalize, degeneracy_check, token_count). This is the only thing
stopping the two copies from drifting.
2. DEGENERACY. A dump where 7 of 12 token rows are pinned (x0 identical
between the two taps) is a masked run: the map is a picture of the
noise mask. The report must SHOUT, not whisper.
3. BLEND MODES on known inputs: max is elementwise max, weighted is the
convex combination, both after per-source rank normalization.
4. JERK PASSTHROUGH is exactly H3JerkOracle. Same latent, same knobs ->
byte-identical hold_map, segments, window. If this ever fails, the A/B
is comparing two compilers instead of two signals.
5. CONTRACT SHAPE. The emitted hold_map is parsed by the real consumers:
H3TimeSmear (adaptive mode), H3ExactRecover (round trip back to the
world clock), and H3WindowPlan's parse.
6. STEP FALLBACK. The shipped pr15375 graphs tap 0,1,12,24 only, so the
default 6->12 pair is NOT on disk there. auto_fallback picks the
closest available pair and says so; off, it raises with the list.
Exit code 0 = pass.
"""
import importlib.util
import json
import os
import shutil
import sys
import tempfile
import numpy as np
import torch
HERE = os.path.dirname(os.path.abspath(__file__))
spec = importlib.util.spec_from_file_location(
"mainodes_motion", os.path.join(os.path.dirname(HERE), "motion.py"))
motion = importlib.util.module_from_spec(spec)
spec.loader.exec_module(motion)
# The validated original. Optional: it lives in a different repo, and cv2 may
# not be importable here. When it is reachable the parity block runs.
X0_JITTER = ("/mnt/work/ai/apps/ComfyUI-ModelCatalog/benchmarks/scripts/"
"indecision/x0_jitter.py")
try:
_s = importlib.util.spec_from_file_location("x0_jitter_ref", X0_JITTER)
ref = importlib.util.module_from_spec(_s)
_s.loader.exec_module(ref)
except Exception as e: # noqa: BLE001
ref = None
REF_WHY = f"{type(e).__name__}: {e}"
FAILS = []
def check(name, ok, detail=""):
print((" PASS " if ok else " FAIL ") + name + (" " + detail if detail else ""))
if not ok:
FAILS.append(name)
# ---------------------------------------------------------------- fixtures
FRAMES, RES = 39, 64 # T = 12 tokens, 4x4 latent, 2x2 grid
T_LAT = (FRAMES - 5) // 17 * 5 + 2 # 12
HL = RES // 16 # 4
def make_dump(root, name, steps, pinned_rows=()):
"""Write fake X0 Tap files: payload["video"] flat, video + 64 audio elems.
`pinned_rows` are token rows held IDENTICAL across every step, which is
what a mask-composited run looks like."""
d = os.path.join(root, name)
os.makedirs(d, exist_ok=True)
g = torch.Generator().manual_seed(7)
base = torch.randn(24, T_LAT, HL, HL, generator=g)
for i, s in enumerate(steps):
z = base + 0.3 * (i + 1) * torch.randn(24, T_LAT, HL, HL, generator=g)
for t in pinned_rows:
z[:, t] = base[:, t]
flat = torch.cat([z.reshape(-1), torch.zeros(64)])
torch.save({"step": s, "total_steps": 25, "sigma": 1.0 - s / 25.0,
"video": flat}, os.path.join(d, f"x0_step{s:03d}.pt"))
return d
def latent():
g = torch.Generator().manual_seed(11)
return {"samples": torch.randn(1, 24, T_LAT, HL, HL, generator=g)}
def images(n=FRAMES, hw=8):
return torch.rand(n, hw, hw, 3, generator=torch.Generator().manual_seed(3))
BASE = dict(length=FRAMES, width=RES, height=RES, step_a=6, step_b=12,
q=0.75, d_max=4, ramp=True)
# ---------------------------------------------------------------- the tests
def main():
node = motion.H3IndecisionOracle()
root = tempfile.mkdtemp(prefix="h3indec_")
try:
# ---- 1. parity with x0_jitter.py -------------------------------
print("\n1. PARITY with x0_jitter.py")
if ref is None:
check("x0_jitter.py importable", False, REF_WHY)
else:
g = torch.Generator().manual_seed(1)
za = torch.randn(24, T_LAT, HL, HL, generator=g)
zb = torch.randn(24, T_LAT, HL, HL, generator=g)
mine, theirs = motion._jitter_map(za, zb), ref.jitter_map(za, zb)
check("jitter_map is bit-identical",
mine.shape == theirs.shape and np.array_equal(mine, theirs),
f"{mine.shape} max|d|={np.abs(mine - theirs).max():.3e}")
check("detrend_phase is bit-identical",
np.array_equal(motion._detrend_phase(mine),
ref.detrend_phase(theirs)))
for m in ("rank", "z"):
check(f"normalize('{m}') is bit-identical",
np.array_equal(motion._map_normalize(mine, m),
ref.normalize(theirs, m)))
check("degeneracy_check is identical",
motion._degeneracy_check(mine) == ref.degeneracy_check(theirs),
str(motion._degeneracy_check(mine)))
check("token_count is identical",
all(motion._x0_token_count(f) == ref.token_count(f)
for f in (5, 39, 73, 124, 192, 294)),
f"39 -> {motion._x0_token_count(39)}, "
f"124 -> {motion._x0_token_count(124)}")
# pinned rows -> exact zeros, on both sides
zc = zb.clone()
zc[:, :7] = za[:, :7]
dm, dt = motion._jitter_map(za, zc), ref.jitter_map(za, zc)
check("pinned rows read EXACTLY zero in both copies",
motion._degeneracy_check(dm)["zero_token_rows"]
== ref.degeneracy_check(dt)["zero_token_rows"] == list(range(7)),
str(motion._degeneracy_check(dm)["zero_token_rows"]))
# ---- 2. the clean run ------------------------------------------
print("\n2. CLEAN RUN, indecision mode")
clean = make_dump(root, "clean", [0, 1, 6, 12, 24])
out = node.read(dump_dir=clean, mode="indecision", samples=latent(),
**BASE)
hold_map, segs, w0, wlen, prof, report, comp, heat = out
holds = json.loads(hold_map)["holds"]
check("hold_map covers every world frame", len(holds) == FRAMES,
f"{len(holds)} holds, world_len="
f"{json.loads(hold_map)['world_len']}")
check("profile has one entry per latent token",
len(prof.split()) == T_LAT, f"{len(prof.split())} tokens")
check("no degeneracy shout on a clean dump",
"DEGENERATE" not in report, report.splitlines()[0])
check("report flags the node as experimental",
"EXPERIMENTAL" in report,
next(l for l in report.splitlines() if "EXPERIMENTAL" in l)[:70])
check("window is on the 17-frame grid and inside the clip",
w0 % 17 == 0 and 0 <= w0 < FRAMES and w0 + wlen <= FRAMES + 17,
f"window_start={w0} window_len={wlen}")
check("comparison carries the A/B numbers",
"Spearman rho" in comp and "top-decile IoU" in comp,
comp.splitlines()[1].strip())
check("comparison names the not-a-superset caveat",
"superset" in comp, comp.splitlines()[-1].strip()[:70])
check("heat previews both maps side by side (no frames wired)",
heat.ndim == 4 and heat.shape[0] == 1 and
heat.shape[2] == 2 * heat.shape[1] * 4, # 2 panels, 8 cols/2 rows
f"heat {tuple(heat.shape)}")
hoverlay = node.read(dump_dir=clean, mode="indecision",
samples=latent(), images=images(), **BASE)[7]
check("heat overlays the frames when images are wired",
tuple(hoverlay.shape) == (FRAMES, 8, 16, 3),
f"heat {tuple(hoverlay.shape)}")
check("comparison is refused (politely) with no latent wired",
"wire the LATENT" in node.read(dump_dir=clean,
mode="indecision", **BASE)[6])
# ---- 3. degeneracy ---------------------------------------------
print("\n3. DEGENERACY (masked / pinned run)")
pinned = make_dump(root, "pinned", [6, 12], pinned_rows=range(7))
rep = node.read(dump_dir=pinned, mode="indecision", **BASE)[5]
line = [l for l in rep.splitlines() if "DEGENERATE" in l]
check("7 of 12 zero rows (58%) fires the loud warning", bool(line),
line[0][:120] if line else rep)
check("...and says it is a picture of the mask",
bool(line) and "NOISE MASK" in line[0])
half = make_dump(root, "onethird", [6, 12], pinned_rows=range(3))
rep3 = node.read(dump_dir=half, mode="indecision", **BASE)[5]
check("3 of 12 zero rows (25%) does NOT fire the loud one",
"DEGENERATE" not in rep3 and "zero jitter" in rep3,
[l for l in rep3.splitlines() if "zero jitter" in l][0][:80])
# ---- 3b. the 0->1 warning --------------------------------------
rep01 = node.read(dump_dir=clean, mode="indecision",
**dict(BASE, step_a=0, step_b=1))[5]
check("the 0->1 pair carries its own degeneracy warning",
"chunk-phase ramp" in rep01,
[l for l in rep01.splitlines() if "chunk-phase" in l][0][:90])
# ---- 4. blend modes on known inputs ----------------------------
print("\n4. BLEND MODES")
A = np.array([[[0.0, 1.0], [2.0, 3.0]]], dtype=np.float32)
B = np.array([[[3.0, 2.0], [1.0, 0.0]]], dtype=np.float32)
An, Bn = motion._map_normalize(A), motion._map_normalize(B)
check("rank normalization maps 4 cells onto 0,1/3,2/3,1",
np.allclose(np.sort(An.ravel()), [0, 1 / 3, 2 / 3, 1.0]),
str(An.ravel().tolist()))
mx = motion._blend_maps("blend max", An, Bn, 0.5)
check("blend max is elementwise max of the ranks",
np.allclose(mx.ravel(), [1.0, 2 / 3, 2 / 3, 1.0]),
str(mx.ravel().tolist()))
for w, want in ((0.0, Bn), (1.0, An), (0.5, 0.5 * An + 0.5 * Bn)):
got = motion._blend_maps("blend weighted w", An, Bn, w)
check(f"blend weighted w={w:g}", np.allclose(got, want),
str(np.round(got.ravel(), 3).tolist()))
lat = latent()
for m in ("blend max", "blend weighted w"):
hm = node.read(dump_dir=clean, mode=m, samples=lat, **BASE)[0]
check(f"'{m}' produces a legal hold map",
len(json.loads(hm)["holds"]) == FRAMES and
min(json.loads(hm)["holds"]) >= 1,
f"peak x{max(json.loads(hm)['holds'])}")
try:
node.read(dump_dir=clean, mode="blend max", **BASE)
check("a blend without `samples` refuses", False, "no assert raised")
except AssertionError as e:
check("a blend without `samples` refuses", "needs the LATENT" in str(e),
str(e)[:80])
# ---- 5. jerk passthrough == H3JerkOracle -----------------------
print("\n5. JERK PASSTHROUGH is byte-identical to H3 Jerk Oracle")
jerk = motion.H3JerkOracle()
for q, d_max, bridge in ((0.75, 4, 8), (0.70, 4, 8), (0.85, 3, 0)):
j = jerk.read(samples=lat, length=FRAMES, q=q, d_max=d_max,
ramp=True, preset="custom", bridge=bridge)
i = node.read(dump_dir=clean, mode="jerk passthrough", samples=lat,
bridge=bridge,
**dict(BASE, q=q, d_max=d_max))
check(f"q={q} d_max={d_max} bridge={bridge}: hold_map identical",
j[0] == i[0],
f"peak x{max(json.loads(i[0])['holds'])}, "
f"{sum(1 for h in json.loads(i[0])['holds'] if h > 1)} held")
check(f"q={q} d_max={d_max} bridge={bridge}: segments+window identical",
(j[1], j[2], j[3]) == (i[1], i[2], i[3]),
f"segments={i[1]!r} window={i[2]}+{i[3]}")
check("passthrough reads no dump and says so",
"jerk passthrough" in node.read(dump_dir="/nonexistent",
mode="jerk passthrough",
samples=lat, **BASE)[6])
# ---- 6. contract shape: the real consumers parse it ------------
print("\n6. CONTRACT SHAPE (the consumers parse the emitted map)")
hold_map = node.read(dump_dir=clean, mode="indecision", **BASE)[0]
imgs = images()
smeared, used, length, srep = motion.H3TimeSmear().smear(
images=imgs, dilation=4, hold_map=hold_map)
check("H3TimeSmear consumes it in ADAPTIVE mode",
smeared.shape[0] == length and "adaptive" in srep,
f"{FRAMES} -> {length} frames; {srep.split(';')[-1].strip()}")
check("the smeared length is on the 17k+5 grid",
(length - 5) % 17 == 0, f"length={length}")
recovered = motion.H3ExactRecover().recover(images=smeared,
hold_map=used)[0]
check("H3ExactRecover inverts it losslessly",
recovered.shape == imgs.shape and torch.equal(recovered, imgs),
f"{tuple(recovered.shape)}")
wholds = [int(h) for h in json.loads(hold_map)["holds"]] # H3WindowPlan
check("H3WindowPlan's parse accepts it",
len(wholds) == FRAMES and all(isinstance(h, int) for h in wholds))
gated = motion.H3ManualHoldMap().build(
length=FRAMES, fps=24, ranges="0-38", hold=4, ramp=True, bridge=8,
oracle_hold_map=hold_map)
check("H3ManualHoldMap gates on it",
len(json.loads(gated[0])["holds"]) == FRAMES,
gated[2].splitlines()[0][:80])
# ---- 7. step fallback -------------------------------------------
print("\n7. STEP FALLBACK (shipped graphs tap 0,1,12,24)")
coarse = make_dump(root, "coarse", [0, 1, 12, 24])
check("available steps are read off disk",
motion._x0_available_steps(coarse) == [0, 1, 12, 24],
str(motion._x0_available_steps(coarse)))
a, b, note = motion.H3IndecisionOracle._resolve_pair(coarse, 6, 12, True)
check("6->12 falls back to the closest available pair",
(a, b) == (1, 12) and "step fallback" in note, f"{a}->{b}")
check("...and the note lists what is on disk and what is validated",
"[0, 1, 12, 24]" in note and "6->12" in note and "12->24" in note,
note.strip()[:110])
rep = node.read(dump_dir=coarse, mode="indecision", **BASE)[5]
check("the fallback is shouted in the report", "step fallback" in rep,
[l for l in rep.splitlines() if "fallback" in l][0][:90])
try:
motion.H3IndecisionOracle._resolve_pair(coarse, 6, 12, False)
check("auto_fallback off raises", False, "no error raised")
except ValueError as e:
check("auto_fallback off raises with the available list",
"[0, 1, 12, 24]" in str(e), str(e)[:110])
check("12->24 is taken as-is when it exists",
motion.H3IndecisionOracle._resolve_pair(coarse, 12, 24, True)
== (12, 24, ""))
try:
node.read(dump_dir=os.path.join(root, "nope"), mode="indecision",
**BASE)
check("an empty dump_dir refuses", False, "no assert raised")
except AssertionError as e:
check("an empty dump_dir refuses", "X0 Tap" in str(e), str(e)[:90])
# ---- 8. the honest headline -------------------------------------
print("\n8. DEFAULTS UNCHANGED ELSEWHERE")
check("H3JerkOracle still defaults to q=0.75 d_max=4 ramp=True",
[motion.H3JerkOracle.INPUT_TYPES()["required"][k]["default"]
if isinstance(motion.H3JerkOracle.INPUT_TYPES()["required"][k], dict)
else motion.H3JerkOracle.INPUT_TYPES()["required"][k][1]["default"]
for k in ("q", "d_max", "ramp")] == [0.75, 4, True])
check("H3TimeSmear still defaults to uniform dilation 4",
motion.H3TimeSmear.INPUT_TYPES()["required"]["dilation"][1]["default"] == 4)
check("the new node is registered and marked experimental",
motion.TIMESMEAR_CLASS_MAPPINGS["H3IndecisionOracle"]
is motion.H3IndecisionOracle and
"experimental" in
motion.TIMESMEAR_DISPLAY_MAPPINGS["H3IndecisionOracle"],
motion.TIMESMEAR_DISPLAY_MAPPINGS["H3IndecisionOracle"])
check("its outputs mirror the jerk oracle's, then add two",
motion.H3IndecisionOracle.RETURN_NAMES[:6]
== motion.H3JerkOracle.RETURN_NAMES and
motion.H3IndecisionOracle.RETURN_TYPES[:6]
== motion.H3JerkOracle.RETURN_TYPES,
str(motion.H3IndecisionOracle.RETURN_NAMES))
print("\n sample comparison report:")
for line in comp.splitlines():
print(" " + line)
finally:
shutil.rmtree(root, ignore_errors=True)
print()
if FAILS:
print(f"FAILED: {len(FAILS)} check(s): " + ", ".join(FAILS))
return 1
print("all checks passed")
return 0
if __name__ == "__main__":
sys.exit(main())
|