Back to home page

sPhenix code displayed by LXR

 
 

    


File indexing completed on 2026-10-16 08:28:59

0001 #include "PHSiliconSeedPrunerHelper.h"
0002 
0003 #include <trackbase/TrkrDefs.h>
0004 
0005 #include <trackbase_historic/TrackSeed.h>
0006 #include <trackbase_historic/TrackSeedContainer.h>
0007 
0008 #include <gsl/gsl_randist.h>
0009 
0010 #include <algorithm>
0011 #include <iostream>
0012 #include <map>
0013 #include <numeric>
0014 #include <unordered_map>
0015 #include <vector>
0016 
0017 namespace PHSiliconSeedPrunerHelper
0018 {
0019   // a container for a seed's cluster keys, its index in the TrackSeedContainer, a flag for whether it has a complete MVTX triplet
0020   struct SeedRecord
0021   {
0022     size_t index = 0;
0023     std::vector<TrkrDefs::cluskey> allClusterKeys;
0024     std::vector<TrkrDefs::cluskey> mvtxClusterKeys;
0025     bool completeMvtx = false;
0026   };
0027 
0028   using ConflictGraph = std::vector<std::vector<size_t>>;
0029 
0030   // standard disjoint-set/union-find data structure
0031   class DisjointSet
0032   {
0033    public:
0034     explicit DisjointSet(size_t n)
0035       : parent_(n)
0036       , rank_(n, 0)
0037     {
0038       std::iota(parent_.begin(), parent_.end(), 0);
0039     }
0040 
0041     size_t Find(size_t value)
0042     {
0043       // find the root of a value, and perform path compression
0044       if (parent_[value] != value)
0045       {
0046         parent_[value] = Find(parent_[value]);  // the path compression step
0047       }
0048       return parent_[value];
0049     }
0050 
0051     // merge sets
0052     void Union(size_t lhs, size_t rhs)
0053     {
0054       size_t root_lhs = Find(lhs);
0055       size_t root_rhs = Find(rhs);
0056       if (root_lhs == root_rhs)
0057       {
0058         return;
0059       }
0060 
0061       // The lower-rank tree is attached under the higher-rank tree
0062       if (rank_[root_lhs] < rank_[root_rhs])
0063       {
0064         std::swap(root_lhs, root_rhs);
0065       }
0066 
0067       // combine the tree and update the rank if necessary
0068       parent_[root_rhs] = root_lhs;
0069       if (rank_[root_lhs] == rank_[root_rhs])
0070       {
0071         ++rank_[root_lhs];
0072       }
0073     }
0074 
0075    private:
0076     std::vector<size_t> parent_;
0077     std::vector<unsigned int> rank_;  // rank -> the depth of the tree
0078   };
0079 
0080   SeedRecord MakeSeedRecord(size_t seedIndex, const TrackSeed &seed, size_t mvtxLayerCount)
0081   {
0082     SeedRecord record;
0083     record.index = seedIndex;
0084 
0085     std::vector<unsigned int> mvtx_layer_counts(mvtxLayerCount, 0);
0086     for (auto iter = seed.begin_cluster_keys(); iter != seed.end_cluster_keys(); ++iter)
0087     {
0088       const TrkrDefs::cluskey key = *iter;
0089       record.allClusterKeys.push_back(key);
0090 
0091       if (TrkrDefs::getTrkrId(key) != TrkrDefs::mvtxId)
0092       {
0093         continue;
0094       }
0095 
0096       record.mvtxClusterKeys.push_back(key);
0097       const unsigned int layer = TrkrDefs::getLayer(key);
0098       if (layer < mvtxLayerCount)
0099       {
0100         ++mvtx_layer_counts[layer];
0101       }
0102     }
0103 
0104     record.completeMvtx =
0105         record.mvtxClusterKeys.size() == mvtxLayerCount &&
0106         mvtx_layer_counts[0] == 1 &&
0107         mvtx_layer_counts[1] == 1 &&
0108         mvtx_layer_counts[2] == 1;
0109     return record;
0110   }
0111 
0112   // Turn a conflict graph into a list of connected components
0113   // seeds that share only INTT clusters are not connected here (they are still in the same ambiguity group)
0114   std::vector<std::vector<size_t>> ConnectedComponents(const ConflictGraph &conflict)
0115   {
0116     std::vector<std::vector<size_t>> components;
0117     std::vector<bool> visited(conflict.size(), false);
0118 
0119     for (size_t start = 0; start < conflict.size(); ++start)
0120     {
0121       if (visited[start])
0122       {
0123         continue;
0124       }
0125 
0126       std::vector<size_t> component;
0127       std::vector<size_t> stack{start};
0128       visited[start] = true;
0129 
0130       while (!stack.empty())
0131       {
0132         const size_t vertex = stack.back();  // look at the top of stack
0133         stack.pop_back();                    // pop
0134         component.push_back(vertex);
0135 
0136         // inspect the neighbors
0137         for (const size_t neighbor : conflict[vertex])
0138         {
0139           if (!visited[neighbor])
0140           {
0141             visited[neighbor] = true;
0142             stack.push_back(neighbor);
0143           }
0144         }
0145       }
0146 
0147       components.push_back(std::move(component));
0148     }
0149 
0150     return components;
0151   }
0152 
0153   // collapse duplicate triplets
0154   struct TripletClass
0155   {
0156     std::vector<TrkrDefs::cluskey> mvtxKeys;  //
0157     std::vector<size_t> memberSeedIndices;    // global seed indices in this triplet class
0158   };
0159 
0160   // Collapse seeds that have the identical MVTX triplet into 1 class (they contribute only 1 representative)
0161   std::vector<TripletClass> CollapseDuplicateTriplets(  //
0162       const std::vector<const SeedRecord *> &completeSeeds,
0163       bool debugVerbosity)
0164   {
0165     if (debugVerbosity)
0166     {
0167       std::cout << __func__ << " : " << __LINE__ << " : collapsing " << completeSeeds.size() << " complete MVTX seeds into unique MVTX-triplet classes" << std::endl;
0168     }
0169 
0170     std::map<std::vector<TrkrDefs::cluskey>, size_t> classByTriplet;
0171     std::vector<TripletClass> classes;
0172 
0173     for (const SeedRecord *seed : completeSeeds)
0174     {
0175       const auto inserted = classByTriplet.emplace(seed->mvtxClusterKeys, classes.size());  // return a std::pair<iterator, bool> where the bool is true if the insertion took place
0176       // if not already present, create a new TripletClass
0177       if (inserted.second)
0178       {
0179         TripletClass cls;
0180         cls.mvtxKeys = seed->mvtxClusterKeys;
0181         classes.push_back(std::move(cls));
0182       }
0183       classes[inserted.first->second].memberSeedIndices.push_back(seed->index);
0184     }
0185 
0186     return classes;
0187   }
0188 
0189   // Build a conflict graph on the TripletClasses
0190   ConflictGraph BuildClassConflictGraph(const std::vector<TripletClass> &classes)
0191   {
0192     // map of cluster keys to the list of triplet classes that contain it
0193     std::unordered_map<TrkrDefs::cluskey, std::vector<size_t>> classesByKey;
0194     for (size_t c = 0; c < classes.size(); ++c)
0195     {
0196       for (const TrkrDefs::cluskey key : classes[c].mvtxKeys)
0197       {
0198         classesByKey[key].push_back(c);
0199       }
0200     }
0201 
0202     // build the conflict graph (nodes and edges) from the key index
0203     ConflictGraph conflict(classes.size());
0204     for (const auto &entry : classesByKey)
0205     {
0206       const std::vector<size_t> &sharing = entry.second;
0207       for (size_t a = 0; a < sharing.size(); ++a)
0208       {
0209         for (size_t b = a + 1; b < sharing.size(); ++b)
0210         {
0211           // make edges in both directions
0212           conflict[sharing[a]].push_back(sharing[b]);
0213           conflict[sharing[b]].push_back(sharing[a]);
0214         }
0215       }
0216     }
0217 
0218     // sort and remove duplicates from the adjacency lists
0219     for (std::vector<size_t> &neighbors : conflict)
0220     {
0221       std::sort(neighbors.begin(), neighbors.end());
0222       neighbors.erase(std::unique(neighbors.begin(), neighbors.end()), neighbors.end());
0223     }
0224 
0225     return conflict;
0226   }
0227 
0228   // a container for 1 connected component of the class-conflict graph
0229   struct ComponentProblem
0230   {
0231     size_t m = 0;
0232     size_t numKeys = 0;
0233     std::vector<std::vector<size_t>> adj;           // local class adjacency
0234     std::vector<std::vector<size_t>> keyIds;        // local key ids per class (3 each)
0235     std::vector<std::vector<size_t>> keyToClasses;  // local key id -> local class ids
0236     std::vector<double> memberCount;                // class multiplicity (sampling weight)
0237     std::vector<size_t> globalClassId;              // local -> global class index
0238   };
0239 
0240   ComponentProblem BuildComponentProblem(const std::vector<TripletClass> &classes, const ConflictGraph &classConflict, const std::vector<size_t> &component)
0241   {
0242     ComponentProblem prob;
0243     prob.m = component.size();
0244     prob.globalClassId = component;
0245     prob.adj.resize(prob.m);
0246     prob.keyIds.resize(prob.m);
0247     prob.memberCount.resize(prob.m);
0248 
0249     std::unordered_map<size_t, size_t> localOf;  // global class index -> local class index
0250     for (size_t local = 0; local < component.size(); ++local)
0251     {
0252       localOf[component[local]] = local;
0253     }
0254 
0255     std::unordered_map<TrkrDefs::cluskey, size_t> keyId;  // MVTX cluster key -> local key id
0256 
0257     for (size_t local = 0; local < prob.m; ++local)
0258     {
0259       const size_t global = component[local];
0260       prob.memberCount[local] = static_cast<double>(classes[global].memberSeedIndices.size());
0261 
0262       for (const TrkrDefs::cluskey key : classes[global].mvtxKeys)
0263       {
0264         const auto inserted = keyId.emplace(key, keyId.size());
0265         prob.keyIds[local].push_back(inserted.first->second);
0266       }
0267 
0268       for (const size_t global_neighbor : classConflict[global])
0269       {
0270         const auto it = localOf.find(global_neighbor);
0271         if (it != localOf.end())
0272         {
0273           prob.adj[local].push_back(it->second);
0274         }
0275       }
0276     }
0277 
0278     prob.numKeys = keyId.size();
0279     prob.keyToClasses.assign(prob.numKeys, {});
0280     for (size_t local = 0; local < prob.m; ++local)
0281     {
0282       for (const size_t kid : prob.keyIds[local])
0283       {
0284         prob.keyToClasses[kid].push_back(local);
0285       }
0286     }
0287 
0288     return prob;
0289   }
0290 
0291   // Actual solver
0292   class ComponentSolver
0293   {
0294    public:
0295     explicit ComponentSolver(             //
0296         const ComponentProblem &problem,  //
0297         bool debugDetailed = false,       //
0298         size_t debugComponentIndex = 0    //
0299         )
0300       : prob_(problem)
0301       ,  //
0302       active_(problem.m, true)
0303       ,  //
0304       scratchKeyCount_(problem.numKeys, 0)
0305       ,  //
0306       covered_(problem.m, false)
0307       ,  //
0308       debugDetailed_(debugDetailed)
0309       ,                                          //
0310       debugComponentIndex_(debugComponentIndex)  //
0311     {
0312     }
0313 
0314     // Search with a budget (the number of nodes to explore). True if the search completes, false if the budget is hit first (then go to `BuildHeuristicSet`)
0315     // This gives "how big can the best non-conflicting set (i.e cardinality) of classes be?" and *one* example of a set that size "bestSet_"
0316     bool SolveBudgeted(size_t budget)
0317     {
0318       std::fill(active_.begin(), active_.end(), true);
0319       bestCardinality_ = 0;
0320       bestSet_.clear();
0321       budget_ = budget;
0322       nodes_ = 0;
0323       budgetHit_ = false;
0324       debugDepth_ = 0;
0325       std::vector<size_t> chosen;
0326       chosen.reserve(prob_.m);
0327       if (debugDetailed_)
0328       {
0329         std::cout << __func__ << " : " << __LINE__ << " : component " << debugComponentIndex_ << " starts exact independent-set search with state budget " << budget_ << std::endl;
0330       }
0331       SearchCardinality(chosen);
0332       return !budgetHit_;
0333     }
0334 
0335     size_t BestCardinality() const { return bestCardinality_; }
0336     const std::vector<size_t> &BestSet() const { return bestSet_; }
0337 
0338     // If SolveBudgeted returns true, do this
0339     // Given that, from SolveBudgeted, we know the best cardinality (the argument "target"), this function collects sets that all have that same size, so later we can randomly pick one at random
0340     std::vector<std::vector<size_t>> CollectRepresentatives(size_t target, size_t maxReps, size_t budget)
0341     {
0342       std::fill(active_.begin(), active_.end(), true);
0343       target_ = target;
0344       maxReps_ = (maxReps < 1) ? 1 : maxReps;
0345       reps_.clear();
0346       budget_ = budget;
0347       nodes_ = 0;
0348       budgetHit_ = false;
0349       std::vector<size_t> chosen;
0350       chosen.reserve(target_);
0351       SearchRepresentatives(chosen);
0352       return reps_;
0353     }
0354 
0355     // Fallback when SolveBudgeted hits the budget; number of restarts is controlled by kHeuristicRestarts in the header
0356     std::vector<size_t> BuildHeuristicSet(size_t restarts, gsl_rng *rng) const
0357     {
0358       std::vector<bool> bestInSet(prob_.m, false);
0359       size_t bestSize = 0;
0360 
0361       // starting point 1: retain the best set found so far in the SolveBudgeted pass
0362       {
0363         std::vector<bool> inSet(prob_.m, false);
0364         for (const size_t v : bestSet_)
0365         {
0366           inSet[v] = true;
0367         }
0368         PolishInSet(inSet);
0369         const size_t sz = CountInSet(inSet);
0370         if (sz > bestSize)
0371         {
0372           bestSize = sz;
0373           bestInSet = inSet;
0374         }
0375       }
0376 
0377       // starting point 2: randomized searches
0378       std::vector<size_t> order(prob_.m);
0379       std::iota(order.begin(), order.end(), 0);
0380       for (size_t t = 0; t < restarts; ++t)  // now do the randomize search (kHeuristicRestarts times)
0381       {
0382         gsl_ran_shuffle(rng, order.data(), order.size(), sizeof(size_t));
0383         std::vector<bool> inSet = GreedyFromOrder(order);
0384         PolishInSet(inSet);
0385         const size_t sz = CountInSet(inSet);
0386         if (sz > bestSize)
0387         {
0388           bestSize = sz;
0389           bestInSet = inSet;
0390         }
0391       }
0392 
0393       std::vector<size_t> out;
0394       for (size_t v = 0; v < prob_.m; ++v)
0395       {
0396         if (bestInSet[v])
0397         {
0398           out.push_back(v);
0399         }
0400       }
0401       return out;
0402     }
0403 
0404    private:
0405     size_t CountActive() const
0406     {
0407       size_t count = 0;
0408       for (size_t v = 0; v < prob_.m; ++v)
0409       {
0410         count += active_[v];
0411       }
0412       return count;
0413     }
0414 
0415     static size_t CountInSet(const std::vector<bool> &inSet)
0416     {
0417       size_t count = 0;
0418       for (const bool x : inSet)
0419       {
0420         count += x;
0421       }
0422       return count;
0423     }
0424 
0425     // Used in BuildHeuristicSet, starting point 2
0426     // walk the list and keep every class that's still conflict-free with the classes already kept
0427     std::vector<bool> GreedyFromOrder(const std::vector<size_t> &order) const
0428     {
0429       std::vector<bool> inSet(prob_.m, false);
0430       std::vector<bool> blocked(prob_.m, false);
0431       for (const size_t v : order)
0432       {
0433         if (blocked[v])
0434         {
0435           continue;
0436         }
0437         inSet[v] = true;
0438         for (const size_t u : prob_.adj[v])
0439         {
0440           blocked[u] = true;
0441         }
0442       }
0443       return inSet;
0444     }
0445 
0446     // Repeatedly check whether dropping one chosen class can give two classes that are not conflicting
0447     // If true, drop the chosen class and add the two new classes. Repeat until no such operation exists
0448     void PolishInSet(std::vector<bool> &inSet) const
0449     {
0450       bool improved = true;
0451       const size_t passLimit = 1000;
0452       size_t pass = 0;
0453       while (improved && pass++ < passLimit)
0454       {
0455         improved = false;
0456         for (size_t y = 0; y < prob_.m && !improved; ++y)
0457         {
0458           if (!inSet[y])
0459           {
0460             continue;
0461           }
0462 
0463           std::vector<size_t> candidates;  // neighbors of y that are only neighbor to y (so dropping y frees them to be added)
0464           for (const size_t x : prob_.adj[y])
0465           {
0466             if (inSet[x])
0467             {
0468               continue;
0469             }
0470             size_t inSetNeighbors = 0;
0471             size_t onlyNeighbor = 0;
0472             for (const size_t u : prob_.adj[x])
0473             {
0474               if (inSet[u])
0475               {
0476                 ++inSetNeighbors;
0477                 onlyNeighbor = u;
0478               }
0479             }
0480             if (inSetNeighbors == 1 && onlyNeighbor == y)
0481             {
0482               candidates.push_back(x);
0483             }
0484           }
0485 
0486           // any two mutually non-adjacent candidates give a net +1
0487           for (size_t a = 0; a < candidates.size() && !improved; ++a)
0488           {
0489             for (size_t b = a + 1; b < candidates.size(); ++b)
0490             {
0491               bool adjacent = false;
0492               for (const size_t u : prob_.adj[candidates[a]])
0493               {
0494                 if (u == candidates[b])
0495                 {
0496                   adjacent = true;
0497                   break;
0498                 }
0499               }
0500               if (!adjacent)
0501               {
0502                 inSet[y] = false;
0503                 inSet[candidates[a]] = true;
0504                 inSet[candidates[b]] = true;
0505                 improved = true;
0506                 break;
0507               }
0508             }
0509           }
0510         }
0511       }
0512     }
0513 
0514     // Solve the clique-cover problem of a graph
0515     // Note that this is not an exact solution, but an approximation (clique cover does not have an exact solution in graph theory)
0516     size_t CliqueCoverBound()
0517     {
0518       std::fill(scratchKeyCount_.begin(), scratchKeyCount_.end(), 0);
0519       size_t remaining = 0;
0520       for (size_t v = 0; v < prob_.m; ++v)
0521       {
0522         if (!active_[v])
0523         {
0524           continue;
0525         }
0526         ++remaining;
0527         covered_[v] = false;
0528         for (const size_t kid : prob_.keyIds[v])
0529         {
0530           ++scratchKeyCount_[kid];
0531         }
0532       }
0533       if (remaining == 0)
0534       {
0535         return 0;
0536       }
0537 
0538       size_t cover = 0;
0539       while (remaining > 0)
0540       {
0541         size_t bestKey = 0;
0542         size_t bestCount = 0;
0543         for (size_t kid = 0; kid < prob_.numKeys; ++kid)
0544         {
0545           if (scratchKeyCount_[kid] > bestCount)
0546           {
0547             bestCount = scratchKeyCount_[kid];
0548             bestKey = kid;
0549           }
0550         }
0551         if (bestCount == 0)
0552         {
0553           break;  // unreachable while remaining > 0, guarded for safety
0554         }
0555 
0556         ++cover;
0557         for (const size_t v : prob_.keyToClasses[bestKey])
0558         {
0559           if (!active_[v] || covered_[v])
0560           {
0561             continue;
0562           }
0563           covered_[v] = true;
0564           --remaining;  // decrement, because it's covered
0565           for (const size_t kid : prob_.keyIds[v])
0566           {
0567             if (scratchKeyCount_[kid] > 0)
0568             {
0569               --scratchKeyCount_[kid];
0570             }
0571           }
0572         }
0573       }
0574 
0575       return cover;
0576     }
0577 
0578     // Choosing the starting point (seed) for the include-exclude search
0579     // The vertex (seed) with the highest degree (most conflicts) is chosen
0580     size_t ChooseBranchVertex() const
0581     {
0582       size_t bestVertex = 0;
0583       size_t bestDegree = 0;
0584       bool found = false;
0585       for (size_t v = 0; v < prob_.m; ++v)
0586       {
0587         if (!active_[v])
0588         {
0589           continue;
0590         }
0591         size_t degree = 0;
0592         for (const size_t neighbor : prob_.adj[v])
0593         {
0594           degree += active_[neighbor];
0595         }
0596         if (!found || degree > bestDegree)
0597         {
0598           found = true;
0599           bestDegree = degree;
0600           bestVertex = v;
0601         }
0602       }
0603       return bestVertex;
0604     }
0605 
0606     void FoldIsolated(std::vector<size_t> &chosen, std::vector<size_t> &folded)
0607     {
0608       for (size_t v = 0; v < prob_.m; ++v)
0609       {
0610         if (!active_[v])
0611         {
0612           continue;
0613         }
0614         bool hasActiveNeighbor = false;
0615         for (const size_t neighbor : prob_.adj[v])
0616         {
0617           if (active_[neighbor])
0618           {
0619             hasActiveNeighbor = true;
0620             break;
0621           }
0622         }
0623         if (!hasActiveNeighbor)
0624         {
0625           folded.push_back(v);
0626         }
0627       }
0628       for (const size_t v : folded)
0629       {
0630         active_[v] = false;
0631         chosen.push_back(v);
0632       }
0633     }
0634 
0635     void Unfold(std::vector<size_t> &chosen, const std::vector<size_t> &folded)
0636     {
0637       for (size_t i = folded.size(); i-- > 0;)
0638       {
0639         active_[folded[i]] = true;
0640         chosen.pop_back();
0641       }
0642     }
0643 
0644     void Deactivate(size_t vertex, std::vector<size_t> &deactivated)
0645     {
0646       active_[vertex] = false;
0647       deactivated.push_back(vertex);
0648       for (const size_t neighbor : prob_.adj[vertex])
0649       {
0650         if (active_[neighbor])
0651         {
0652           active_[neighbor] = false;
0653           deactivated.push_back(neighbor);
0654         }
0655       }
0656     }
0657 
0658     void Reactivate(const std::vector<size_t> &deactivated)
0659     {
0660       for (const size_t vertex : deactivated)
0661       {
0662         active_[vertex] = true;
0663       }
0664     }
0665 
0666     void SearchCardinality(std::vector<size_t> &chosen)
0667     {
0668       if (budgetHit_)
0669       {
0670         return;
0671       }
0672       if (++nodes_ > budget_)
0673       {
0674         budgetHit_ = true;
0675         if (debugDetailed_)
0676         {
0677           std::cout << __func__ << " : " << __LINE__ << " : component " << debugComponentIndex_ << " budget hit at node " << nodes_ << " with best size " << bestCardinality_ << std::endl;
0678         }
0679         return;
0680       }
0681 
0682       const size_t activeBeforeFold = CountActive();
0683       if (debugDetailed_)
0684       {
0685         std::cout << __func__ << " : " << __LINE__ << " : component " << debugComponentIndex_ << " node " << nodes_ << " depth " << debugDepth_ << " enters with chosen=" << chosen.size()
0686                   << ", active=" << activeBeforeFold << ", best=" << bestCardinality_ << std::endl;
0687       }
0688 
0689       std::vector<size_t> folded;
0690       FoldIsolated(chosen, folded);
0691       if (debugDetailed_ && !folded.empty())
0692       {
0693         std::cout << __func__ << " : " << __LINE__ << " : component " << debugComponentIndex_ << " folded isolated class(es)";
0694         for (const size_t v : folded)
0695         {
0696           std::cout << " " << v;
0697         }
0698         std::cout << "; chosen is now " << chosen.size() << std::endl;
0699       }
0700 
0701       const size_t activeAfterFold = CountActive();
0702       if (activeAfterFold == 0)
0703       {
0704         if (chosen.size() > bestCardinality_)
0705         {
0706           bestCardinality_ = chosen.size();
0707           bestSet_ = chosen;
0708           if (debugDetailed_)
0709           {
0710             std::cout << __func__ << " : " << __LINE__ << " : component " << debugComponentIndex_ << " updates best set to size " << bestCardinality_ << " at a closed leaf" << std::endl;
0711           }
0712         }
0713         else if (debugDetailed_)
0714         {
0715           std::cout << __func__ << " : " << __LINE__ << " : component " << debugComponentIndex_ << " closed leaf has size " << chosen.size() << " and does not improve best " << bestCardinality_
0716                     << std::endl;
0717         }
0718         Unfold(chosen, folded);
0719         return;
0720       }
0721 
0722       const size_t cover = CliqueCoverBound();
0723       if (debugDetailed_)
0724       {
0725         std::cout << __func__ << " : " << __LINE__ << " : component " << debugComponentIndex_ << " clique-cover upper bound = " << cover << "; chosen + bound = " << chosen.size() + cover
0726                   << std::endl;
0727       }
0728       if (chosen.size() + cover <= bestCardinality_)
0729       {
0730         if (debugDetailed_)
0731         {
0732           std::cout << __func__ << " : " << __LINE__ << " : component " << debugComponentIndex_ << " prunes branch because " << chosen.size() + cover << " <= current best " << bestCardinality_
0733                     << std::endl;
0734         }
0735         Unfold(chosen, folded);
0736         return;
0737       }
0738       if (cover == 1)
0739       {
0740         if (chosen.size() + 1 > bestCardinality_)
0741         {
0742           size_t pick = prob_.m;
0743           for (size_t v = 0; v < prob_.m; ++v)
0744           {
0745             if (active_[v])
0746             {
0747               pick = v;
0748               break;
0749             }
0750           }
0751           bestCardinality_ = chosen.size() + 1;
0752           bestSet_ = chosen;
0753           if (pick < prob_.m)
0754           {
0755             bestSet_.push_back(pick);
0756           }
0757           if (debugDetailed_)
0758           {
0759             std::cout << __func__ << " : " << __LINE__ << " : component " << debugComponentIndex_ << " single-clique shortcut picks class " << pick << " and updates best size to "
0760                       << bestCardinality_ << std::endl;
0761           }
0762         }
0763         else if (debugDetailed_)
0764         {
0765           std::cout << __func__ << " : " << __LINE__ << " : component " << debugComponentIndex_ << " single-clique shortcut cannot improve best " << bestCardinality_ << std::endl;
0766         }
0767         Unfold(chosen, folded);
0768         return;
0769       }
0770 
0771       const size_t vertex = ChooseBranchVertex();
0772       if (debugDetailed_)
0773       {
0774         std::cout << __func__ << " : " << __LINE__ << " : component " << debugComponentIndex_ << " branches on class " << vertex << std::endl;
0775       }
0776 
0777       std::vector<size_t> deactivated;
0778       Deactivate(vertex, deactivated);
0779       if (debugDetailed_)
0780       {
0781         std::cout << __func__ << " : " << __LINE__ << " : component " << debugComponentIndex_ << " include class " << vertex << " and deactivate";
0782         for (const size_t v : deactivated)
0783         {
0784           std::cout << " " << v;
0785         }
0786         std::cout << std::endl;
0787       }
0788       chosen.push_back(vertex);
0789       ++debugDepth_;
0790       SearchCardinality(chosen);
0791       --debugDepth_;
0792       chosen.pop_back();
0793       Reactivate(deactivated);
0794 
0795       active_[vertex] = false;
0796       if (debugDetailed_)
0797       {
0798         std::cout << __func__ << " : " << __LINE__ << " : component " << debugComponentIndex_ << " exclude class " << vertex << " and continue residual search" << std::endl;
0799       }
0800       ++debugDepth_;
0801       SearchCardinality(chosen);
0802       --debugDepth_;
0803       active_[vertex] = true;
0804 
0805       Unfold(chosen, folded);
0806     }
0807 
0808     void Record(const std::vector<size_t> &chosen)
0809     {
0810       if (reps_.size() >= maxReps_)
0811       {
0812         return;
0813       }
0814       std::vector<size_t> sorted = chosen;
0815       std::sort(sorted.begin(), sorted.end());
0816       for (const std::vector<size_t> &existing : reps_)
0817       {
0818         if (existing == sorted)
0819         {
0820           return;
0821         }
0822       }
0823       reps_.push_back(std::move(sorted));
0824     }
0825 
0826     // This does slightly differently than SearchCardinality
0827     // SearchCardinality: does a search from scratch to discover the best possible size and track a single best set as it goes
0828     // SearchRepresentatives: given the size of the best set known, search and collect several different sets of that size, so that later we can randomly pick one of them
0829     void SearchRepresentatives(std::vector<size_t> &chosen)
0830     {
0831       if (budgetHit_ || reps_.size() >= maxReps_)
0832       {
0833         return;
0834       }
0835       if (++nodes_ > budget_)
0836       {
0837         budgetHit_ = true;
0838         return;
0839       }
0840 
0841       std::vector<size_t> folded;
0842       FoldIsolated(chosen, folded);
0843 
0844       if (CountActive() == 0)
0845       {
0846         if (chosen.size() == target_)
0847         {
0848           Record(chosen);
0849         }
0850         Unfold(chosen, folded);
0851         return;
0852       }
0853 
0854       const size_t cover = CliqueCoverBound();
0855       if (chosen.size() + cover < target_)  // cannot reach a maximum packing
0856       {
0857         Unfold(chosen, folded);
0858         return;
0859       }
0860       if (cover == 1)
0861       {
0862         for (size_t v = 0; v < prob_.m && reps_.size() < maxReps_; ++v)
0863         {
0864           if (!active_[v])
0865           {
0866             continue;
0867           }
0868           chosen.push_back(v);
0869           Record(chosen);
0870           chosen.pop_back();
0871         }
0872         Unfold(chosen, folded);
0873         return;
0874       }
0875 
0876       const size_t vertex = ChooseBranchVertex();
0877 
0878       std::vector<size_t> deactivated;
0879       Deactivate(vertex, deactivated);
0880       chosen.push_back(vertex);
0881       SearchRepresentatives(chosen);
0882       chosen.pop_back();
0883       Reactivate(deactivated);
0884 
0885       if (reps_.size() < maxReps_)
0886       {
0887         active_[vertex] = false;
0888         SearchRepresentatives(chosen);
0889         active_[vertex] = true;
0890       }
0891 
0892       Unfold(chosen, folded);
0893     }
0894 
0895     const ComponentProblem &prob_;
0896     std::vector<bool> active_;
0897     std::vector<size_t> scratchKeyCount_;
0898     std::vector<bool> covered_;
0899     size_t bestCardinality_ = 0;
0900     std::vector<size_t> bestSet_;
0901     size_t target_ = 0;
0902     size_t maxReps_ = 1;
0903     std::vector<std::vector<size_t>> reps_;
0904     size_t budget_ = 0;
0905     size_t nodes_ = 0;
0906     bool budgetHit_ = false;
0907     bool debugDetailed_ = false;
0908     size_t debugComponentIndex_ = 0;
0909     size_t debugDepth_ = 0;
0910   };
0911 
0912   std::vector<size_t> SelectLargestMvtxDisjointSubset(       //
0913       const std::vector<const SeedRecord *> &completeSeeds,  //
0914       gsl_rng *rng,                                          //
0915       size_t maxRepresentatives,                             //
0916       size_t searchBudget,                                   //
0917       size_t heuristicRestarts,                              //
0918       size_t debugMinimumGroupSize,                          //
0919       std::vector<size_t> &uncertifiedOut,                   //
0920       size_t ambiguityGroupSize,
0921       bool debugVerbosity,
0922       bool debugDetailedVerbosity)
0923   {
0924     if (completeSeeds.empty())
0925     {
0926       return {};
0927     }
0928 
0929     const bool debug = (debugVerbosity || debugDetailedVerbosity) && ambiguityGroupSize > debugMinimumGroupSize;
0930 
0931     const std::vector<TripletClass> classes = CollapseDuplicateTriplets(completeSeeds, debugVerbosity);
0932     const ConflictGraph classConflict = BuildClassConflictGraph(classes);
0933     if (debug)
0934     {
0935       std::cout << __func__ << " : " << __LINE__ << " : collapsed " << completeSeeds.size() << " complete MVTX seeds into " << classes.size() << " unique MVTX-triplet classes" << std::endl;
0936     }
0937 
0938     std::vector<size_t> selectedSeedIndices;
0939 
0940     const std::vector<std::vector<size_t>> components = ConnectedComponents(classConflict);
0941     if (debug)
0942     {
0943       std::cout << __func__ << " : " << __LINE__ << " : class conflict graph split into " << components.size() << " connected component(s)" << std::endl;
0944     }
0945 
0946     size_t componentIndex = 0;
0947     for (const std::vector<size_t> &component : components)
0948     {
0949       const ComponentProblem prob = BuildComponentProblem(classes, classConflict, component);
0950       ComponentSolver solver(prob, debugDetailedVerbosity && ambiguityGroupSize > debugMinimumGroupSize, componentIndex);
0951 
0952       if (debug)
0953       {
0954         std::cout << __func__ << " : " << __LINE__ << " : component " << componentIndex << " has " << prob.m << " class(es) and " << prob.numKeys << " MVTX key(s)" << std::endl;
0955       }
0956 
0957       const bool certified = solver.SolveBudgeted(searchBudget);
0958       if (debug)
0959       {
0960         std::cout << __func__ << " : " << __LINE__ << " : component " << componentIndex << " exact search " << (certified ? "closed" : "hit the state budget")
0961                   << "; best independent-set size = " << solver.BestCardinality() << std::endl;
0962       }
0963 
0964       std::vector<size_t> chosenLocal;
0965       if (certified)  // search completed, collect equally maximal sets and draw one at random
0966       {
0967         const size_t target = solver.BestCardinality();
0968         const std::vector<std::vector<size_t>> reps = solver.CollectRepresentatives(target, maxRepresentatives, searchBudget);
0969         if (debug)
0970         {
0971           std::cout << __func__ << " : " << __LINE__ << " : component " << componentIndex << " collected " << reps.size() << " representative maximum set(s) for target size " << target
0972                     << std::endl;
0973         }
0974 
0975         if (reps.empty())
0976         {
0977           chosenLocal = solver.BestSet();
0978         }
0979         else
0980         {
0981           size_t chosenRep = 0;
0982           if (reps.size() > 1)
0983           {
0984             std::vector<double> weights;
0985             weights.reserve(reps.size());
0986             for (const std::vector<size_t> &rep : reps)
0987             {
0988               double weight = 1.0;
0989               for (const size_t local : rep)
0990               {
0991                 weight *= prob.memberCount[local];
0992               }
0993               weights.push_back(weight);
0994             }
0995             gsl_ran_discrete_t *repPick = gsl_ran_discrete_preproc(weights.size(), weights.data());
0996             chosenRep = gsl_ran_discrete(rng, repPick);
0997             gsl_ran_discrete_free(repPick);  // GNU Scientific Library function that deallocates the memory
0998           }
0999           chosenLocal = reps[chosenRep];
1000         }
1001       }
1002       else  // search hit budget, do heuristic search
1003       {
1004         chosenLocal = solver.BuildHeuristicSet(heuristicRestarts, rng);
1005         if (debug)
1006         {
1007           std::cout << __func__ << " : " << __LINE__ << " : component " << componentIndex << " heuristic polished set size = " << chosenLocal.size() << std::endl;
1008         }
1009       }
1010 
1011       for (const size_t local : chosenLocal)
1012       {
1013         const std::vector<size_t> &members = classes[prob.globalClassId[local]].memberSeedIndices;
1014         const size_t seedIndex = members[gsl_rng_uniform_int(rng, members.size())];
1015         selectedSeedIndices.push_back(seedIndex);
1016         if (!certified)
1017         {
1018           uncertifiedOut.push_back(seedIndex);
1019         }
1020       }
1021       if (debug)
1022       {
1023         std::cout << __func__ << " : " << __LINE__ << " : component " << componentIndex << " selected " << chosenLocal.size() << " seed(s)" << std::endl;
1024       }
1025       ++componentIndex;
1026     }
1027 
1028     return selectedSeedIndices;
1029   }
1030 
1031   std::vector<std::vector<size_t>> BuildAmbiguityGroups(const std::vector<SeedRecord> &seeds)
1032   {
1033     DisjointSet disjoint_set(seeds.size());
1034     std::unordered_map<TrkrDefs::cluskey, size_t> first_seed_by_cluster_key;
1035 
1036     for (size_t iseed = 0; iseed < seeds.size(); ++iseed)
1037     {
1038       for (const TrkrDefs::cluskey key : seeds[iseed].allClusterKeys)
1039       {
1040         const auto inserted = first_seed_by_cluster_key.emplace(key, iseed);
1041         if (!inserted.second)
1042         {
1043           disjoint_set.Union(iseed, inserted.first->second);
1044         }
1045       }
1046     }
1047 
1048     std::unordered_map<size_t, std::vector<size_t>> groups_by_root;
1049     for (size_t iseed = 0; iseed < seeds.size(); ++iseed)
1050     {
1051       groups_by_root[disjoint_set.Find(iseed)].push_back(iseed);
1052     }
1053 
1054     std::vector<std::vector<size_t>> groups;
1055     groups.reserve(groups_by_root.size());
1056     for (auto &entry : groups_by_root)
1057     {
1058       groups.push_back(entry.second);
1059     }
1060 
1061     return groups;
1062   }
1063 
1064   // randomly select seeds from a group if multiple choices are available
1065   std::vector<size_t> SelectRandomSeedFromGroup(  //
1066       const std::vector<SeedRecord> &seeds,       //
1067       const std::vector<size_t> &group,           //
1068       gsl_rng *rng                                //
1069   )
1070   {
1071     const size_t selected = gsl_rng_uniform_int(rng, group.size());
1072     return {seeds[group[selected]].index};
1073   }
1074 
1075   std::vector<size_t> SelectAmbiguityGroup(  //
1076       const std::vector<SeedRecord> &seeds,  //
1077       const std::vector<size_t> &group,      //
1078       gsl_rng *rng,                          //
1079       size_t maxRepresentatives,             //
1080       size_t searchBudget,                   //
1081       size_t heuristicRestarts,              //
1082       size_t debugMinimumGroupSize,          //
1083       std::vector<size_t> &uncertifiedOut,
1084       bool debugVerbosity,
1085       bool debugDetailedVerbosity)
1086   {
1087     const bool debug = (debugVerbosity || debugDetailedVerbosity) && group.size() > debugMinimumGroupSize;
1088 
1089     if (debug)
1090     {
1091       std::cout << __func__ << " : " << __LINE__ << " : ambiguity group size = " << group.size() << std::endl;
1092     }
1093 
1094     if (group.size() == 1)
1095     {
1096       return {seeds[group.front()].index};
1097     }
1098 
1099     std::vector<const SeedRecord *> complete_seeds;
1100     complete_seeds.reserve(group.size());
1101 
1102     bool all_complete = true;
1103     for (const size_t local_seed_index : group)
1104     {
1105       const SeedRecord &seed = seeds[local_seed_index];
1106       if (seed.completeMvtx)
1107       {
1108         complete_seeds.push_back(&seed);
1109       }
1110       else
1111       {
1112         all_complete = false;
1113       }
1114     }
1115 
1116     if (debug)
1117     {
1118       std::cout << __func__ << " : " << __LINE__ << " : complete MVTX seeds in group = " << complete_seeds.size() << "/" << group.size() << std::endl;
1119       // print out all seed cluster keys for debugging if debugdetail is enabled
1120       if (debugDetailedVerbosity)
1121       {
1122         for (const size_t local_seed_index : group)
1123         {
1124           const SeedRecord &seed = seeds[local_seed_index];
1125           std::cout << __func__ << " : " << __LINE__ << " : seed index " << seed.index << " completeMvtx=" << seed.completeMvtx << " cluster keys: ";
1126           for (const TrkrDefs::cluskey key : seed.allClusterKeys)
1127           {
1128             std::cout << key << " ";
1129           }
1130           std::cout << std::endl;
1131         }
1132       }
1133     }
1134 
1135     if (all_complete)
1136     {
1137       bool all_same_triplet = true;
1138       const SeedRecord &first_seed = seeds[group.front()];
1139       for (const size_t local_seed_index : group)
1140       {
1141         if (first_seed.mvtxClusterKeys != seeds[local_seed_index].mvtxClusterKeys)
1142         {
1143           all_same_triplet = false;
1144           break;
1145         }
1146       }
1147 
1148       if (all_same_triplet)
1149       {
1150         if (debug)
1151         {
1152           std::cout << __func__ << " : " << __LINE__ << " : all complete seeds share one MVTX triplet; selecting one random representative" << std::endl;
1153         }
1154         return SelectRandomSeedFromGroup(seeds, group, rng);
1155       }
1156 
1157       if (debug)
1158       {
1159         std::cout << __func__ << " : " << __LINE__ << " : all complete seeds have distinct MVTX triplets; selecting largest disjoint subset" << std::endl;
1160       }
1161       return SelectLargestMvtxDisjointSubset(complete_seeds, rng, maxRepresentatives, searchBudget, heuristicRestarts, debugMinimumGroupSize, uncertifiedOut, group.size(), debugVerbosity, debugDetailedVerbosity);
1162     }
1163 
1164     if (complete_seeds.empty())
1165     {
1166       if (debug)
1167       {
1168         std::cout << __func__ << " : " << __LINE__ << " : no complete MVTX seeds in group; selecting one random representative" << std::endl;
1169       }
1170       return SelectRandomSeedFromGroup(seeds, group, rng);
1171     }
1172 
1173     return SelectLargestMvtxDisjointSubset(complete_seeds, rng, maxRepresentatives, searchBudget, heuristicRestarts, debugMinimumGroupSize, uncertifiedOut, group.size(), debugVerbosity, debugDetailedVerbosity);
1174   }
1175 
1176   Result SelectPrunedSiliconSeeds(
1177       const std::vector<SeedRecord> &seeds,
1178       gsl_rng *rng,
1179       size_t maxRepresentatives,
1180       size_t searchBudget,
1181       size_t heuristicRestarts,
1182       size_t debugMinimumGroupSize,
1183       bool debugVerbosity,
1184       bool debugDetailedVerbosity)
1185   {
1186     Result result;
1187     if (seeds.empty())
1188     {
1189       return result;
1190     }
1191 
1192     const std::vector<std::vector<size_t>> groups = BuildAmbiguityGroups(seeds);
1193     for (const auto &group : groups)
1194     {
1195       const std::vector<size_t> selected_group_indices =
1196           SelectAmbiguityGroup(seeds, group, rng, maxRepresentatives, searchBudget, heuristicRestarts, debugMinimumGroupSize, result.uncertifiedSeedIndices, debugVerbosity, debugDetailedVerbosity);
1197       result.selectedSeedIndices.insert(
1198           result.selectedSeedIndices.end(),
1199           selected_group_indices.begin(),
1200           selected_group_indices.end());
1201     }
1202 
1203     std::sort(result.selectedSeedIndices.begin(), result.selectedSeedIndices.end());
1204     std::sort(result.uncertifiedSeedIndices.begin(), result.uncertifiedSeedIndices.end());
1205     return result;
1206   }
1207 
1208   Result SelectSeeds(
1209       const TrackSeedContainer &container,
1210       const std::vector<std::size_t> &seedIndices,
1211       gsl_rng *rng,
1212       std::size_t mvtxLayerCount,
1213       std::size_t maxRepresentatives,
1214       std::size_t searchBudget,
1215       std::size_t heuristicRestarts,
1216       std::size_t debugMinimumGroupSize,
1217       bool debugVerbosity,
1218       bool debugDetailedVerbosity)
1219   {
1220     std::vector<SeedRecord> seeds;
1221     seeds.reserve(seedIndices.size());
1222     for (const std::size_t seedIndex : seedIndices)
1223     {
1224       seeds.push_back(MakeSeedRecord(seedIndex, *container.get(seedIndex), mvtxLayerCount));
1225     }
1226 
1227     return SelectPrunedSiliconSeeds(
1228         seeds,
1229         rng,
1230         maxRepresentatives,
1231         searchBudget,
1232         heuristicRestarts,
1233         debugMinimumGroupSize,
1234         debugVerbosity,
1235         debugDetailedVerbosity);
1236   }
1237 }  // namespace PHSiliconSeedPrunerHelper