File indexing completed on 2025-10-30 07:57:41
0001 
0002 
0003 
0004 
0005 
0006 
0007 
0008 import sys, getopt, os, re, importlib
0009 import pprint
0010 import subprocess, shlex
0011 import math
0012 import numpy as np
0013 
0014 
0015 def theta_to_eta(th):
0016     return -math.log(math.tan(0.5 * th))
0017 def eta_to_theta(et):
0018     return 2 * math.atan(math.exp(-et))
0019 
0020 
0021 
0022 acceptanceDict = {
0023     'drich': {
0024         
0025         'thetaMin': math.degrees(eta_to_theta(3.5)),
0026         'thetaMax': math.degrees(eta_to_theta(1.6)),
0027         
0028         'thetaIdeal': math.degrees(eta_to_theta(2.0)),
0029     },
0030     'pfrich': {
0031         
0032         'thetaMin': 180.0 - 10.0, 
0033         'thetaMax': 180.0 - 70.0, 
0034         
0035         'thetaIdeal': math.degrees(eta_to_theta(-2.0)), 
0036     },
0037 }
0038 momMax = {
0039   'aerogel': 20,
0040   'gas':     60,
0041 }
0042 
0043 
0044 
0045 
0046 
0047 inputFileName = ''
0048 testNum = -1
0049 standalone = False
0050 compactFileCustom = ''
0051 zDirection = 1
0052 particle_name = 'pi+'
0053 particle_momentum = 20.0 
0054 particle_theta = acceptanceDict['drich']['thetaIdeal'] 
0055 particle_eta = 10001
0056 runType = 'run'
0057 numEvents = 50
0058 numTestSamples = 0
0059 restrict_sector = True
0060 outputImageType = ''
0061 outputFileName = ''
0062 
0063 
0064 helpStr = f'''
0065 {sys.argv[0]} <INPUT_FILE or TEST_NUM> [OPTIONS]
0066 
0067 <REQUIRED ARGUMENTS>: provide either an INPUT_FILE or a TEST_NUM
0068 
0069     INPUT_FILE: -i <input file>: specify an input file, e.g., hepmc
0070 
0071     TEST_NUM:  -t <testnum>: specify which test to run
0072             >> acceptance tests:
0073 
0074                fixed angle:
0075                 1:  aim pions at center of aerogel sector
0076                 2:  inner edge test
0077                 3:  outer edge test
0078 
0079                acceptance scans: (-k = number of polar steps)
0080                                  (-n = number of particles per step)
0081                 4:  polar scan test, fixed azimuth
0082                 5:  azimuthal + polar scan test
0083                 6:  eta scan, varied azimuth for each eta value
0084 
0085                momentum scans: (-k = number of momentum steps)
0086                                (-n = number of particles per step)
0087                 7:  momentum scan, for aerogel, fixed (theta,phi)
0088                 8:  momentum scan, for gas,     fixed (theta,phi)
0089                 9:  momentum scan, for aerogel, fixed theta, varied phi
0090                 10: momentum scan, for gas,     fixed theta, varied phi
0091 
0092             >> optics tests: use these to test the dRICH optics;
0093                make sure to apply the recommended settings in drich.xml
0094                 100:   focal point, in RICH acceptance
0095                         set DRICH_debug_optics  = 1
0096                             DRICH_debug_mirror  = 0
0097                             DRICH_debug_sensors = 1
0098                 101:   focal point, broad range test
0099                         set DRICH_debug_optics  = 1
0100                             DRICH_debug_mirror  = 1
0101                             DRICH_debug_sensors = 1
0102                 102:   parallel-to-point focal test
0103                         set DRICH_debug_optics  = 1
0104                             DRICH_debug_mirror  = 0
0105                             DRICH_debug_sensors = 0
0106                 103:   evenly distributed sensor hits test
0107                         set DRICH_debug_optics  = 3
0108                             DRICH_debug_mirror  = 0
0109                             DRICH_debug_sensors = 0
0110                 104:   parallel-to-point focal test, beams over entire acceptance
0111                         set DRICH_debug_optics  = 4
0112                             DRICH_debug_mirror  = 0
0113                             DRICH_debug_sensors = 0
0114 
0115 [OPTIONAL ARGUMENTS]
0116 
0117     OPTIONS:    -d: direction to throw particles (may not be used by all tests)
0118                     1 = toward dRICH (default)
0119                    -1 = toward pfRICH
0120                 -s: enable standalone RICH-only simulation (default is full detector)
0121                 -c [compact file]: specify a custom compact file
0122                    (this will override -d and -s options)
0123                 -p [particle]: name of particle to throw; default: {particle_name}
0124                    examples:
0125                     - e- / e+
0126                     - pi+ / pi-
0127                     - kaon+ / kaon-
0128                     - proton / anti_proton
0129                     - opticalphoton
0130                 -m [momentum]: momentum (GeV) for mono-energetic runs (default={particle_momentum})
0131                 -a [angle]: fixed polar angle for certain tests [deg] (default={particle_theta})
0132                 -b [pseudorapidity]: fixed pseudorapidity for certain tests (default={theta_to_eta(math.radians(particle_theta))})
0133                      note: [pseudorapidity] will override [angle]
0134                 -n [numEvents]: number of events to process (default={numEvents})
0135                    - if using TEST_NUM, this is usually the number of events PER fixed momentum
0136                    - if using INPUT_FILE, you can set to 0 to run ALL events in the file, otherwise
0137                      it will run the default amount of {numEvents}
0138                 -k [numTestSamples]: some tests throw particles in multiple different directions,
0139                    such as "polar scan test"; for this test, use [numTestSamples] to control
0140                    how many directions are tested
0141                    - many tests offer a similar usage of [numTestSamples]
0142                    - these tests also have default [numTestSamples] values
0143                 -l: allow azimuthal scans to cover the full 2*pi range, rather than restricting
0144                     to a single sector
0145                 -r: run, instead of visualize (default)
0146                 -v: visualize, instead of run
0147                    - it is HIGHLY recommended to set `DRICH_debug_sector` to `1` in `drich.xml`,
0148                      which will draw one sector and set visibility such that you can see inside
0149                      the dRICH
0150                    - standalone mode will be automatically enabled
0151                 -e [output image extension]: save visual with specified type (svg,pdf,ps)
0152                    - useful tip: if you want to suppress the drawing of the visual, but
0153                      still save an output image, use Xvbf (start EIC container shell
0154                      as `xvfb-run eic-shell`); this is good for batch processing
0155                 -o [output file]: output root file name (overrides any default name)
0156     '''
0157 
0158 if (len(sys.argv) <= 1):
0159     print(helpStr)
0160     sys.exit(2)
0161 try:
0162     opts, args = getopt.getopt(sys.argv[1:], 'i:t:d:sc:p:m:a:b:n:k:lrve:o:')
0163 except getopt.GetoptError:
0164     print('\n\nERROR: invalid argument\n', helpStr, file=sys.stderr)
0165     sys.exit(2)
0166 for opt, arg in opts:
0167     if (opt == '-i'): inputFileName = arg.lstrip()
0168     if (opt == '-t'): testNum = int(arg)
0169     if (opt == '-d'): zDirection = int(arg)
0170     if (opt == '-s'): standalone = True
0171     if (opt == '-c'): compactFileCustom = arg.lstrip()
0172     if (opt == '-p'): particle_name = arg.lstrip()
0173     if (opt == '-m'): particle_momentum = float(arg)
0174     if (opt == '-a'): particle_theta = float(arg)
0175     if (opt == '-b'): particle_eta = float(arg)
0176     if (opt == '-n'): numEvents = int(arg)
0177     if (opt == '-k'): numTestSamples = int(arg)
0178     if (opt == '-l'): restrict_sector = False
0179     if (opt == '-r'): runType = 'run'
0180     if (opt == '-v'): runType = 'vis'
0181     if (opt == '-e'): outputImageType = arg.lstrip()
0182     if (opt == '-o'): outputFileName = arg.lstrip()
0183 if (testNum < 0 and inputFileName == ''):
0184     print('\n\nERROR: Please specify either an input file (`-i`) or a test number (`-t`).\n', helpStr, file=sys.stderr)
0185     sys.exit(2)
0186 elif (testNum > 0 and inputFileName != ''):
0187     print('\n\nWARNING: You specified both an input file and a test number; proceeding with the input file only.\n', file=sys.stderr)
0188     testNum = -1
0189 
0190 
0191 if (testNum >= 100):
0192     print("optics test, overriding some settings...")
0193     particle_name = 'opticalphoton'
0194     standalone = True
0195     if (testNum in [100,101,102]):
0196         print("-- this is a visual test --")
0197         runType = 'vis'
0198 if (particle_name == "opticalphoton"):
0199     particle_momentum = 3e-9
0200     print(f'optical photons test: using energy {particle_momentum}')
0201 if runType == 'vis':
0202     standalone = True
0203 
0204 
0205 if particle_eta < 10000:
0206     particle_theta = math.degrees(eta_to_theta(particle_eta))
0207 else:
0208     particle_eta = theta_to_eta(math.radians(particle_theta))
0209 
0210 
0211 
0212 workDir = os.getcwd()
0213 
0214 if inputFileName != '':
0215     if not bool(re.search('^/', inputFileName)): inputFileName = workDir + "/" + inputFileName
0216 
0217 if outputFileName == '':
0218     outputFileName = workDir + "/out/sim.edm4hep.root"  
0219 elif not bool(re.search('^/', outputFileName)):
0220     outputFileName = workDir + "/" + outputFileName  
0221 
0222 outputName = re.sub('\.root$', '', outputFileName)
0223 outputName = re.sub('^.*/', '', outputName)
0224 
0225 
0226 zDirection /= abs(zDirection)
0227 if (zDirection < 0):
0228     xrich = 'pfrich'
0229     XRICH = 'PFRICH'
0230     xRICH = 'pfRICH'
0231 else:
0232     xrich = 'drich'
0233     XRICH = 'DRICH'
0234     xRICH = 'dRICH'
0235 
0236 
0237 
0238 detMain = os.environ['DETECTOR_CONFIG']
0239 detPath = os.environ['DETECTOR_PATH']
0240 outDir  = os.environ['DRICH_DEV'] + '/out'
0241 
0242 
0243 compactFileFull = detPath + '/' + detMain + '.xml'
0244 compactFileRICH = detPath + '/' + detMain + '_' + xrich + '_only.xml'
0245 compactFile = compactFileRICH if standalone else compactFileFull
0246 if compactFileCustom != '':
0247     if not bool(re.search('^/', compactFileCustom)):
0248         compactFileCustom = workDir + "/" + compactFileCustom  
0249     compactFile = compactFileCustom
0250 
0251 
0252 sep = '-' * 40
0253 print(f'''
0254 {sep}
0255 ** simulation args **
0256 inputFileName  = {inputFileName}
0257 testNum        = {testNum}
0258 particle       = {particle_name}
0259 particle_theta = {particle_theta} deg
0260 particle_eta   = {particle_eta}
0261 numEvents      = {numEvents}
0262 numTestSamples = {numTestSamples}
0263 runType        = {runType}
0264 direction      = toward {xRICH}
0265 outputFileName = {outputFileName}
0266 outputName     = {outputName}
0267 compactFile    = {compactFile}
0268 {sep}
0269 ''')
0270 
0271 
0272 
0273 
0274 
0275 m = open(workDir + "/macro/macro_" + outputName + ".mac", 'w+')
0276 
0277 
0278 m.write(f'/control/verbose 2\n')
0279 m.write(f'/run/initialize\n')
0280 
0281 
0282 
0283 if (runType == 'vis'):
0284     m.write(f'/vis/open OGL 800x800-0+0\n')  
0285     m.write(f'/vis/scene/create\n')
0286     m.write(f'/vis/scene/add/volume\n')
0287     m.write(f'/vis/scene/add/axes 0 0 0 1 m\n')
0288     m.write(f'/vis/scene/add/trajectories smooth\n')
0289     m.write(f'/vis/scene/add/hits\n')
0290     m.write(f'/vis/sceneHandler/attach\n')
0291     
0292     
0293     m.write(f'/vis/viewer/set/viewpointThetaPhi -90 -89\n')  
0294     
0295     
0296     m.write(f'/vis/viewer/set/style wireframe\n')
0297     m.write(f'/vis/modeling/trajectories/create/drawByCharge\n')
0298     m.write(f'/vis/modeling/trajectories/drawByCharge-0/setRGBA 0 0.8 0 0 1\n')
0299     m.write(f'/vis/modeling/trajectories/drawByCharge-0/setRGBA 1 0 0.5 0.5 1\n')
0300 
0301 
0302 m.write(f'/gps/verbose 2\n')
0303 m.write(f'/gps/particle {particle_name}\n')
0304 m.write(f'/gps/number 1\n')
0305 
0306 
0307 def momentum_to_kinetic_energy(p, part):
0308     
0309     mass = 0.0
0310     if bool(re.search('^e[+-]$',part)):
0311         mass = 0.000510999
0312     elif bool(re.search('^pi[+-]$',part)):
0313         mass = 0.13957
0314     elif bool(re.search('^kaon[+-]$',part)):
0315         mass = 0.493677
0316     elif bool(re.search('proton$',part)):
0317         mass = 0.938272
0318     elif (part == "opticalphoton"):
0319         mass = 0.0
0320     else:
0321         print(f'WARNING: mass for particle "{part}" needs to be added to simulate.py; assuming momentum==energy for now', file=sys.stderr)
0322     
0323     en = math.sqrt( math.pow(p,2) + math.pow(mass,2) )
0324     kin_en = en - mass 
0325     print(f'Momentum {p} GeV converted to Kinetic Energy {kin_en} GeV')
0326     return kin_en
0327 fixed_energy = momentum_to_kinetic_energy(particle_momentum, particle_name)
0328 
0329 
0330 m.write(f'/gps/position 0 0 0 cm\n')
0331 
0332 
0333 
0334 
0335 
0336 thetaMin = math.radians(acceptanceDict[xrich]['thetaMin'])
0337 thetaMax = math.radians(acceptanceDict[xrich]['thetaMax'])
0338 etaMin = theta_to_eta(thetaMax)
0339 etaMax = theta_to_eta(thetaMin)
0340 print(sep)
0341 print('** acceptance limits **')
0342 print(f'thetaMin = {math.degrees(thetaMin)} deg')
0343 print(f'thetaMax = {math.degrees(thetaMax)} deg')
0344 print(f'etaMin = {etaMin}')
0345 print(f'etaMax = {etaMax}')
0346 print(sep)
0347 
0348 evnum = 0 
0349 
0350 
0351 
0352 
0353 
0354 
0355 if testNum == 1:
0356     m.write(f'\n# aim at +x {xRICH} sector\n')
0357     x = math.sin(math.radians(particle_theta))
0358     y = 0.0
0359     z = math.cos(math.radians(particle_theta)) * zDirection
0360     m.write(f'/gps/direction {x} {y} {z}\n')
0361     m.write(f'/gps/ene/mono {fixed_energy} GeV\n')
0362     m.write(f'/run/beamOn {numEvents}\n')
0363 
0364 elif testNum == 2:
0365     m.write(f'\n# inner edge of acceptance\n')
0366     x = math.sin(thetaMin)
0367     y = 0.0
0368     z = math.cos(thetaMin) * zDirection
0369     m.write(f'/gps/direction {x} {y} {z}\n')
0370     m.write(f'/gps/ene/mono {fixed_energy} GeV\n')
0371     m.write(f'/run/beamOn {numEvents}\n')
0372 
0373 elif testNum == 3:
0374     m.write(f'\n# outer edge of acceptance\n')
0375     x = math.sin(thetaMax)
0376     y = 0.0
0377     z = math.cos(thetaMax) * zDirection
0378     m.write(f'/gps/direction {x} {y} {z}\n')
0379     m.write(f'/gps/ene/mono {fixed_energy} GeV\n')
0380     m.write(f'/run/beamOn {numEvents}\n')
0381 
0382 elif testNum == 4:
0383     m.write(f'\n# polar scan test\n')
0384     numTheta = 4 if numTestSamples==0 else numTestSamples  
0385     if (runType == "vis"):
0386         m.write(f'/vis/scene/endOfEventAction accumulate\n')
0387         m.write(f'/vis/scene/endOfRunAction accumulate\n')
0388     for theta in list(np.linspace(thetaMin, thetaMax, numTheta)):
0389         x = math.sin(theta)
0390         y = 0.0
0391         z = math.cos(theta) * zDirection
0392         m.write(f'/gps/direction {x} {y} {z}\n')
0393         m.write(f'/gps/ene/mono {fixed_energy} GeV\n')
0394         m.write(f'/run/beamOn {numEvents}\n')
0395         for _ in range(numEvents):
0396             print(f'evnum = {evnum}   theta = {math.degrees(theta)} deg   eta = {theta_to_eta(theta)}')
0397             evnum += 1
0398 
0399 elif testNum == 5:
0400     m.write(f'\n# polar+azimuthal scan test\n')
0401     numTheta = 4 if numTestSamples==0 else numTestSamples 
0402     numPhi = 24  
0403     if (runType == "vis"):
0404         m.write(f'/vis/scene/endOfEventAction accumulate\n')
0405         m.write(f'/vis/scene/endOfRunAction accumulate\n')
0406     print(f'SET theta range to {math.degrees(thetaMin)} to {math.degrees(thetaMax)} deg')
0407     for theta in list(np.linspace(thetaMin, thetaMax, numTheta)):
0408         for phi in list(np.linspace(0, 2 * math.pi, numPhi, endpoint=False)):
0409             if restrict_sector and (math.pi / 6 < phi < (2 * math.pi - math.pi / 6)): continue  
0410             if (abs(phi) > 0.001 and abs(theta - thetaMin) < 0.001): continue  
0411             x = math.sin(theta) * math.cos(phi)
0412             y = math.sin(theta) * math.sin(phi)
0413             z = math.cos(theta) * zDirection
0414             m.write(f'/gps/direction {x} {y} {z}\n')
0415             m.write(f'/gps/ene/mono {fixed_energy} GeV\n')
0416             m.write(f'/run/beamOn {numEvents}\n')
0417 
0418 elif testNum == 6:
0419     m.write(f'\n# eta scan, with varied azimuth for each eta bin\n')
0420     numEtaPoints = 3 if numTestSamples==0 else numTestSamples 
0421     for eta in list(np.linspace(etaMin, etaMax, numEtaPoints)):
0422         m.write(f'\n# eta={eta}  theta={math.degrees(eta_to_theta(eta))} deg\n')
0423         for phi in list(2 * np.pi * np.random.random_sample(size=numEvents) - np.pi): 
0424             x = math.sin(eta_to_theta(eta)) * math.cos(phi)
0425             y = math.sin(eta_to_theta(eta)) * math.sin(phi)
0426             z = math.cos(eta_to_theta(eta)) * zDirection
0427             m.write(f'/gps/direction {x} {y} {z}\n')
0428             m.write(f'/gps/ene/mono {fixed_energy} GeV\n')
0429             m.write(f'/run/beamOn 1\n')
0430 
0431 elif testNum in [7,8]:
0432     rad = 'aerogel' if testNum==7 else 'gas'
0433     m.write(f'\n# momentum scan for {rad}, fixed theta, fixed phi\n')
0434     numMomPoints = 10 if numTestSamples==0 else numTestSamples 
0435     x = math.sin(math.radians(particle_theta))
0436     y = 0.0
0437     z = math.cos(math.radians(particle_theta)) * zDirection
0438     m.write(f'/gps/direction {x} {y} {z}\n')
0439     for mom in list(np.linspace(1, momMax[rad], numMomPoints)):
0440         en = momentum_to_kinetic_energy(mom, particle_name)
0441         m.write(f'/gps/ene/mono {en} GeV\n')
0442         m.write(f'/run/beamOn {numEvents}\n')
0443 
0444 elif testNum in [9,10]:
0445     rad = 'aerogel' if testNum==9 else 'gas'
0446     m.write(f'\n# momentum scan for {rad}, fixed theta, varied phi\n')
0447     numMomPoints = 10 if numTestSamples==0 else numTestSamples 
0448     for mom in list(np.linspace(1, momMax[rad], numMomPoints)):
0449         en = momentum_to_kinetic_energy(mom, particle_name)
0450         for phi in list(2 * np.pi * np.random.random_sample(size=numEvents) - np.pi): 
0451             x = math.sin(math.radians(particle_theta)) * math.cos(phi)
0452             y = math.sin(math.radians(particle_theta)) * math.sin(phi)
0453             z = math.cos(math.radians(particle_theta)) * zDirection
0454             m.write(f'/gps/direction {x} {y} {z}\n')
0455             m.write(f'/gps/ene/mono {en} GeV\n')
0456             m.write(f'/run/beamOn 1\n')
0457 
0458 elif testNum == 100:
0459     m.write(f'\n# opticalphoton scan test, {xRICH} range\n')
0460     m.write(f'/vis/scene/endOfEventAction accumulate\n')
0461     m.write(f'/gps/pos/type Point\n')
0462     m.write(f'/gps/pos/radius 0.1 mm\n')
0463     m.write(f'/gps/ang/type iso\n')
0464     m.write(f'/gps/ang/mintheta {math.pi - thetaMax} rad\n')
0465     m.write(f'/gps/ang/maxtheta {math.pi - thetaMin} rad\n')
0466     m.write(f'/gps/ang/minphi {math.pi} rad\n')
0467     m.write(f'/gps/ang/maxphi {math.pi + 0.01} rad\n')
0468     m.write(f'/gps/ene/mono {fixed_energy} GeV\n')
0469     m.write(f'/run/beamOn {numEvents}\n')
0470 
0471 elif testNum == 101:
0472     m.write(f'\n# opticalphoton scan test, broad range\n')
0473     m.write(f'/vis/scene/endOfEventAction accumulate\n')
0474     m.write(f'/gps/pos/type Point\n')
0475     m.write(f'/gps/pos/radius 0.1 mm\n')
0476     m.write(f'/gps/ang/type iso\n')
0477     m.write(f'/gps/ang/mintheta {math.pi / 2} rad\n')
0478     m.write(f'/gps/ang/maxtheta {math.pi - thetaMin} rad\n')
0479     m.write(f'/gps/ang/minphi {math.pi} rad\n')
0480     m.write(f'/gps/ang/maxphi {math.pi + 0.01} rad\n')
0481     m.write(f'/gps/ene/mono {fixed_energy} GeV\n')
0482     m.write(f'/run/beamOn {numEvents}\n')
0483 
0484 elif testNum == 102:
0485     numTheta = 5 if numTestSamples==0 else numTestSamples 
0486     m.write(f'\n# opticalphoton parallel-to-point focusing\n')
0487     m.write(f'/vis/scene/endOfEventAction accumulate\n')
0488     m.write(f'/vis/scene/endOfRunAction accumulate\n')
0489     m.write(f'/gps/pos/type Beam\n')
0490     m.write(f'/gps/ang/type beam1d\n')
0491     for theta in list(np.linspace(thetaMin, thetaMax, numTheta)):
0492         x = math.sin(theta)
0493         y = 0.0
0494         z = math.cos(theta) * zDirection
0495         m.write(f'/gps/ang/rot1 -{z} {y} {x}\n') 
0496         m.write(f'/gps/pos/rot1 -{z} {y} {x}\n')
0497         m.write(f'/gps/pos/halfx 16 cm\n')  
0498         m.write(f'/gps/ene/mono {fixed_energy} GeV\n')
0499         m.write(f'/run/beamOn {numEvents}\n')
0500 
0501 elif testNum == 103:
0502     m.write(f'\n# evenly distributed sensor hits test\n')
0503     if runType == "vis":
0504         m.write(f'/vis/scene/endOfEventAction accumulate\n')
0505         m.write(f'/vis/scene/endOfRunAction accumulate\n')
0506 
0507     from scripts import createAngles
0508     num_rings = 120 if numTestSamples==0 else numTestSamples  
0509     hit_density = 80  
0510     angles = createAngles.makeAngles(thetaMin, thetaMax, num_rings, hit_density)  
0511 
0512     print(f'SET theta range to {math.degrees(thetaMin)} to {math.degrees(thetaMax)} deg')
0513     for angle in angles:
0514         theta, phi = angle[0], angle[1]
0515         if restrict_sector and (math.pi / 6 < phi < (2 * math.pi - math.pi / 6)): continue  
0516         if abs(phi) > 0.001 and abs(theta - thetaMin) < 0.001: continue  
0517         x = math.sin(theta) * math.cos(phi)
0518         y = math.sin(theta) * math.sin(phi)
0519         z = math.cos(theta) * zDirection
0520         m.write(f'/gps/direction {x} {y} {z}\n')
0521         m.write(f'/gps/ene/mono {fixed_energy} GeV\n')
0522         m.write(f'/run/beamOn {numEvents}\n')
0523         
0524 elif testNum == 104:
0525     m.write(f'\n# opticalphoton parallel-to-point focusing, full coverage\n')
0526     
0527     
0528     m.write(f'/gps/pos/type Beam\n')
0529     m.write(f'/gps/ang/type beam1d\n')
0530     def makeBasicAngles(theta_min, theta_max, num_theta, num_phi):
0531         angles = []
0532         thetas = np.linspace(theta_min, theta_max, num=num_theta)
0533         phis = np.linspace(-math.pi/6, math.pi/6, num=num_phi)
0534         for i in range(num_phi):
0535             if phis[i] < 0:
0536                 phis[i] = phis[i]+2*np.pi
0537         for i in thetas:
0538             for j in phis:
0539                 angles.append(tuple((i,j)))
0540         return angles
0541     angles = makeBasicAngles(thetaMin, thetaMax, 50, 50)
0542 
0543     for angle in angles:
0544         theta, phi = angle[0], angle[1]
0545         if math.pi / 6 < phi < (2 * math.pi - math.pi / 6): continue  
0546         x = math.sin(theta) * math.cos(phi)
0547         y = math.sin(theta) * math.sin(phi)
0548         z = math.cos(theta) * zDirection
0549         m.write(f'/gps/direction {x} {y} {z} \n')
0550         m.write(f'/gps/pos/halfx 16 cm\n')  
0551         m.write(f'/gps/ene/mono {fixed_energy} GeV\n')
0552         m.write(f'/run/beamOn {numEvents}\n')
0553 
0554 elif testNum > 0:
0555     print("ERROR: unknown test number\n", file=sys.stderr)
0556     m.close()
0557     sys.exit(2)
0558 
0559 
0560 if (runType == "vis"):
0561     m.write(f'/vis/viewer/flush\n')
0562     m.write(f'/vis/viewer/refresh\n')
0563     if outputImageType!='':
0564         m.write(f'/vis/ogl/export {re.sub("root$",outputImageType,outputFileName)}\n')
0565 
0566 
0567 m.seek(0, 0)
0568 if (testNum > 0):
0569     print(m.read())
0570 m.close()
0571 
0572 
0573 
0574 
0575 
0576 cmd = [
0577         f'npsim',
0578         
0579         f'--runType {runType}',
0580         f'--compactFile {compactFile}',
0581         f'--outputFile {outputFileName}',
0582         "--part.userParticleHandler=''", 
0583         
0584         
0585         ]
0586 if (testNum > 0):
0587     cmd.extend([
0588         f'--macro {m.name}',
0589         '--enableG4GPS',
0590         ])
0591 else:
0592     cmd.extend([
0593         f'--inputFiles \'{inputFileName}\'',
0594         ])
0595     if (numEvents > 0):
0596         cmd.extend([ f'-N {numEvents}' ])
0597     else:
0598         cmd.extend([ f'-N -1' ])
0599 
0600 
0601 cmdShell = shlex.split(" ".join(cmd))
0602 print(f'{sep}\nRUN SIMULATION:\n{shlex.join(cmdShell)}\n{sep}')
0603 subprocess.run(cmdShell, cwd=detPath)
0604 
0605 
0606 
0607 print("\nPRODUCED SIMULATION OUTPUT FILE: " + outputFileName)