ATLAS Offline Software
Loading...
Searching...
No Matches
ReadRPCRun2DataFile.py
Go to the documentation of this file.
1#!/usr/bin/env python
2
3# Copyright (C) 2002-2021 CERN for the benefit of the ATLAS collaboration
4
5import sys
6import argparse
7from itertools import groupby
8from operator import attrgetter
9from TrigConfMuctpi.XMLReader import MioctGeometryXMLReader
10from math import pi as PI
11from copy import copy
12
13# since this reads the geometry data from the .dat file and the 2015 geometry xml
14# it is best to define a new (small) class hierarchy to hold this data
15class ROI(object):
16 def __init__( self, eta=0, phi=0, phimin=0, phimax=0, etamin=0, etamax=0,
17 etacode=0, phicode=0, roiid=0 ):
18 self.eta = eta
19 self.phi = phi
20 self.phimin = phimin
21 self.phimax = phimax
22 self.etamin = etamin
23 self.etamax = etamax
24 self.etacode = etacode
25 self.phicode = phicode
26 self.roiid = roiid
27
28 @classmethod
29 def fromROIelement(cls,roi):
30 newroi = cls( eta=float(roi['eta']), phi=float(roi['phi']), phimin=float(roi['phimin']), phimax=float(roi['phimax']), etamin=float(roi['etamin']), etamax=float(roi['etamax']),
31 etacode=int(roi['etacode'],16), phicode=int(roi['phicode'],16), roiid=int(roi['roiid']))
32 return newroi
33 def asXML(self,depth):
34 s = ' ' * depth + '<ROI eta="%f" phi="%f" etacode="0x%s" phicode="0x%s" etamin="%f" etamax="%f" phimin="%f" phimax="%f" roiid="%i"/>\n' % (self.eta, self.phi, self.etacode, self.phicode, self.etamin, self.etamax, self.phimin, self.phimax, self.roiid )
35 return s
36 def getStats(self,stats):
37 pass
38
39
40class Sector(object):
41 def __init__(self, name, connector, rois=[]):
42 self.name = name
43 self.connector = connector
44 self.id = int(name.lstrip('BECFA'))
45 self.rois = copy(rois)
46 def addROI(self,roi):
47 self.rois += [ roi ]
48 def isBarrel(self):
49 return self.name.startswith('B')
50 def getStats(self,stats):
51 stats['rois'] += len(self.rois)
52 for roi in self.rois:
53 roi.getStats(stats)
54 def asXML(self, depth):
55 s = ' '*depth + '<Sector connector="%s" name="%s">\n' % (self.connector, self.name)
56 s += ' '*depth + ' <!-- contains %i ROIs -->\n' % len(self.rois)
57 s += ' '*depth + ' <!-- mapping from ROI to coding scheme -->\n'
58 for roi in sorted(self.rois, key=lambda roi: int(roi.roiid)):
59 s += roi.asXML(depth + 4 )
60 s += ' '*depth + "</Sector>\n"
61 return s
62
63
65 def __init__(self, etacode, phicode, eta, phi, ieta, iphi, etamin, etamax , phimin, phimax):
66 self.etacode = etacode
67 self.phicode = phicode
68 self.eta = eta
69 self.phi = phi
70 self.ieta = ieta
71 self.iphi = iphi
72 self.etamin = etamin
73 self.etamax = etamax
74 self.phimin = phimin
75 self.phimax = phimax
76
77 @classmethod
79 newtc = cls( etacode=int(tc['etacode'],16), phicode=int(tc['phicode'],16),
80 eta=float(tc['eta']), phi=float(tc['phi']),
81 ieta=float(tc['ieta']), iphi=float(tc['iphi']),
82 etamin=float(tc['etamin']), etamax=float(tc['etamax']), phimin=float(tc['phimin']), phimax=float(tc['phimax']) )
83 return newtc
84 def asXML(self, depth):
85 return ' '*depth + '<TopoCell etacode="0x%s" phicode="0x%s" eta="%f" phi="%f" ieta="%i" iphi="%i" etamin="%f" etamax="%f" phimin="%f" phimax="%f"/>\n' % (self.etacode, self.phicode,
86 self.eta, self.phi,
87 self.ieta, self.iphi,
88 self.etamin, self.etamax, self.phimin, self.phimax)
89
90
92 def __init__(self, topocells=[]):
93 self.topocells = copy(topocells)
94
95 @classmethod
96 def fromAllRois(cls,allrois):
97 newDecodes = cls()
98 for (etacode,phicode), roisInCell in groupby( sorted(allrois, key=attrgetter('etacode','phicode')), key=attrgetter('etacode','phicode')):
99 # make a new cell
100 (eta, phi, ieta, iphi, etamin, etamax, phimin, phimax) = cls.getTopoCellPosition(etacode, phicode, roisInCell)
101 newDecodes.addTopoCell( TopoCell(etacode=etacode, phicode=phicode, eta=eta, phi=phi, ieta=ieta, iphi=iphi, etamin=etamin, etamax=etamax, phimin=phimin, phimax=phimax) )
102 return newDecodes
103
104 @classmethod
105 def getTopoCellPosition(cls, etacode, phicode, rois):
106 rois = list(rois)
107
108 # ETA
109
110 # eta min and max are different in the RPC (barrel) and TGC (endcap and forward)
111 # in the RPC: abs(etamin)<abs(etamax)
112 # in the TGC: etamin<etamax
113 # we are going to use the TGC definition: etamin<etamax
114
115 cellsEta = [(min(e.etamin,e.etamax), max(e.etamin,e.etamax)) for e in rois]
116 etamins, etamaxs = zip(*cellsEta) # transpose
117 etamin = min( [ min(e.etamin,e.etamax) for e in rois] )
118 etamax = max( [ max(e.etamin,e.etamax) for e in rois] )
119 eta = (etamin+etamax)/2
120
121
122 # PHI CONVENTION
123 cellsPhi = [(e.phimin, e.phimax) for e in rois]
124
125 # check if there are cells on both sides of the PI boundary
126 upperedge = any([e.phi>6.2 for e in rois])
127 loweredge = any([e.phi<0.2 for e in rois])
128 splitTopoCell = upperedge and loweredge
129
130
131 if splitTopoCell:
132 maxAtLowerEdge = max([e.phimax for e in rois if e.phi<1])
133 minAtUpperEdge = min([e.phimin for e in rois if e.phi>5])
134 centerTopoCell = minAtUpperEdge + maxAtLowerEdge
135 if centerTopoCell>=2*PI: # shift down
136 phimin = minAtUpperEdge - 2 * PI
137 phimax = maxAtLowerEdge
138 else: # shift up
139 phimin = minAtUpperEdge
140 phimax = maxAtLowerEdge + 2 * PI
141 phi = (phimin+phimax)/2
142 else:
143
144 phimins, phimaxs = zip(*cellsPhi) # transpose
145 phimin = min(phimins)
146 phimax = max(phimaxs)
147 phi = (phimin+phimax)/2
148
149
150 # IETA
151 ieta = round(eta*10)
152 if ieta== 12: ieta= 11
153 if ieta==-12: ieta=-11
154 if ieta== 9: ieta= 8
155 if ieta==-9: ieta=-8
156
157 # IPHI
158 iphi = int(phi*10)
159 if abs(ieta) in [2,5]:
160 if phi>2.05 and phi<2.35: iphi += 1
161 if phi>2.75 and phi<3.25: iphi += 1
162 if phi>3.45: iphi += 1
163
164 if abs(ieta) == 8:
165 if phi>2.05 and phi<2.35: iphi += 1
166 if phi>2.75 and phi<3.25: iphi += 1
167 if phi>3.45 and phi<4.95: iphi += 1
168 if phi>5.05: iphi += 1
169
170 if abs(ieta) in [15,18]:
171 if phi>2.65: iphi += 1
172
173 if abs(ieta) == 11:
174 if phi>2.15 and phi<2.35: iphi += 1
175 if phi>2.65 and phi<3.25: iphi += 1
176 if phi>3.35: iphi += 1
177
178 if abs(ieta) == 22:
179 if phi>0.05 and phi<5.35: iphi += 1
180 if phi>5.35: iphi += 2
181
182 return (eta, phi, ieta, iphi, etamin, etamax, phimin, phimax)
183
184 def addTopoCell(self,topocell):
185 self.topocells += [ topocell ]
186 def getStats(self,stats):
187 stats['decodes'] += 1
188 stats['topocells'] += len(self.topocells)
189 def asXML(self, depth):
190 s = ' '*depth + '<Decode>\n'
191 for tc in self.topocells:
192 s += tc.asXML(depth+4)
193 s += ' '*depth + '</Decode>\n'
194 return s
195
196
198 def __init__(self, id, slot, sectors=[]):
199 self.id = id
200 self.slot = slot
201 self.sectors = copy(sectors)
202 self.sectordic = dict([ (s.connector, s) for s in sectors ])
203 self.decodes = None
204 def getSector(self,connector):
205 return self.sectordic.get(connector)
206 def addSector(self,sector):
207 if sector.connector in self.sectordic:
208 raise RuntimeError("Sector with connector %i already exists" % sector.connector)
209 self.sectors += [ sector ]
210 self.sectordic[sector.connector] = sector
211 return sector
212 def getStats(self,stats):
213 stats['sectors'] += len(self.sectors)
214 for sector in self.sectors:
215 sector.getStats(stats)
216 if self.decodes:
217 self.decodes.getStats(stats)
218 def asXML(self, depth):
219 sectorNames = [s.name for s in self.sectors]
220 s = ' '*depth + '<MIOCT id="%s" slot="%i">\n' % (self.id, self.slot)
221 s += ' '*depth + ' <!-- contains sectors %s -->\n' % ", ".join(sorted(sectorNames))
222 for sector in self.sectors:
223 s += sector.asXML(depth + 4)
224 if self.decodes:
225 s += self.decodes.asXML(depth + 4)
226 s += ' '*depth + "</MIOCT>"
227 return s
228 def fillTopoCells( self):
229 # add all ROIs in this MIOCT
230 allROIsInMioct = []
231 for sector in self.sectors:
232 allROIsInMioct += sector.rois
233 self.decodes = Decodes.fromAllRois(allROIsInMioct)
234
235
237 def __init__(self, name, miocts=[]):
238 self.name = name
239 self.miocts = copy(miocts)
240 self.mioctdic = dict([ (m.id, m) for m in miocts])
241 def getMioct(self,mioctId):
242 return self.mioctdic.get(mioctId)
243 def addMioct(self,mioct):
244 if mioct.id in self.mioctdic:
245 raise RuntimeError("MIOCT with ID %i already exists" % mioct.id)
246 self.miocts += [ mioct ]
247 self.mioctdic[mioct.id] = mioct
248 return mioct
249 def writeXML(self,outfilename):
250 f = open(outfilename,"write")
251 print('<?xml version="1.0" ?>\n', file=f)
252 print('<!DOCTYPE MuCTPiGeometry SYSTEM "MUCTPIGeometry.dtd">\n', file=f)
253 print('<MuCTPiGeometry>', file=f)
254 for mioct in sorted(self.miocts,key=lambda m:m.id):
255 print(mioct.asXML(4), file=f)
256 print(' <PtEncoding>', file=f)
257 print(' <PtCodeElement pt="1" code="0" value="4"/>', file=f)
258 print(' <PtCodeElement pt="2" code="1" value="6"/>', file=f)
259 print(' <PtCodeElement pt="3" code="2" value="10"/>', file=f)
260 print(' <PtCodeElement pt="4" code="2" value="11"/>', file=f)
261 print(' <PtCodeElement pt="5" code="2" value="15"/>', file=f)
262 print(' <PtCodeElement pt="6" code="2" value="20"/>', file=f)
263 print(' </PtEncoding>', file=f)
264 print("</MuCTPiGeometry>", file=f)
265 f.close()
266 print("Wrote %s" % outfilename)
267 def getStats(self,stats):
268 stats['miocts'] = len(self.miocts)
269 for mioct in self.miocts:
270 mioct.getStats(stats)
271 def printStats(self):
272 stats = {'miocts' : 0, 'sectors' : 0, 'rois' : 0, 'decodes' : 0, 'topocells' : 0}
273 self.getStats(stats)
274 print("Numbers for %s" % self.name)
275 print("#MIOCTs : %i" % stats['miocts'])
276 print("#Sectors : %i" % stats['sectors'])
277 print("#Decodes : %i" % stats['decodes'])
278 print("#ROIs : %i" % stats['rois'])
279 print("#Topocells : %i" % stats['topocells'])
280
281
282
283
284
286 sectorName = "B%02i" % sectorID
287 sectorConnector = (sectorID+2) % 4
288
289 mioctID = ( (sectorID+2) % 32 ) / 4
290 mioctSlot = mioctID + 4
291 if sectorID >= 32:
292 mioctID += 8
293 mioctSlot = mioctID + 6
294
295 return mioctID, mioctSlot, sectorConnector, sectorName
296
297
298
300 xmlgeometry = MioctGeometryXMLReader(fn)
301 miocts = []
302 for MIOCT in xmlgeometry.getMIOCTs():
303 sectors = []
304 for sector in MIOCT.Sectors:
305 #if sn.startswith('B'): continue
306 rois = []
307 for roiElem in sector.ROIs:
308 rois += [ ROI.fromROIelement(roiElem) ]
309 sectors += [ Sector(name=sector['name'], connector=int(sector['connector']), rois=rois) ]
310 miocts += [ Mioct( id=int(MIOCT['id']), slot=int(MIOCT['slot']), sectors=sectors) ]
311 decodes = Decodes()
312 for tc in MIOCT.Decode.TopoCells:
313 decodes.addTopoCell( TopoCell.fromTopoCellElement(tc) )
314 miocts[-1].decodes=decodes
315 return MuonGeometry("full geometry 2015", miocts=miocts)
316
317
318
320 mapping = {}
321 for mioct in geometry.miocts:
322 for sector in mioct.sectors:
323 if sector.isBarrel():
324 mapping[sector.id] = dict( [ (roi.roiid, (roi.etacode, roi.phicode)) for roi in sector.rois ] )
325 return mapping
326
327
328
329
330def feetRegionMapping(sectorId, roiId):
331 """ provides the new mapping for the feet and elevator region
332 The numbers can be found in doc/roi_map_R2.pdf
333 """
334
335 etacode = -1
336 phicode = -1
337
338 if sectorId in [23,24,55,56]:
339 # elevator region
340
341 # eta
342 etaMap = { 23 : [ [0,1,2,3,5,7], [4,6,8,9,10,11], range(12,20) + [21,23], [20,22] + range(24,28) ],
343 24 : [ [0,1,2,3,4,6], [5,7,8,9,10,11], range(12,20) + [20,22], [21,23] + range(24,28) ] }
344 etaMap[55] = etaMap[24]
345 etaMap[56] = etaMap[23]
346
347 for ec, tt in enumerate(etaMap[sectorId]):
348 if roiId in tt:
349 etacode = ec
350 break
351
352 # phi
353 if sectorId in [23,55]:
354 if roiId in [2,3,6,7,10,11,18,19,22,23,27]:
355 phicode = 2
356 if roiId in [0,1,4,5,8,9,12,13,14,15,16,17,20,21,25]:
357 phicode = 3
358 elif sectorId in [24,56]:
359 if roiId in [0,1,4,5,8,9,12,13,14,15,16,17,20,21,24]:
360 phicode = 4
361 if roiId in [2,3,6,7,10,11,18,19,22,23,26]:
362 phicode = 5
363
364 if sectorId == 23:
365 if roiId == 27: phicode = 2
366 elif roiId == 25: phicode = 3
367 elif sectorId == 24:
368 if roiId == 24: phicode = 4
369 elif roiId == 26: phicode = 5
370 elif sectorId == 55:
371 if roiId == 24: phicode = 3
372 elif roiId == 26: phicode = 2
373 elif sectorId == 56:
374 if roiId == 27: phicode = 5
375 elif roiId == 25: phicode = 4
376
377
378
379 elif [21,22,25,26,53,54,57,58]:
380 # feet region
381
382 # eta
383 etaMap = { 21 : [ range(0,8), range(8,16) + [17,19], [16,18] + range(20,32) ],
384 22 : [ range(0,8), range(8,16) + [16,18], [17,19] + range(20,32) ] }
385 etaMap[25] = etaMap[21]
386 etaMap[26] = etaMap[22]
387 etaMap[53] = etaMap[22]
388 etaMap[54] = etaMap[21]
389 etaMap[57] = etaMap[53]
390 etaMap[58] = etaMap[54]
391
392 for ec, tt in enumerate(etaMap[sectorId]):
393 if roiId in tt:
394 etacode = ec
395 break
396
397 # phi
398 if sectorId in [21,53]:
399 if roiId in [2,3,6,7,10,11,14,15,18,19,22,23,26,27,30,31]:
400 phicode = 6
401 elif roiId in [0,1,4,5,8,9,12,13,16,17,20,21,24,25,28,29]:
402 phicode = 7
403 elif sectorId in [22,54]:
404 if roiId in [0,1,4,5,8,9,12,13,16,17,20,21,24,25,28,29]:
405 phicode = 0
406 elif roiId in [2,3,6,7,10,11,14,15,18,19,22,23,26,27,30,31]:
407 phicode = 1
408 elif sectorId in [25,57]:
409 if roiId in [2,3,6,7,10,11,14,15,18,19,22,23,26,27,30,31]:
410 phicode = 6
411 elif roiId in [0,1,4,5,8,9,12,13,16,17,20,21,24,25,28,29]:
412 phicode = 7
413 elif sectorId in [26,58]:
414 if roiId in [0,1,4,5,8,9,12,13,16,17,20,21,24,25,28,29]:
415 phicode = 0
416 elif roiId in [2,3,6,7,10,11,14,15,18,19,22,23,26,27,30,31]:
417 phicode = 1
418
419 if etacode == -1:
420 raise RuntimeError("No etacode for SL %i and ROI %i" % (sectorId, roiId))
421 if phicode == -1:
422 raise RuntimeError("No phicode for SL %i and ROI %i" % (sectorId, roiId))
423
424 return (etacode,phicode)
425
426
427
428
429def read2016RPCGeomData(fn, oldMapping):
430
431 rpcGeometry2016 = MuonGeometry("RPC geometry 2016")
432
433 print("read2016RPCGeomData: Reading %s", fn)
434 f = open(fn,"read")
435 for line in f:
436 if line.lstrip().startswith("#"): # remove comments
437 continue
438 ls = line.split()
439 side = int(ls[0])
440 sector = int(ls[1])
441 roiid = int(ls[2])
442 etamin = float(ls[3])
443 etamax = float(ls[4])
444 phimin = float(ls[5])
445 phimax = float(ls[6])
446 if max(phimin, phimax) < 0:
447 phimin += 2 * PI
448 phimax += 2 * PI
449 eta = (etamin + etamax) / 2
450 phi = (phimin + phimax) / 2
451
452 sectorId = sector + 32 * side
453 if sectorId in [21,22,23,24,25,26,53,54,55,56,57,58]:
454 (etacode, phicode) = feetRegionMapping(sectorId, roiid)
455 else:
456 (etacode, phicode) = oldMapping[sectorId][roiid]
457
458 roi = ROI( eta=eta, phi=phi, phimin=phimin, phimax=phimax, etamin=etamin, etamax=etamax, etacode=etacode, phicode=phicode, roiid=roiid )
459
460 # now find MIOCT and Sector where to add the ROI to
461 mioctId, mioctSlot, sectorConnector, sectorName = rpcMioctAndSectorInfo(sectorId)
462 mioct = rpcGeometry2016.getMioct(mioctId)
463 if not mioct:
464 mioct = rpcGeometry2016.addMioct(Mioct(id=mioctId, slot=mioctSlot))
465
466 sector = mioct.getSector(sectorConnector)
467 if not sector:
468 sector = mioct.addSector( Sector(name=sectorName, connector=sectorConnector) )
469
470 sector.addROI( roi )
471
472 return rpcGeometry2016
473
474
475def updateGeometryTGC(newGeometry, oldGeometry):
476 newGeometry.name = "updated full geometry for 2016"
477 for mioct in newGeometry.miocts:
478 oldMioct = oldGeometry.getMioct(mioct.id)
479 for sector in oldMioct.sectors:
480 if sector.isBarrel(): continue
481 mioct.addSector(sector)
482 mioct.fillTopoCells()
483
484
485
486def sectorAsXML_2(sector, depth, stats):
487 attr = ["connector", "name"]
488 s = ' '*depth + "<%s %s>\n" % (sector.tag, " ".join(['%s="%s"' % (a, sector[a]) for a in attr]) )
489 s += ' '*depth + ' <!-- contains %i ROIs -->\n' % len(sector.ROIs)
490 s += ' '*depth + ' <!-- mapping from ROI to coding scheme -->\n'
491 for roi in sorted(sector.ROIs, key=lambda roi: int(roi['roiid'])):
492 s += roiAsXML_2(roi, depth + 4 )
493 s += ' '*depth + "</%s>\n" % sector.tag
494 stats['rois'] += len(sector.ROIs)
495 return s
496
497def roiAsXML_2(roi, depth):
498 attr = ["eta", "phi", "etacode", "phicode", "etamin", "etamax", "phimin", "phimax", "roiid"]
499 s = ' ' * depth + "<ROI %s/>\n" % (" ".join(['%s="%s"' % (a, roi[a]) for a in attr]) )
500 return s
501
502
503def sectorAsXML(xxx_todo_changeme, depth, stats):
504 (connector, name, rois) = xxx_todo_changeme
505 s = ' '*depth + '<Sector connector="%s" name="%s">\n' % (connector, name)
506 s += ' '*depth + ' <!-- contains %i ROIs -->\n' % len(rois)
507 s += ' '*depth + ' <!-- mapping from ROI to coding scheme -->\n'
508 for roi in rois:
509 s += roiAsXML(roi, depth + 4 )
510 s += ' '*depth + "</Sector>\n"
511 stats['rois'] += len(rois)
512 return s
513
514def roiAsXML(roi, depth):
515 roi['eta'] = (float(roi['etamin']) + float(roi['etamax'])) / 2
516 roi['phi'] = (float(roi['phimin']) + float(roi['phimax'])) / 2
517 s = ' ' * depth + '<ROI eta="%f" phi="%f" etacode="0x%s" phicode="0x%s" etamin="%f" etamax="%f" phimin="%f" phimax="%f" roiid="%i"/>\n' % (roi["eta"], roi["phi"], roi["etacode"], roi["phicode"], roi["etamin"], roi["etamax"], roi["phimin"], roi["phimax"], roi["roiid"] )
518 return s
519
520
521
522
523
524
525
526
527
528def main(args):
529
530 # read 2015 geometry xml file
531 muonGeometry2015 = read2015Geometry( args.infile2015 )
532
533 muonGeometry2015.printStats()
534
535 # maps roi number to etacode and phicode
536 rpcCodeMapping = read2015RPCCodeMapping( muonGeometry2015 )
537
538
539 muonGeometry2016 = read2016RPCGeomData( args.infile, rpcCodeMapping )
540 muonGeometry2016.printStats()
541
542
543 updateGeometryTGC(muonGeometry2016, muonGeometry2015)
544 muonGeometry2016.printStats()
545
546 # write out the RPC info and the TGC info
547 muonGeometry2016.writeXML("TestMioctGeometry2016.xml")
548
549
550
551
552if __name__=="__main__":
553
554 parser = argparse.ArgumentParser( description=__doc__,
555 formatter_class = argparse.RawTextHelpFormatter)
556
557 parser.add_argument('-i', dest='infile', default="../data/TestMioctGeometry2016.dat", type=str,
558 help='name of input RPC muon geometry file for 2016 [../data/TestMioctGeometry2016.dat]')
559
560 parser.add_argument('-i1', dest='infile2015', default="../data/TestMioctGeometry.xml", type=str,
561 help='name of input RPC muon geometry file used in 2015 [../data/TestMioctGeometry.xml]')
562
563 args = parser.parse_args()
564
565 sys.exit( main(args) )
void print(char *figname, TCanvas *c1)
#define min(a, b)
Definition cfImp.cxx:40
#define max(a, b)
Definition cfImp.cxx:41
getTopoCellPosition(cls, etacode, phicode, rois)
__init__(self, id, slot, sectors=[])
__init__(self, eta=0, phi=0, phimin=0, phimax=0, etamin=0, etamax=0, etacode=0, phicode=0, roiid=0)
__init__(self, name, connector, rois=[])
__init__(self, etacode, phicode, eta, phi, ieta, iphi, etamin, etamax, phimin, phimax)
T * get(TKey *tobj)
get a TObject* from a TKey* (why can't a TObject be a TKey?)
Definition hcg.cxx:130
int main()
Definition hello.cxx:18
feetRegionMapping(sectorId, roiId)
updateGeometryTGC(newGeometry, oldGeometry)
sectorAsXML_2(sector, depth, stats)
read2016RPCGeomData(fn, oldMapping)
sectorAsXML(xxx_todo_changeme, depth, stats)