BonanDing commited on
Commit
6469fad
·
1 Parent(s): a76aea2

Add DeMemWM dynamic selector diagnostics

Browse files
.exp_artifact/dememwm_revisit_dynamic_selector_diagnostic.py ADDED
@@ -0,0 +1,363 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Generate selector diagnostics for the DeMemWM dynamic-stream fix."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import csv
7
+ import sys
8
+ from pathlib import Path
9
+
10
+ REPO_ROOT = Path(__file__).resolve().parents[1]
11
+ if str(REPO_ROOT) not in sys.path:
12
+ sys.path.insert(0, str(REPO_ROOT))
13
+
14
+ import numpy as np
15
+ from PIL import Image, ImageDraw
16
+
17
+ from datasets.video.memory_selection import (
18
+ SEGMENT_KEYS,
19
+ _build_dynamic_stream,
20
+ _select_dynamic_from_stream,
21
+ select_memory_indices,
22
+ )
23
+ from datasets.video.minecraft_video_dememwm_latent_dataset import (
24
+ MinecraftVideoDeMemWMLatentDataset,
25
+ _ACTION_DIM,
26
+ convert_action_space,
27
+ )
28
+
29
+ DATA_ROOT = Path("/share_1/users/bonan_ding/worldmem_data/minecraft")
30
+ INPUT_CSV = REPO_ROOT / ".exp_artifact" / "worldmem_vs_dememwm_memory_samples.csv"
31
+ OUTPUT_DIR = REPO_ROOT / ".exp_artifact" / "dememwm_revisit_dynamic_selector_diagnostic"
32
+
33
+ INITIAL_FRAME_OFFSET = 100
34
+ CONTEXT_LENGTH = 100
35
+ N_FRAMES = 8
36
+
37
+ MEMORY_SELECTION = {
38
+ "enabled": True,
39
+ "max_anchor_frames": 2,
40
+ "max_dynamic_frames": 4,
41
+ "max_revisit_frames": 2,
42
+ "pose_similarity_threshold": 0.6,
43
+ "training_use_plucker": True,
44
+ "training_plucker_weight": 1.0,
45
+ "fov_overlap_threshold": 0.6,
46
+ "min_total_selected_coverage": 0.1,
47
+ "local_context_exclusion_frames": 8,
48
+ "plucker_moment_radius": 30.0,
49
+ "anchor_diverse_selection": True,
50
+ "pose_preselect_topk": 64,
51
+ "candidate_chunk_size": 64,
52
+ "dynamic": {
53
+ "selection_policy": "event_triggered",
54
+ "scene_threshold": 2.5,
55
+ "state_threshold": 2.5,
56
+ "stable_threshold": 1.0,
57
+ "stable_frames": 3,
58
+ "min_event_gap": 8,
59
+ "min_anchor_score": 2.0,
60
+ "max_event_anchors": None,
61
+ "b_pose": 0.3,
62
+ "b_action": 0.2,
63
+ },
64
+ }
65
+
66
+
67
+ def _feature_path(video_path: Path) -> Path:
68
+ relative = video_path.relative_to(DATA_ROOT)
69
+ return DATA_ROOT / "vae_features" / relative.parent / f"{relative.stem}_vae_feature.npy"
70
+
71
+
72
+ def _load_video_arrays(relative_video: str):
73
+ video_path = DATA_ROOT / relative_video
74
+ feature_path = _feature_path(video_path)
75
+ action_path = video_path.with_suffix(".npz")
76
+ latents = np.load(feature_path, mmap_mode="r")
77
+ with np.load(action_path, allow_pickle=False) as data:
78
+ raw_actions = np.asarray(data["actions"])
79
+ if raw_actions.ndim == 2 and raw_actions.shape[1] == _ACTION_DIM:
80
+ actions = raw_actions.astype(np.float32, copy=False)
81
+ else:
82
+ actions = convert_action_space(raw_actions).numpy()
83
+ poses = MinecraftVideoDeMemWMLatentDataset._sanitize_poses(
84
+ action_path,
85
+ np.asarray(data["poses"], dtype=np.float32),
86
+ len(actions),
87
+ )
88
+ return video_path, latents, actions, poses
89
+
90
+
91
+ def _valid(indices, masks, key: str) -> list[int]:
92
+ return [int(x) for x in np.asarray(indices[key], dtype=np.int64)[np.asarray(masks[key], dtype=bool)]]
93
+
94
+
95
+ def _rel(values: list[int], target: int) -> list[int]:
96
+ return [int(value) - int(target) for value in values]
97
+
98
+
99
+ def _nearest_revisit_delta(frame: int, revisit: list[int]) -> int | None:
100
+ if not revisit:
101
+ return None
102
+ revisit_np = np.asarray(revisit, dtype=np.int64)
103
+ nearest = int(revisit_np[np.argmin(np.abs(revisit_np - int(frame)))])
104
+ return int(frame) - nearest
105
+
106
+
107
+ def _stream_ranks(stream: np.ndarray, frames: list[int]) -> list[int]:
108
+ ranks = []
109
+ for frame in frames:
110
+ hits = np.nonzero(stream == int(frame))[0]
111
+ ranks.append(int(hits[0]) if len(hits) else -1)
112
+ return ranks
113
+
114
+
115
+ def _stream_window(eligible_stream: np.ndarray, revisit: list[int], count: int = 8) -> list[int]:
116
+ if len(eligible_stream) == 0:
117
+ return []
118
+ if not revisit:
119
+ return [int(x) for x in eligible_stream[-count:]]
120
+ refs = np.asarray(revisit, dtype=np.int64)
121
+ distance = np.min(np.abs(eligible_stream[:, None] - refs[None, :]), axis=1)
122
+ order = np.lexsort((eligible_stream, distance))[:count]
123
+ return [int(x) for x in np.sort(eligible_stream[order])]
124
+
125
+
126
+ def _list_text(values) -> str:
127
+ return "[" + ", ".join("" if value is None else str(value) for value in values) + "]"
128
+
129
+
130
+ def _read_samples() -> list[dict]:
131
+ with INPUT_CSV.open("r", newline="", encoding="utf-8") as handle:
132
+ return list(csv.DictReader(handle))
133
+
134
+
135
+
136
+ def _analyze_sample(row: dict) -> tuple[dict, dict]:
137
+ sample = int(row["sample"])
138
+ relative_video = row["video"]
139
+ target_raw = int(row["worldmem_at_demem_target_start"])
140
+ clip_offset = int(row["clip_offset"])
141
+ video_path, latents, actions, poses = _load_video_arrays(relative_video)
142
+
143
+ dynamic_stream = _build_dynamic_stream(
144
+ latents,
145
+ actions=actions,
146
+ poses=poses,
147
+ cfg=MEMORY_SELECTION,
148
+ min_candidate_frame=INITIAL_FRAME_OFFSET,
149
+ )
150
+ target_positions = target_raw + np.arange(N_FRAMES, dtype=np.int64)
151
+ anchor_start = max(INITIAL_FRAME_OFFSET, target_raw - CONTEXT_LENGTH)
152
+ indices, masks = select_memory_indices(
153
+ poses,
154
+ target_positions,
155
+ MEMORY_SELECTION,
156
+ split="training",
157
+ min_candidate_frame=INITIAL_FRAME_OFFSET,
158
+ latents=latents,
159
+ actions=actions,
160
+ anchor_candidate_start=anchor_start,
161
+ anchor_candidate_stop=target_raw,
162
+ dynamic_stream=dynamic_stream,
163
+ )
164
+
165
+ selected = {key: _valid(indices, masks, key) for key in SEGMENT_KEYS}
166
+ exclusion = int(MEMORY_SELECTION["local_context_exclusion_frames"])
167
+ causal_cutoff = target_raw - exclusion
168
+ eligible_stream = dynamic_stream[(dynamic_stream >= INITIAL_FRAME_OFFSET) & (dynamic_stream < causal_cutoff)]
169
+ stream_window = _stream_window(eligible_stream, selected["revisit"], count=8)
170
+ dynamic_revisit_rel = [_nearest_revisit_delta(frame, selected["revisit"]) for frame in selected["dynamic"]]
171
+ dynamic_stream_rank = _stream_ranks(dynamic_stream, selected["dynamic"])
172
+ stream_flags = {
173
+ key: {
174
+ "causal": bool(all(frame < target_raw for frame in selected[key])),
175
+ "outside": bool(all(frame < causal_cutoff for frame in selected[key])),
176
+ }
177
+ for key in SEGMENT_KEYS
178
+ }
179
+
180
+ record = {
181
+ "sample": sample,
182
+ "video": relative_video,
183
+ "clip_offset": clip_offset,
184
+ "target_raw": target_raw,
185
+ "anchor_candidate_window": f"{anchor_start}..{target_raw - 1}",
186
+ "causal_cutoff": causal_cutoff,
187
+ "anchor_raw": _list_text(selected["anchor"]),
188
+ "anchor_rel": _list_text(_rel(selected["anchor"], target_raw)),
189
+ "revisit_raw": _list_text(selected["revisit"]),
190
+ "revisit_rel": _list_text(_rel(selected["revisit"], target_raw)),
191
+ "eligible_stream_count": int(len(eligible_stream)),
192
+ "stream_window_raw": _list_text(stream_window),
193
+ "dynamic_raw": _list_text(selected["dynamic"]),
194
+ "dynamic_rel": _list_text(_rel(selected["dynamic"], target_raw)),
195
+ "dynamic_rel_to_nearest_revisit": _list_text(dynamic_revisit_rel),
196
+ "dynamic_stream_rank": _list_text(dynamic_stream_rank),
197
+ "anchor_causal": stream_flags["anchor"]["causal"],
198
+ "anchor_outside_local_exclusion": stream_flags["anchor"]["outside"],
199
+ "revisit_causal": stream_flags["revisit"]["causal"],
200
+ "revisit_outside_local_exclusion": stream_flags["revisit"]["outside"],
201
+ "dynamic_causal": stream_flags["dynamic"]["causal"],
202
+ "dynamic_outside_local_exclusion": stream_flags["dynamic"]["outside"],
203
+ "dynamic_causal_outside_local_exclusion": stream_flags["dynamic"]["causal"] and stream_flags["dynamic"]["outside"],
204
+ "old_dynamic_raw": row["demem_dynamic"],
205
+ "old_revisit_raw": row["demem_revisit"],
206
+ }
207
+ visual = {
208
+ "sample": sample,
209
+ "video_path": video_path,
210
+ "target_raw": target_raw,
211
+ "selected": selected,
212
+ "dynamic_revisit_rel": dynamic_revisit_rel,
213
+ "dynamic_stream_rank": dynamic_stream_rank,
214
+ }
215
+ return record, visual
216
+
217
+
218
+ def _read_frame(video_path: Path, frame_idx: int):
219
+ try:
220
+ import cv2
221
+ except ImportError:
222
+ return None
223
+ cap = cv2.VideoCapture(str(video_path))
224
+ if not cap.isOpened():
225
+ return None
226
+ cap.set(cv2.CAP_PROP_POS_FRAMES, int(frame_idx))
227
+ ok, frame = cap.read()
228
+ cap.release()
229
+ if not ok:
230
+ return None
231
+ frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
232
+ return Image.fromarray(frame)
233
+
234
+
235
+ def _tile(video_path: Path, frame_idx: int | None, label: str, size=(160, 90), label_h=52) -> Image.Image:
236
+ image = Image.new("RGB", (size[0], size[1] + label_h), (230, 230, 230))
237
+ if frame_idx is not None and frame_idx >= 0:
238
+ frame = _read_frame(video_path, frame_idx)
239
+ if frame is not None:
240
+ image.paste(frame.resize(size, Image.BILINEAR), (0, label_h))
241
+ draw = ImageDraw.Draw(image)
242
+ draw.rectangle((0, 0, size[0], label_h), fill=(20, 20, 20))
243
+ for line_idx, line in enumerate(label.split("\n")[:3]):
244
+ draw.text((4, 4 + line_idx * 15), line[:28], fill=(255, 255, 255))
245
+ return image
246
+
247
+
248
+ def _make_contact_sheet(visual_rows: list[dict], output_path: Path) -> bool:
249
+ try:
250
+ import cv2 # noqa: F401
251
+ except ImportError:
252
+ return False
253
+
254
+ columns = ["target", "anchor0", "anchor1", "revisit0", "revisit1", "dynamic0", "dynamic1", "dynamic2", "dynamic3"]
255
+ tile_w, tile_h = 160, 142
256
+ gap = 6
257
+ sheet = Image.new(
258
+ "RGB",
259
+ (len(columns) * tile_w + (len(columns) - 1) * gap, len(visual_rows) * tile_h + (len(visual_rows) - 1) * gap),
260
+ (245, 245, 245),
261
+ )
262
+ for row_idx, row in enumerate(visual_rows):
263
+ target = int(row["target_raw"])
264
+ selected = row["selected"]
265
+ video_path = row["video_path"]
266
+ cells: list[tuple[int | None, str]] = [
267
+ (target, f"target raw{target}\nrelT 0\nsample {row['sample']}"),
268
+ ]
269
+ for slot in range(2):
270
+ frame = selected["anchor"][slot] if slot < len(selected["anchor"]) else None
271
+ label = f"anchor{slot}" if frame is None else f"anchor{slot} raw{frame}\nrelT {frame - target}\n"
272
+ cells.append((frame, label))
273
+ for slot in range(2):
274
+ frame = selected["revisit"][slot] if slot < len(selected["revisit"]) else None
275
+ label = f"revisit{slot}" if frame is None else f"revisit{slot} raw{frame}\nrelT {frame - target}\n"
276
+ cells.append((frame, label))
277
+ for slot in range(4):
278
+ frame = selected["dynamic"][slot] if slot < len(selected["dynamic"]) else None
279
+ if frame is None:
280
+ label = f"dynamic{slot}"
281
+ else:
282
+ rel_r = row["dynamic_revisit_rel"][slot] if slot < len(row["dynamic_revisit_rel"]) else None
283
+ rank = row["dynamic_stream_rank"][slot] if slot < len(row["dynamic_stream_rank"]) else -1
284
+ label = f"dynamic{slot} raw{frame}\nrelT {frame - target} relR {rel_r}\nrank {rank}"
285
+ cells.append((frame, label))
286
+ y = row_idx * (tile_h + gap)
287
+ for col_idx, (frame, label) in enumerate(cells):
288
+ x = col_idx * (tile_w + gap)
289
+ sheet.paste(_tile(video_path, frame, label), (x, y))
290
+ sheet.save(output_path)
291
+ return True
292
+
293
+
294
+ def _write_outputs(records: list[dict], visual_rows: list[dict]) -> None:
295
+ OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
296
+ csv_path = OUTPUT_DIR / "selector_diagnostic.csv"
297
+ with csv_path.open("w", newline="", encoding="utf-8") as handle:
298
+ writer = csv.DictWriter(handle, fieldnames=list(records[0].keys()))
299
+ writer.writeheader()
300
+ writer.writerows(records)
301
+
302
+ sheet_written = _make_contact_sheet(visual_rows, OUTPUT_DIR / "selector_contact_sheet.png")
303
+ md_path = OUTPUT_DIR / "selector_diagnostic.md"
304
+ with md_path.open("w", encoding="utf-8") as handle:
305
+ handle.write("# DeMemWM Revisit-Conditioned Dynamic Selector Diagnostic\n\n")
306
+ handle.write(f"- samples: {len(records)} from `{INPUT_CSV}`\n")
307
+ handle.write(f"- data root: `{DATA_ROOT}`\n")
308
+ handle.write("- selector: anchor prefix + wrapped pose deltas + cached dynamic stream around revisit\n")
309
+ handle.write(f"- contact sheet: `selector_contact_sheet.png` ({'written' if sheet_written else 'skipped: cv2 unavailable'})\n\n")
310
+ header = [
311
+ "sample",
312
+ "target",
313
+ "anchor",
314
+ "revisit",
315
+ "stream window",
316
+ "dynamic",
317
+ "dyn rel target",
318
+ "dyn rel revisit",
319
+ "dyn rank",
320
+ "stream causal/outside",
321
+ ]
322
+ handle.write("| " + " | ".join(header) + " |\n")
323
+ handle.write("| " + " | ".join(["---"] * len(header)) + " |\n")
324
+ for row in records:
325
+ causal = (
326
+ f"a {row['anchor_causal']}/{row['anchor_outside_local_exclusion']}; "
327
+ f"r {row['revisit_causal']}/{row['revisit_outside_local_exclusion']}; "
328
+ f"d {row['dynamic_causal']}/{row['dynamic_outside_local_exclusion']}"
329
+ )
330
+ values = [
331
+ str(row["sample"]),
332
+ str(row["target_raw"]),
333
+ row["anchor_raw"],
334
+ row["revisit_raw"],
335
+ row["stream_window_raw"],
336
+ row["dynamic_raw"],
337
+ row["dynamic_rel"],
338
+ row["dynamic_rel_to_nearest_revisit"],
339
+ row["dynamic_stream_rank"],
340
+ causal,
341
+ ]
342
+ handle.write("| " + " | ".join(values) + " |\n")
343
+
344
+
345
+ def main() -> None:
346
+ records = []
347
+ visual_rows = []
348
+ for row in _read_samples():
349
+ record, visual = _analyze_sample(row)
350
+ records.append(record)
351
+ visual_rows.append(visual)
352
+ _write_outputs(records, visual_rows)
353
+ for row in records:
354
+ print(
355
+ f"sample={row['sample']} target={row['target_raw']} "
356
+ f"anchor={row['anchor_raw']} revisit={row['revisit_raw']} "
357
+ f"dynamic={row['dynamic_raw']} outside={row['dynamic_causal_outside_local_exclusion']}"
358
+ )
359
+ print(f"wrote {OUTPUT_DIR}")
360
+
361
+
362
+ if __name__ == "__main__":
363
+ main()
.exp_artifact/dememwm_revisit_dynamic_selector_diagnostic/selector_contact_sheet.png ADDED

