/
githubmirror
/
cmssw
Обзор
Документация
Войти
/
githubmirror
/
cmssw
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
RecoTracker/CkfPattern/src/CkfTrackCandidateMakerBase.cc
568 строк
25 KB
Shahzad Malik Muzaffar
[RECONSTRUCTION] [LLVM21] Apply code-checks and formats
10 фев 2026, 00:26
10 фев 2026, 00:26
b6f313d
Код
Авторство
О чём код?
#include "DataFormats/Common/interface/Handle.h" #include "FWCore/Framework/interface/EventSetup.h" #include "FWCore/MessageLogger/interface/MessageLogger.h" #include "FWCore/ParameterSet/interface/PluginDescription.h" #include "FWCore/Utilities/interface/isFinite.h" #include "FWCore/Utilities/interface/ESInputTag.h" #include "DataFormats/Common/interface/OwnVector.h" #include "DataFormats/TrackCandidate/interface/TrackCandidateCollection.h" #include "DataFormats/Common/interface/View.h" #include "DataFormats/TrackReco/interface/SeedStopInfo.h" #include "Geometry/TrackerGeometryBuilder/interface/TrackerGeometry.h" #include "TrackingTools/PatternTools/interface/Trajectory.h" #include "TrackingTools/TrajectoryCleaning/interface/TrajectoryCleanerBySharedHits.h" #include "TrackingTools/Records/interface/TrackingComponentsRecord.h" #include "TrackingTools/TrajectoryState/interface/TrajectoryStateTransform.h" #include "RecoTracker/CkfPattern/interface/CkfTrackCandidateMakerBase.h" #include "RecoTracker/CkfPattern/interface/TransientInitialStateEstimator.h" #include "RecoTracker/Record/interface/TrackerRecoGeometryRecord.h" #include "RecoTracker/Record/interface/CkfComponentsRecord.h" #include "RecoTracker/CkfPattern/interface/BaseCkfTrajectoryBuilderFactory.h" #include "RecoTracker/CkfPattern/interface/CachingSeedCleanerBySharedInput.h" #include "RecoTracker/MeasurementDet/interface/MeasurementTrackerEvent.h" #include "RecoTracker/Record/interface/NavigationSchoolRecord.h" #include "TrackingTools/DetLayers/interface/NavigationSchool.h" #include "RecoTracker/TransientTrackingRecHit/interface/Traj2TrackHits.h" #include <algorithm> #include <functional> // #define VI_SORTSEED // #define VI_REPRODUCIBLE // #define VI_TBB #include <thread> #ifdef VI_TBB #include "oneapi/tbb/parallel_for.h" #endif #include "RecoTracker/CkfPattern/interface/PrintoutHelper.h" using namespace edm; using namespace std; namespace { std::unique_ptr<BaseCkfTrajectoryBuilder> createBaseCkfTrajectoryBuilder(const edm::ParameterSet& pset, edm::ConsumesCollector& iC) { return BaseCkfTrajectoryBuilderFactory::get()->create(pset.getParameter<std::string>("ComponentType"), pset, iC); } } // namespace namespace cms { CkfTrackCandidateMakerBase::CkfTrackCandidateMakerBase(edm::ParameterSet const& conf, edm::ConsumesCollector&& iC) : theTrackCandidateOutput(true), theTrajectoryOutput(false), useSplitting(conf.getParameter<bool>("useHitsSplitting")), doSeedingRegionRebuilding(conf.getParameter<bool>("doSeedingRegionRebuilding")), cleanTrajectoryAfterInOut(conf.getParameter<bool>("cleanTrajectoryAfterInOut")), reverseTrajectories(conf.getParameter<bool>("reverseTrajectories")), theMaxNSeeds(conf.getParameter<unsigned int>("maxNSeeds")), theTrajectoryBuilder( createBaseCkfTrajectoryBuilder(conf.getParameter<edm::ParameterSet>("TrajectoryBuilderPSet"), iC)), theTrajectoryCleanerToken( iC.esConsumes(edm::ESInputTag("", conf.getParameter<std::string>("TrajectoryCleaner")))), theTrajectoryCleaner(nullptr), theInitialState(std::make_unique<TransientInitialStateEstimator>( conf.getParameter<ParameterSet>("TransientInitialStateEstimatorParameters"), iC)), theNavigationSchoolToken( iC.esConsumes(edm::ESInputTag("", conf.getParameter<std::string>("NavigationSchool")))), theNavigationSchool(nullptr), thePropagatorToken(iC.esConsumes(edm::ESInputTag("", "AnyDirectionAnalyticalPropagator"))), #ifdef VI_REPRODUCIBLE maxSeedsBeforeCleaning_(0), #else maxSeedsBeforeCleaning_(conf.getParameter<unsigned int>("maxSeedsBeforeCleaning")), #endif theMTELabel(iC.consumes<MeasurementTrackerEvent>(conf.getParameter<edm::InputTag>("MeasurementTrackerEvent"))), clustersToSkipTag_(conf.getParameter<edm::InputTag>("clustersToSkip")), skipClusters_(!clustersToSkipTag_.label().empty()), phase2ClustersToSkipTag_(conf.getParameter<edm::InputTag>("phase2clustersToSkip")), skipPhase2Clusters_(!phase2ClustersToSkipTag_.label().empty()) { theSeedLabel = iC.consumes<edm::View<TrajectorySeed>>(conf.getParameter<edm::InputTag>("src")); if (skipClusters_) { maskPixels_ = iC.consumes<PixelClusterMask>(clustersToSkipTag_); maskStrips_ = iC.consumes<StripClusterMask>(clustersToSkipTag_); } //FIXME:: just temporary solution for phase2! if (skipPhase2Clusters_) { maskPixels_ = iC.consumes<PixelClusterMask>(phase2ClustersToSkipTag_); maskPhase2OTs_ = iC.consumes<Phase2OTClusterMask>(phase2ClustersToSkipTag_); } #ifndef VI_REPRODUCIBLE std::string cleaner = conf.getParameter<std::string>("RedundantSeedCleaner"); if (cleaner == "CachingSeedCleanerBySharedInput") { int numHitsForSeedCleaner = conf.getParameter<int>("numHitsForSeedCleaner"); bool onlyPixelHits = conf.getParameter<bool>("onlyPixelHitsForSeedCleaner"); theSeedCleaner = std::make_unique<CachingSeedCleanerBySharedInput>(numHitsForSeedCleaner, onlyPixelHits); } else if (cleaner != "none") { throw cms::Exception("RedundantSeedCleaner not found, please use CachingSeedCleanerBySharedInput ro none", cleaner); } #endif #ifdef EDM_ML_DEBUG if (theTrackCandidateOutput) { theTrackerToken = iC.esConsumes(); } #endif #ifdef VI_REPRODUCIBLE std::cout << "CkfTrackCandidateMaker in reproducible setting" << std::endl; assert(nullptr == theSeedCleaner); assert(0 >= maxSeedsBeforeCleaning_); #endif } // Virtual destructor needed. CkfTrackCandidateMakerBase::~CkfTrackCandidateMakerBase() noexcept(false) {} void CkfTrackCandidateMakerBase::beginRunBase(edm::Run const& r, EventSetup const& es) { /* no op*/ } void CkfTrackCandidateMakerBase::setEventSetup(const edm::EventSetup& es) { //services theTrajectoryCleaner = &es.getData(theTrajectoryCleanerToken); theNavigationSchool = &es.getData(theNavigationSchoolToken); theTrajectoryBuilder->setNavigationSchool(theNavigationSchool); } // Functions that gets called by framework every event void CkfTrackCandidateMakerBase::produceBase(edm::Event& e, const edm::EventSetup& es) { // getting objects from the EventSetup setEventSetup(es); // set the correct navigation // NavigationSetter setter( *theNavigationSchool); // propagator auto const& propagator = es.getData(thePropagatorToken); // method for Debugging printHitsDebugger(e); // Step A: set Event for the TrajectoryBuilder edm::Handle<MeasurementTrackerEvent> data; e.getByToken(theMTELabel, data); std::unique_ptr<MeasurementTrackerEvent> dataWithMasks; if (skipClusters_) { edm::Handle<PixelClusterMask> pixelMask; e.getByToken(maskPixels_, pixelMask); edm::Handle<StripClusterMask> stripMask; e.getByToken(maskStrips_, stripMask); dataWithMasks = std::make_unique<MeasurementTrackerEvent>(*data, *stripMask, *pixelMask); //std::cout << "Trajectory builder " << conf_.getParameter<std::string>("@module_label") << " created with masks " << std::endl; theTrajectoryBuilder->setEvent(e, es, &*dataWithMasks); } else if (skipPhase2Clusters_) { //FIXME:just temporary solution for phase2! edm::Handle<PixelClusterMask> pixelMask; e.getByToken(maskPixels_, pixelMask); edm::Handle<Phase2OTClusterMask> phase2OTMask; e.getByToken(maskPhase2OTs_, phase2OTMask); dataWithMasks = std::make_unique<MeasurementTrackerEvent>(*data, *pixelMask, *phase2OTMask); //std::cout << "Trajectory builder " << conf_.getParameter<std::string>("@module_label") << " created with phase2 masks " << std::endl; theTrajectoryBuilder->setEvent(e, es, &*dataWithMasks); } else { //std::cout << "Trajectory builder " << conf_.getParameter<std::string>("@module_label") << " created without masks " << std::endl; theTrajectoryBuilder->setEvent(e, es, &*data); } // TISE ES must be set here due to dependence on theTrajectoryBuilder theInitialState->setEventSetup( es, static_cast<TkTransientTrackingRecHitBuilder const*>(theTrajectoryBuilder->hitBuilder())->cloner()); // Step B: Retrieve seeds edm::Handle<View<TrajectorySeed>> collseed; e.getByToken(theSeedLabel, collseed); // Step C: Create empty output collection auto output = std::make_unique<TrackCandidateCollection>(); auto outputT = std::make_unique<std::vector<Trajectory>>(); auto outputSeedStopInfos = std::make_unique<std::vector<SeedStopInfo>>(collseed->size()); if ((*collseed).size() > theMaxNSeeds) { LogError("TooManySeeds") << "Exceeded maximum numeber of seeds! theMaxNSeeds=" << theMaxNSeeds << " nSeed=" << (*collseed).size(); if (theTrackCandidateOutput) { e.put(std::move(output)); } if (theTrajectoryOutput) { e.put(std::move(outputT)); } e.put(std::move(outputSeedStopInfos)); return; } // Step D: Invoke the building algorithm if (!(*collseed).empty()) { unsigned int lastCleanResult = 0; std::vector<Trajectory> rawResult; rawResult.reserve(collseed->size() * 4); if (theSeedCleaner) theSeedCleaner->init(&rawResult); // method for debugging countSeedsDebugger(); // the mutex std::mutex theMutex; using Lock = std::unique_lock<std::mutex>; // Loop over seeds size_t collseed_size = collseed->size(); unsigned int indeces[collseed_size]; for (auto i = 0U; i < collseed_size; ++i) indeces[i] = i; #ifdef VI_SORTSEED // std::random_shuffle(indeces,indeces+collseed_size); // here only for reference: does not seems to help auto const& seeds = *collseed; float val[collseed_size]; for (auto i = 0U; i < collseed_size; ++i) { val[i] = seeds[i].startingState().pt(); }; // { val[i] = std::abs((*seeds[i].recHits().first).surface()->eta());} /* unsigned long long val[collseed_size]; for (auto i=0U; i< collseed_size; ++i) { if (seeds[i].nHits()<2) { val[i]=0; continue;} auto h = seeds[i].recHits().first; auto const & hit = static_cast<BaseTrackerRecHit const&>(*h); val[i] = hit.firstClusterRef().key(); if (++h != seeds[i].recHits().second) { auto const & hit = static_cast<BaseTrackerRecHit const&>(*h); val[i] |= (unsigned long long)(hit.firstClusterRef().key())<<32; } } */ std::sort(indeces, indeces + collseed_size, [&](unsigned int i, unsigned int j) { return val[i] < val[j]; }); // std::cout << spt(indeces[0]) << ' ' << spt(indeces[collseed_size-1]) << std::endl; #endif std::atomic<unsigned int> ntseed(0); auto theLoop = [&](size_t ii) { auto j = indeces[ii]; ntseed++; // to be moved inside a par section (how with tbb??) std::vector<Trajectory> theTmpTrajectories; LogDebug("CkfPattern") << "======== Begin to look for trajectories from seed " << j << " ========\n"; { Lock lock(theMutex); // Check if seed hits already used by another track if (theSeedCleaner && !theSeedCleaner->good(&((*collseed)[j]))) { LogDebug("CkfTrackCandidateMakerBase") << " Seed cleaning kills seed " << j; (*outputSeedStopInfos)[j].setStopReason(SeedStopReason::SEED_CLEANING); return; // from the lambda! } } // Build trajectory from seed outwards theTmpTrajectories.clear(); unsigned int nCandPerSeed = 0; theTrajectoryBuilder->buildTrajectories((*collseed)[j], theTmpTrajectories, nCandPerSeed, nullptr); { Lock lock(theMutex); (*outputSeedStopInfos)[j].setCandidatesPerSeed(nCandPerSeed); if (theTmpTrajectories.empty()) { (*outputSeedStopInfos)[j].setStopReason(SeedStopReason::NO_TRAJECTORY); return; // from the lambda! } } LogDebug("CkfPattern") << "======== In-out trajectory building found " << theTmpTrajectories.size() << " trajectories from seed " << j << " ========\n" << PrintoutHelper::dumpCandidates(theTmpTrajectories); if (cleanTrajectoryAfterInOut) { // Select the best trajectory from this seed (declare others invalid) theTrajectoryCleaner->clean(theTmpTrajectories); LogDebug("CkfPattern") << "======== In-out trajectory cleaning gave the following " << theTmpTrajectories.size() << " valid trajectories from seed " << j << " ========\n" << PrintoutHelper::dumpCandidates(theTmpTrajectories); } // Optionally continue building trajectory back through // seed and if possible further inwards. if (doSeedingRegionRebuilding) { theTrajectoryBuilder->rebuildTrajectories((*collseed)[j], theTmpTrajectories); LogDebug("CkfPattern") << "======== Out-in trajectory building found " << theTmpTrajectories.size() << " valid/invalid trajectories from seed " << j << " ========\n" << PrintoutHelper::dumpCandidates(theTmpTrajectories); if (theTmpTrajectories.empty()) { Lock lock(theMutex); (*outputSeedStopInfos)[j].setStopReason(SeedStopReason::SEED_REGION_REBUILD); return; } } // Select the best trajectory from this seed (after seed region rebuilding, can be more than one) theTrajectoryCleaner->clean(theTmpTrajectories); LogDebug("CkfPattern") << "======== Trajectory cleaning gave the following " << theTmpTrajectories.size() << " valid trajectories from seed " << j << " ========\n" << PrintoutHelper::dumpCandidates(theTmpTrajectories); { Lock lock(theMutex); for (vector<Trajectory>::iterator it = theTmpTrajectories.begin(); it != theTmpTrajectories.end(); it++) { if (it->isValid()) { it->setSeedRef(collseed->refAt(j)); (*outputSeedStopInfos)[j].setStopReason(SeedStopReason::NOT_STOPPED); // Store trajectory rawResult.push_back(std::move(*it)); // Tell seed cleaner which hits this trajectory used. //TO BE FIXED: this cut should be configurable via cfi file if (theSeedCleaner && rawResult.back().foundHits() > 3) theSeedCleaner->add(&rawResult.back()); //if (theSeedCleaner ) theSeedCleaner->add( & (*it) ); } } } theTmpTrajectories.clear(); LogDebug("CkfPattern") << "rawResult trajectories found so far = " << rawResult.size(); { Lock lock(theMutex); if (maxSeedsBeforeCleaning_ > 0 && rawResult.size() > maxSeedsBeforeCleaning_ + lastCleanResult) { theTrajectoryCleaner->clean(rawResult); rawResult.erase( std::remove_if(rawResult.begin() + lastCleanResult, rawResult.end(), std::not_fn(&Trajectory::isValid)), rawResult.end()); lastCleanResult = rawResult.size(); } } }; // end of loop over seeds #ifdef VI_TBB tbb::parallel_for(0UL, collseed_size, 1UL, theLoop); #else #ifdef VI_OMP #pragma omp parallel for schedule(dynamic, 4) #endif for (size_t j = 0; j < collseed_size; j++) { theLoop(j); } #endif assert(ntseed == collseed_size); if (theSeedCleaner) theSeedCleaner->done(); // std::cout << "VICkfPattern " << "rawResult trajectories found = " << rawResult.size() << " in " << ntseed << " seeds " << collseed_size << std::endl; #ifdef VI_REPRODUCIBLE // sort trajectory std::sort(rawResult.begin(), rawResult.end(), [](const Trajectory& a, const Trajectory& b) { return a.seedRef().key() < b.seedRef().key(); }); //{ return a.chiSquared()*b.ndof() < b.chiSquared()*a.ndof();}); #endif // Step E: Clean the results to avoid duplicate tracks // Rejected ones just flagged as invalid. theTrajectoryCleaner->clean(rawResult); LogDebug("CkfPattern") << "======== Final cleaning of entire event found " << rawResult.size() << " valid/invalid trajectories =======" << endl << PrintoutHelper::dumpCandidates(rawResult); LogDebug("CkfPattern") << "removing invalid trajectories."; // Assuming here that theLoop() gives at most one Trajectory per seed for (const auto& traj : rawResult) { if (!traj.isValid()) { const auto seedIndex = traj.seedRef().key(); if ((*outputSeedStopInfos)[seedIndex].stopReason() == SeedStopReason::NOT_STOPPED) { (*outputSeedStopInfos)[seedIndex].setStopReason(SeedStopReason::FINAL_CLEAN); } } } vector<Trajectory>& unsmoothedResult(rawResult); unsmoothedResult.erase( std::remove_if(unsmoothedResult.begin(), unsmoothedResult.end(), std::not_fn(&Trajectory::isValid)), unsmoothedResult.end()); unsmoothedResult.shrink_to_fit(); // If requested, reverse the trajectories creating a new 1-hit seed on the last measurement of the track if (reverseTrajectories) { for (auto it = unsmoothedResult.begin(), ed = unsmoothedResult.end(); it != ed; ++it) { // reverse the trajectory only if it has valid hit on the last measurement (should happen) if (it->lastMeasurement().updatedState().isValid() && it->lastMeasurement().recHit().get() != nullptr && it->lastMeasurement().recHit()->isValid()) { // I can't use reverse in place, because I want to change the seed // 1) reverse propagation direction PropagationDirection direction = it->direction(); if (direction == alongMomentum) direction = oppositeToMomentum; else if (direction == oppositeToMomentum) direction = alongMomentum; // 2) make a seed TrajectoryStateOnSurface const& initState = it->lastMeasurement().updatedState(); auto initId = it->lastMeasurement().recHitR().rawId(); PTrajectoryStateOnDet&& state = trajectoryStateTransform::persistentState(initState, initId); TrajectorySeed::RecHitContainer hits; hits.push_back(it->lastMeasurement().recHit()->hit()->clone()); // 3) make a trajectory Trajectory trajectory{std::make_shared<TrajectorySeed>(state, std::move(hits), direction), direction}; trajectory.setNLoops(it->nLoops()); trajectory.setSeedRef(it->seedRef()); trajectory.setStopReason(it->stopReason()); // 4) push states in reversed order Trajectory::DataContainer& meas = it->measurements(); trajectory.reserve(meas.size()); for (auto itmeas = meas.rbegin(), endmeas = meas.rend(); itmeas != endmeas; ++itmeas) { trajectory.push(std::move(*itmeas)); } // replace (*it) = std::move(trajectory); } else { edm::LogWarning("CkfPattern_InvalidLastMeasurement") << "Last measurement of the trajectory is invalid, cannot reverse it"; } } } int viTotHits = 0; if (theTrackCandidateOutput) { // Step F: Convert to TrackCandidates output->reserve(unsmoothedResult.size()); Traj2TrackHits t2t(theTrajectoryBuilder->hitBuilder(), true); for (vector<Trajectory>::const_iterator it = unsmoothedResult.begin(); it != unsmoothedResult.end(); ++it) { LogDebug("CkfPattern") << "copying " << (useSplitting ? "splitted" : "un-splitted") << " hits from trajectory"; edm::OwnVector<TrackingRecHit> recHits; if (it->direction() != alongMomentum) LogDebug("CkfPattern") << "not along momentum... " << std::endl; t2t(*it, recHits, useSplitting); viTotHits += recHits.size(); LogDebug("CkfPattern") << "getting initial state."; Trajectory trialTrajectory = (*it); std::pair<TrajectoryStateOnSurface, const GeomDet*> initState; bool failed = false; do { // Drop last hit if previous backFitter was not successful if (failed) { LogDebug("CkfPattern") << "removing last hit"; trialTrajectory.pop(); LogDebug("CkfPattern") << "hits remaining " << trialTrajectory.foundHits(); } // Get inner state const bool doBackFit = (!doSeedingRegionRebuilding) & (!reverseTrajectories); initState = theInitialState->innerState(trialTrajectory, doBackFit); // Check if that was successful failed = (!initState.first.isValid()) || initState.second == nullptr || edm::isNotFinite(initState.first.globalPosition().x()); } while (failed && trialTrajectory.foundHits() > 3); if (failed) { const auto seedIndex = it->seedRef().key(); (*outputSeedStopInfos)[seedIndex].setStopReason(SeedStopReason::SMOOTHING_FAILED); continue; } PTrajectoryStateOnDet state; if (useSplitting && (initState.second != recHits.front().det()) && recHits.front().det()) { LogDebug("CkfPattern") << "propagating to hit front in case of splitting."; TrajectoryStateOnSurface&& propagated = propagator.propagate(initState.first, recHits.front().det()->surface()); if (!propagated.isValid()) continue; state = trajectoryStateTransform::persistentState(propagated, recHits.front().rawId()); } else state = trajectoryStateTransform::persistentState(initState.first, initState.second->geographicalId().rawId()); LogDebug("CkfPattern") << "pushing a TrackCandidate."; output->emplace_back(recHits, it->seed(), state, it->seedRef(), it->nLoops(), (uint8_t)it->stopReason()); } } //output trackcandidates LogTrace("CkfPattern|TrackingRegressionTest") << "========== CkfTrackCandidateMaker Info ==========" << "number of Seed: " << collseed->size() << '\n' << PrintoutHelper::regressionTest(es.getData(theTrackerToken), unsmoothedResult); assert(viTotHits >= 0); // just to use it... // std::cout << "VICkfPattern result " << output->size() << " " << viTotHits << std::endl; if (theTrajectoryOutput) { outputT->swap(unsmoothedResult); } } // end of ((*collseed).size()>0) // method for debugging deleteAssocDebugger(); // Step G: write output to file if (theTrackCandidateOutput) { e.put(std::move(output)); } if (theTrajectoryOutput) { e.put(std::move(outputT)); } e.put(std::move(outputSeedStopInfos)); } void CkfTrackCandidateMakerBase::fillPSetDescription(edm::ParameterSetDescription& desc) { desc.add<bool>("cleanTrajectoryAfterInOut", true); desc.add<bool>("doSeedingRegionRebuilding", true); desc.add<bool>("onlyPixelHitsForSeedCleaner", false); desc.add<bool>("reverseTrajectories", false); desc.add<bool>("useHitsSplitting", true); desc.add<edm::InputTag>("MeasurementTrackerEvent", edm::InputTag("MeasurementTrackerEvent")); desc.add<edm::InputTag>("src", edm::InputTag("globalMixedSeeds")); desc.add<edm::InputTag>("clustersToSkip", edm::InputTag("")); desc.add<edm::InputTag>("phase2clustersToSkip", edm::InputTag("")); edm::ParameterSetDescription psdTB; psdTB.addNode(edm::PluginDescription<BaseCkfTrajectoryBuilderFactory>("ComponentType", true)); desc.add<edm::ParameterSetDescription>("TrajectoryBuilderPSet", psdTB); edm::ParameterSetDescription psd1; psd1.add<std::string>("propagatorAlongTISE", "PropagatorWithMaterial"); psd1.add<std::string>("propagatorOppositeTISE", "PropagatorWithMaterialOpposite"); psd1.add<int>("numberMeasurementsForFit", 4); desc.add<edm::ParameterSetDescription>("TransientInitialStateEstimatorParameters", psd1); desc.add<int>("numHitsForSeedCleaner", 4); desc.add<std::string>("NavigationSchool", "SimpleNavigationSchool"); desc.add<std::string>("RedundantSeedCleaner", "CachingSeedCleanerBySharedInput"); desc.add<std::string>("TrajectoryCleaner", "TrajectoryCleanerBySharedHits"); desc.add<unsigned int>("maxNSeeds", 500000); desc.add<unsigned int>("maxSeedsBeforeCleaning", 0); } } // namespace cms