Drop orphaned child tracks instead of nulling secondary targets
A listed child_track_id can fail to match any first-step row (e.g. a secondary absorbed below the tracking threshold at birth). The parent->child left join in _add_secondary_attributes left these as nulls, which silently became NaN once the parquet round-tripped through the loader's float32 padding — poisoning every later secondary slot in that step via the cumulative "remaining budget" in encode_secondaries, while e_sec quietly undercounted and n_sec (from len(child_track_ids)) overcounted relative to the actual lists. Drop orphans from both the per-secondary lists and child_track_ids itself so downstream counts stay consistent, and thread the per-file orphaned count back through convert_steps_to_parquet so both the sequential and --jobs>1 batch paths in `dwarf convert` can report an aggregate total instead of relying on grepping printed output. Also floors encode_secondaries' slot-0 budget to _EPS (matching the i>0 branch), fixing a harmless but noisy 0/0 divide warning on zero-secondary steps. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -22,7 +22,8 @@ def _frame() -> pl.DataFrame:
|
||||
|
||||
|
||||
def test_e_sec_sums_child_first_step_energy():
|
||||
out = steps_to_parquet._add_secondary_attributes(_frame())
|
||||
out, n_orphaned = steps_to_parquet._add_secondary_attributes(_frame())
|
||||
assert n_orphaned == 0
|
||||
e_sec = dict(
|
||||
zip(zip(out["track_id"], out["step_no"], out["event_id"]), out["e_sec"])
|
||||
)
|
||||
@@ -31,7 +32,7 @@ def test_e_sec_sums_child_first_step_energy():
|
||||
|
||||
|
||||
def test_e_sec_zero_when_no_children():
|
||||
out = steps_to_parquet._add_secondary_attributes(_frame())
|
||||
out, _ = steps_to_parquet._add_secondary_attributes(_frame())
|
||||
childless = out.filter(
|
||||
(pl.col("event_id") == 0) & (pl.col("track_id") == 1) & (pl.col("step_no") == 1)
|
||||
)
|
||||
@@ -40,6 +41,38 @@ def test_e_sec_zero_when_no_children():
|
||||
|
||||
def test_e_sec_preserves_row_count_and_order():
|
||||
df = _frame()
|
||||
out = steps_to_parquet._add_secondary_attributes(df)
|
||||
out, _ = steps_to_parquet._add_secondary_attributes(df)
|
||||
assert out.height == df.height
|
||||
assert out["pre_E"].to_list() == df["pre_E"].to_list()
|
||||
|
||||
|
||||
def test_orphaned_child_track_is_dropped_not_nulled():
|
||||
"""A listed child_track_id with no first step of its own (e.g. absorbed
|
||||
below the tracking threshold at birth) must not leave a null in
|
||||
sec_E_list/sec_pdg_list/etc: that null turns into NaN once the parquet
|
||||
round-trips through the loader, poisoning every later secondary slot in
|
||||
the step via encode_secondaries' cumulative "remaining budget". It must
|
||||
also be dropped from child_track_ids itself, so n_sec (len(child_track_ids)
|
||||
downstream) matches the actual, orphan-free secondary lists."""
|
||||
df = pl.DataFrame(
|
||||
{
|
||||
"event_id": [0, 0, 0],
|
||||
"track_id": [1, 1, 2],
|
||||
"step_no": [0, 1, 0],
|
||||
"pre_E": [100.0, 80.0, 15.0],
|
||||
"pdg": [11, 11, 22],
|
||||
"pre_dx": [0.0, 0.0, 1.0],
|
||||
"pre_dy": [0.0, 0.0, 0.0],
|
||||
"pre_dz": [1.0, 1.0, 0.0],
|
||||
# track 3 is listed as a child but never appears with its own step.
|
||||
"child_track_ids": [[2, 3], [], []],
|
||||
}
|
||||
)
|
||||
out, n_orphaned = steps_to_parquet._add_secondary_attributes(df)
|
||||
row = out.filter((pl.col("event_id") == 0) & (pl.col("track_id") == 1) & (pl.col("step_no") == 0))
|
||||
|
||||
assert n_orphaned == 1
|
||||
assert row["child_track_ids"].to_list() == [[2]]
|
||||
assert row["e_sec"].item() == 15.0
|
||||
assert row["sec_E_list"].to_list() == [[15.0]]
|
||||
assert None not in row["sec_E_list"].item()
|
||||
|
||||
Reference in New Issue
Block a user