File indexing completed on 2025-07-05 08:14:13
0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012 """
0013 Make a 2d slice through a dd4hep detector model.
0014 Present results as a series of TH2F
0015
0016 - density (1/X0), materials
0017
0018 in each bin, we consider the material along two lines,
0019 parallel to the histogram axes and passing through the bin centre.
0020
0021 The G4 geometry is scanned by shooting a geantino along various paths
0022
0023 D. Jeans, KEK. 2025/2/3
0024
0025 for usage instructions:
0026
0027 python3 g4GraphicalScan.py -h
0028
0029 e.g. for a scan at z=1000mm, in the range -10mm < x,y < 10mm, with 100x100 bins:
0030
0031 python3 g4GraphicalScan.py -c myModel.xml -s XY -x -10,10 -y -10,10 -z 1000 -n 100 -o scanOutput.root
0032 """
0033 import os
0034 import sys
0035 import optparse
0036 import subprocess
0037 import ROOT
0038
0039
0040
0041 parser = optparse.OptionParser()
0042 parser.formatter.width = 132
0043 parser.description = '2-dimensional material scan using Geant4.'
0044 parser.add_option('-c', '--compact', dest='compact', default=None,
0045 help='compact xml input file',
0046 metavar='<FILE>')
0047 parser.add_option('-s', '--sliceType',
0048 dest='sliceType', default='ZX',
0049 help='slice plane [XY, ZX, or ZY]',
0050 metavar='<string>')
0051 parser.add_option('-x', '--xRange',
0052 dest='xRange', default='-1000.,1000',
0053 help='range to scan in x [in mm; give tuple "min,max" as string, or just "val" in case of ZY]',
0054 metavar='<tuple>')
0055 parser.add_option('-y', '--yRange',
0056 dest='yRange', default='-1000.,1000',
0057 help='range to scan in y [in mm; give tuple "min,max" as string, or just "val" in case of ZX]',
0058 metavar='<tuple>')
0059 parser.add_option('-z', '--zRange',
0060 dest='zRange', default='-1000.,1000',
0061 help='range to scan in z [in mm; give tuple "min,max" as string, or just "val" in case of XY]',
0062 metavar='<tuple>')
0063 parser.add_option('-n', '--nBins',
0064 dest='nBins', default='100',
0065 help='number of bins in output histograms',
0066 metavar='<int>')
0067 parser.add_option('-o', '--outputFile',
0068 dest='outFile', default='output.root',
0069 help='name of ouput root file',
0070 metavar='<string>')
0071 parser.add_option("-P", "--noPilot",
0072 action="store_true", dest="noPilot", default=False,
0073 help="don't run the pilot job (e.g. if you're sure the geometry is good)")
0074 parser.add_option('-t', '--timeOut',
0075 dest='timeOutValue', default='600',
0076 help='Time-out for a single scan [in seconds]',
0077 metavar='<int>')
0078
0079 (opts, args) = parser.parse_args()
0080
0081
0082
0083 infileName = str(opts.compact)
0084 if not os.path.isfile(infileName):
0085 print('ERROR: cannot find requested input geometry file', infileName, file=sys.stderr)
0086 exit(1)
0087 print(infileName)
0088
0089 sliceType = str(opts.sliceType)
0090 if sliceType != 'XY' and sliceType != 'ZX' and sliceType != 'ZY':
0091 print('ERROR: unknown slice Type', sliceType, '. Choose XY, ZX or ZY.', file=sys.stderr)
0092 exit(1)
0093 print(sliceType)
0094
0095 planePos = -99999.
0096 planeAxis = ''
0097
0098 aa = str(opts.xRange).split(',')
0099 if len(aa) == 2 and sliceType != 'ZY':
0100 xRange = (float(aa[0]), float(aa[1]))
0101 if xRange[1] <= xRange[0]:
0102 print('ERROR, xmin is larger than xmax', file=sys.stderr)
0103 exit(1)
0104 elif len(aa) == 1 and sliceType == 'ZY':
0105 xRange = (float(aa[0]))
0106 planePos = xRange
0107 planeAxis = 'X'
0108 else:
0109 print('ERROR: could not determine xRange, or inconsistent with sliceType', file=sys.stderr)
0110 exit(1)
0111 print('xRange', xRange)
0112
0113 aa = str(opts.yRange).split(',')
0114 if len(aa) == 2 and sliceType != 'ZX':
0115 yRange = (float(aa[0]), float(aa[1]))
0116 if yRange[1] <= yRange[0]:
0117 print('ERROR, ymin is larger than ymax', file=sys.stderr)
0118 exit(1)
0119 elif len(aa) == 1 and sliceType == 'ZX':
0120 yRange = (float(aa[0]))
0121 planePos = yRange
0122 planeAxis = 'Y'
0123 else:
0124 print('ERROR: could not determine yRange, or inconsistent with sliceType', file=sys.stderr)
0125 exit(1)
0126 print('yRange', yRange)
0127
0128 aa = str(opts.zRange).split(',')
0129 if len(aa) == 2 and sliceType != 'XY':
0130 zRange = (float(aa[0]), float(aa[1]))
0131 if zRange[1] <= zRange[0]:
0132 print('ERROR, zmin is larger than zmax', file=sys.stderr)
0133 exit(1)
0134 elif len(aa) == 1 and sliceType == 'XY':
0135 zRange = (float(aa[0]))
0136 planePos = zRange
0137 planeAxis = 'Z'
0138 else:
0139 print('ERROR: could not determine zRange, or inconsistent with sliceType', file=sys.stderr)
0140 exit(1)
0141 print('zRange', zRange)
0142
0143 nBins = int(opts.nBins)
0144 print('nBins', nBins)
0145
0146 if nBins < 1:
0147 print('ERROR: crazy number of bins requested', nBins, file=sys.stderr)
0148 exit(1)
0149
0150 noPilot = bool(opts.noPilot)
0151 print('noPilot', noPilot)
0152
0153 outFileName = str(opts.outFile)
0154
0155
0156
0157 fout = ROOT.TFile(outFileName, 'recreate')
0158
0159 if sliceType == 'XY':
0160 h2 = ROOT.TH2F('hMat', 'h2', nBins, xRange[0], xRange[1], nBins, yRange[0], yRange[1])
0161 h2.GetXaxis().SetTitle('x [mm]')
0162 h2.GetYaxis().SetTitle('y [mm]')
0163 elif sliceType == 'ZX':
0164 h2 = ROOT.TH2F('hMat', 'h2', nBins, zRange[0], zRange[1], nBins, xRange[0], xRange[1])
0165 h2.GetXaxis().SetTitle('z [mm]')
0166 h2.GetYaxis().SetTitle('x [mm]')
0167 elif sliceType == 'ZY':
0168 h2 = ROOT.TH2F('hMat', 'h2', nBins, zRange[0], zRange[1], nBins, yRange[0], yRange[1])
0169 h2.GetXaxis().SetTitle('z [mm]')
0170 h2.GetYaxis().SetTitle('y [mm]')
0171 h2.Fill(0, 0, 0.0)
0172
0173
0174
0175
0176 mats = {}
0177
0178
0179
0180
0181 pilotName = '_pilot_' + outFileName + '.mac'
0182 pilotMac = open(pilotName, 'w')
0183 pilotMac.write('/gun/particle geantino' + '\n')
0184 pilotMac.write('/gun/energy 20 GeV' + '\n')
0185 pilotMac.write('/gun/number 1' + '\n')
0186
0187 steerName = '_' + outFileName + '.mac'
0188 steerMac = open(steerName, 'w')
0189 steerMac.write('/gun/particle geantino' + '\n')
0190 steerMac.write('/gun/energy 20 GeV' + '\n')
0191 steerMac.write('/gun/number 1' + '\n')
0192
0193 requestedStartPositions = {}
0194
0195 for iDir in range(0, 2):
0196 npilot = 0
0197 mats[iDir] = {}
0198 requestedStartPositions[iDir] = []
0199 if iDir == 0:
0200 axis = h2.GetXaxis()
0201 else:
0202 axis = h2.GetYaxis()
0203
0204
0205
0206 if sliceType == 'XY':
0207 if iDir == 0:
0208 dirn = '0 1 0'
0209 else:
0210 dirn = '1 0 0'
0211 elif sliceType == 'ZX':
0212 if iDir == 0:
0213 dirn = '1 0 0'
0214 else:
0215 dirn = '0 0 1'
0216 elif sliceType == 'ZY':
0217 if iDir == 0:
0218 dirn = '0 1 0'
0219 else:
0220 dirn = '0 0 1'
0221 steerMac.write('/gun/direction ' + dirn + '\n')
0222
0223
0224
0225 for iX in range(1, nBins + 1):
0226
0227 mats[iDir][iX] = {}
0228
0229
0230
0231 X = axis.GetBinCenter(iX)
0232 if iDir == 0:
0233 if sliceType == 'XY':
0234 startPos = str(X) + ' '
0235 startPos += str(yRange[0]) + ' '
0236 startPos += str(zRange)
0237 elif sliceType == 'ZX':
0238 startPos = str(xRange[0]) + ' '
0239 startPos += str(yRange) + ' '
0240 startPos += str(X)
0241 elif sliceType == 'ZY':
0242 startPos = str(xRange) + ' '
0243 startPos += str(yRange[0]) + ' '
0244 startPos += str(X)
0245 else:
0246 if sliceType == 'XY':
0247 startPos = str(xRange[0]) + ' '
0248 startPos += str(X) + ' '
0249 startPos += str(zRange)
0250 elif sliceType == 'ZX':
0251 startPos = str(X) + ' '
0252 startPos += str(yRange) + ' '
0253 startPos += str(zRange[0])
0254 elif sliceType == 'ZY':
0255 startPos = str(xRange) + ' '
0256 startPos += str(X) + ' '
0257 startPos += str(zRange[0])
0258
0259 steerMac.write('/gun/position ' + startPos + ' mm \n')
0260 steerMac.write('/run/beamOn' + '\n')
0261 if npilot < 1:
0262 pilotMac.write('/gun/position ' + startPos + ' mm \n')
0263 pilotMac.write('/run/beamOn' + '\n')
0264 npilot += 1
0265 requestedStartPositions[iDir].append(startPos)
0266
0267 steerMac.write('exit')
0268 steerMac.close()
0269
0270 pilotMac.write('exit')
0271 pilotMac.close()
0272
0273
0274
0275
0276 if not noPilot:
0277 cmd = ['ddsim', '--compactFile', infileName, '--runType', 'run', '--enableG4Gun',
0278 '--action.step', 'Geant4MaterialScanner/MaterialScan', '-M', pilotName]
0279 print('running test pilot job...\n')
0280 for cc in cmd:
0281 print(cc, end=' ')
0282 print('\n')
0283 pilotresult = subprocess.run(cmd, capture_output=True, text=True, timeout=int(opts.timeOutValue))
0284 print('done, checking pilot result')
0285 has_Material_scan_between = 0
0286 has_Finished_run = 0
0287 for ll in pilotresult.stdout.splitlines():
0288 if 'Material scan between' in ll:
0289 has_Material_scan_between += 1
0290 if 'Finished run' in ll:
0291 has_Finished_run += 1
0292 if has_Material_scan_between != 2 or has_Finished_run != 2:
0293 print('ERROR, pilot job seems not to have finished successfully')
0294 for ll in pilotresult.stdout.splitlines():
0295 print(ll)
0296 print('ERROR, pilot job seems not to have finished successfully')
0297 print('pilot job seems OK')
0298
0299
0300
0301 cmd = ['ddsim', '--compactFile', infileName, '--runType', 'run', '--enableG4Gun',
0302 '--action.step', 'Geant4MaterialScanner/MaterialScan', '-M', steerName]
0303 print('now running main ddsim job..this may take some time')
0304 result = subprocess.run(cmd, capture_output=True, text=True, timeout=int(opts.timeOutValue))
0305
0306
0307
0308 iscan = 1
0309 inScan = False
0310 iDir = 0
0311 for line in result.stdout.splitlines():
0312 if 'Material scan between' in line:
0313 gg = line.split(')')[0].split('(')[1].split(',')
0314 startx = 10 * float(gg[0])
0315 starty = 10 * float(gg[1])
0316 startz = 10 * float(gg[2])
0317 inScan = True
0318
0319
0320
0321 pp = requestedStartPositions[iDir][iscan - 1].split()
0322 rx = float(pp[0])
0323 ry = float(pp[1])
0324 rz = float(pp[2])
0325 if abs(rx - startx) > 1. or abs(ry - starty) > 1. or abs(rz - startz) > 1.:
0326 print('ERROR inconsistent starting gun position')
0327 print(' REQUESTED:', pp)
0328 print(' USED:', gg)
0329 print('The requested range probably lies partially outside the world volume')
0330 print(' use a more reasonable range and try again!')
0331 exit(1)
0332 elif 'Finished run' in line:
0333 iscan += 1
0334 if iscan == nBins + 1:
0335 iDir = 1
0336 iscan = 1
0337 inScan = False
0338 elif r"+-----------------" in line:
0339 continue
0340 elif r"| \ Material" in line:
0341 continue
0342 elif r"| Num. \ Name" in line:
0343 continue
0344 elif r"| Layer \ " in line:
0345 continue
0346 elif inScan and \
0347 '(' in line and \
0348 len(line.split('(')[0].split()) == 12 and \
0349 line.split()[0] == '|':
0350 index = int(line.split()[1])
0351 material = line.split()[2]
0352 radlen = 10 * float(line.split()[6])
0353 thickness = 10 * float(line.split()[8])
0354 endpos = line.split('(')[1].split(')')[0].split(',')
0355 endx = 10 * float(endpos[0])
0356 endy = 10 * float(endpos[1])
0357 endz = 10 * float(endpos[2])
0358 mats[iDir][iscan][index] = [material, radlen, thickness, endx, endy, endz]
0359
0360
0361
0362 h2.SetTitle('materialScan at ' + planeAxis + '=' + str(planePos) + ' mm : 1/X_{0}')
0363
0364 hists = {}
0365 hists['x0'] = h2
0366
0367 for iDir in range(0, 2):
0368 if iDir == 1:
0369 mainaxis = h2.GetYaxis()
0370 scanaxis = h2.GetXaxis()
0371 elif iDir == 0:
0372 mainaxis = h2.GetXaxis()
0373 scanaxis = h2.GetYaxis()
0374
0375 for jj in range(1, mainaxis.GetNbins() + 1):
0376 mainaxis.GetBinCenter(jj)
0377 scandat = mats[iDir][jj]
0378
0379 hxn = scanaxis.GetNbins()
0380 hxl = scanaxis.GetBinLowEdge(1)
0381 hxh = scanaxis.GetBinUpEdge(hxn)
0382 hxbw = scanaxis.GetBinWidth(1)
0383
0384 curpos = hxl
0385
0386 for value in scandat.values():
0387 begpos = curpos
0388
0389 endx = value[3]
0390 endy = value[4]
0391 endz = value[5]
0392
0393 if sliceType == 'XY':
0394 if iDir == 0:
0395 endpos = endy
0396 elif iDir == 1:
0397 endpos = endx
0398 elif sliceType == 'ZX':
0399 if iDir == 0:
0400 endpos = endx
0401 elif iDir == 1:
0402 endpos = endz
0403 elif sliceType == 'ZY':
0404 if iDir == 0:
0405 endpos = endy
0406 elif iDir == 1:
0407 endpos = endz
0408
0409 radlen = value[1]
0410 thick = value[2]
0411 matStr = value[0]
0412
0413 if begpos < hxl and endpos < hxl:
0414 pass
0415 elif begpos > hxh and endpos > hxh:
0416 pass
0417 else:
0418 if matStr not in hists.keys():
0419 hists[matStr] = h2.Clone(h2.GetName() + '_' + matStr)
0420 hists[matStr].SetTitle(hists[matStr].GetTitle().replace('1/X_{0}', matStr))
0421 hists[matStr].Reset()
0422 hists[matStr].Fill(0, 0, 0.0)
0423
0424 iy1 = scanaxis.FindBin(begpos)
0425 iy2 = scanaxis.FindBin(endpos)
0426
0427 if iy1 == iy2:
0428 if iDir == 1:
0429 hists['x0'] .AddBinContent(iy1, jj, thick / radlen / hxbw)
0430 hists[matStr].AddBinContent(iy1, jj, thick / hxbw)
0431 else:
0432 hists['x0'] .AddBinContent(jj, iy1, thick / radlen / hxbw)
0433 hists[matStr].AddBinContent(jj, iy1, thick / hxbw)
0434 else:
0435 firstBinStubLength = scanaxis.GetBinUpEdge(iy1) - begpos
0436 lastBinStubLength = endpos - scanaxis.GetBinLowEdge(iy2)
0437 if iDir == 1:
0438 hists['x0'].AddBinContent(iy1, jj, firstBinStubLength / radlen / hxbw)
0439 hists['x0'].AddBinContent(iy2, jj, lastBinStubLength / radlen / hxbw)
0440 hists[matStr].AddBinContent(iy1, jj, firstBinStubLength / hxbw)
0441 hists[matStr].AddBinContent(iy2, jj, lastBinStubLength / hxbw)
0442 else:
0443 hists['x0'].AddBinContent(jj, iy1, firstBinStubLength / radlen / hxbw)
0444 hists['x0'].AddBinContent(jj, iy2, lastBinStubLength / radlen / hxbw)
0445 hists[matStr].AddBinContent(jj, iy1, firstBinStubLength / hxbw)
0446 hists[matStr].AddBinContent(jj, iy2, lastBinStubLength / hxbw)
0447
0448 if iy2 - iy1 > 1:
0449 for i in range(iy1 + 1, iy2):
0450 if iDir == 1:
0451 hists['x0'] .AddBinContent(i, jj, 1. / radlen)
0452 hists[matStr].AddBinContent(i, jj, 1.)
0453 else:
0454 hists['x0'] .AddBinContent(jj, i, 1. / radlen)
0455 hists[matStr].AddBinContent(jj, i, 1.)
0456 curpos = endpos
0457
0458 for mm in hists.keys():
0459 hists[mm].Scale(1. / 2)
0460
0461 print('done filling histograms, now closing root file')
0462 fout.Write()
0463 fout.Close()
0464
0465
0466 os.remove(steerName)
0467 os.remove(pilotName)