LArSoft  v07_13_02
Liquid Argon Software toolkit - http://larsoft.org/
TwoDSlidingFitSplittingAndSplicingAlgorithm.cc
Go to the documentation of this file.
1 
9 #include "Pandora/AlgorithmHeaders.h"
10 
13 
15 
16 using namespace pandora;
17 
18 namespace lar_content
19 {
20 
21 TwoDSlidingFitSplittingAndSplicingAlgorithm::TwoDSlidingFitSplittingAndSplicingAlgorithm() :
22  m_shortHalfWindowLayers(10),
23  m_longHalfWindowLayers(20),
24  m_minClusterLength(7.5f),
25  m_vetoDisplacement(1.5f),
26  m_runCosmicMode(false)
27 {
28 }
29 
30 //------------------------------------------------------------------------------------------------------------------------------------------
31 
33 {
34  const ClusterList *pClusterList = NULL;
35  PANDORA_RETURN_RESULT_IF(STATUS_CODE_SUCCESS, !=, PandoraContentApi::GetCurrentList(*this, pClusterList));
36 
37  TwoDSlidingFitResultMap branchSlidingFitResultMap, replacementSlidingFitResultMap;
38 
39  unsigned int nIterations(0);
40 
41  while (++nIterations < 100) // Protect against flip-flopping between two answers
42  {
43  // Get ordered list of candidate clusters
44  ClusterVector clusterVector;
45  this->GetListOfCleanClusters(pClusterList, clusterVector);
46 
47  // Calculate sliding fit results for branch clusters (use a soft sliding fit for these)
48  this->BuildSlidingFitResultMap(clusterVector, m_shortHalfWindowLayers, branchSlidingFitResultMap);
49 
50  // Calculate sliding fit results for replacement clusters (use a hard linear fit for these)
51  this->BuildSlidingFitResultMap(clusterVector, m_longHalfWindowLayers, replacementSlidingFitResultMap);
52 
53  // Compile a list of possible splits
54  ClusterExtensionList splitList;
55 
56  if (m_runCosmicMode)
57  {
58  this->BuildClusterExtensionList(clusterVector, branchSlidingFitResultMap, replacementSlidingFitResultMap, splitList);
59  }
60  else
61  {
62  ClusterExtensionList intermediateList;
63  this->BuildClusterExtensionList(clusterVector, branchSlidingFitResultMap, replacementSlidingFitResultMap, intermediateList);
64  this->PruneClusterExtensionList(intermediateList, branchSlidingFitResultMap, replacementSlidingFitResultMap, splitList);
65  }
66 
67  // Run splitting and extension
68  if (STATUS_CODE_SUCCESS != this->RunSplitAndExtension(splitList, branchSlidingFitResultMap, replacementSlidingFitResultMap))
69  break;
70  }
71 
72  return STATUS_CODE_SUCCESS;
73 }
74 
75 //------------------------------------------------------------------------------------------------------------------------------------------
76 
77 void TwoDSlidingFitSplittingAndSplicingAlgorithm::GetListOfCleanClusters(const ClusterList *const pClusterList, ClusterVector &clusterVector) const
78 {
79  for (ClusterList::const_iterator iter = pClusterList->begin(), iterEnd = pClusterList->end(); iter != iterEnd; ++iter)
80  {
81  const Cluster *const pCluster = *iter;
82 
84  continue;
85 
86  clusterVector.push_back(pCluster);
87  }
88 
89  std::sort(clusterVector.begin(), clusterVector.end(), LArClusterHelper::SortByNHits);
90 }
91 
92 //------------------------------------------------------------------------------------------------------------------------------------------
93 
94 void TwoDSlidingFitSplittingAndSplicingAlgorithm::BuildSlidingFitResultMap(const ClusterVector &clusterVector, const unsigned int halfWindowLayers,
95  TwoDSlidingFitResultMap &slidingFitResultMap) const
96 {
97  const float slidingFitPitch(LArGeometryHelper::GetWireZPitch(this->GetPandora()));
98 
99  for (ClusterVector::const_iterator iter = clusterVector.begin(), iterEnd = clusterVector.end(); iter != iterEnd; ++iter)
100  {
101  if (slidingFitResultMap.end() == slidingFitResultMap.find(*iter))
102  {
103  try
104  {
105  const TwoDSlidingFitResult slidingFitResult(*iter, halfWindowLayers, slidingFitPitch);
106 
107  if (!slidingFitResultMap.insert(TwoDSlidingFitResultMap::value_type(*iter, slidingFitResult)).second)
108  throw StatusCodeException(STATUS_CODE_FAILURE);
109  }
110  catch (StatusCodeException &statusCodeException)
111  {
112  if (STATUS_CODE_FAILURE == statusCodeException.GetStatusCode())
113  throw statusCodeException;
114  }
115  }
116  }
117 }
118 
119 //------------------------------------------------------------------------------------------------------------------------------------------
120 
122  const TwoDSlidingFitResultMap &branchSlidingFitResultMap, const TwoDSlidingFitResultMap &replacementSlidingFitResultMap,
123  ClusterExtensionList &clusterExtensionList) const
124 {
125  // Loop over each possible pair of clusters
126  for (ClusterVector::const_iterator iterI = clusterVector.begin(), iterEndI = clusterVector.end(); iterI != iterEndI; ++iterI)
127  {
128  const Cluster *const pClusterI = *iterI;
129 
130  for (ClusterVector::const_iterator iterJ = iterI, iterEndJ = clusterVector.end(); iterJ != iterEndJ; ++iterJ)
131  {
132  const Cluster *const pClusterJ = *iterJ;
133 
134  if (pClusterI == pClusterJ)
135  continue;
136 
137  // Get the branch and replacement sliding fits for this pair of clusters
138  TwoDSlidingFitResultMap::const_iterator iterBranchI = branchSlidingFitResultMap.find(*iterI);
139  TwoDSlidingFitResultMap::const_iterator iterBranchJ = branchSlidingFitResultMap.find(*iterJ);
140 
141  TwoDSlidingFitResultMap::const_iterator iterReplacementI = replacementSlidingFitResultMap.find(*iterI);
142  TwoDSlidingFitResultMap::const_iterator iterReplacementJ = replacementSlidingFitResultMap.find(*iterJ);
143 
144  if (branchSlidingFitResultMap.end() == iterBranchI || branchSlidingFitResultMap.end() == iterBranchJ ||
145  replacementSlidingFitResultMap.end() == iterReplacementI || replacementSlidingFitResultMap.end() == iterReplacementJ)
146  {
147  // TODO May want to raise an exception under certain conditions
148  continue;
149  }
150 
151  const TwoDSlidingFitResult &branchSlidingFitI(iterBranchI->second);
152  const TwoDSlidingFitResult &branchSlidingFitJ(iterBranchJ->second);
153 
154  const TwoDSlidingFitResult &replacementSlidingFitI(iterReplacementI->second);
155  const TwoDSlidingFitResult &replacementSlidingFitJ(iterReplacementJ->second);
156 
157  // Search for a split in clusterI
158  float branchChisqI(0.f);
159  CartesianVector branchSplitPositionI(0.f, 0.f, 0.f);
160  CartesianVector branchSplitDirectionI(0.f, 0.f, 0.f);
161  CartesianVector replacementStartPositionJ(0.f, 0.f, 0.f);
162 
163  try
164  {
165  this->FindBestSplitPosition(branchSlidingFitI, replacementSlidingFitJ, replacementStartPositionJ, branchSplitPositionI, branchSplitDirectionI);
166  branchChisqI = this->CalculateBranchChi2(pClusterI, branchSplitPositionI, branchSplitDirectionI);
167  }
168  catch (StatusCodeException &)
169  {
170  }
171 
172  // Search for a split in clusterJ
173  float branchChisqJ(0.f);
174  CartesianVector branchSplitPositionJ(0.f, 0.f, 0.f);
175  CartesianVector branchSplitDirectionJ(0.f, 0.f, 0.f);
176  CartesianVector replacementStartPositionI(0.f, 0.f, 0.f);
177 
178  try
179  {
180  this->FindBestSplitPosition(branchSlidingFitJ, replacementSlidingFitI, replacementStartPositionI, branchSplitPositionJ, branchSplitDirectionJ);
181  branchChisqJ = this->CalculateBranchChi2(pClusterJ, branchSplitPositionJ, branchSplitDirectionJ);
182  }
183  catch (StatusCodeException &)
184  {
185  }
186 
187  // Re-calculate chi2 values if both clusters have a split
188  if (branchChisqI > 0.f && branchChisqJ > 0.f)
189  {
190  const CartesianVector relativeDirection((branchSplitPositionJ - branchSplitPositionI).GetUnitVector());
191 
192  if (branchSplitDirectionI.GetDotProduct(relativeDirection) > 0.f &&
193  branchSplitDirectionJ.GetDotProduct(relativeDirection) < 0.f )
194  {
195  try
196  {
197  const float newBranchChisqI(this->CalculateBranchChi2(pClusterI, branchSplitPositionI, relativeDirection));
198  const float newBranchChisqJ(this->CalculateBranchChi2(pClusterJ, branchSplitPositionJ, relativeDirection * -1.f));
199  branchChisqI = newBranchChisqI;
200  branchChisqJ = newBranchChisqJ;
201  }
202  catch (StatusCodeException &)
203  {
204  }
205  }
206  }
207 
208  // Select the overall best split position
209  if (branchChisqI > branchChisqJ)
210  {
211  clusterExtensionList.push_back(ClusterExtension(pClusterI, pClusterJ, replacementStartPositionJ, branchSplitPositionI, branchSplitDirectionI));
212  }
213 
214  else if (branchChisqJ > branchChisqI)
215  {
216  clusterExtensionList.push_back(ClusterExtension(pClusterJ, pClusterI, replacementStartPositionI, branchSplitPositionJ, branchSplitDirectionJ));
217  }
218  }
219  }
220 }
221 
222 //------------------------------------------------------------------------------------------------------------------------------------------
223 
225  const TwoDSlidingFitResultMap &branchMap, const TwoDSlidingFitResultMap &replacementMap, ClusterExtensionList &outputList) const
226 {
227  ClusterList branchList;
228  for (const auto &mapEntry : branchMap) branchList.push_back(mapEntry.first);
229  branchList.sort(LArClusterHelper::SortByNHits);
230 
231  ClusterList replacementList;
232  for (const auto &mapEntry : replacementMap) replacementList.push_back(mapEntry.first);
233  replacementList.sort(LArClusterHelper::SortByNHits);
234 
235  for (const ClusterExtension &thisSplit : inputList)
236  {
237  const CartesianVector &branchVertex = thisSplit.GetBranchVertex();
238  const CartesianVector &replacementVertex = thisSplit.GetReplacementVertex();
239 
240  const float distanceSquared((branchVertex - replacementVertex).GetMagnitudeSquared());
241  const float vetoDistanceSquared(m_vetoDisplacement * m_vetoDisplacement);
242 
243  bool branchVeto(false), replacementVeto(false);
244 
245  // Veto the merge if another cluster is closer to the replacement vertex
246  for (const Cluster *const pBranchCluster : branchList)
247  {
248  const TwoDSlidingFitResult &slidingFit(branchMap.at(pBranchCluster));
249 
250  if (slidingFit.GetCluster() == thisSplit.GetReplacementCluster() || slidingFit.GetCluster() == thisSplit.GetBranchCluster())
251  continue;
252 
253  const float minDistanceSquared((replacementVertex - slidingFit.GetGlobalMinLayerPosition()).GetMagnitudeSquared());
254  const float maxDistanceSquared((replacementVertex - slidingFit.GetGlobalMaxLayerPosition()).GetMagnitudeSquared());
255 
256  if (std::min(minDistanceSquared, maxDistanceSquared) < std::max(distanceSquared, vetoDistanceSquared))
257  {
258  branchVeto = true;
259  break;
260  }
261  }
262 
263  // Veto the merge if another cluster is closer to the branch vertex
264  for (const Cluster *const pReplacementCluster : replacementList)
265  {
266  const TwoDSlidingFitResult &slidingFit(replacementMap.at(pReplacementCluster));
267 
268  if (slidingFit.GetCluster() == thisSplit.GetReplacementCluster() || slidingFit.GetCluster() == thisSplit.GetBranchCluster())
269  continue;
270 
271  const float minDistanceSquared((branchVertex - slidingFit.GetGlobalMinLayerPosition()).GetMagnitudeSquared());
272  const float maxDistanceSquared((branchVertex - slidingFit.GetGlobalMaxLayerPosition()).GetMagnitudeSquared());
273 
274  if (std::min(minDistanceSquared, maxDistanceSquared) < std::max(distanceSquared, vetoDistanceSquared))
275  {
276  replacementVeto = true;
277  break;
278  }
279  }
280 
281  if (branchVeto || replacementVeto)
282  continue;
283 
284  outputList.push_back(thisSplit);
285  }
286 }
287 
288 //------------------------------------------------------------------------------------------------------------------------------------------
289 
290 float TwoDSlidingFitSplittingAndSplicingAlgorithm::CalculateBranchChi2(const Cluster* const pCluster, const CartesianVector &splitPosition,
291  const CartesianVector &splitDirection) const
292 {
293  CaloHitList principalCaloHitList, branchCaloHitList;
294 
295  this->SplitBranchCluster(pCluster, splitPosition, splitDirection, principalCaloHitList, branchCaloHitList);
296 
297  float totalChi2(0.f);
298  float totalHits(0.f);
299 
300  for (CaloHitList::const_iterator iter = branchCaloHitList.begin(), iterEnd = branchCaloHitList.end(); iter != iterEnd; ++iter)
301  {
302  const CaloHit *const pCaloHit = *iter;
303 
304  const CartesianVector hitPosition(pCaloHit->GetPositionVector());
305  const CartesianVector projectedPosition(splitPosition + splitDirection * splitDirection.GetDotProduct(hitPosition - splitPosition));
306 
307  totalChi2 += (hitPosition - projectedPosition).GetMagnitudeSquared();
308  totalHits += 1.f;
309  }
310 
311  if (totalHits > 0.f)
312  return std::sqrt(totalChi2/totalHits);
313 
314  throw StatusCodeException(STATUS_CODE_NOT_ALLOWED);
315 }
316 
317 //------------------------------------------------------------------------------------------------------------------------------------------
318 
319 void TwoDSlidingFitSplittingAndSplicingAlgorithm::SplitBranchCluster(const Cluster* const pCluster, const CartesianVector &splitPosition,
320  const CartesianVector &splitDirection, CaloHitList &principalCaloHitList, CaloHitList &branchCaloHitList) const
321 {
322  // Distribute hits in branch cluster between new principal and residual clusters
323  CaloHitList caloHitsToDistribute;
324  pCluster->GetOrderedCaloHitList().FillCaloHitList(caloHitsToDistribute);
325 
326  for (CaloHitList::const_iterator iter = caloHitsToDistribute.begin(), iterEnd = caloHitsToDistribute.end(); iter != iterEnd; ++iter)
327  {
328  const CaloHit *const pCaloHit = *iter;
329 
330  if (splitDirection.GetDotProduct((pCaloHit->GetPositionVector() - splitPosition)) > 0.f)
331  {
332  branchCaloHitList.push_back(pCaloHit);
333  }
334  else
335  {
336  principalCaloHitList.push_back(pCaloHit);
337  }
338  }
339 
340  if (branchCaloHitList.empty())
341  throw StatusCodeException(STATUS_CODE_NOT_ALLOWED);
342 }
343 
344 //------------------------------------------------------------------------------------------------------------------------------------------
345 
347  TwoDSlidingFitResultMap &branchResultMap, TwoDSlidingFitResultMap &replacementResultMap) const
348 {
349  bool foundSplit(false);
350 
351  for (ClusterExtensionList::const_iterator iter = splitList.begin(), iterEnd = splitList.end(); iter != iterEnd; ++iter)
352  {
353  const ClusterExtension &thisSplit = *iter;
354 
355  const Cluster *const pBranchCluster = thisSplit.GetBranchCluster();
356  const Cluster *const pReplacementCluster = thisSplit.GetReplacementCluster();
357  const CartesianVector &branchSplitPosition = thisSplit.GetBranchVertex();
358  const CartesianVector &branchSplitDirection = thisSplit.GetBranchDirection();
359 
360  TwoDSlidingFitResultMap::iterator iterBranch1 = branchResultMap.find(pBranchCluster);
361  TwoDSlidingFitResultMap::iterator iterBranch2 = branchResultMap.find(pReplacementCluster);
362 
363  TwoDSlidingFitResultMap::iterator iterReplacement1 = replacementResultMap.find(pBranchCluster);
364  TwoDSlidingFitResultMap::iterator iterReplacement2 = replacementResultMap.find(pReplacementCluster);
365 
366  if (branchResultMap.end() == iterBranch1 || branchResultMap.end() == iterBranch2 ||
367  replacementResultMap.end() == iterReplacement1 || replacementResultMap.end() == iterReplacement2)
368  continue;
369 
370  PANDORA_RETURN_RESULT_IF(STATUS_CODE_SUCCESS, !=, this->ReplaceBranch(pBranchCluster, pReplacementCluster,
371  branchSplitPosition, branchSplitDirection));
372  branchResultMap.erase(iterBranch1);
373  branchResultMap.erase(iterBranch2);
374 
375  replacementResultMap.erase(iterReplacement1);
376  replacementResultMap.erase(iterReplacement2);
377 
378  foundSplit = true;
379  }
380 
381  if (foundSplit)
382  return STATUS_CODE_SUCCESS;
383 
384  return STATUS_CODE_NOT_FOUND;
385 }
386 
387 //------------------------------------------------------------------------------------------------------------------------------------------
388 
389 StatusCode TwoDSlidingFitSplittingAndSplicingAlgorithm::ReplaceBranch(const Cluster *const pBranchCluster, const Cluster *const pReplacementCluster,
390  const CartesianVector &branchSplitPosition, const CartesianVector &branchSplitDirection) const
391 {
392  ClusterList clusterList;
393  clusterList.push_back(pBranchCluster);
394  clusterList.push_back(pReplacementCluster);
395 
396  std::string clusterListToSaveName, clusterListToDeleteName;
397  PANDORA_RETURN_RESULT_IF(STATUS_CODE_SUCCESS, !=, PandoraContentApi::InitializeFragmentation(*this, clusterList, clusterListToDeleteName,
398  clusterListToSaveName));
399 
400  // Entire replacement cluster goes into new principal cluster
401  PandoraContentApi::Cluster::Parameters principalParameters;
402  pReplacementCluster->GetOrderedCaloHitList().FillCaloHitList(principalParameters.m_caloHitList);
403 
404  // Distribute hits in branch cluster between new principal and residual clusters
405  PandoraContentApi::Cluster::Parameters residualParameters;
406  this->SplitBranchCluster(pBranchCluster, branchSplitPosition, branchSplitDirection, principalParameters.m_caloHitList, residualParameters.m_caloHitList);
407 
408  const Cluster *pPrincipalCluster(NULL), *pResidualCluster(NULL);
409  PANDORA_RETURN_RESULT_IF(STATUS_CODE_SUCCESS, !=, PandoraContentApi::Cluster::Create(*this, principalParameters, pPrincipalCluster));
410  PANDORA_RETURN_RESULT_IF(STATUS_CODE_SUCCESS, !=, PandoraContentApi::Cluster::Create(*this, residualParameters, pResidualCluster));
411  PANDORA_RETURN_RESULT_IF(STATUS_CODE_SUCCESS, !=, PandoraContentApi::EndFragmentation(*this, clusterListToSaveName, clusterListToDeleteName));
412 
413  return STATUS_CODE_SUCCESS;
414 }
415 
416 //------------------------------------------------------------------------------------------------------------------------------------------
417 
418 StatusCode TwoDSlidingFitSplittingAndSplicingAlgorithm::ReadSettings(const TiXmlHandle xmlHandle)
419 {
420  PANDORA_RETURN_RESULT_IF_AND_IF(STATUS_CODE_SUCCESS, STATUS_CODE_NOT_FOUND, !=, XmlHelper::ReadValue(xmlHandle,
421  "ShortHalfWindow", m_shortHalfWindowLayers));
422 
423  PANDORA_RETURN_RESULT_IF_AND_IF(STATUS_CODE_SUCCESS, STATUS_CODE_NOT_FOUND, !=, XmlHelper::ReadValue(xmlHandle,
424  "LongHalfWindow", m_longHalfWindowLayers));
425 
426  PANDORA_RETURN_RESULT_IF_AND_IF(STATUS_CODE_SUCCESS, STATUS_CODE_NOT_FOUND, !=, XmlHelper::ReadValue(xmlHandle,
427  "MinClusterLength", m_minClusterLength));
428 
429  PANDORA_RETURN_RESULT_IF_AND_IF(STATUS_CODE_SUCCESS, STATUS_CODE_NOT_FOUND, !=, XmlHelper::ReadValue(xmlHandle,
430  "VetoDisplacement", m_vetoDisplacement));
431 
432  PANDORA_RETURN_RESULT_IF_AND_IF(STATUS_CODE_SUCCESS, STATUS_CODE_NOT_FOUND, !=, XmlHelper::ReadValue(xmlHandle,
433  "CosmicMode", m_runCosmicMode));
434 
435  return STATUS_CODE_SUCCESS;
436 }
437 
438 } // namespace lar_content
static bool SortByNHits(const pandora::Cluster *const pLhs, const pandora::Cluster *const pRhs)
Sort clusters by number of hits, then layer span, then inner layer, then position, then pulse-height.
const pandora::Cluster * GetReplacementCluster() const
return the address of the replacement Cluster
virtual void FindBestSplitPosition(const TwoDSlidingFitResult &branchSlidingFit, const TwoDSlidingFitResult &replacementSlidingFit, pandora::CartesianVector &replacementStartPosition, pandora::CartesianVector &branchSplitPosition, pandora::CartesianVector &branchSplitDirection) const =0
Output the best split positions in branch and replacement clusters.
intermediate_table::iterator iterator
void BuildSlidingFitResultMap(const pandora::ClusterVector &clusterVector, const unsigned int halfWindowLayers, TwoDSlidingFitResultMap &slidingFitResultMap) const
Build the map of sliding fit results.
void BuildClusterExtensionList(const pandora::ClusterVector &clusterVector, const TwoDSlidingFitResultMap &branchResultMap, const TwoDSlidingFitResultMap &replacementResultMap, ClusterExtensionList &clusterExtensionList) const
Build a list of candidate splits.
const pandora::Cluster * GetBranchCluster() const
return the address of the branch Cluster
const pandora::CartesianVector & GetBranchVertex() const
return the split position of the branch cluster
static float GetWireZPitch(const pandora::Pandora &pandora, const float maxWirePitchDiscrepancy=0.01)
Return the wire pitch.
std::unordered_map< const pandora::Cluster *, TwoDSlidingFitResult > TwoDSlidingFitResultMap
const pandora::CartesianVector & GetBranchDirection() const
return the split direction of the branch cluster
TFile f
Definition: plotHisto.C:6
Header file for the geometry helper class.
Int_t max
Definition: plot.C:27
void GetListOfCleanClusters(const pandora::ClusterList *const pClusterList, pandora::ClusterVector &clusterVector) const
Populate cluster vector with subset of cluster list, containing clusters judged to be clean...
intermediate_table::const_iterator const_iterator
pandora::StatusCode RunSplitAndExtension(const ClusterExtensionList &splitList, TwoDSlidingFitResultMap &branchResultMap, TwoDSlidingFitResultMap &replacementResultMap) const
Run the machinary that performs the cluster splitting and extending.
virtual pandora::StatusCode ReadSettings(const pandora::TiXmlHandle xmlHandle)
pandora::StatusCode ReplaceBranch(const pandora::Cluster *const pBranchCluster, const pandora::Cluster *const pReplacementCluster, const pandora::CartesianVector &branchSplitPosition, const pandora::CartesianVector &branchSplitDirection) const
Remove a branch from a cluster and replace it with a second cluster.
std::vector< art::Ptr< recob::Cluster > > ClusterVector
Int_t min
Definition: plot.C:26
Header file for the two dimensional sliding fit splitting and splicing algorithm class.
static float GetLengthSquared(const pandora::Cluster *const pCluster)
Get length squared of cluster.
void SplitBranchCluster(const pandora::Cluster *const pCluster, const pandora::CartesianVector &splitPosition, const pandora::CartesianVector &splitDirection, pandora::CaloHitList &principalCaloHitList, pandora::CaloHitList &branchCaloHitList) const
Separate cluster into the branch hits to be split from the primary cluster.
float CalculateBranchChi2(const pandora::Cluster *const pCluster, const pandora::CartesianVector &splitPosition, const pandora::CartesianVector &splitDirection) const
Calculate RMS deviation of branch hits relative to the split direction.
void PruneClusterExtensionList(const ClusterExtensionList &inputList, const TwoDSlidingFitResultMap &branchResultMap, const TwoDSlidingFitResultMap &replacementResultMap, ClusterExtensionList &outputList) const
Finalize the list of candidate splits.