Git LFS Details

  • SHA256: 338e8534a013a3adf0ab4229797838b9f3fa6cb684a23948f4716afcffb45696
  • Pointer size: 131 Bytes
  • Size of remote file: 979 kB
.exp_artifact/dememwm_revisit_dynamic_selector_diagnostic/selector_diagnostic.csv ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ sample,video,clip_offset,target_raw,anchor_candidate_window,causal_cutoff,anchor_raw,anchor_rel,revisit_raw,revisit_rel,eligible_stream_count,stream_window_raw,dynamic_raw,dynamic_rel,dynamic_rel_to_nearest_revisit,dynamic_stream_rank,anchor_causal,anchor_outside_local_exclusion,revisit_causal,revisit_outside_local_exclusion,dynamic_causal,dynamic_outside_local_exclusion,dynamic_causal_outside_local_exclusion,old_dynamic_raw,old_revisit_raw
2
+ 0,training/place_item_4/000011.mp4,783,983,883..982,975,"[883, 923]","[-100, -60]","[973, 974]","[-10, -9]",52,"[808, 822, 834, 851, 863, 880, 915, 943]","[863, 880, 915, 943]","[-120, -103, -68, -40]","[-110, -93, -58, -30]","[48, 49, 50, 51]",True,True,True,True,True,True,True,"[880, 915, 932, 943]","[973, 974]"
3
+ 1,training/savanna_2/000056.mp4,670,870,770..869,862,"[770, 869]","[-100, -1]","[542, 543]","[-328, -327]",41,"[481, 493, 512, 532, 553, 577, 591, 607]","[512, 532, 553, 577]","[-358, -338, -317, -293]","[-30, -10, 10, 34]","[21, 22, 23, 24]",True,False,True,True,True,True,True,"[512, 532, 553, 577]","[542, 543]"
4
+ 2,training/sunflower_0/000070.mp4,150,350,250..349,342,"[250, 301]","[-100, -49]","[150, 151]","[-200, -199]",15,"[100, 117, 147, 158, 177, 188, 207, 218]","[117, 147, 158, 177]","[-233, -203, -192, -173]","[-33, -3, 7, 26]","[1, 2, 3, 4]",True,True,True,True,True,True,True,"[117, 147, 177, 193]","[150, 151]"
5
+ 3,training/ice_plains_w_updown_0/000071.mp4,686,886,786..885,878,"[786, 866]","[-100, -20]","[425, 427]","[-461, -459]",38,"[333, 352, 397, 417, 433, 447, 482, 517]","[397, 417, 433, 447]","[-489, -469, -453, -439]","[-28, -8, 6, 20]","[15, 16, 17, 18]",True,True,True,True,True,True,True,"[397, 417, 433, 447]","[425, 427]"
6
+ 4,training/place_item_w_updown_3/000169.mp4,481,681,581..680,673,"[581, 680]","[-100, -1]","[453, 454]","[-228, -227]",34,"[391, 410, 421, 438, 460, 471, 498, 515]","[421, 438, 460, 471]","[-260, -243, -221, -210]","[-32, -15, 6, 17]","[20, 21, 22, 23]",True,False,True,True,True,True,True,"[410, 438, 460, 496]","[453, 454]"
7
+ 5,training/place_item_w_updown_7/001332.mp4,1137,1337,1237..1336,1329,"[1237, 1332]","[-100, -5]","[998, 1004]","[-339, -333]",70,"[923, 935, 969, 1008, 1020, 1039, 1050, 1081]","[969, 1008, 1020, 1039]","[-368, -329, -317, -298]","[-29, 4, 16, 35]","[51, 52, 53, 54]",True,False,True,True,True,True,True,"[969, 1008, 1020, 1039]","[998, 1004]"
.exp_artifact/dememwm_revisit_dynamic_selector_diagnostic/selector_diagnostic.md ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # DeMemWM Revisit-Conditioned Dynamic Selector Diagnostic
2
+
3
+ - samples: 6 from `/share_1/users/bonan_ding/WorldMem/.exp_artifact/worldmem_vs_dememwm_memory_samples.csv`
4
+ - data root: `/share_1/users/bonan_ding/worldmem_data/minecraft`
5
+ - selector: anchor prefix + wrapped pose deltas + cached dynamic stream around revisit
6
+ - contact sheet: `selector_contact_sheet.png` (written)
7
+
8
+ | sample | target | anchor | revisit | stream window | dynamic | dyn rel target | dyn rel revisit | dyn rank | stream causal/outside |
9
+ | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
10
+ | 0 | 983 | [883, 923] | [973, 974] | [808, 822, 834, 851, 863, 880, 915, 943] | [863, 880, 915, 943] | [-120, -103, -68, -40] | [-110, -93, -58, -30] | [48, 49, 50, 51] | a True/True; r True/True; d True/True |
11
+ | 1 | 870 | [770, 869] | [542, 543] | [481, 493, 512, 532, 553, 577, 591, 607] | [512, 532, 553, 577] | [-358, -338, -317, -293] | [-30, -10, 10, 34] | [21, 22, 23, 24] | a True/False; r True/True; d True/True |
12
+ | 2 | 350 | [250, 301] | [150, 151] | [100, 117, 147, 158, 177, 188, 207, 218] | [117, 147, 158, 177] | [-233, -203, -192, -173] | [-33, -3, 7, 26] | [1, 2, 3, 4] | a True/True; r True/True; d True/True |
13
+ | 3 | 886 | [786, 866] | [425, 427] | [333, 352, 397, 417, 433, 447, 482, 517] | [397, 417, 433, 447] | [-489, -469, -453, -439] | [-28, -8, 6, 20] | [15, 16, 17, 18] | a True/True; r True/True; d True/True |
14
+ | 4 | 681 | [581, 680] | [453, 454] | [391, 410, 421, 438, 460, 471, 498, 515] | [421, 438, 460, 471] | [-260, -243, -221, -210] | [-32, -15, 6, 17] | [20, 21, 22, 23] | a True/False; r True/True; d True/True |
15
+ | 5 | 1337 | [1237, 1332] | [998, 1004] | [923, 935, 969, 1008, 1020, 1039, 1050, 1081] | [969, 1008, 1020, 1039] | [-368, -329, -317, -298] | [-29, 4, 16, 35] | [51, 52, 53, 54] | a True/False; r True/True; d True/True |