-
Notifications
You must be signed in to change notification settings - Fork 0
/
ebitsim.py
executable file
·396 lines (316 loc) · 19.9 KB
/
ebitsim.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
#!/usr/bin/env python
# Jon Ringuette - 03/2019
# I know in python one is supposed to use snake_case instead of camelCase but I kind of liked camelCase for this
# program better and I stuck with it throughout... If someone is disturbed by this i'm happy to change it.
# other than that I have kept everything PEP8 complient for the most part except line length because we
# all have widescreen monitors these days..
import argparse
import configparser
import csv
import ebitChargeDistribution
from ebitsim_docs import *
from ebitChargeDistribution import probeFnAddPop
from geant4MacroOutput import geant4MacroOutput
from commonUtils import getElementAbv, column
__ECHG__ = 1.6e-19 # "Electron charge"
__TORR__ = 3.537e16 # "1 torr in cm-3"
import platform
import sys
import os
import time
# I suspect this class will grow with some additional options as they become needed
class OutputFormating:
def __init__(self, outputFileName='output.png',
outputType='matplotlib',
logOutput=0,
stepPlot = 0,
badGuessPlot = 0,
xmin=0,
xmax=0,
ymin=0,
ymax=0,
logx=0,
eventsPerTimeSlice=0,
subDivisionOfTime=0):
self.outputFileName = outputFileName
self.outputType = outputType
self.logOutput = logOutput
self.stepPlot = stepPlot
self.badGuessPlot = badGuessPlot
# matPlotLib stuff
self.xmin = xmin
self.xmax = xmax
self.ymin = ymin
self.ymax = ymax
self.logx = logx
#geant4MacroOutput stuff
self.eventsPerTimeSlice = eventsPerTimeSlice
self.subDivisionOfTime = subDivisionOfTime
def plotSpeciesResults(species, ebitParams, outputConfig):
# Plot all species and charge state populations vs. time via matplotlib
import matplotlib
matplotlib.use("TKAgg") # This is done as otherwise it goes wierd with a MacOS
import matplotlib.pyplot as plt
from cycler import cycler
# Should figure out how to put tick marks on the top and right side of graphs, they're useful!
plt.rcParams['legend.loc'] = 'best'
plt.figure()
for myspecies in species:
for chargeStateResults in range(0, len(myspecies.results)):
mylabel = getElementAbv(myspecies.Z) + str(myspecies.chargeStates[chargeStateResults]) + '+'
default_cycler = (cycler(linestyle=['--', '-', ':', '-.']) * cycler(color=['b', 'k', 'g', 'y', 'c', 'm', 'r']))
plt.rc('lines', linewidth=2)
plt.rc('grid', color='k', linestyle=':', linewidth=0.5)
plt.rc('axes', grid=True, prop_cycle=default_cycler)
plt.plot(column(myspecies.results[chargeStateResults], 0), column(myspecies.results[chargeStateResults], 1), label=mylabel)
if outputConfig.xmin or outputConfig.xmax != 0:
plt.xlim(outputConfig.xmin, outputConfig.xmax)
if outputConfig.ymin or outputConfig.ymax != 0:
plt.ylim(outputConfig.ymin, outputConfig.ymax)
if outputConfig.logx == 1:
plt.xscale('log')
plt.ylabel('Population')
# plt.xlabel(r'$log(J\tau)$'))
plt.xlabel('Breeding time (s)')
beamEnergies = ''
for ebitIndex in range(0, len(ebitParams)):
if ebitIndex != 0:
beamEnergies = beamEnergies + '->'
beamEnergies = beamEnergies + str(ebitParams[ebitIndex].beamEnergy)
plt.title("$I_e$ = %.2fA, $V_e$ = (%s)eV, $r_e$ = %.2Ecm, $P_H$ = %.2ET" % (ebitParams[0].beamCurrent, beamEnergies, ebitParams[0].beamRadius, ebitParams[0].pressure))
plt.legend(framealpha=0.5)
plt.savefig(outputConfig.outputFileName, dpi=300)
return
def plotSpeciesEnergies(species, ebitParams, outputConfig):
import matplotlib
# matplotlib.use("TKAgg")
import matplotlib.pyplot as plt
from cycler import cycler
plt.rcParams['legend.loc'] = 'best'
plt.figure()
for mySpecies in species:
for qResults in range(0, len(mySpecies.results)):
mylabel = getElementAbv(mySpecies.Z) + str(mySpecies.chargeStates[qResults])
plt.plot(column(mySpecies.results[qResults], 0), column(mySpecies.results[qResults], 2), label=mylabel)
if outputConfig.xmin or outputConfig.xmax != 0:
plt.xlim(outputConfig.xmin, outputConfig.xmax)
if outputConfig.ymin or outputConfig.ymax != 0:
plt.ylim(outputConfig.ymin, outputConfig.ymax)
if outputConfig.logx == 1:
plt.xscale('log')
# plt.yscale('log')
plt.ylabel("Energy [eV]")
plt.xlabel("Breeding time [s]")
plt.legend(framealpha=0.5)
plt.savefig(outputConfig.outputFileName.split(".")[0]+"_energy.png", dpi=300)
return
def plotStepsVsIteration(species, outputConfig):
import matplotlib
import matplotlib.pyplot as plt
plt.figure()
plt.plot(column(species[0].results[0], 0), column(species[0].results[0], 3))
plt.ylabel("size of time step used [s]")
plt.xlabel("time [s]")
plt.xscale("log")
plt.yscale("log")
plt.savefig(outputConfig.outputFileName.split(".")[0]+"_stepSize.png", dpi=300)
return
def writeCSVFile(species, ebitParams, outputConfig):
newentry = []
with open(outputConfig.outputFileName, 'w', newline='') as csvfile:
csvwriter = csv.writer(csvfile, delimiter=',', quoting=csv.QUOTE_NONE)
for myspecies in species:
for chargeStateResults in range(0, len(myspecies.results)):
mylabel = getElementAbv(myspecies.Z) + str(myspecies.chargeStates[chargeStateResults])
for myrow in range(len(myspecies.results[chargeStateResults])):
newentry = mylabel, myspecies.results[chargeStateResults][myrow][0], myspecies.results[chargeStateResults][myrow][1]
csvwriter.writerow(newentry)
return
def writeRates(species, ebitParams, outputConfig):
# An option to write out the rate matricies for diagnostic purposes.
""" Added some sloppy factors to get the actual cross sections. This could be more robust in the future
"""
with open(outputConfig.outputFileName, 'w', newline='') as ratesfile:
csvwriter = csv.writer(ratesfile, delimiter=',', quoting=csv.QUOTE_NONE)
# Only showing one of the EBIT parameters, no beam energy scanning!
csvwriter.writerow(['Breeding time: %s' % ebitParams[0].breedingTime])
csvwriter.writerow(['Beam energy: %s' % ebitParams[0].beamEnergy])
for idx, myspecies in enumerate(species):
csvwriter.writerow(['Species = %s' % getElementAbv(myspecies.Z)])
csvwriter.writerow(['ionization rates for q=0 to %s:' % str(len(myspecies.ionizationRates)-1)])
for i, j in enumerate([k*__ECHG__/ebitParams[idx].currentDensity for k in myspecies.ionizationRates]):
csvwriter.writerow(['q = %s' %i] + [j])
csvwriter.writerow(['radiative recombination rates:'])
for i, j in enumerate([k*__ECHG__/ebitParams[idx].currentDensity for k in myspecies.rrRates]):
csvwriter.writerow(['q = %s' %i] + [j])
csvwriter.writerow(['charge exchange rates:'])
for i, j in enumerate([k/(ebitParams[idx].pressure*__TORR__) for k in myspecies.chargeExchangeRates]):
csvwriter.writerow(['q = %s' %i] + [j])
csvwriter.writerow(['END'])
def runSimulation(species, ebitParams, probeFnAddPop, outputConfig):
# Runs the actual simulation via ebitChargeDistribution.calcChargePopulations and then determines how to handle the output
# Option to write to a log.log file in the same directory as the outputFileName
if outputConfig.logOutput in ["True", "T", "Yes", "Y", 1]:
print("Writing output to log file...")
old_stdout = sys.stdout
log_file = open(os.path.dirname(outputConfig.outputFileName)+os.sep+"log.log", "w")
sys.stdout = log_file
print(time.asctime(time.localtime()))
print("Running simulation! ....")
ebitChargeDistribution.calcChargePopulations(species, ebitParams, probeFnAddPop)
if outputConfig.outputType == 'rates':
print("Writing rates to csv: %s \n" % outputConfig.outputFileName)
writeRates(species, ebitParams, outputConfig)
if outputConfig.outputType == 'matplotlib':
print("Writing charge state graph to : %s" % outputConfig.outputFileName)
plotSpeciesResults(species, ebitParams, outputConfig) # Think about fixing ebitParams later to deal with multiple beam energies..
print("Writing step size graph to : %s" % (outputConfig.outputFileName.split(".")[0]+"_stepSize.png"))
plotStepsVsIteration(species, outputConfig)
# if species[0].initSCITemp != None: # if including energy dynamics, plot that too
# print("Writing energy graph to : %s" % (outputConfig.outputFileName.split(".")[0]+"_energy.png"))
# plotSpeciesEnergies(species, ebitParams, outputConfig)
if outputConfig.outputType == 'csv':
print("Writing csv to : %s \n" % outputConfig.outputFileName)
writeCSVFile(species, ebitParams[0], outputConfig)
if outputConfig.outputType == 'geant4Macro':
print("Writing GEANT4 Macro to : %s \n" % outputConfig.outputFileName)
geant4MacroOutput(species, ebitParams[0], outputConfig)
if outputConfig.stepPlot != 0:
print("Writing step size plot to %s \n" % os.path.dirname(outputConfig.outputFileName)+os.sep+outputConfig.stepPlot)
plotStepSize(species, ebitParams, outputConfig)
if outputConfig.logOutput in ["True", "T", "Yes", "Y", 1]:
sys.stdout = old_stdout
log_file.close
return
def getConfigEntry(config, heading, item, reqd=False, remove_spaces=True, default_val=''):
# Just a helper function to process config file lines, strip out white spaces and check if requred etc.
if config.has_option(heading, item):
if remove_spaces:
config_item = config.get(heading, item).replace(" ", "")
else:
config_item = config.get(heading, item)
elif reqd:
print("The required config file setting \'%s\' under [%s] is missing") % (item, heading)
sys.exit(1)
else:
config_item = default_val
return config_item
def processConfigFile(configFileName):
#
# We will read the entire config file here and push it into a different classes and then call runSimulation()
#
config = configparser.RawConfigParser()
# baseDir = os.path.dirname(os.path.realpath(__file__))[:-3]
if os.path.exists(configFileName):
config.read(configFileName)
outputConfig = OutputFormating()
# Read output information
outputConfig.outputType = getConfigEntry(config, 'Output', 'outputType', reqd=True, remove_spaces=True)
outputConfig.outputFileName = getConfigEntry(config, 'Output', 'outputFileName', reqd=True, remove_spaces=True)
outputConfig.logOutput = getConfigEntry(config, 'Output', 'logOutput', reqd=False, remove_spaces=True, default_val=0)
outputConfig.stepPlot = getConfigEntry(config, 'Output', 'stepPlot', reqd=False, remove_spaces=True, default_val=0)
outputConfig.badGuessPlot = getConfigEntry(config, 'Output', 'badGuessPlot', reqd=False, remove_spaces=True, default_val=0)
# matPlotLib stuff
outputConfig.xmin = float(getConfigEntry(config, 'matPlotLib', 'graphXMinTime', reqd=False, remove_spaces=True, default_val=0))
outputConfig.xmax = float(getConfigEntry(config, 'matPlotLib', 'graphXMaxTime', reqd=False, remove_spaces=True, default_val=0))
outputConfig.ymin = float(getConfigEntry(config, 'matPlotLib', 'graphYMinPop', reqd=False, remove_spaces=True, default_val=0))
outputConfig.ymax = float(getConfigEntry(config, 'matPlotLib', 'graphYMaxPop', reqd=False, remove_spaces=True, default_val=0))
outputConfig.logx = float(getConfigEntry(config, 'matPlotLib', 'graphXScale', reqd=False, remove_spaces=True, default_val=0))
# geant4MacroOutput stuff
outputConfig.eventsPerTimeSlice = float(getConfigEntry(config, 'geant4MacroOutput', 'eventsPerTimeSlice', reqd=False, remove_spaces=True, default_val=0))
outputConfig.subDivisionOfTime = float(getConfigEntry(config, 'geant4MacroOutput', 'subDivisionOfTime', reqd=False, remove_spaces=True, default_val=0))
ebitParamsList = tuple(getConfigEntry(config, 'Run', 'beamList', reqd=True, remove_spaces=True).split(","))
ebitParams = []
for myebitParams in ebitParamsList:
# Read in Beam information
beamEnergy = float(getConfigEntry(config, myebitParams, 'beamEnergy', reqd=True, remove_spaces=True))
breedingTime = float(getConfigEntry(config, myebitParams, 'breedingTime', reqd=True, remove_spaces=True))
probeEvery = float(getConfigEntry(config, myebitParams, 'probeEvery', reqd=False, remove_spaces=True, default_val=0.0))
ionEbeamOverlap = float(getConfigEntry(config, myebitParams, 'ionEbeamOverlap', reqd=False, remove_spaces=True, default_val=0.0))
beamCurrent = float(getConfigEntry(config, myebitParams, 'beamCurrent', reqd=False, remove_spaces=True, default_val=0.0))
beamRadius = float(getConfigEntry(config, myebitParams, 'beamRadius', reqd=False, remove_spaces=True, default_val=0.0))
pressure = float(getConfigEntry(config, myebitParams, 'pressure', reqd=False, remove_spaces=True, default_val=0.0))
ionTemperature = float(getConfigEntry(config, myebitParams, 'ionTemperature', reqd=False, remove_spaces=True, default_val=0.0))
toggleChargeExchange = float(getConfigEntry(config, 'Run', 'chargeExchange', reqd=False, remove_spaces=True, default_val=1.0))
ebitParams.append(ebitChargeDistribution.EbitParams(breedingTime, probeEvery, ionEbeamOverlap, beamEnergy, beamCurrent, beamRadius, pressure, ionTemperature, toggleChargeExchange))
# Gets the speciesList from under the [Run] headline
speciesList = tuple(getConfigEntry(config, 'Run', 'speciesList', reqd=True, remove_spaces=True).split(","))
species = []
for myspecies in speciesList:
# Collect all the species and parameters for each
protons = int(getConfigEntry(config, myspecies, 'z', reqd=True, remove_spaces=True))
nucleons = int(getConfigEntry(config, myspecies, 'nucleons', reqd=True, remove_spaces=True))
population = float(getConfigEntry(config, myspecies, 'populationPercent', reqd=True, remove_spaces=True))
chargeStates = list(map(int, getConfigEntry(config, myspecies, 'chargeStates', reqd=True, remove_spaces=True).split(",")))
betaHalfLife = float(getConfigEntry(config, myspecies, 'betaHalfLife', reqd=False, remove_spaces=True, default_val=0.0))
halfLife = float(getConfigEntry(config, myspecies, 'halfLife', reqd=False, remove_spaces=True, default_val=0.0))
populationNumber = float(getConfigEntry(config, myspecies, 'populationNumber', reqd=False, remove_spaces=True, default_val=0.0))
initSCITemp = float(getConfigEntry(config, myspecies, 'initSCITemp', reqd=False, remove_spaces=True, default_val=-1.0))
species.append(ebitChargeDistribution.Species(protons, nucleons, 0.0, betaHalfLife, population, chargeStates, halfLife, populationNumber, initSCITemp))
else:
print("Config file does not appear to exist : %s" % configFileName)
sys.exit(1)
# if sum([*map(lambda x: x.initSCIPop, species)]) !=1.0:
# print("Population fractions do not sum to 1.0, please check the configuration file.")
# sys.exit(1)
runSimulation(species, ebitParams, probeFnAddPop, outputConfig)
return 0
def processCommandLine(args):
# Process command line arguments and then call runSimulation()
outputConfig = OutputFormating()
species = []
ebitParams = []
outputConfig.outputType = args.outputType
outputConfig.outputFileName = args.outputFileName
species.append(ebitChargeDistribution.Species(args.protons, args.nucleons, 0.0, 0.0, 1.0, args.chargeStates))
ebitParams.append(ebitChargeDistribution.EbitParams(breedingTime=args.breedingTime, beamEnergy=args.beamEnergy, pressure=args.pressure, beamCurrent=args.beamCurrent, beamRadius=args.beamRadius, probeEvery=args.probeEvery))
runSimulation(species, ebitParams, probeFnAddPop, outputConfig)
return
def main():
parser = argparse.ArgumentParser(description='EBIT Charge Breeding Simulation')
parser.add_argument('-docs', dest='docs', required=False,
help='Obtain documentation about CBSim implementation. Options:\n - physics\n - timestepping\n')
parser.add_argument('--configFile', dest='configFile', required=False,
help="Specify the complete path to the config file, by default we'll use ebitsim.cfg")
parser.add_argument('--outputType', dest='outputType', default='matplotlib', required=False,
help="Specify how to output the data, defaults to a matplotlib graph, csv will be available at some point")
parser.add_argument('--outputFileName', dest='outputFileName', default='output.png', required=False,
help="Specify filename for csv or png file, please include .csv or .png extension")
parser.add_argument('-z', dest='protons', type=int, default=0, required=False,
help="Specify the num of protons")
parser.add_argument('-Z', dest='protons', type=int, default=0, required=False, # there's probably a better way to handle this... sorry
help="Specify the num of protons")
parser.add_argument('-a', dest='nucleons', type=int, required=False,
help="Specify the num of nucleons")
parser.add_argument('--chargeStates', nargs='+', dest='chargeStates', type=int, required=False,
help="Specify charge states such as : --charge-states 32 33 34 35")
parser.add_argument('--breedingTime', dest='breedingTime', default=11.0, type=float, required=False,
help="Specify breeding time (sec)")
parser.add_argument('--probeEvery', dest='probeEvery', default=0.001, type=float, required=False, # You can probably crank this up if you want to or need to
help="Get results in this incriment (sec)")
parser.add_argument('--beamEnergy', dest='beamEnergy', default=7000, type=float, required=False,
help="Beam energy (eV)")
parser.add_argument('--beamCurrent', dest='beamCurrent', default=0.02, type=float, required=False,
help="Beam current (Amps)")
parser.add_argument('--beamRadius', dest='beamRadius', default=90e-4, type=float, required=False,
help="Beam radius (cm)")
parser.add_argument('--pressure', dest='pressure', default=1e-10, type=float, required=False,
help="ebit vacuum pressure (Torr / cm^3 I think)")
parser.set_defaults(configFile="ebitsim.cfg")
args, unknown = parser.parse_known_args()
# sg.config_file = args.config_file
if platform.python_implementation() == 'CPython':
print("*** !!WARNING!! : While this can run via normal cPython it is highly recommended that you run it via pypy3 for a HUGE speedup ***")
# Printing out documentation
if args.docs:
try:
print (globals()['docs_'+args.docs].__doc__)
except KeyError:
print('Doc topic does not exist.')
sys.exit(1)
if args.protons != 0:
processCommandLine(args)
else:
processConfigFile(args.configFile)
if __name__ == "__main__":
main()