Skip to content

Commit cd48fe0

Browse files
authored
Merge pull request #47883 from rovere/FixOrphanSimClusters
Fix orphan sim clusters
2 parents b656ba0 + a912731 commit cd48fe0

File tree

6 files changed

+114
-70
lines changed

6 files changed

+114
-70
lines changed

SimCalorimetry/HGCalAssociatorProducers/plugins/SimTauCPLinkProducer.cc

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,9 @@ void SimTauProducer::buildSimTau(SimTauCPLink& t,
6969
bool is_leaf = (daughters.empty());
7070

7171
if (is_leaf) {
72-
LogDebug("SimTauProducer").format(" TO BE SAVED {} ", resonance_idx);
7372
auto const& gen_particle_barcode = gen_particle_barcodes[gen_particle_key];
73+
LogDebug("SimTauProducer")
74+
.format(" TO BE SAVED {}, key {}, barcode {}", resonance_idx, gen_particle_key, gen_particle_barcode);
7475
auto const& found_in_caloparticles = std::find_if(caloPartVec.begin(), caloPartVec.end(), [&](const auto& p) {
7576
return p.g4Tracks()[0].genpartIndex() == gen_particle_barcode;
7677
});
@@ -87,13 +88,13 @@ void SimTauProducer::buildSimTau(SimTauCPLink& t,
8788
} else if (generation != 0) {
8889
t.resonances.push_back({gen_particle.pdgId(), resonance_idx});
8990
resonance_idx = t.resonances.size() - 1;
90-
LogDebug("SimTauProducer").format(" RESONANCE/INTERMEDIATE {} ", resonance_idx);
91+
LogDebug("SimTauProducer").format(" RESONANCE/INTERMEDIATE {}", resonance_idx);
9192
}
9293

9394
++generation;
9495
for (auto daughter = daughters.begin(); daughter != daughters.end(); ++daughter) {
9596
int gen_particle_key = (*daughter).key();
96-
LogDebug("SimTauProducer").format(" gen {} {} {} ", generation, gen_particle_key, (*daughter)->pdgId());
97+
LogDebug("SimTauProducer").format(" gen {} {} {}", generation, gen_particle_key, (*daughter)->pdgId());
9798
buildSimTau(t, generation, resonance_idx, *(*daughter), gen_particle_key, calo_particle_h, gen_particle_barcodes);
9899
}
99100
}

SimDataFormats/CaloAnalysis/interface/SimTauCPLink.h

Lines changed: 28 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -31,31 +31,38 @@ class SimTauCPLink {
3131

3232
enum decayModes {
3333
kNull = -1,
34-
kOneProng0PiZero,
35-
kOneProng1PiZero,
36-
kOneProng2PiZero,
37-
kOneProng3PiZero,
38-
kOneProngNPiZero,
39-
kTwoProng0PiZero,
40-
kTwoProng1PiZero,
41-
kTwoProng2PiZero,
42-
kTwoProng3PiZero,
43-
kTwoProngNPiZero,
44-
kThreeProng0PiZero,
45-
kThreeProng1PiZero,
46-
kThreeProng2PiZero,
47-
kThreeProng3PiZero,
48-
kThreeProngNPiZero,
49-
kRareDecayMode,
50-
kElectron,
51-
kMuon
34+
kOneProng0PiZero, // 0
35+
kOneProng1PiZero, // 1
36+
kOneProng2PiZero, // 2
37+
kOneProng3PiZero, // 3
38+
kOneProngNPiZero, // 4
39+
kTwoProng0PiZero, // 5
40+
kTwoProng1PiZero, // 6
41+
kTwoProng2PiZero, // 7
42+
kTwoProng3PiZero, // 8
43+
kTwoProngNPiZero, // 9
44+
kThreeProng0PiZero, // 10
45+
kThreeProng1PiZero, // 11
46+
kThreeProng2PiZero, // 12
47+
kThreeProng3PiZero, // 13
48+
kThreeProngNPiZero, // 14
49+
kRareDecayMode, // 15
50+
kElectron, // 16
51+
kMuon // 17
5252
};
5353

5454
void dump(void) const {
55+
LogDebug("SimTauProducer")
56+
.format("Decay mode: {} ", buildDecayModes())
57+
.format("Leaves: {} ", leaves.size())
58+
.format("Resonances: {}", resonances.size());
5559
for (auto const &l : leaves) {
5660
LogDebug("SimTauProducer")
57-
.format(
58-
"L {} {} CP: {} GenP idx: {}", l.pdgId(), l.resonance_idx(), l.calo_particle_idx(), l.gen_particle_idx());
61+
.format("L {} {} CP: {} GenP idx: {}",
62+
l.pdgId(),
63+
l.resonance_idx(),
64+
(int)((l.calo_particle_idx() == -1) ? -1 : calo_particle_leaves[l.calo_particle_idx()].key()),
65+
l.gen_particle_idx());
5966
}
6067
for (auto const &r : resonances) {
6168
LogDebug("SimTauProducer").format("R {} {}", r.first, r.second);
@@ -78,7 +85,7 @@ class SimTauCPLink {
7885
}
7986
}
8087

81-
int buildDecayModes() {
88+
int buildDecayModes() const {
8289
int numElectrons = 0;
8390
int numMuons = 0;
8491
int numHadrons = 0;

SimGeneral/CaloAnalysis/plugins/CaloTruthAccumulator.cc

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,8 @@ namespace {
160160
simHitBarcodeToIndex_(simHitBarcodeToIndex),
161161
simTrackDetIdEnergyMap_(simTrackDetIdEnergyMap),
162162
vertex_time_map_(vertex_time_map),
163-
selector_(selector) {}
163+
selector_(selector),
164+
insideCP_(false) {}
164165
template <typename Vertex, typename Graph>
165166
void discover_vertex(Vertex u, const Graph &g) {
166167
// If we reach the vertex 0, it means that we are backtracking with respect
@@ -173,7 +174,7 @@ namespace {
173174
auto trackIdx = vertex_property.simTrack->trackId();
174175
IfLogDebug(DEBUG, messageCategoryGraph_)
175176
<< " Found " << simHitBarcodeToIndex_.count(trackIdx) << " associated simHits" << std::endl;
176-
if (simHitBarcodeToIndex_.count(trackIdx)) {
177+
if (insideCP_ && simHitBarcodeToIndex_.count(trackIdx)) {
177178
output_.pSimClusters->emplace_back(*vertex_property.simTrack);
178179
auto &simcluster = output_.pSimClusters->back();
179180
std::unordered_map<uint32_t, float> acc_energy;
@@ -193,10 +194,13 @@ namespace {
193194
auto edge_property = get(edge_weight, g, e);
194195
IfLogDebug(DEBUG, messageCategoryGraph_) << "Considering CaloParticle: " << edge_property.simTrack->trackId();
195196
if (selector_(edge_property)) {
197+
insideCP_ = true;
196198
IfLogDebug(DEBUG, messageCategoryGraph_) << "Adding CaloParticle: " << edge_property.simTrack->trackId();
197199
output_.pCaloParticles->emplace_back(*(edge_property.simTrack));
198200
output_.pCaloParticles->back().setSimTime(vertex_time_map_[(edge_property.simTrack)->vertIndex()]);
199201
caloParticles_.sc_start_.push_back(output_.pSimClusters->size());
202+
} else {
203+
insideCP_ = false;
200204
}
201205
}
202206
}
@@ -209,6 +213,7 @@ namespace {
209213
auto edge_property = get(edge_weight, g, e);
210214
if (selector_(edge_property)) {
211215
caloParticles_.sc_stop_.push_back(output_.pSimClusters->size());
216+
insideCP_ = false;
212217
}
213218
}
214219
}
@@ -220,6 +225,7 @@ namespace {
220225
std::unordered_map<int, std::map<int, float>> &simTrackDetIdEnergyMap_;
221226
std::unordered_map<uint32_t, float> &vertex_time_map_;
222227
Selector selector_;
228+
bool insideCP_;
223229
};
224230
} // namespace
225231

SimGeneral/Debugging/plugins/CaloParticleDebugger.cc

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -163,17 +163,19 @@ void CaloParticleDebugger::analyze(const edm::Event& iEvent, const edm::EventSet
163163
auto const& t = tracks[i];
164164
LogVerbatim("CaloParticleDebuggerSimTracks")
165165
<< i << "\t" << t.trackId() << "\t" << t << " Crossed Boundary: " << t.crossedBoundary()
166+
<< " Vtx: " << t.vertIndex() << " isFromBackScattering: " << t.isFromBackScattering()
167+
<< " isPrimary: " << t.isPrimary() << " ParentID: " << t.getPrimaryID()
166168
<< " Position Boundary: " << t.getPositionAtBoundary() << " Momentum Boundary: " << t.getMomentumAtBoundary()
167-
<< " Vtx: " << t.vertIndex() << " Momemtum Origin: " << t.momentum();
169+
<< " Momemtum Origin: " << t.momentum();
168170
trackid_to_track_index[t.trackId()] = i;
169171
}
170172

171173
LogVerbatim("CaloParticleDebuggerGenParticles") << "\n\n**Printing GenParticles information **";
172-
LogVerbatim("CaloParticleDebuggerGenParticles") << "BARCODE\tPDGID\tMOMENTUM(x,y,z)\tVertex(x,y,z)";
174+
LogVerbatim("CaloParticleDebuggerGenParticles") << "BARCODE\tID\tPDGID\tMOMENTUM(x,y,z)\tVertex(x,y,z)";
173175
for (size_t i = 0; i < genParticles.size(); ++i) {
174176
auto const& gp = genParticles[i];
175177
LogVerbatim("CaloParticleDebuggerGenParticles")
176-
<< genBarcodes[i] << "\t" << gp.pdgId() << "\t" << gp.momentum() << "\t" << gp.vertex();
178+
<< genBarcodes[i] << "\t" << i << "\t" << gp.pdgId() << "\t" << gp.momentum() << "\t" << gp.vertex();
177179
}
178180

179181
LogVerbatim("CaloParticleDebuggerSimVertices") << "\n\n**Printing SimVertex information **";
@@ -190,32 +192,38 @@ void CaloParticleDebugger::analyze(const edm::Event& iEvent, const edm::EventSet
190192
}
191193

192194
LogVerbatim("CaloParticleDebuggerCaloParticles") << "\n\n**Printing CaloParticles information **";
195+
int totalSimCl_in_event = 0;
196+
std::set<int> simClusters_in_CaloParticles;
193197
for (size_t i = 0; i < calopart.size(); ++i) {
194198
auto const& cp = calopart[i];
195199
LogVerbatim("CaloParticleDebuggerCaloParticles") << "\n\n"
196200
<< i << "\tType: " << cp.pdgId() << "\tEnergy: " << cp.energy()
197201
<< "\tBarcode: " << cp.g4Tracks()[0].genpartIndex()
198202
<< " G4_trackID: " << cp.g4Tracks()[0].trackId(); // << cp ;
199203
double total_sim_energy = 0.;
200-
double total_cp_energy = 0.;
201204
LogVerbatim("CaloParticleDebuggerCaloParticles") << "--> Overall simclusters in CP: " << cp.simClusters().size();
205+
totalSimCl_in_event += cp.simClusters().size();
202206
// All the next mess just to print the simClusters ordered
203207
auto const& simcs = cp.simClusters();
204208
for (size_t j = 0; j < simcs.size(); ++j) {
205209
LogVerbatim("CaloParticleDebuggerCaloParticles") << *(simcs[j]);
210+
simClusters_in_CaloParticles.insert(simcs[j]->g4Tracks()[0].trackId());
206211
}
207212

208213
for (auto const& sc : cp.simClusters()) {
209214
for (auto const& cl : sc->hits_and_fractions()) {
210215
total_sim_energy += detIdToTotalSimEnergy[cl.first] * cl.second;
211-
total_cp_energy += cp.energy() * cl.second;
212216
}
213217
}
214218
LogVerbatim("CaloParticleDebuggerCaloParticles")
215219
<< "--> Overall SC energy (sum using sim energies): " << total_sim_energy;
216-
LogVerbatim("CaloParticleDebuggerCaloParticles")
217-
<< "--> Overall SC energy (sum using CaloP energies): " << total_cp_energy;
218220
}
221+
LogVerbatim("CaloParticleDebuggerCaloParticles")
222+
<< "--> Overall SCs attached to CPs: " << totalSimCl_in_event;
223+
LogVerbatim("CaloParticleDebuggerCaloParticles")
224+
<< "--> Overall SCs in the event: " << simclusters.size();
225+
LogVerbatim("CaloParticleDebuggerCaloParticles")
226+
<< "--> Orphan SCs in the event: " << simclusters.size() - totalSimCl_in_event;
219227

220228
LogVerbatim("CaloParticleDebuggerSimClusters") << "\n\n**Printing SimClusters information **";
221229
for (size_t i = 0; i < simclusters.size(); ++i) {
@@ -224,9 +232,13 @@ void CaloParticleDebugger::analyze(const edm::Event& iEvent, const edm::EventSet
224232
<< "\n\n"
225233
<< i << "\tType: " << simcl.pdgId() << "\tEnergy: " << simcl.energy() << "\tKey: " << i; // << simcl ;
226234
auto const& simtrack = simcl.g4Tracks()[0];
227-
LogVerbatim("CaloParticleDebuggerSimClusters") << " Crossed Boundary: " << simtrack.crossedBoundary()
228-
<< " Position Boundary: " << simtrack.getPositionAtBoundary()
229-
<< " Momentum Boundary: " << simtrack.getMomentumAtBoundary();
235+
LogVerbatim("CaloParticleDebuggerSimClusters") << "\n Primary GenParticleID: " << simtrack.getPrimaryID()
236+
<< "\n Crossed Boundary: " << simtrack.crossedBoundary()
237+
<< "\n Position Boundary: " << simtrack.getPositionAtBoundary()
238+
<< "\n Momentum Boundary: " << simtrack.getMomentumAtBoundary();
239+
if (simClusters_in_CaloParticles.find(simcl.g4Tracks()[0].trackId()) == simClusters_in_CaloParticles.end()) {
240+
LogVerbatim("CaloParticleDebuggerSimClusters") << " Orphan SimCluster: " << simtrack.trackId();
241+
}
230242
double total_sim_energy = 0.;
231243
LogVerbatim("CaloParticleDebuggerSimClusters") << "--> Overall simclusters's size: " << simcl.numberOfRecHits();
232244
for (auto const& cl : simcl.hits_and_fractions()) {

SimGeneral/Debugging/python/caloParticleDebugger_cfg.py

Lines changed: 48 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,60 +2,75 @@
22

33
# The line below always has to be included to make VarParsing work
44
from FWCore.ParameterSet.VarParsing import VarParsing
5-
options = VarParsing ('analysis')
5+
6+
options = VarParsing("analysis")
67
options.parseArguments()
78

89
process = cms.Process("Demo")
910

1011
process.load("FWCore.MessageService.MessageLogger_cfi")
11-
process.load('Configuration.Geometry.GeometryExtended2026D96_cff')
12-
process.load('Configuration.StandardSequences.FrontierConditions_GlobalTag_cff')
12+
process.load("Configuration.Geometry.GeometryExtendedRun4D110Reco_cff")
13+
process.load("Configuration.StandardSequences.FrontierConditions_GlobalTag_cff")
1314
from Configuration.AlCa.GlobalTag import GlobalTag
14-
process.GlobalTag = GlobalTag(process.GlobalTag, 'auto:phase2_realistic_T21', '')
1515

16-
process.maxEvents = cms.untracked.PSet( input = cms.untracked.int32(-1) )
16+
process.GlobalTag = GlobalTag(process.GlobalTag, "auto:phase2_realistic_T21", "")
1717

18+
process.maxEvents = cms.untracked.PSet(input=cms.untracked.int32(-1))
1819

19-
input_filename = 'default.root' if len(options.inputFiles) == 0 else options.inputFiles[0]
20-
#input_filename='step2SingleElectronPt15Eta1p7_2p7_SimTracksters.root'
21-
#input_filename='step2FineCaloSingleElectronPt15Eta1p7_2p7_SimTracksters.root'
22-
#input_filename='step2SingleElectronPt15Eta1p7_2p7_CBWEAndSimTracksters.root'
23-
#input_filename='step2FineCaloSingleElectronPt15Eta1p7_2p7_CBWEAndSimTracksters.root'
2420

25-
process.source = cms.Source("PoolSource",
26-
inputCommands = cms.untracked.vstring(['keep *',
27-
'drop l1tEMTFHit2016Extras_simEmtfDigis_CSC_HLT',
28-
'drop l1tEMTFHit2016Extras_simEmtfDigis_RPC_HLT',
29-
'drop l1tEMTFHit2016s_simEmtfDigis__HLT',
30-
'drop l1tEMTFTrack2016Extras_simEmtfDigis__HLT',
31-
'drop l1tEMTFTrack2016s_simEmtfDigis__HLT']),
32-
# replace 'myfile.root' with the source file you want to use
33-
fileNames = cms.untracked.vstring(
34-
# 'file:/data/rovere/HGCAL/study/CMSSW_9_4_0/src/SimGeneral/Debugging/test/20800.0_FourMuPt1_200+FourMuPt_1_200_pythia8_2023D20_GenSimHLBeamSpotFull+DigiFull_2023D20+RecoFullGlobal_2023D20+HARVESTFullGlobal_2023D20/step2.root'
35-
# 'file:/data/rovere/HGCAL/study/CMSSW_9_4_0/src/SimGeneral/Debugging/test/20824.0_TTbar_13+TTbar_13TeV_TuneCUETP8M1_2023D20_GenSimHLBeamSpotFull+DigiFull_2023D20+RecoFullGlobal_2023D20+HARVESTFullGlobal_2023D20/step2.root'
36-
# 'file:/data/rovere/HGCAL/study/CMSSW_9_4_0/src/SimGeneral/Debugging/test/20002.0_SingleElectronPt35+SingleElectronPt35_pythia8_2023D17_GenSimHLBeamSpotFull+DigiFullTrigger_2023D17+RecoFullGlobal_2023D17+HARVESTFullGlobal_2023D17/step2.root'
37-
# 'file:/data/rovere/HGCAL/study/CMSSW_9_4_0/src/SimGeneral/Debugging/test/20016.0_SingleGammaPt35Extended+DoubleGammaPt35Extended_pythia8_2023D17_GenSimHLBeamSpotFull+DigiFullTrigger_2023D17+RecoFullGlobal_2023D17+HARVESTFullGlobal_2023D17/step2.root'
38-
# 'file:/data/rovere/HGCAL/study/CMSSW_9_4_0/src/SimGeneral/Debugging/test/20088.0_SinglePiPt25Eta1p7_2p7+SinglePiPt25Eta1p7_2p7_2023D17_GenSimHLBeamSpotFull+DigiFullTrigger_2023D17+RecoFullGlobal_2023D17+HARVESTFullGlobal_2023D17/step2.root'
39-
'file:%s'%input_filename
21+
input_filename = (
22+
"default.root" if len(options.inputFiles) == 0 else options.inputFiles[0]
23+
)
24+
# input_filename='step2SingleElectronPt15Eta1p7_2p7_SimTracksters.root'
25+
# input_filename='step2FineCaloSingleElectronPt15Eta1p7_2p7_SimTracksters.root'
26+
# input_filename='step2SingleElectronPt15Eta1p7_2p7_CBWEAndSimTracksters.root'
27+
# input_filename='step2FineCaloSingleElectronPt15Eta1p7_2p7_CBWEAndSimTracksters.root'
4028

41-
)
29+
process.source = cms.Source(
30+
"PoolSource",
31+
inputCommands=cms.untracked.vstring(
32+
[
33+
"keep *",
34+
"drop l1tEMTFHit2016Extras_simEmtfDigis_CSC_HLT",
35+
"drop l1tEMTFHit2016Extras_simEmtfDigis_RPC_HLT",
36+
"drop l1tEMTFHit2016s_simEmtfDigis__HLT",
37+
"drop l1tEMTFTrack2016Extras_simEmtfDigis__HLT",
38+
"drop l1tEMTFTrack2016s_simEmtfDigis__HLT",
39+
]
40+
),
41+
# replace 'myfile.root' with the source file you want to use
42+
fileNames=cms.untracked.vstring(
43+
# 'file:/data/rovere/HGCAL/study/CMSSW_9_4_0/src/SimGeneral/Debugging/test/20800.0_FourMuPt1_200+FourMuPt_1_200_pythia8_2023D20_GenSimHLBeamSpotFull+DigiFull_2023D20+RecoFullGlobal_2023D20+HARVESTFullGlobal_2023D20/step2.root'
44+
# 'file:/data/rovere/HGCAL/study/CMSSW_9_4_0/src/SimGeneral/Debugging/test/20824.0_TTbar_13+TTbar_13TeV_TuneCUETP8M1_2023D20_GenSimHLBeamSpotFull+DigiFull_2023D20+RecoFullGlobal_2023D20+HARVESTFullGlobal_2023D20/step2.root'
45+
# 'file:/data/rovere/HGCAL/study/CMSSW_9_4_0/src/SimGeneral/Debugging/test/20002.0_SingleElectronPt35+SingleElectronPt35_pythia8_2023D17_GenSimHLBeamSpotFull+DigiFullTrigger_2023D17+RecoFullGlobal_2023D17+HARVESTFullGlobal_2023D17/step2.root'
46+
# 'file:/data/rovere/HGCAL/study/CMSSW_9_4_0/src/SimGeneral/Debugging/test/20016.0_SingleGammaPt35Extended+DoubleGammaPt35Extended_pythia8_2023D17_GenSimHLBeamSpotFull+DigiFullTrigger_2023D17+RecoFullGlobal_2023D17+HARVESTFullGlobal_2023D17/step2.root'
47+
# 'file:/data/rovere/HGCAL/study/CMSSW_9_4_0/src/SimGeneral/Debugging/test/20088.0_SinglePiPt25Eta1p7_2p7+SinglePiPt25Eta1p7_2p7_2023D17_GenSimHLBeamSpotFull+DigiFullTrigger_2023D17+RecoFullGlobal_2023D17+HARVESTFullGlobal_2023D17/step2.root'
48+
"file:%s" % input_filename
49+
),
4250
)
4351

4452
process.load("SimGeneral.Debugging.caloParticleDebugger_cfi")
4553

4654
# MessageLogger customizations
4755
process.MessageLogger.cerr.enable = False
4856
process.MessageLogger.cout.enable = False
49-
labels = ['SimTracks', 'SimVertices', 'GenParticles', 'TrackingParticles', 'CaloParticles', 'SimClusters']
57+
labels = [
58+
"SimTracks",
59+
"SimVertices",
60+
"GenParticles",
61+
"TrackingParticles",
62+
"CaloParticles",
63+
"SimClusters",
64+
]
5065
messageLogger = dict()
5166
for category in labels:
52-
main_key = '%sMessageLogger'%(category)
53-
category_key = 'CaloParticleDebugger%s'%(category)
67+
main_key = "%sMessageLogger" % (category)
68+
category_key = "CaloParticleDebugger%s" % (category)
5469
messageLogger[main_key] = dict(
55-
filename = '%s_%s.log' % (input_filename.replace('.root',''), category),
56-
threshold = 'INFO',
57-
default = dict(limit=0)
58-
)
70+
filename="%s_%s.log" % (input_filename.replace(".root", ""), category),
71+
threshold="INFO",
72+
default=dict(limit=0),
73+
)
5974
messageLogger[main_key][category_key] = dict(limit=-1)
6075
# First create defaults
6176
setattr(process.MessageLogger.files, category, dict())

SimGeneral/MixingModule/interface/DecayGraph.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,10 @@ namespace {
103103
std::string graphviz_vertex(const VertexProperty &v) {
104104
std::ostringstream oss;
105105
oss << "{id: " << (v.simTrack ? v.simTrack->trackId() : 0) << ",\\ntype: " << (v.simTrack ? v.simTrack->type() : 0)
106-
<< ",\\nchits: " << v.cumulative_simHits << "}";
106+
<< ",\\nPVtxIdx?: " << (v.simTrack ? v.simTrack->vertIndex() : -1)
107+
<< ",\\nPrim?: " << (v.simTrack ? v.simTrack->isPrimary() : -1)
108+
<< ",\\nXB?: " << (v.simTrack ? v.simTrack->crossedBoundary() : -1) << ",\\nchits: " << v.cumulative_simHits
109+
<< "}";
107110
return oss.str();
108111
}
109112

0 commit comments

Comments
 (0)