1414//
1515// This tool fixes all three problems in one pass per DF_ directory:
1616//
17- // Stage 0 — Sort & deduplicate the BC table. Build BC permutation map:
18- // bcPerm[oldBCrow] = newBCrow.
17+ // Stage 0 — Deduplicate the BC table in place (order-preserving; the input
18+ // must already be globalBC-sorted). Build BC permutation map:
19+ // bcPerm[oldBCrow] = newBCrow (monotonic non-decreasing).
1920//
2021// Stage 1 — Process every table that carries fIndexBCs / fIndexBC.
2122// Remap the index via bcPerm, sort rows by the new index, and
@@ -501,18 +502,31 @@ static PermMap rewriteTable(TTree *src, TDirectory *dirOut,
501502}
502503
503504// ============================================================================
504- // SECTION 4 — Stage 0: BC table sort + deduplication
505+ // SECTION 4 — Stage 0: BC table deduplication (order-preserving)
505506// ============================================================================
506507//
507- // Reads fGlobalBC from the BC tree, sorts rows, drops exact-duplicate BC
508- // values, and writes the compacted table. Returns bcPerm[oldRow] = newRow.
508+ // Reads fGlobalBC from the BC tree, drops exact-duplicate BC values IN PLACE
509+ // (preserving input row order), and writes the compacted table. Returns
510+ // bcPerm[oldRow] = newRow.
511+ //
512+ // The dedup is deliberately order-preserving so that bcPerm is monotonic
513+ // non-decreasing. This matters because every BC-indexed table (collisions,
514+ // FT0/FV0/FDD/Zdc, ...) is sliced per BC and must stay sorted by its fIndexBCs,
515+ // and collisions are in turn the grouping anchor for tracks (sorted by
516+ // fIndexCollisions). A non-order-preserving BC remap would force a full reorder
517+ // cascade BC -> collisions -> tracks to keep all those groupings valid; keeping
518+ // bcPerm monotonic means none of those tables need to be reordered at all.
519+ //
520+ // This REQUIRES the input BC table to already be sorted by fGlobalBC (the
521+ // standard AO2D invariant; also asserted on the output by validateDF check #1).
522+ // We assert it loudly rather than silently emit a non-monotonic BC table.
509523
510524struct BCStage0Result {
511- PermMap bcPerm ; // bcPerm[oldRow] = newRow in sorted/ deduped BC table
525+ PermMap bcPerm ; // bcPerm[oldRow] = newRow in the deduped BC table
512526 Long64_t nUnique = 0 ;
513527};
514528
515- static BCStage0Result stage0_sortBCs (TTree * treeBCs , TDirectory * dirOut ) {
529+ static BCStage0Result stage0_dedupBCs (TTree * treeBCs , TDirectory * dirOut ) {
516530 BCStage0Result res ;
517531 Long64_t n = treeBCs -> GetEntries ();
518532 if (n == 0 ) return res ;
@@ -525,19 +539,29 @@ static BCStage0Result stage0_sortBCs(TTree *treeBCs, TDirectory *dirOut) {
525539 std ::vector < ULong64_t > gbcs (n );
526540 for (Long64_t i = 0 ; i < n ; ++ i ) { treeBCs -> GetEntry (i ); gbcs [i ] = gbc ; }
527541
528- // Sort row indices by fGlobalBC
529- std ::vector < Long64_t > order (n );
530- std ::iota (order .begin (), order .end (), 0 );
531- std ::stable_sort (order .begin (), order .end (),
532- [& ](Long64_t a , Long64_t b ){ return gbcs [a ] < gbcs [b ]; });
542+ // The BC table must already be sorted by fGlobalBC: the dedup below is
543+ // order-preserving (merges only adjacent equal-globalBC rows), which keeps
544+ // bcPerm monotonic and avoids a reorder cascade through collisions/tracks.
545+ // A non-monotonic input would silently break that guarantee, so abort loudly.
546+ for (Long64_t i = 1 ; i < n ; ++ i ) {
547+ if (gbcs [i ] < gbcs [i - 1 ]) {
548+ std ::cerr << "FATAL: O2bc_* table is not sorted by fGlobalBC (row " << i
549+ << " globalBC=" << gbcs [i ] << " < row " << (i - 1 )
550+ << " globalBC=" << gbcs [i - 1 ] << ").\n"
551+ << " AODBcRewriter requires a globalBC-sorted BC table so that\n"
552+ << " BC deduplication is order-preserving; aborting.\n" ;
553+ std ::abort ();
554+ }
555+ }
533556
534- // Build deduplicated row list and the permutation
557+ // Build the deduplicated row list and the (monotonic) permutation in source
558+ // row order: adjacent rows sharing a globalBC collapse onto one output row.
535559 res .bcPerm .assign (n , -1 );
536560 std ::vector < Long64_t > rowOrder ; // source rows to keep, in output order
537- ULong64_t prev = ULong64_t ( -1 ) ;
561+ ULong64_t prev = 0 ;
538562 Int_t newRow = -1 ;
539- for (Long64_t srcRow : order ) {
540- if (gbcs [srcRow ] != prev ) {
563+ for (Long64_t srcRow = 0 ; srcRow < n ; ++ srcRow ) {
564+ if (newRow < 0 || gbcs [srcRow ] != prev ) {
541565 ++ newRow ;
542566 prev = gbcs [srcRow ];
543567 rowOrder .push_back (srcRow );
@@ -547,7 +571,7 @@ static BCStage0Result stage0_sortBCs(TTree *treeBCs, TDirectory *dirOut) {
547571 }
548572 res .nUnique = rowOrder .size ();
549573
550- std ::cout << " BC stage: " << n << " rows -> " << res .nUnique << " unique\n" ;
574+ std ::cout << " BC stage: " << n << " rows -> " << res .nUnique << " unique (in-place dedup) \n" ;
551575
552576 // Write the BC table (no index remapping needed for the table itself)
553577 rewriteTable (treeBCs , dirOut , rowOrder , /*indexBranch=*/ "" , /*parentPerm=*/ {});
@@ -1020,10 +1044,10 @@ static void processDF(TDirectory *dirIn, TDirectory *dirOut) {
10201044 return ;
10211045 }
10221046
1023- // ---- Stage 0: sort & deduplicate BCs ----
1047+ // ---- Stage 0: deduplicate BCs (order-preserving) ----
10241048 std ::cout << "-- Stage 0: BCs --\n" ;
10251049 dirOut -> cd ();
1026- BCStage0Result s0 = stage0_sortBCs (treeBCs , dirOut );
1050+ BCStage0Result s0 = stage0_dedupBCs (treeBCs , dirOut );
10271051 if (treeFlags ) stage0_copyBCFlags (treeFlags , dirOut , s0 .bcPerm );
10281052
10291053 // Track which tree names have been written so we don't double-write
0 commit comments