1
0
Fork 0
mirror of https://github.com/gwm17/spspy.git synced 2024-11-26 12:08:51 -05:00

First commit

This commit is contained in:
Gordon McCann 2021-12-06 15:37:47 -05:00
commit f11c611c18
18 changed files with 2740 additions and 0 deletions

7
.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
*.pickle
*.txt
!test_config.txt
__pycache__
*.sublime-project
*.sublime-workspace
!.gitignore

21
README.md Normal file
View File

@ -0,0 +1,21 @@
# SPSPy
SPSPy is a Python based package of tools for use with the Super-Enge Split-Pole Spectrograph at FSU. Much of the code here is based on Java programs originally written at Yale University by D.W. Visser, C.M. Deibel, and others. Currently the package contains spsplot, a tool aimed at informing users which states should appear at the focal plane of the SESPS, and spanc, a tool for calibrating the position spectra from the focal plane.
# Depencencies and Requirements
SPSPy requires the Python packages qtpy (along with a functional Qt distriubtion for python), matplotlib, numpy, lxml, and scipy to guarantee full functionality. The most straightforward way to intstall all dependencies is by using either pip or having a distribution such as Anaconda.
Spsplot also requires that the user have an internet connection.
## spsplot
This tool is intended to be used for guiding the settings of the SPS to show specific states on the focal plane detector. The user gives the program reaction information, and the program runs through the kinematics to calculate the energies of ejecta into the the SESPS. To evaluate different states, the program scrapes a list of levels from www.nndc.bnl.gov, and these levels are then passed on to the reaction handler. These levels are then shown on the screen with labels. The labels can be modified to show either the excitation energy of the state, or the kinetic energy of the ejectile.
## spanc
SPANC is the program used to calibrate SESPS focal plane spectra. It works by the user specifying a target, reaction, calibration peaks, and output peaks. The target is a description of the physical target foil used in the SPS, which is used to calculate energy loss effects. The target must contain the isotope used as the target in the reaction description. The reaction indicates to the program what type of ejecta are expected, as well as the settings of the spectrograph. Calibration data is given as centroids from a spectrum with correspoding excitation energies, as well as associated uncertainties. The calibration peaks are then fit using the scipy ODR package (see scipy ODR for more documentation). The fit is plotted, and the results are shown in a table. Additionally, residuals are plotted and shown in a table. The user can then feed the program an output peak, or a peak for which the user would like to calculate the excitation energy of a state using the calibration fit. The peak excitation energy will then be reported, with uncertainty. The user can also give a FWHM to be converted from focal plane position to energy.
# Running the tools
Use `./bin/spanc` or `./bin/spsplot`. Note that they should be run from the SPSPy directory, as there are some file paths which need to be maintained.
### Known issues
1. NNDC sometimes puts annoying characters in the ENDSF list; each of these "special characters" needs to be added to a list of exclusions
2. Not really an issue but with high level density reactions, spsplot becomes quite crowded. Working on implementing level removal.
3. Debian -- mostly relevant to SPS DAQ machine, but Qt on debian running with a TightVNC instance causes a crash of the VNC server. Current fix is to implement a virtual env for a ssh into the DAQ machine from which the tools will be used, while leaving the VNC window free from the Qt related code.

3
bin/spanc Executable file
View File

@ -0,0 +1,3 @@
#!/bin/bash
./spanc/SpancGUI.py

3
bin/spsplot Executable file
View File

@ -0,0 +1,3 @@
#!/bin/bash
./spsplot/SPSPlotGUI.py

213
spanc/EnergyLoss.py Executable file
View File

@ -0,0 +1,213 @@
#!/usr/bin/env python3
import numpy as np
import EnergyLossData as edata
from NucData import Masses
class EnergyLoss:
MAX_FRACTIONAL_STEP=0.001
MAX_H_E_PER_U=100000.0
AVOGADRO=0.60221367
MEV2U=1.0/931.4940954
def __init__(self):
self.ZP = 0
self.AP = 0
self.MP = 0
self.ZT = np.zeros(0)
self.AT = np.zeros(0)
self.Stoich = np.zeros(0)
self.illegalFlag = True
def SetTargetData(self, zt, at, stoich):
self.ZT = zt
self.AT = at
total = np.sum(stoich)
self.Stoich = stoich/total
for z in self.ZT:
if z >= edata.MaxZ:
self.illegalFlag = True
return
self.illegalFlag = False
def GetEnergyLoss(self, zp, ap, e_initial, thickness):
if self.illegalFlag:
print("Unable to get energy loss with unset target data... returning 0")
return 0.0
if self.ZP != zp:
self.ZP = zp
self.AP = ap
self.MP = Masses.GetMass(self.ZP, self.AP)*self.MEV2U
e_final = e_initial
x_traversed = 0
x_step = 0.25*thickness
e_step = self.GetTotalStoppingPower(e_final)*x_step/1000.0
if thickness == 0.0:
return 0.0
go = True
while go:
if e_step/e_final > self.MAX_FRACTIONAL_STEP:
x_step *= 0.5
e_step = self.GetTotalStoppingPower(e_final)*x_step/1000.0
elif x_step+x_traversed >= thickness:
go = False
x_step = thickness - x_traversed #get valid portion of last chunk
e_final -= self.GetTotalStoppingPower(e_final)*x_step/1000.0
if e_final <= 0.0:
return e_initial
else:
x_traversed += x_step
e_step = self.GetTotalStoppingPower(e_final)*x_step/1000.0
e_final -= e_step
if e_final <= 0.0:
return e_initial
return e_initial - e_final
def GetReverseEnergyLoss(self, zp, ap, e_final, thickness):
if self.illegalFlag:
print("Unable to get energy loss with unset target data... returning 0")
return 0.0
if self.ZP != zp:
self.ZP = zp
self.AP = ap
self.MP = Masses.GetMass(self.ZP, self.AP)*self.MEV2U
e_initial = e_final
x_traversed = 0
x_step = 0.25*thickness
e_step = self.GetTotalStoppingPower(e_initial)*x_step/1000.0
if thickness == 0.0:
return 0.0
go = True
while go:
if e_step/e_final > self.MAX_FRACTIONAL_STEP:
x_step *= 0.5
e_step = self.GetTotalStoppingPower(e_initial)*x_step/1000.0
elif x_step+x_traversed >= thickness:
go = False
x_step = thickness - x_traversed #get valid portion of last chunk
e_initial += self.GetTotalStoppingPower(e_initial)*x_step/1000.0
else:
x_traversed += x_step
e_step = self.GetTotalStoppingPower(e_initial)*x_step/1000.0
e_final += e_step
if e_final <= 0.0:
return e_initial
return abs(e_initial - e_final)
def GetTotalStoppingPower(self, energy):
return self.GetElectronicStoppingPower(energy)+self.GetNuclearStoppingPower(energy)
def GetElectronicStoppingPower(self, energy):
e_per_u = energy*1000.0/self.MP
values = np.zeros(len(self.ZT))
if e_per_u > self.MAX_H_E_PER_U:
print("Bombarding energy exceeds maximum allowed value for energy loss! Returning 0")
return 0.0
elif e_per_u > 1000.0:
for i in range(len(self.ZT)):
values[i] = self.Hydrogen_dEdx_High(e_per_u, energy, self.ZT[i])
elif e_per_u > 10.0:
for i in range(len(self.ZT)):
values[i] = self.Hydrogen_dEdx_Med(e_per_u, self.ZT[i])
elif e_per_u > 0.0:
for i in range(len(self.ZT)):
values[i] = self.Hydrogen_dEdx_Low(e_per_u, self.ZT[i])
else:
print("Negative energy encountered at EnergyLoss::GetElectronicStoppingPower! Returning 0")
return 0.0
if self.ZP > 1:
for i in range(len(values)):
values[i] *= self.CalculateEffectiveChargeRatio(e_per_u, self.ZT[i])
stopping_total = np.sum(values*self.Stoich)
conv_factor = 0.0
for i in range(len(self.ZT)):
conv_factor += self.Stoich[i]*edata.NaturalMass[self.ZT[i]]
stopping_total *= self.AVOGADRO/conv_factor
return stopping_total
def GetNuclearStoppingPower(self, energy):
e = energy*1000.0
stopping_total = 0.0
for i in range(len(self.ZT)):
zt = self.ZT[i]
mt = edata.NaturalMass[self.ZT[i]]
x = (self.MP + mt) * np.sqrt(self.ZP**(2.0/3.0) + zt**(2.0/3.0))
epsilon = 32.53*mt*e/(self.ZP*zt*x)
sn = 8.462*(0.5*np.log(1.0+epsilon)/(epsilon+0.10718*(epsilon**0.37544)))*self.ZP*zt*self.MP/x
conversion_factor = self.AVOGADRO/mt
stopping_total += sn*conversion_factor*self.Stoich[i]
return stopping_total
def Hydrogen_dEdx_Low(self, e_per_u, zt):
return np.sqrt(e_per_u)*edata.HydrogenCoeff[zt][0]
def Hydrogen_dEdx_Med(self, e_per_u, zt):
x = edata.HydrogenCoeff[zt][1]*e_per_u**0.45
y = edata.HydrogenCoeff[zt][2]/e_per_u * np.log(1.0 + edata.HydrogenCoeff[zt][3]/e_per_u + edata.HydrogenCoeff[zt][4]*e_per_u)
return x*y/(x+y)
def Hydrogen_dEdx_High(self, e_per_u, energy, zt):
beta_sq = energy * (energy + 2.0*self.MP/self.MEV2U)/((energy + self.MP/self.MEV2U)**2.0)
alpha = edata.HydrogenCoeff[zt][5]/beta_sq
epsilon = edata.HydrogenCoeff[zt][6]*beta_sq/(1.0 - beta_sq) - beta_sq - edata.HydrogenCoeff[zt][7]
for i in range(1,5):
epsilon += edata.HydrogenCoeff[zt][7+i]*(np.log(e_per_u))**float(i)
return alpha * np.log(epsilon)
def CalculateEffectiveChargeRatio(self, e_per_u, zt):
z_ratio=0
if self.ZP == 2:
ln_epu = np.log(e_per_u)
gamma = 1.0+(0.007+0.00005*zt)*np.exp(-1.0*(7.6-ln_epu)**2.0)
alpha = 0.7446 + 0.1429*ln_epu + 0.01562*ln_epu**2.0 - 0.00267*ln_epu**3.0 + 1.338e-6*ln_epu**8.0
z_ratio = gamma*(1.0-np.exp(-alpha))*2.0
elif self.ZP == 3:
ln_epu = np.log(e_per_u)
gamma = 1.0+(0.007+0.00005*zt)*np.exp(-1.0*(7.6-ln_epu)**2.0)
alpha = 0.7138+0.002797*e_per_u+1.348e-6*e_per_u**2.0
z_ratio = gamma*(1-np.exp(-alpha))*3.0
else:
B = 0.886*(e_per_u/25.0)**0.5/(self.ZP**(2.0/3.0))
A = B + 0.0378*np.sin(np.pi/2.0*B)
z_ratio = (1.0 - np.exp(-A)*(1.034-0.1777*np.exp(-0.08114*self.ZP)))*self.ZP
return z_ratio*z_ratio
def main():
targetA = np.array([12])
targetZ = np.array([6])
targetS = np.array([1])
beamKE = 16.0
thickness = 20.0
eloss = EnergyLoss()
eloss.SetTargetData(targetZ, targetA, targetS)
print("Testing various cases for energy loss. Using 12C target with 20 ug/cm^2 thickness. Compare to values given by LISE++ or SRIM")
result = eloss.GetEnergyLoss(1, 1, beamKE, 20.0)
print("Case 1: ZP = 1, AP=1, Beam energy = 16 MeV -> Resulting energy loss = ", result, " MeV")
beamKE = 1.0
result = eloss.GetEnergyLoss(1, 1, beamKE, 20.0)
print("Case 2: ZP = 1, AP=1, Beam energy = 1.0 MeV -> Resulting energy loss = ", result, " MeV")
beamKE = 0.1
result = eloss.GetEnergyLoss(1, 1, beamKE, 20.0)
print("Case 3: ZP = 1, AP=1, Beam energy = 0.1 MeV -> Resulting energy loss = ", result, " MeV")
beamKE = 0.01
result = eloss.GetEnergyLoss(1, 1, beamKE, 20.0)
print("Case 4: ZP = 1, AP=1, Beam energy = 0.01 MeV -> Resulting energy loss = ", result, " MeV")
beamKE = 24.0
result = eloss.GetEnergyLoss(2, 4, beamKE, 20.0)
print("Case 5: ZP = 2, AP=4, Beam energy = 24.0 MeV -> Resulting energy loss = ", result, " MeV")
beamKE = 24.0
result = eloss.GetEnergyLoss(3, 6, beamKE, 20.0)
print("Case 6: ZP = 3, AP=6, Beam energy = 24 MeV -> Resulting energy loss = ", result, " MeV")
print("Finished.")
if __name__ == '__main__':
main()

122
spanc/EnergyLossData.py Normal file
View File

@ -0,0 +1,122 @@
#!/usr/bin/env python3
import numpy as np
MaxZ = 93
NaturalMass = np.array([0, 1.00797, 4.0026, 6.939, 9.0122, 10.818,
12.01115, 14.0067, 15.9994, 18.99984, 20.183,
22.9898, 24.312, 26.9815, 28.086, 30.9738,
32.064, 35.453, 39.948, 39.102, 40.08,
44.956, 47.90, 50.942, 51.996, 54.938,
55.847, 58.933, 58.71, 63.54, 65.37,
69.72, 72.59, 74.922, 78.96, 79.909,
83.80, 85.47, 87.62, 88.909, 91.22,
92.906, 95.94, 98., 101.07, 102.905,
106.4, 107.87, 112.4, 114.82, 118.69,
121.75, 127.60, 126.904, 131.3, 132.905,
137.34, 138.91, 140.12, 140.907, 144.24,
146., 150.35, 151.96, 157.25, 158.924,
162.50, 164.93, 167.26, 168.934, 173.04,
174.97, 178.49, 180.948, 183.85, 186.2,
190.2, 192.2, 195.09, 196.967, 200.59,
204.37, 207.19, 208.98, 209., 210.,
222., 223., 226., 227., 232.038,
231., 238.03])
HydrogenCoeff = np.array([
[0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.],#Blank
[1.262,1.44,242.6,1.2E4,0.1159,0.0005099,5.436E4,-5.052,2.049,-0.3044,0.01966,-0.0004659],#H
[1.229,1.397,484.5,5873,0.05225,0.00102,2.451E4,-2.158,0.8278,-0.1172,0.007259,-0.000166],#He
[1.411,1.6,725.6,3013,0.04578,0.00153,2.147E4,-0.5831,0.562,-0.1183,0.009298,-0.0002498],#Li
[2.248,2.59,966,153.8,0.03475,0.002039,1.63E4,0.2779,0.1745,-0.05684,0.005155,-0.0001488],#Be
[2.474,2.815,1206,1060,0.02855,0.002549,1.345E4,-2.445,1.283,-0.2205,0.0156,-0.000393],#B
[2.631,2.989,1445,957.2,0.02819,0.003059,1.322E4,-4.38,2.044,-0.3283,0.02221,-0.0005417],#C
[2.954,3.35,1683,1900,0.02513,0.003569,1.179E4,-5.054,2.325,-0.3713,0.02506,-0.0006109],#N
[2.652,3,1920,2000,0.0223,0.004079,1.046E4,-6.734,3.019,-0.4748,0.03171,-0.0007669],#O
[2.085,2.352,2157,2634,0.01816,0.004589,8517,-5.571,2.449,-0.3781,0.02483,-0.0005919],#F
[1.951,2.199,2393,2699,0.01568,0.005099,7353,-4.408,1.879,-0.2814,0.01796,-0.0004168],#Ne
[2.542,2.869,2628,1854,0.01472,0.005609,6905,-4.959,2.073,-0.3054,0.01921,-0.0004403],#Na
[3.792,4.293,2862,1009,0.01397,0.006118,6551,-5.51,2.266,-0.3295,0.02047,-0.0004637],#Mg
[4.154,4.739,2766,164.5,0.02023,0.006628,6309,-6.061,2.46,-0.3535,0.02173,-0.0004871],#Al
[4.15,4.7,3329,550,0.01321,0.007138,6194,-6.294,2.538,-0.3628,0.0222,-0.0004956],#Si
[3.232,3.647,3561,1560,0.01267,0.007648,5942,-6.527,2.616,-0.3721,0.02267,-0.000504],#P
[3.447,3.891,3792,1219,0.01211,0.008158,5678,-6.761,2.694,-0.3814,0.02314,-0.0005125],#S
[5.047,5.714,4023,878.6,0.01178,0.008668,5524,-6.994,2.773,-0.3907,0.02361,-0.0005209],#Cl
[5.731,6.5,4253,530,0.01123,0.009178,5268,-7.227,2.851,-0.4,0.02407,-0.0005294],#Ar
[5.151,5.833,4482,545.7,0.01129,0.009687,5295,-7.44,2.923,-0.4094,0.02462,-0.0005411],#K
[5.521,6.252,4710,553.3,0.01112,0.0102,5214,-7.653,2.995,-0.4187,0.02516,-0.0005529],#Ca
[5.201,5.884,4938,560.9,0.009995,0.01071,4688,-8.012,3.123,-0.435,0.02605,-0.0005707],#Sc.....
[4.862,5.496,5165,568.5,0.009474,0.01122,4443,-8.371,3.251,-0.4513,0.02694,-0.0005886],
[4.48,5.055,5391,952.3,0.009117,0.01173,4276,-8.731,3.379,-0.4676,0.02783,-0.0006064],
[3.983,4.489,5616,1336,0.008413,0.01224,3946,-9.09,3.507,-0.4838,0.02872,-0.0006243],
[3.469,3.907,5725,1461,0.008829,0.01275,3785,-9.449,3.635,-0.5001,0.02961,-0.0006421],
[3.519,3.963,6065,1243,0.007782,0.01326,3650,-9.809,3.763,-0.5164,0.0305,-0.00066],
[3.14,3.535,6288,1372,0.007361,0.01377,3453,-10.17,3.891,-0.5327,0.03139,-0.0006779],
[3.553,4.004,6205,555.1,0.008763,0.01428,3297,-10.53,4.019,-0.549,0.03229,-0.0006957],
[3.696,4.175,4673,387.8,0.02188,0.01479,3174,-11.18,4.252,-0.5791,0.03399,-0.0007314],
[4.21,4.75,6953,295.2,0.006809,0.0153,3194,-11.57,4.394,-0.598,0.03506,-0.0007537],
[5.041,5.697,7173,202.6,0.006725,0.01581,3154,-11.95,4.537,-0.6169,0.03613,-0.0007759],
[5.554,6.3,6496,110,0.009689,0.01632,3097,-12.34,4.68,-0.6358,0.03721,-0.0007981],
[5.323,6.012,7611,292.5,0.006447,0.01683,3024,-12.72,4.823,-0.6547,0.03828,-0.0008203],
[5.874,6.656,7395,117.5,0.007684,0.01734,3006,-13.11,4.965,-0.6735,0.03935,-0.0008425],
[5.611,6.335,8046,365.2,0.006244,0.01785,2928,-13.4,5.083,-0.6906,0.04042,-0.0008675],
[6.411,7.25,8262,220,0.006087,0.01836,2855,-13.69,5.2,-0.7076,0.0415,-0.0008925],
[5.694,6.429,8478,292.9,0.006087,0.01886,2855,-13.92,5.266,-0.714,0.04173,-0.0008943],
[6.339,7.159,8693,330.3,0.006003,0.01937,2815,-14.14,5.331,-0.7205,0.04196,-0.0008962],
[6.407,7.234,8907,367.8,0.005889,0.01988,2762,-14.36,5.397,-0.7269,0.04219,-0.000898],
[6.734,7.603,9120,405.2,0.005765,0.02039,2704,-14.59,5.463,-0.7333,0.04242,-0.0008998],
[6.902,7.791,9333,442.7,0.005587,0.0209,2621,-16.22,6.094,-0.8225,0.04791,-0.001024],
[6.425,7.248,9545,480.2,0.005367,0.02141,2517,-17.85,6.725,-0.9116,0.05339,-0.001148],
[6.799,7.671,9756,517.6,0.005315,0.02192,2493,-17.96,6.752,-0.9135,0.05341,-0.001147],
[6.108,6.887,9966,555.1,0.005151,0.02243,2416,-18.07,6.779,-0.9154,0.05342,-0.001145],
[5.924,6.677,1.018E4,592.5,0.004919,0.02294,2307,-18.18,6.806,-0.9173,0.05343,-0.001143],
[5.238,5.9,1.038E4,630,0.004758,0.02345,2231,-18.28,6.833,-0.9192,0.05345,-0.001142],
[5.623,6.354,7160,337.6,0.01394,0.02396,2193,-18.39,6.86,-0.9211,0.05346,-0.00114],
[5.814,6.554,1.08E4,355.5,0.004626,0.02447,2170,-18.62,6.915,-0.9243,0.0534,-0.001134],
[6.23,7.024,1.101E4,370.9,0.00454,0.02498,2129,-18.85,6.969,-0.9275,0.05335,-0.001127],
[6.41,7.227,1.121E4,386.4,0.004474,0.02549,2099,-19.07,7.024,-0.9308,0.05329,-0.001121],
[7.5,8.48,8608,348,0.009074,0.026,2069,-19.57,7.225,-0.9603,0.05518,-0.001165],
[6.979,7.871,1.162E4,392.4,0.004402,0.02651,2065,-20.07,7.426,-0.9899,0.05707,-0.001209],
[7.725,8.716,1.183E4,394.8,0.004376,0.02702,2052,-20.56,7.627,-1.019,0.05896,-0.001254],
[8.231,9.289,1.203E4,397.3,0.004384,0.02753,2056,-21.06,7.828,-1.049,0.06085,-0.001298],
[7.287,8.218,1.223E4,399.7,0.004447,0.02804,2086,-20.4,7.54,-1.004,0.05782,-0.001224],
[7.899,8.911,1.243E4,402.1,0.004511,0.02855,2116,-19.74,7.252,-0.9588,0.05479,-0.001151],
[8.041,9.071,1.263E4,404.5,0.00454,0.02906,2129,-19.08,6.964,-0.9136,0.05176,-0.001077],
[7.489,8.444,1.283E4,406.9,0.00442,0.02957,2073,-18.43,6.677,-0.8684,0.04872,-0.001003],
[7.291,8.219,1.303E4,409.3,0.004298,0.03008,2016,-17.77,6.389,-0.8233,0.04569,-0.0009292],
[7.098,8,1.323E4,411.8,0.004182,0.03059,1962,-17.11,6.101,-0.7781,0.04266,-0.0008553],
[6.91,7.786,1.343E4,414.2,0.00405,0.0311,1903,-16.45,5.813,-0.733,0.03963,-0.0007815],
[6.728,7.58,1.362E4,416.6,0.003976,0.03161,1865,-15.79,5.526,-0.6878,0.0366,-0.0007077],
[6.551,7.38,1.382E4,419,0.003877,0.03212,1819,-15.13,5.238,-0.6426,0.03357,-0.0006339],
[6.739,7.592,1.402E4,421.4,0.003863,0.03263,1812,-14.47,4.95,-0.5975,0.03053,-0.0005601],
[6.212,6.996,1.421E4,423.9,0.003725,0.03314,1747,-14.56,4.984,-0.6022,0.03082,-0.0005668],
[5.517,6.21,1.44E4,426.3,0.003632,0.03365,1703,-14.65,5.018,-0.6069,0.03111,-0.0005734],
[5.219,5.874,1.46E4,428.7,0.003498,0.03416,1640,-14.74,5.051,-0.6117,0.03141,-0.0005801],
[5.071,5.706,1.479E4,433,0.003405,0.03467,1597,-14.83,5.085,-0.6164,0.0317,-0.0005867],
[4.926,5.542,1.498E4,433.5,0.003342,0.03518,1567,-14.91,5.119,-0.6211,0.03199,-0.0005933],
[4.787, 5.386,1.517E4,435.9,0.003292,0.03569,1544,-15,5.153,-0.6258,0.03228,-0.0006],
[4.893, 5.505,1.536E4,438.4,0.003243,0.0362,1521,-15.09,5.186,-0.6305,0.03257,-0.0006066],
[5.028, 5.657,1.555E4,440.8,0.003195,0.03671,1499,-15.18,5.22,-0.6353,0.03286,-0.0006133],
[4.738, 5.329,1.574E4,443.2,0.003186,0.03722,1494,-15.27,5.254,-0.64,0.03315,-0.0006199],
[4.574, 5.144,1.593E4,442.4,0.003144,0.03773,1475,-15.67,5.392,-0.6577,0.03418,-0.0006426],
[5.2, 5.851,1.612E4,441.6,0.003122,0.03824,1464,-16.07,5.529,-0.6755,0.03521,-0.0006654],
[5.07, 5.704,1.63E4,440.9,0.003082,0.03875,1446,-16.47,5.667,-0.6932,0.03624,-0.0006881],
[4.945, 5.563,1.649E4,440.1,0.002965,0.03926,1390,-16.88,5.804,-0.711,0.03727,-0.0007109],
[4.476, 5.034,1.667E4,439.3,0.002871,0.03977,1347,-17.28,5.942,-0.7287,0.0383,-0.0007336],
[4.856, 5.46,1.832E4,438.5,0.002542,0.04028,1354,-17.02,5.846,-0.7149,0.0374,-0.0007114],
[4.308, 4.843,1.704E4,487.8,0.002882,0.04079,1352,-17.84,6.183,-0.7659,0.04076,-0.0007925],
[4.723, 5.311,1.722E4,537,0.002913,0.0413,1366,-18.66,6.52,-0.8169,0.04411,-0.0008737],
[5.319, 5.982,1.74E4,586.3,0.002871,0.04181,1347,-19.48,6.857,-0.8678,0.04747,-0.0009548],
[5.956, 6.7,1.78E4,677,0.00266,0.04232,1336,-19.55,6.871,-0.8686,0.04748,-0.0009544],
[6.158, 6.928,1.777E4,586.3,0.002812,0.04283,1319,-19.62,6.884,-0.8694,0.04748,-0.000954],
[6.204, 6.979,1.795E4,586.3,0.002776,0.04334,1302,-19.69,6.898,-0.8702,0.04749,-0.0009536],
[6.181, 6.954,1.812E4,586.3,0.002748,0.04385,1289,-19.76,6.912,-0.871,0.04749,-0.0009532],
[6.949, 7.82,1.83E4,586.3,0.002737,0.04436,1284,-19.83,6.926,-0.8718,0.0475,-0.0009528],
[7.506, 8.448,1.848E4,586.3,0.002727,0.04487,1279,-19.9,6.94,-0.8726,0.04751,-0.0009524],
[7.649, 8.609,1.866E4,586.3,0.002697,0.04538,1265,-19.97,6.953,-0.8733,0.04751,-0.000952],
[7.71, 8.679,1.883E4,586.3,0.002641,0.04589,1239,-20.04,6.967,-0.8741,0.04752,-0.0009516],
[7.407, 8.336,1.901E4,586.3,0.002603,0.0464,1221,-20.11,6.981,-0.8749,0.04752,-0.0009512],
[7.29, 8.204,1.918E4,586.3,0.002573,0.04691,1207,-20.18,6.995,-0.8757,0.04753,-0.0009508]
])

158
spanc/Fitter.py Executable file
View File

@ -0,0 +1,158 @@
#!/usr/bin/env python3
import numpy as np
from scipy.odr import RealData, ODR, polynomial
class Fit:
def __init__(self, order):
self.poly_order = order
self.model = polynomial(order)
self.x_data = None
self.y_data = None
self.x_errors = None
self.y_errors = None
self.fit_data = None
self.fitter = None
self.output = None
self.parameters = None
def __getstate__(self):
return self.x_data, self.y_data, self.x_errors, self.y_errors, self.parameters, self.poly_order
def __setstate__(self, state):
self.x_data, self.y_data, self.x_errors, self.y_errors, self.parameters, self.poly_order = state
self.model = polynomial(self.poly_order)
def RunFit(self, xarray, yarray, y_errors, x_errors):
self.x_data = xarray
self.y_data = yarray
self.x_errors = x_errors
self.y_errors = y_errors
self.fit_data = RealData(self.x_data, y=self.y_data, sx=self.y_errors, sy=self.x_errors)
self.fitter = ODR(self.fit_data, self.model)
self.output = self.fitter.run()
self.parameters = self.output.beta
def GetParameterError(self, par_index):
return self.output.sd_beta[par_index]
def GetNDF(self):
return len(self.x_data) - self.poly_order+1
class LinearFit(Fit):
def __init__(self):
super().__init__(1)
def EvaluateFunction(self, x):
return self.parameters[0] + self.parameters[1]*x
def EvaluateFunctionDeriv(self, x):
return self.parameters[1]
def EvaluateFunctionParamDeriv(self, x, par_index):
if par_index == 0:
return 1.0
elif par_index == 1:
return x
else:
return 0.0
def ReducedChiSquare(self):
ndf = len(self.x_data)-len(self.parameters)
y_eff_errors = np.zeros(len(self.x_data))
for i in range(len(self.x_data)):
y_eff_errors[i] = np.sqrt(self.y_errors[i]**2.0 + (self.x_errors[i]*self.EvaluateFunctionDeriv(self.x_data[i]))**2.0)
chisq = np.sum(((self.y_data - self.EvaluateFunction(self.x_data))/y_eff_errors)**2.0)
if ndf > 0:
return chisq/ndf
else:
return 0
class QuadraticFit(Fit):
def __init__(self):
super().__init__(2)
def EvaluateFunction(self, x):
return self.parameters[0] + self.parameters[1]*x + self.parameters[2]*x**2.0
def EvaluateFunctionDeriv(self, x):
return self.parameters[1] + 2.0*self.parameters[2]*x
def EvaluateFunctionParamDeriv(self, x, par_index):
if par_index == 0:
return 1.0
elif par_index == 1:
return x
elif par_index == 2:
return x**2.0
else:
return 0.0
def ReducedChiSquare(self):
ndf = len(self.x_data) - len(self.parameters)
y_eff_errors = np.zeros(len(self.x_data))
for i in range(len(self.x_data)):
y_eff_errors[i] = np.sqrt(self.y_errors[i]**2.0 + (self.x_errors[i]*self.EvaluateFunctionDeriv(self.x_data[i]))**2.0)
chisq = np.sum(((self.y_data - self.EvaluateFunction(self.x_data))/y_eff_errors)**2.0)
if ndf >= 0:
return chisq/ndf
else:
return 0
class CubicFit(Fit):
def __init__(self):
super().__init__(3)
def EvaluateFunction(self, x):
return self.parameters[0] + self.parameters[1]*x + self.parameters[2]*x**2.0 + self.parameters[3]*x**3.0
def EvaluateFunctionDeriv(self, x):
return self.parameters[1] + 2.0*self.parameters[2]*x + 3.0*self.parameters[3]*x**2.0
def EvaluateFunctionParamDeriv(self, x, par_index):
if par_index == 0:
return 1.0
elif par_index == 1:
return x
elif par_index == 2:
return x**2.0
elif par_index == 3:
return x**3.0
else:
return 0.0
def ReducedChiSquare(self):
ndf = len(self.x_data) - len(self.parameters)
y_eff_errors = np.zeros(len(self.x_data))
for i in range(len(self.x_data)):
y_eff_errors[i] = np.sqrt(self.y_errors[i]**2.0 + (self.x_errors[i]*self.EvaluateFunctionDeriv(self.x_data[i]))**2.0)
chisq = np.sum(((self.y_data - self.EvaluateFunction(self.x_data))/y_eff_errors)**2.0)
if ndf >= 0:
return chisq/ndf
else:
return 0
def main():
ndata = 100
x = np.zeros(ndata)
y = np.zeros(ndata)
dy = np.zeros(ndata)
dx = np.zeros(ndata)
for i in range(ndata):
x[i] = i
y[i] = i+0.0001*i*i+7.0
dy[i] = 0.1
dx[i] = 0.1
my_fit = LinearFit()
print("Testing SPANC fitting routine using test data and linear function...")
my_fit.RunFit(x, y, dy, dx)
print("Results from fit with y-errors: param[0] =",my_fit.parameters[0],"param[1] =",my_fit.parameters[1],"Reduced chi-sq =",my_fit.ReducedChiSquare())
if __name__ == '__main__':
main()

335
spanc/IngoFit.py Executable file
View File

@ -0,0 +1,335 @@
#!/usr/bin/env python3
import numpy as np
class FitFunction :
def __init__(self, nparams, numericFlag=True):
self.nparams = nparams
self.numericFlag = numericFlag
def Evaluate(self, x, params):
return None
def EvaluateParamDeriv(self, x, params, this_param):
return None
def EvaluateDeriv(self, x, params):
return None
def NumericDeriv(self, x, y, dx, params):
return (self.Evaluate(x+dx, params) - y)/dx
def NumericParamDeriv(self, x, y, params, this_param, dpar):
par_val = params[this_param]
params[this_param] += dpar
value = (self.Evaluate(x, params) - y)/dpar
params[this_param] = par_val
return value
class SpancFit:
PRECISION = 1e-6
MAX_ITERS = 100
def __init__(self, func) :
self.function = func
self.nparams = func.nparams
self.numericFlag = func.numericFlag
self.covMatrix = np.zeros((self.nparams, self.nparams))
self.xErrorFlag = False
self.chiSq = 0
self.ndf = 0
self.redChiSq = 0
self.Lambda = 0.1
self.parameters = np.zeros(self.nparams)
self.x_data = np.zeros(0)
self.y_data = np.zeros(0)
self.y_errors = np.zeros(0)
self.x_errors = np.zeros(0)
self.x_incr = np.zeros(0)
self.param_incr = np.zeros(self.nparams)
self.ndata = 0
def EvaluateFunction(self, x):
return self.function.Evaluate(x, self.parameters)
def SetParamIncrement(self, func_vals):
vor = 0.0
nach = 0.0
zoom = 0
index = 0
for i in range(len(self.parameters)):
zoom = 0
self.param_incr[i] = abs(self.parameters[i]*1e-3)
if self.param_incr[i] == 0.0:
self.param_incr[i] = 1e-3
for j in range(5):
index = j*self.ndata/5
nach = self.function.NumericParamDeriv(self.x_data[index], func_vals[index], self.parameters, i, self.param_incr[i])
vor = 0
while zoom <= 4 and abs(nach - vor) >= vor:
zoom += 1
vor = nach
self.param_incr[i] *= 0.1
nach = self.function.NumericParamDeriv(self.x_data[index], func_vals[index], self.parameters, i, self.param_incr[i])
self.param_incr[i]*10.0
def SetXIncrement(self, func_vals):
vor = 0.0
nach = 0.0
zoom = 0
for i in range(self.ndata):
zoom = 0
self.x_incr[i] = abs(self.x_data[i]*1e-3)
if self.x_incr[i] == 0.0:
self.x_incr[i] = 1e-3
nach = self.function.NumericDeriv(self.x_data[i], func_vals[i], self.x_incr[i], self.parameters)
vor = 0
while zoom <= 4 and abs(nach - vor) >= vor:
zoom += 1
vor = nach
self.x_incr[i] *= 0.1
nach = self.function.NumericDeriv(self.x_data[index], func_vals[index], self.x_incr[i], self.parameters)
self.x_incr[i]*10.0
def Fit(self, x_array, y_array, yerr_array, xerr_array=np.empty(0)):
self.x_data = x_array
self.y_data = y_array
self.y_errors = yerr_array
self.x_errors = xerr_array
self.ndata = len(self.x_data)
self.x_incr = np.zeros(self.ndata)
self.ndf = self.ndata - self.nparams
if len(xerr_array) == 0:
self.xErrorFlag = False
else:
self.xErrorFlag = True
self.CurveFit()
def GetParameterError(self, param_index):
return np.sqrt(abs(self.covMatrix[param_index][param_index]))
def CalculateChiSquare(self, func_vals):
y_eff_errors = np.zeros(self.ndata)
if self.numericFlag:
if self.xErrorFlag:
for i in range(self.ndata):
y_eff_errors[i] = np.sqrt(self.y_errors[i]**2.0 + (self.x_errors[i]*self.function.NumericDeriv(self.x_data[i], func_vals[i], self.x_incr[i], self.parameters))**2.0)
else:
y_eff_errors = self.y_errors
elif self.xErrorFlag:
for i in range(self.ndata):
y_eff_errors[i] = np.sqrt(self.y_errors[i]**2.0 + (self.x_errors[i]*self.function.EvaluateDeriv(self.x_data[i], self.parameters))**2.0)
else:
y_eff_errors = self.y_errors
chisq = np.sum(((self.y_data - func_vals)/y_eff_errors)**2.0)
return chisq, y_eff_errors
def CurveFit(self):
hauptschritt=0
sub_iters=0
residuum = np.zeros(self.ndata)
y = 0.0
dy = 0.0
chi_lastmain = 0.0
chi_lastsub = 0.0
chi_fit = 0.0
schlechter = True
b = np.zeros(self.nparams)
abl = np.zeros(self.nparams)
norm = np.zeros(self.nparams)
func_vals = np.zeros(self.ndata)
y_eff_errors = np.zeros(self.ndata)
a = np.zeros((self.nparams, self.nparams))
afaktor = np.zeros((self.nparams, self.nparams))
inv = np.zeros((self.nparams, self.nparams))
for i in range(self.ndata):
func_vals[i] = self.function.Evaluate(self.x_data[i], self.parameters)
if self.numericFlag:
self.SetParamIncrement(func_vals)
if self.xErrorFlag:
self.SetXIncrement(func_vals)
chi_lastsub, y_eff_errors = self.CalculateChiSquare(func_vals)
chi_lastmain = chi_lastsub
temp_params = np.zeros(self.nparams)
#Main optimization loop
while True:
chi_fit = chi_lastmain
for i in range(self.nparams):
for j in range(self.nparams):
a[i][j] = 0.0
b[i] = 0.0
residuum = (self.y_data - func_vals)/(y_eff_errors**2.0)
for i in range(self.ndata):
for j in range(self.nparams):
if(self.numericFlag):
abl[j] = self.function.NumericParamDeriv(self.x_data[i], func_vals[i], self.parameters, j, param_incr[j])
else:
abl[j] = self.function.EvaluateParamDeriv(self.x_data[i], self.parameters, j)
b[j] += abl[j]*residuum[i]
for j in range(self.nparams):
for k in range(self.nparams):
a[j][k] += abl[j]*abl[k]/(y_eff_errors[i]**2.0)
for i in range(self.nparams):
if a[i][i] < 1e-15:
a[i][i] = 1e-15
norm[i] = np.sqrt(a[i][i])
temp_params = self.parameters
#sub-loop looking for the best next step
sub_iters=0
while True:
chi_lastsub = 0.0
for i in range(self.nparams):
for j in range(self.nparams):
afaktor[i][j] = a[i][j]/(norm[i]*norm[j])
afaktor[i][i] = 1.0 + self.Lambda
inv = np.linalg.inv(afaktor)
for i in range(self.nparams):
for j in range(self.nparams):
self.parameters[i] += b[j]*inv[i][j]/(norm[i]*norm[j])
for i in range(self.ndata):
func_vals[i] = self.function.Evaluate(self.x_data[i], self.parameters)
chi_lastsub, y_eff_errors = self.CalculateChiSquare(func_vals)
schlechter = (chi_lastsub - chi_lastmain > 1e-5) and self.Lambda != 0
sub_iters += 1
if sub_iters > self.MAX_ITERS:
break
elif schlechter:
self.parameters = temp_params
self.Lambda *= 10.0
else:
break
#end sub-loop
chi_lastmain = chi_lastsub
if self.numericFlag:
self.SetParamIncrement(func_vals)
self.Lambda *= 0.1
hauptschritt += 1
if abs(chi_fit - chi_lastmain) < self.PRECISION*chi_fit or hauptschritt > self.MAX_ITERS or self.Lambda == 0.0:
break
#end main loop
for i in range(self.nparams):
for j in range(self.nparams):
afaktor[i][j] = a[i][j]/(norm[i]*norm[j])
self.covMatrix = np.linalg.inv(afaktor)
self.chiSq = chi_lastmain
if self.ndata > self.nparams:
self.redChiSq = self.chiSq/self.ndf
else:
self.redChiSq = 0.0
return hauptschritt
class LinearFunction(FitFunction):
def __init__(self):
super().__init__(2, numericFlag=False)
def Evaluate(self, x, params):
return params[0] + x*params[1]
def EvaluateDeriv(self, x, params):
return params[1]
def EvaluateParamDeriv(self, x, params, this_param):
if this_param == 0:
return 1.0
elif this_param == 1:
return x
else:
return 0.0
class QuadraticFunction(FitFunction):
def __init__(self):
super().__init__(3, numericFlag=False)
def Evaluate(self, x, params):
return params[0] + x*params[1] + (x**2.0)*params[2]
def EvaluateDeriv(self, x, params):
return params[1]+2.0*x*params[2]
def EvaluateParamDeriv(self, x, params, this_param):
if this_param == 0:
return 1.0
elif this_param == 1:
return x
elif this_param == 2:
return x**2.0
else:
return 0.0
class CubicFunction(FitFunction):
def __init__(self):
super().__init__(4, numericFlag=False)
def Evaluate(self, x, params):
return params[0] + x*params[1] + (x**2.0)*params[2] + (x**3.0)*params[3]
def EvaluateDeriv(self, x, params):
return params[1]+2.0*x*params[2]+3.0*(x**2.0)*params[3]
def EvaluateParamDeriv(self, x, params, this_param):
if this_param == 0:
return 1.0
elif this_param == 1:
return x
elif this_param == 2:
return x**2.0
elif this_param == 3:
return x**3.0
else:
return 0.0
def main():
ndata = 100
x = np.zeros(ndata)
y = np.zeros(ndata)
dy = np.zeros(ndata)
dx = np.zeros(ndata)
for i in range(ndata):
x[i] = i
y[i] = i+0.0001*i*i+7.0
dy[i] = 0.1
dx[i] = 0.1
fit_func = LinearFunction()
my_fit = SpancFit(fit_func)
my_fit.parameters[0] = 1.0
my_fit.parameters[1] = 1.0
print("Testing SPANC fitting routine using test data and linear function...")
my_fit.Fit(x, y, dy)
print("Results from fit with y-errors: param[0] =",my_fit.parameters[0],"param[1] =",my_fit.parameters[1],"Reduced chi-sq =",my_fit.redChiSq)
if __name__ == '__main__':
main()

133
spanc/LayeredTarget.py Normal file
View File

@ -0,0 +1,133 @@
#!/bin/usr/env python3
import numpy as np
import EnergyLoss as eloss
class Target:
def __init__(self):
self.thickness=0
self.Z = np.empty(0)
self.A = np.empty(0)
self.S = np.empty(0)
self.energy_loss = eloss.EnergyLoss()
def SetElements(self, z, a, s, thick):
self.Z = z
self.A = a
self.S = s
self.thickness = thick
self.energy_loss.SetTargetData(self.Z, self.A, self.S)
def GetComposition(self):
comp_string = "("
for i in range(len(self.Z)):
comp_string += "("+ str(self.Z[i]) +","+ str(self.A[i]) +","+ str(self.S[i]) +")"
if not(i == len(self.Z)-1):
comp_string += ","
comp_string += ")"
return comp_string
def HasElement(self, z, a):
for i in range(len(self.Z)):
if self.Z[i] == z and self.A[i] == a:
return True
return False
def GetEnergyLossTotal(self, zp, ap, start_energy, theta):
if theta == np.pi/2.0:
return start_energy
elif theta > np.pi/2.0:
return self.energy_loss.GetEnergyLoss(zp, ap, start_energy, self.thickness/abs(np.cos(np.pi - theta)))
else:
return self.energy_loss.GetEnergyLoss(zp, ap, start_energy, self.thickness/abs(np.cos(theta)))
def GetEnergyLossHalf(self, zp, ap, start_energy, theta):
if theta == np.pi/2.0:
return start_energy
elif theta > np.pi/2.0:
return self.energy_loss.GetEnergyLoss(zp, ap, start_energy, self.thickness/abs(2.0*np.cos(np.pi - theta)))
else:
return self.energy_loss.GetEnergyLoss(zp, ap, start_energy, self.thickness/abs(2.0*np.cos(theta)))
def GetReverseEnergyLossTotal(self, zp, ap, final_energy, theta):
if theta == np.pi/2.0:
return final_energy
elif theta > np.pi/2.0:
return self.energy_loss.GetReverseEnergyLoss(zp, ap, final_energy, self.thickness/abs(np.cos(np.pi - theta)))
else:
return self.energy_loss.GetReverseEnergyLoss(zp, ap, final_energy, self.thickness/abs(np.cos(theta)))
def GetReverseEnergyLossHalf(self, zp, ap, final_energy, theta):
if theta == np.pi/2.0:
return final_energy
elif theta > np.pi/2.0:
return self.energy_loss.GetReverseEnergyLoss(zp, ap, final_energy, self.thickness/abs(2.0*np.cos(np.pi - theta)))
else:
return self.energy_loss.GetReverseEnergyLoss(zp, ap, final_energy, self.thickness/abs(2.0*np.cos(theta)))
class LayeredTarget:
def __init__(self):
self.targets = [] #Order of layers matters!
self.name = ''
def AddLayer(self, z, a, s, thick):
targ = Target()
targ.SetElements(z, a, s, thick)
self.targets.append(targ)
def AddLayer(self, targ):
if not isinstance(targ, Target) :
print("Cannot add layer if it is not of type Target!")
return
else :
self.targets.append(targ)
def FindLayerContainingElement(self, z, a):
for i in range(len(self.targets)):
if self.targets[i].HasElement(z, a):
return i
return -1
def GetEnergyLoss(self, zp, ap, initial_energy, theta, rxn_layer, kind="projectile"):
if rxn_layer < 0 or rxn_layer > len(self.targets):
print("Bad reaction layer at LayeredTarget::GetEnergyLoss")
return 0.0
e_lost = 0.0
new_energy = initial_energy
if kind == "projectile":
for i in range(rxn_layer+1):
if i == rxn_layer:
e_lost += self.targets[i].GetEnergyLossHalf(zp, ap, new_energy, theta)
new_energy = initial_energy - e_lost
else:
e_lost += self.targets[i].GetEnergyLossTotal(zp, ap, new_energy, theta)
new_energy = initial_energy - e_lost
elif kind == "ejectile":
for i in range(rxn_layer, len(self.targets)):
if i == rxn_layer:
e_lost += self.targets[i].GetEnergyLossHalf(zp, ap, new_energy, theta)
new_energy = initial_energy - e_lost
else:
e_lost = self.targets[i].GetEnergyLossTotal(zp, ap, new_energy, theta)
new_energy = initial_energy - e_lost
else:
print("Invalid kind at LayeredTarget::GetEnergyLoss")
return e_lost
def GetReverseEnergyLoss(self, zp, ap, final_energy, theta, rxn_layer):
if rxn_layer < 0 or rxn_layer > len(self.targets):
print("Bad reaction layer at LayeredTarget::GetReverseEnergyLoss")
return 0.0
e_lost = 0.0
new_energy = final_energy
for i in reversed(range(rxn_layer, len(self.targets))):
if i == rxn_layer:
e_lost += self.targets[i].GetReverseEnergyLossHalf(zp, ap, new_energy, theta)
new_energy = final_energy + e_lost
else:
e_lost += self.targets[i].GetReverseEnergyLossTotal(zp, ap, new_energy, theta)
new_energy = final_energy + e_lost
return e_lost

70
spanc/NucData.py Executable file
View File

@ -0,0 +1,70 @@
#!/usr/bin/env python3
import numpy as np
import requests
import lxml.html as xhtml
class MassTable:
def __init__(self):
file = open("./etc/mass.txt","r")
self.mtable = {}
u2mev = 931.4940954
me = 0.000548579909
self.etable = {}
file.readline()
file.readline()
for line in file:
entries = line.split()
n = entries[0]
z = entries[1]
a = entries[2]
element = entries[3]
massBig = float(entries[4])
massSmall = float(entries[5])
key = '('+z+','+a+')'
value = ((massBig+massSmall*1e-6) - float(z)*me)*u2mev
self.mtable[key] = value
self.etable[key] = element
file.close()
def GetMass(self, z, a):
key = '('+str(z)+','+str(a)+')'
if key in self.mtable:
return self.mtable[key]
else:
return 0
def GetSymbol(self, z, a):
key = '('+str(z)+','+str(a)+')'
if key in self.etable:
return str(a)+self.etable[key]
else:
return 'none'
Masses = MassTable()
def GetExcitations(symbol):
levels = np.array(np.empty(0))
text = ''
site = requests.get("https://www.nndc.bnl.gov/nudat2/getdatasetClassic.jsp?nucleus="+symbol+"&unc=nds")
contents = xhtml.fromstring(site.content)
tables = contents.xpath("//table")
rows = tables[2].xpath("./tr")
for row in rows[1:-2]:
entries = row.xpath("./td")
if len(entries) != 0:
entry = entries[0]
data = entry.xpath("./a")
if len(data) == 0:
text = entry.text
else:
text = data[0].text
text = text.replace('?', '')
text = text.replace('\xa0\xa0','')
levels = np.append(levels, float(text)/1000.0)
return levels

90
spanc/Reaction.py Normal file
View File

@ -0,0 +1,90 @@
#!/usr/bin/env python3
from LayeredTarget import LayeredTarget, Target
from NucData import Masses
import numpy as np
class Nucleus:
def __init__(self, z, a):
self.Z = z
self.A = a
self.Symbol = Masses.GetSymbol(self.Z, self.A)
self.GSMass = Masses.GetMass(self.Z, self.A)
def Minus(self, rhs):
final_Z = self.Z - rhs.Z
final_A = self.A - rhs.A
if final_A < 0 or final_Z < 0:
print("Illegal minus operation on Nuclei!")
return Nucleus(0,0)
else:
return Nucleus(final_Z, final_A)
def Plus(self, rhs):
return Nucleus(self.Z + rhs.Z, self.A + rhs.A)
class Reaction:
DEG2RAD = np.pi/180.0 #degrees to radians
C = 299792458 #speed of light m/s
QBRHO2P = 1.0E-9*C #Converts qbrho to p (kG*cm -> MeV/c)
def __init__(self, zt, at, zp, ap, ze, ae, beamKE, theta, bfield, tdata):
self.Target = Nucleus(zt, at)
self.Projectile = Nucleus(zp, ap)
self.Ejectile = Nucleus(ze, ae)
self.Residual = (self.Target.Plus(self.Projectile)).Minus(self.Ejectile)
self.BKE = beamKE
self.Theta = theta * self.DEG2RAD
self.Bfield = bfield
self.Name = self.Target.Symbol +"("+ self.Projectile.Symbol +","+ self.Ejectile.Symbol +")"+ self.Residual.Symbol
self.target_data = tdata
self.rxn_layer = self.target_data.FindLayerContainingElement(self.Target.Z, self.Target.A)
def GetBKEAtRxn(self):
return self.BKE - self.target_data.GetEnergyLoss(self.Projectile.Z, self.Projectile.A, self.BKE, self.Theta, self.rxn_layer)
def GetEjectileKineticEnergyAtRxn(self, Elevel) :
Q = self.Target.GSMass + self.Projectile.GSMass - (self.Ejectile.GSMass + self.Residual.GSMass + Elevel)
Ethresh = -Q*(self.Ejectile.GSMass+self.Residual.GSMass)/(self.Ejectile.GSMass + self.Residual.GSMass - self.Projectile.GSMass)
BKE_rxn = self.GetBKEAtRxn()
if BKE_rxn < Ethresh:
return 0.0
term1 = np.sqrt(self.Projectile.GSMass*self.Ejectile.GSMass*BKE_rxn)/(self.Ejectile.GSMass + self.Residual.GSMass)*np.cos(self.Theta)
term2 = (BKE_rxn*(self.Residual.GSMass - self.Projectile.GSMass) + self.Residual.GSMass*Q)/(self.Ejectile.GSMass + self.Residual.GSMass)
ke1 = term1 + np.sqrt(term1**2.0 + term2)
ke2 = term1 - np.sqrt(term1**2.0 + term2)
if ke1 > 0:
return ke1**2.0
else :
return ke2**2.0
def GetEjectileKineticEnergyAtDet(self, Elevel):
KE_at_rxn = self.GetEjectileKineticEnergyAtRxn(Elevel)
KE_at_det = KE_at_rxn - self.target_data.GetEnergyLoss(self.Ejectile.Z, self.Ejectile.A, KE_at_rxn, self.Theta, self.rxn_layer, kind="ejectile")
return KE_at_det
def GetEjectileRho(self, Elevel):
KE_at_det = self.GetEjectileKineticEnergyAtDet(Elevel)
p = np.sqrt(KE_at_det*(KE_at_det + 2.0*self.Ejectile.GSMass))
qbrho = p/self.QBRHO2P
return qbrho/(self.Ejectile.Z*self.Bfield)
def GetResidualExcitation(self, rho):
p_eject_at_det = rho*self.Ejectile.Z*self.Bfield*self.QBRHO2P
KE_eject_at_det = np.sqrt(p_eject_at_det**2.0 + self.Ejectile.GSMass**2.0) - self.Ejectile.GSMass
KE_eject_at_rxn = KE_eject_at_det + self.target_data.GetReverseEnergyLoss(self.Ejectile.Z, self.Ejectile.A, KE_eject_at_det, self.Theta, self.rxn_layer)
p_eject_at_rxn = np.sqrt(KE_eject_at_rxn*(KE_eject_at_rxn + 2.0*self.Ejectile.GSMass))
E_eject_at_rxn = KE_eject_at_rxn+self.Ejectile.GSMass
BKE_atRxn = self.GetBKEAtRxn()
E_project = BKE_atRxn + self.Projectile.GSMass
p_project = np.sqrt(BKE_atRxn*(BKE_atRxn + 2.0*self.Projectile.GSMass))
E_resid = E_project + self.Target.GSMass - E_eject_at_rxn
p2_resid = p_project**2.0 + p_eject_at_rxn**2.0 - 2.0*p_project*p_eject_at_rxn*np.cos(self.Theta)
m_resid = np.sqrt(E_resid**2.0 - p2_resid)
return m_resid - self.Residual.GSMass
def ChangeReactionParameters(self, bke, theta, bf) :
self.BKE = bke
self.Theta = theta*self.DEG2RAD
self.Bfield = bf

161
spanc/Spanc.py Normal file
View File

@ -0,0 +1,161 @@
#!/usr/bin/env python3
from Reaction import Reaction
from LayeredTarget import LayeredTarget, Target
from IngoFit import SpancFit, LinearFunction, QuadraticFunction, CubicFunction
from Fitter import LinearFit, QuadraticFit, CubicFit
import numpy as np
class Peak :
def __init__(self):
self.Ex = 0.0
self.uEx = 0.0
self.x = 0.0
self.ux_sys = 0.0
self.ux_stat = 0.0
self.rho = 0.0
self.urho = 0.0
self.fwhm_x = 0.0
self.ufwhm_x = 0.0
self.fwhm_Ex = 0.0
self.ufwhm_Ex = 0.0
self.reaction = ""
class Spanc:
def __init__(self):
self.reactions = {}
self.targets = {}
self.calib_peaks = {}
self.output_peaks = {}
self.fitters = {}
self.InitFits()
def WriteConfig(self):
return
def ReadConfig(self):
return
def InitFits(self):
"""
self.fitters["linear"] = SpancFit(LinearFunction())
self.fitters["quadratic"] = SpancFit(QuadraticFunction())
self.fitters["cubic"] = SpancFit(CubicFunction())
"""
self.fitters["linear"] = LinearFit()
self.fitters["quadratic"] = QuadraticFit()
self.fitters["cubic"] = CubicFit()
def PerformFits(self):
xarray = np.empty(0)
yarray = np.empty(0)
uxarray = np.empty(0)
uyarray = np.empty(0)
for peak in self.calib_peaks.values():
xarray = np.append(xarray, peak.x)
uxarray = np.append(uxarray, np.sqrt(peak.ux_sys**2.0 + peak.ux_stat**2.0))
yarray = np.append(yarray, peak.rho)
uyarray = np.append(uyarray, peak.urho)
for key in self.fitters.keys():
#self.fitters[key].Fit(xarray, yarray, uyarray, uxarray)
self.fitters[key].RunFit(xarray, yarray, uyarray, uxarray)
return xarray, yarray, uxarray, uyarray
def CalculateResiduals(self, fit_name):
fit = self.fitters[fit_name]
npeaks = len(self.calib_peaks)
resids = np.empty(npeaks)
student_resids = np.empty(npeaks)
xarray = np.empty(npeaks)
counter=0
for peak in self.calib_peaks.values():
fval = fit.EvaluateFunction(peak.x)
dval = peak.rho
resids[counter] = (dval - fval)
xarray[counter] = peak.x
counter += 1
mean_x = np.average(xarray)/npeaks
rmse = np.sqrt(np.sum(resids**2.0)/fit.GetNDF())
#get leverage
counter=0
sq_diff=0
for peak in self.calib_peaks.values():
sq_diff += (peak.x - mean_x)**2.0
sq_diff = sq_diff/npeaks
for peak in self.calib_peaks.values():
leverage = 1.0/npeaks + (peak.x - mean_x)/sq_diff
student_resids[counter] = resids[counter]/(rmse*np.sqrt(1.0 - leverage))
counter += 1
return xarray, resids, student_resids
def AddCalibrationPeak(self, rxn_name, cal_name, position, ux_stat, ux_sys, ex, uex) :
new_peak = Peak()
new_peak.x = position
new_peak.reaction = rxn_name
new_peak.ux_stat = ux_stat
new_peak.ux_sys = ux_sys
new_peak.Ex = ex
new_peak.uEx = uex
new_peak.rho = self.reactions[rxn_name].GetEjectileRho(ex)
new_peak.urho = abs(self.reactions[rxn_name].GetEjectileRho(ex+uex)-new_peak.rho)
self.calib_peaks[cal_name] = new_peak
def AddOutputPeak(self, rxn_name, out_name, position, ux_stat, ux_sys, fwhm_x, ufwhm_x) :
new_peak = Peak()
new_peak.x = position
new_peak.ux_stat = ux_stat
new_peak.ux_sys = ux_sys
new_peak.reaction = rxn_name
new_peak.fwhm_x = fwhm_x
new_peak.ufwhm_x = ufwhm_x
self.output_peaks[out_name] = new_peak
def CalculateRhoUncertainty(self, peak, fit, deltax=0.0, udeltax=0.0):
urho = 0
for i in range(len(fit.parameters)):
urho += (fit.EvaluateFunctionParamDeriv(peak.x+deltax, i)*fit.GetParameterError(i))**2.0
urho += (fit.EvaluateFunctionDeriv(peak.x+deltax)*np.sqrt(peak.ux_stat**2.0 + peak.ux_sys**2.0 + udeltax**2.0))**2.0
urho = np.sqrt(urho)
return urho
def CalculateOutputs(self, fit_name):
fit = self.fitters[fit_name]
for output in self.output_peaks.values():
output.rho = fit.EvaluateFunction(output.x)
output.urho = self.CalculateRhoUncertainty(output, fit)
output.Ex = self.reactions[output.reaction].GetResidualExcitation(output.rho)
output.uEx = abs(self.reactions[output.reaction].GetResidualExcitation(output.rho + output.urho) - output.Ex)
if output.fwhm_x == 0:
output.fwhm_Ex = 0
output.ufwhm_Ex = 0
else:
rhoLo = fit.EvaluateFunction(output.x - output.fwhm_x/2.0)
urhoLo = self.CalculateRhoUncertainty(output, fit, deltax=-1.0*output.fwhm_x/2.0, udeltax=output.ufwhm_x/2.0)
rhoHi = fit.EvaluateFunction(output.x + output.fwhm_x/2.0)
urhoHi = self.CalculateRhoUncertainty(output, fit, deltax=output.fwhm_x/2.0, udeltax=output.ufwhm_x/2.0)
exLo = self.reactions[output.reaction].GetResidualExcitation(rhoLo)
uexLo = abs(self.reactions[output.reaction].GetResidualExcitation(rhoLo+urhoLo) - exLo)
exHi = self.reactions[output.reaction].GetResidualExcitation(rhoHi)
uexHi = abs(self.reactions[output.reaction].GetResidualExcitation(rhoHi+urhoHi) - exHi)
output.fwhm_Ex = abs(exHi - exLo)
output.ufwhm_Ex = output.ufwhm_x/output.fwhm_x*output.fwhm_Ex
def CalculateCalibrations(self):
for peak in self.calib_peaks.values():
peak.rho = self.reactions[peak.reaction].GetEjectileRho(peak.Ex)
peak.urho = abs(self.reactions[peak.reaction].GetEjectileRho(peak.Ex+peak.uEx) - peak.rho)

892
spanc/SpancGUI.py Executable file
View File

@ -0,0 +1,892 @@
#!/usr/bin/env python3
import Spanc as spc
from LayeredTarget import LayeredTarget, Target
from Reaction import Reaction
import sys
from qtpy.QtWidgets import QApplication, QWidget, QMainWindow
from qtpy.QtWidgets import QLabel, QMenuBar, QAction
from qtpy.QtWidgets import QHBoxLayout, QVBoxLayout, QGridLayout, QGroupBox
from qtpy.QtWidgets import QPushButton, QButtonGroup, QRadioButton
from qtpy.QtWidgets import QSpinBox, QDoubleSpinBox, QComboBox
from qtpy.QtWidgets import QDialog, QFileDialog, QDialogButtonBox
from qtpy.QtWidgets import QTableWidget, QTableWidgetItem
from qtpy.QtWidgets import QLineEdit, QTabWidget, QFormLayout
from qtpy.QtCore import Signal
import matplotlib as mpl
import pickle as pickle
import numpy as np
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
from matplotlib.figure import Figure
class MPLCanvas(FigureCanvasQTAgg):
def __init__(self, parent=None, width=5, height=4, dpi=100):
self.fig = Figure(figsize=(width, height), dpi=dpi, edgecolor="black",linewidth=0.5,constrained_layout=True)
self.axes = self.fig.add_subplot(111)
self.axes.spines['top'].set_visible(False)
super(MPLCanvas, self).__init__(self.fig)
class TargetDialog(QDialog):
new_target = Signal(list, str)
def __init__(self, parent=None, target=None):
super().__init__(parent)
self.setWindowTitle("Add A Target")
nameLabel = QLabel("Target name", self)
self.nameInput = QLineEdit(self)
QBtn = QDialogButtonBox.Ok | QDialogButtonBox.Cancel
self.buttonBox = QDialogButtonBox(QBtn)
self.buttonBox.accepted.connect(self.accept)
self.buttonBox.accepted.connect(self.SendTarget)
self.buttonBox.rejected.connect(self.reject)
self.layout = QVBoxLayout()
self.setLayout(self.layout)
self.layout.addWidget(nameLabel)
self.layout.addWidget(self.nameInput)
self.CreateTargetInputs()
if target is not None:
self.SetInitialValues(target)
self.layout.addWidget(self.buttonBox)
def CreateTargetInputs(self):
self.layerAInputs = []
self.layerZInputs = []
self.layerSInputs = []
self.layerThickInputs = []
self.layer1GroupBox = QGroupBox("Layer 1", self)
layer1Layout = QVBoxLayout()
thick1Label = QLabel("Thickness(ug/cm^2)", self.layer1GroupBox)
self.layerThickInputs.append(QDoubleSpinBox(self.layer1GroupBox))
self.layerThickInputs[0].setRange(0, 999.0)
self.layerThickInputs[0].setDecimals(4)
self.layer1ComponentsBox = QGroupBox("Layer 1 Components", self.layer1GroupBox)
layer1compLayout = QGridLayout()
layer1compLayout.addWidget(QLabel("Z", self.layer1ComponentsBox), 0, 1)
layer1compLayout.addWidget(QLabel("A", self.layer1ComponentsBox), 0, 2)
layer1compLayout.addWidget(QLabel("Stoich", self.layer1ComponentsBox), 0, 3)
for i in range(3):
layer1compLayout.addWidget(QLabel("Component"+str(i), self.layer1ComponentsBox), i+1, 0)
self.layerZInputs.append(QSpinBox(self.layer1ComponentsBox))
self.layerAInputs.append(QSpinBox(self.layer1ComponentsBox))
self.layerSInputs.append(QSpinBox(self.layer1ComponentsBox))
layer1compLayout.addWidget(self.layerZInputs[i], i+1, 1)
layer1compLayout.addWidget(self.layerAInputs[i], i+1, 2)
layer1compLayout.addWidget(self.layerSInputs[i], i+1, 3)
self.layer1ComponentsBox.setLayout(layer1compLayout)
layer1Layout.addWidget(thick1Label)
layer1Layout.addWidget(self.layerThickInputs[0])
layer1Layout.addWidget(self.layer1ComponentsBox)
self.layer1GroupBox.setLayout(layer1Layout)
self.layer2GroupBox = QGroupBox("Layer 2", self)
layer2Layout = QVBoxLayout()
thick2Label = QLabel("Thickness(ug/cm^2)", self.layer2GroupBox)
self.layerThickInputs.append(QDoubleSpinBox(self.layer2GroupBox))
self.layerThickInputs[1].setRange(0, 999.0)
self.layerThickInputs[1].setDecimals(4)
self.layer2ComponentsBox = QGroupBox("Layer 2 Components", self.layer2GroupBox)
layer2compLayout = QGridLayout()
layer2compLayout.addWidget(QLabel("Z", self.layer2ComponentsBox), 0, 1)
layer2compLayout.addWidget(QLabel("A", self.layer2ComponentsBox), 0, 2)
layer2compLayout.addWidget(QLabel("Stoich", self.layer2ComponentsBox), 0, 3)
for i in range(3):
layer2compLayout.addWidget(QLabel("Component"+str(i), self.layer2ComponentsBox), i+1, 0)
self.layerZInputs.append(QSpinBox(self.layer2ComponentsBox))
self.layerAInputs.append(QSpinBox(self.layer2ComponentsBox))
self.layerSInputs.append(QSpinBox(self.layer2ComponentsBox))
layer2compLayout.addWidget(self.layerZInputs[i+3], i+1, 1)
layer2compLayout.addWidget(self.layerAInputs[i+3], i+1, 2)
layer2compLayout.addWidget(self.layerSInputs[i+3], i+1, 3)
self.layer2ComponentsBox.setLayout(layer2compLayout)
layer2Layout.addWidget(thick2Label)
layer2Layout.addWidget(self.layerThickInputs[1])
layer2Layout.addWidget(self.layer2ComponentsBox)
self.layer2GroupBox.setLayout(layer2Layout)
self.layer3GroupBox = QGroupBox("Layer 3", self)
layer3Layout = QVBoxLayout()
thick3Label = QLabel("Thickness(ug/cm^2)", self.layer3GroupBox)
self.layerThickInputs.append(QDoubleSpinBox(self.layer3GroupBox))
self.layerThickInputs[2].setRange(0, 999.0)
self.layerThickInputs[2].setDecimals(4)
self.layer3ComponentsBox = QGroupBox("Layer 3 Components", self.layer3GroupBox)
layer3compLayout = QGridLayout()
layer3compLayout.addWidget(QLabel("Z", self.layer3ComponentsBox), 0, 1)
layer3compLayout.addWidget(QLabel("A", self.layer3ComponentsBox), 0, 2)
layer3compLayout.addWidget(QLabel("Stoich", self.layer3ComponentsBox), 0, 3)
for i in range(3):
layer3compLayout.addWidget(QLabel("Component"+str(i), self.layer3ComponentsBox), i+1, 0)
self.layerZInputs.append(QSpinBox(self.layer3ComponentsBox))
self.layerAInputs.append(QSpinBox(self.layer3ComponentsBox))
self.layerSInputs.append(QSpinBox(self.layer3ComponentsBox))
layer3compLayout.addWidget(self.layerZInputs[i+6], i+1, 1)
layer3compLayout.addWidget(self.layerAInputs[i+6], i+1, 2)
layer3compLayout.addWidget(self.layerSInputs[i+6], i+1, 3)
self.layer3ComponentsBox.setLayout(layer3compLayout)
layer3Layout.addWidget(thick3Label)
layer3Layout.addWidget(self.layerThickInputs[2])
layer3Layout.addWidget(self.layer3ComponentsBox)
self.layer3GroupBox.setLayout(layer3Layout)
self.layout.addWidget(self.layer1GroupBox)
self.layout.addWidget(self.layer2GroupBox)
self.layout.addWidget(self.layer3GroupBox)
def SetInitialValues(self, target):
self.nameInput.setText(target.name)
self.nameInput.setReadOnly(True)
for i in range(len(target.targets)):
self.layerThickInputs[i].setValue(target.targets[i].thickness)
for j in range(len(target.targets[i].Z)):
self.layerZInputs[j+i*3].setValue(target.targets[i].Z[j])
self.layerAInputs[j+i*3].setValue(target.targets[i].A[j])
self.layerSInputs[j+i*3].setValue(target.targets[i].S[j])
def SendTarget(self):
name = self.nameInput.text()
if name == "":
return
t1 = Target()
t2 = Target()
t3 = Target()
tlist = []
Z1 = []
A1 = []
S1 = []
Z2 = []
A2 = []
S2 = []
Z3 = []
A3 = []
S3 = []
z = 0
a = 0
s = 0
thick1 = self.layerThickInputs[0].value()
thick2 = self.layerThickInputs[1].value()
thick3 = self.layerThickInputs[2].value()
for i in range(3):
z = self.layerZInputs[i].value()
a = self.layerAInputs[i].value()
s = self.layerSInputs[i].value()
if z != 0 and a != 0 and s != 0:
Z1.append(z)
A1.append(a)
S1.append(s)
z = self.layerZInputs[i+3].value()
a = self.layerAInputs[i+3].value()
s = self.layerSInputs[i+3].value()
if z != 0 and a != 0 and s != 0:
Z2.append(z)
A2.append(a)
S2.append(s)
z = self.layerZInputs[i+6].value()
a = self.layerAInputs[i+6].value()
s = self.layerSInputs[i+6].value()
if z != 0 and a != 0 and s != 0:
Z3.append(z)
A3.append(a)
S3.append(s)
if len(Z1) != 0:
t1.SetElements(Z1, A1, S1, thick1)
tlist.append(t1)
if len(Z2) != 0:
t2.SetElements(Z2, A2, S2, thick2)
tlist.append(t2)
if len(Z3) != 0:
t3.SetElements(Z3, A3, S3, thick3)
tlist.append(t3)
if len(tlist) != 0:
self.new_target.emit(tlist, name)
class ReactionDialog(QDialog):
new_reaction = Signal(int, int, int, int, int, int, float, float, float, str)
update_reaction = Signal(float, float, float, str)
def __init__(self, parent=None, rxn=None, rxnKey=None):
super().__init__(parent)
self.setWindowTitle("Add A Reaction")
tnameLabel = QLabel("Target Name", self)
self.targetNameBox = QComboBox(self)
for target in parent.spanc.targets:
self.targetNameBox.addItem(target)
QBtn = QDialogButtonBox.Ok | QDialogButtonBox.Cancel
self.buttonBox = QDialogButtonBox(QBtn)
self.buttonBox.accepted.connect(self.accept)
if rxn is not None:
self.buttonBox.accepted.connect(self.SendReactionUpdate)
else:
self.buttonBox.accepted.connect(self.SendReaction)
self.buttonBox.rejected.connect(self.reject)
self.layout = QVBoxLayout()
self.setLayout(self.layout)
self.layout.addWidget(tnameLabel)
self.layout.addWidget(self.targetNameBox)
self.CreateReactionInputs()
if rxn is not None:
self.SetInitialValues(rxn)
self.rxnKey = rxnKey
self.layout.addWidget(self.buttonBox)
def SendReaction(self) :
self.new_reaction.emit(self.ztInput.value(),self.atInput.value(),self.zpInput.value(),self.apInput.value(),self.zeInput.value(),self.aeInput.value(), self.bkeInput.value(), self.thetaInput.value(), self.bfieldInput.value(), self.targetNameBox.currentText())
def SendReactionUpdate(self):
self.update_reaction.emit(self.bkeInput.value(), self.thetaInput.value(), self.bfieldInput.value(), self.rxnKey)
def CreateReactionInputs(self) :
self.nucleiGroupBox = QGroupBox("Reaction Nuclei",self)
inputLayout = QFormLayout()
self.ztInput = QSpinBox(self.nucleiGroupBox)
self.ztInput.setRange(1, 110)
self.atInput = QSpinBox(self.nucleiGroupBox)
self.atInput.setRange(1,270)
self.zpInput = QSpinBox(self.nucleiGroupBox)
self.zpInput.setRange(1, 110)
self.apInput = QSpinBox(self.nucleiGroupBox)
self.apInput.setRange(1,270)
self.zeInput = QSpinBox(self.nucleiGroupBox)
self.zeInput.setRange(1, 110)
self.aeInput = QSpinBox(self.nucleiGroupBox)
self.aeInput.setRange(1,270)
inputLayout.addRow("ZT",self.ztInput)
inputLayout.addRow("AT",self.atInput)
inputLayout.addRow("ZP",self.zpInput)
inputLayout.addRow("AP",self.apInput)
inputLayout.addRow("ZE",self.zeInput)
inputLayout.addRow("AE",self.aeInput)
self.parameterGroupBox = QGroupBox("Reaction Parameters", self)
parameterLayout = QFormLayout()
self.bkeInput = QDoubleSpinBox(self.parameterGroupBox)
self.bkeInput.setRange(0.0, 40.0)
self.bkeInput.setDecimals(4)
self.thetaInput = QDoubleSpinBox(self.parameterGroupBox)
self.thetaInput.setRange(0.0, 180.0)
self.thetaInput.setDecimals(4)
self.bfieldInput = QDoubleSpinBox(self.parameterGroupBox)
self.bfieldInput.setRange(0.0, 16.0)
self.bfieldInput.setDecimals(6)
parameterLayout.addRow("Beam KE(Mev)",self.bkeInput)
parameterLayout.addRow("Theta(deg)",self.thetaInput)
parameterLayout.addRow("Bfield(kG)",self.bfieldInput)
self.nucleiGroupBox.setLayout(inputLayout)
self.parameterGroupBox.setLayout(parameterLayout)
self.layout.addWidget(self.nucleiGroupBox)
self.layout.addWidget(self.parameterGroupBox)
def SetInitialValues(self, rxn):
self.targetNameBox.setCurrentIndex(self.targetNameBox.findText(rxn.target_data.name))
self.targetNameBox.setEnabled(False)
self.ztInput.setValue(rxn.Target.Z)
self.ztInput.setEnabled(False)
self.atInput.setValue(rxn.Target.A)
self.atInput.setEnabled(False)
self.zpInput.setValue(rxn.Projectile.Z)
self.zpInput.setEnabled(False)
self.apInput.setValue(rxn.Projectile.A)
self.apInput.setEnabled(False)
self.zeInput.setValue(rxn.Ejectile.Z)
self.zeInput.setEnabled(False)
self.aeInput.setValue(rxn.Ejectile.A)
self.aeInput.setEnabled(False)
self.bkeInput.setValue(rxn.BKE)
self.thetaInput.setValue(rxn.Theta/rxn.DEG2RAD)
self.bfieldInput.setValue(rxn.Bfield)
class PeakDialog(QDialog):
new_calibration = Signal(float, float, float, float, float, str)
update_calibration = Signal(float, float, float, float, float, str, str)
new_output = Signal(float, float, float, float, float, str)
update_output = Signal(float, float, float, float, float, str, str)
def __init__(self, peakType, parent=None, peak=None, peakKey=None):
super().__init__(parent)
self.setWindowTitle("Add A "+peakType+" Peak")
rnameLabel = QLabel("Reaction Name", self)
self.rxnNameBox = QComboBox(self)
for reaction in parent.spanc.reactions:
self.rxnNameBox.addItem(reaction)
QBtn = QDialogButtonBox.Ok | QDialogButtonBox.Cancel
self.buttonBox = QDialogButtonBox(QBtn)
self.buttonBox.accepted.connect(self.accept)
if peak is not None and peakType == "Calibration":
self.buttonBox.accepted.connect(self.SendUpdateCalibrationPeak)
elif peakType == "Calibration":
self.buttonBox.accepted.connect(self.SendCalibrationPeak)
elif peak is not None and peakType == "Output":
self.buttonBox.accepted.connect(self.SendUpdateOutputPeak)
elif peakType == "Output":
self.buttonBox.accepted.connect(self.SendOutputPeak)
self.buttonBox.rejected.connect(self.reject)
self.layout = QVBoxLayout()
self.setLayout(self.layout)
self.layout.addWidget(rnameLabel)
self.layout.addWidget(self.rxnNameBox)
if peakType == "Calibration":
self.CreateCalibrationInputs()
if peak is not None:
self.peakKey = peakKey
self.SetCalibrationInputs(peak)
elif peakType == "Output":
self.CreateOutputInputs()
if peak is not None:
self.peakKey = peakKey
self.SetOutputInputs(peak)
self.layout.addWidget(self.buttonBox)
def CreateCalibrationInputs(self):
self.inputGroupBox = QGroupBox("Peak Parameters",self)
inputLayout = QFormLayout()
self.xInput = QDoubleSpinBox(self.inputGroupBox)
self.xInput.setRange(-999, 999)
self.xInput.setDecimals(6)
self.uxsysInput = QDoubleSpinBox(self.inputGroupBox)
self.uxsysInput.setRange(-999, 999)
self.uxsysInput.setDecimals(6)
self.uxstatInput = QDoubleSpinBox(self.inputGroupBox)
self.uxstatInput.setRange(-999, 999)
self.uxstatInput.setDecimals(6)
self.exInput = QDoubleSpinBox(self.inputGroupBox)
self.exInput.setDecimals(6)
self.uexInput = QDoubleSpinBox(self.inputGroupBox)
self.uexInput.setDecimals(6)
inputLayout.addRow("Position(mm)", self.xInput)
inputLayout.addRow("Position Stat. Error(mm)", self.uxstatInput)
inputLayout.addRow("Position Sys. Error(mm)", self.uxsysInput)
inputLayout.addRow("Excitation Energy(MeV)", self.exInput)
inputLayout.addRow("Excitation Energy Error(MeV)", self.uexInput)
self.inputGroupBox.setLayout(inputLayout)
self.layout.addWidget(self.inputGroupBox)
def CreateOutputInputs(self):
self.inputGroupBox = QGroupBox("Peak Parameters",self)
inputLayout = QFormLayout()
self.xInput = QDoubleSpinBox(self.inputGroupBox)
self.xInput.setRange(-999, 999)
self.xInput.setDecimals(6)
self.uxsysInput = QDoubleSpinBox(self.inputGroupBox)
self.uxsysInput.setRange(-999, 999)
self.uxsysInput.setDecimals(6)
self.uxstatInput = QDoubleSpinBox(self.inputGroupBox)
self.uxstatInput.setRange(-999, 999)
self.uxstatInput.setDecimals(6)
self.fwhmInput = QDoubleSpinBox(self.inputGroupBox)
self.fwhmInput.setDecimals(6)
self.ufwhmInput = QDoubleSpinBox(self.inputGroupBox)
self.ufwhmInput.setDecimals(6)
inputLayout.addRow("Position(mm)", self.xInput)
inputLayout.addRow("Position Stat. Error(mm)", self.uxstatInput)
inputLayout.addRow("Position Sys. Error(mm)", self.uxsysInput)
inputLayout.addRow("Position FWHM(mm)", self.fwhmInput)
inputLayout.addRow("Position FWHM Error(mm)", self.ufwhmInput)
self.inputGroupBox.setLayout(inputLayout)
self.layout.addWidget(self.inputGroupBox)
def SetCalibrationInputs(self, peak):
self.rxnNameBox.setCurrentIndex(self.rxnNameBox.findText(peak.reaction))
self.xInput.setValue(peak.x)
self.uxsysInput.setValue(peak.ux_sys)
self.uxstatInput.setValue(peak.ux_stat)
self.exInput.setValue(peak.Ex)
self.uexInput.setValue(peak.uEx)
def SetOutputInputs(self, peak):
self.rxnNameBox.setCurrentIndex(self.rxnNameBox.findText(peak.reaction))
self.xInput.setValue(peak.x)
self.uxsysInput.setValue(peak.ux_sys)
self.uxstatInput.setValue(peak.ux_stat)
self.fwhmInput.setValue(peak.fwhm_x)
self.ufwhmInput.setValue(peak.ufwhm_x)
def SendCalibrationPeak(self):
self.new_calibration.emit(self.xInput.value(), self.uxstatInput.value(), self.uxsysInput.value(), self.exInput.value(), self.uexInput.value(), self.rxnNameBox.currentText())
def SendUpdateCalibrationPeak(self):
self.update_calibration.emit(self.xInput.value(), self.uxstatInput.value(), self.uxsysInput.value(), self.exInput.value(), self.uexInput.value(), self.rxnNameBox.currentText(),self.peakKey)
def SendOutputPeak(self):
self.new_output.emit(self.xInput.value(), self.uxstatInput.value(), self.uxsysInput.value(), self.fwhmInput.value(), self.ufwhmInput.value(), self.rxnNameBox.currentText())
def SendUpdateOutputPeak(self):
self.update_output.emit(self.xInput.value(), self.uxstatInput.value(), self.uxsysInput.value(), self.fwhmInput.value(), self.ufwhmInput.value(), self.rxnNameBox.currentText(),self.peakKey)
class SpancGUI(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("SPANC")
self.spanc = spc.Spanc()
self.tablelayout = QVBoxLayout()
self.plotlayout = QGridLayout() #2x2 grid
self.layout = QVBoxLayout()
self.centralWidget = QTabWidget(self)
self.setCentralWidget(self.centralWidget)
self.centralWidget.setLayout(self.layout)
self.tableTab = QWidget(self.centralWidget)
self.tableTab.setLayout(self.tablelayout)
self.plotTab = QWidget(self.centralWidget)
self.plotTab.setLayout(self.plotlayout)
self.centralWidget.addTab(self.tableTab, "Data Tables")
self.centralWidget.addTab(self.plotTab, "Plots and Fits")
self.fitFlag = False
self.CreateMenus()
self.CreateFitCanvas() #(0,0)
self.CreateResidualCanvas() #(1,0)
self.CreateTargetTable()
self.CreateReactionTable()
self.CreateCalibrationTable()
self.CreateOutputTable()
self.CreateFitTable() #(0,1)
self.CreateResidualTable() #(1,1)
self.show()
def CreateMenus(self):
self.fileMenu = self.menuBar().addMenu("&File")
saveAction = QAction("&Save...",self)
openAction = QAction("&Open...",self)
self.fileMenu.addAction(saveAction)
self.fileMenu.addAction(openAction)
self.fileMenu.addAction("&Exit", self.close)
saveAction.triggered.connect(self.HandleSave)
openAction.triggered.connect(self.HandleOpen)
self.addMenu = self.menuBar().addMenu("&New")
newTargetAction = QAction("New target...", self)
newReactionAction = QAction("New reaction...", self)
newCalibrationAction = QAction("New calibration...", self)
newOutputAction = QAction("New output...", self)
self.addMenu.addAction(newTargetAction)
self.addMenu.addAction(newReactionAction)
self.addMenu.addAction(newCalibrationAction)
self.addMenu.addAction(newOutputAction)
newTargetAction.triggered.connect(self.HandleNewTarget)
newReactionAction.triggered.connect(self.HandleNewReaction)
newCalibrationAction.triggered.connect(self.HandleNewCalibration)
newOutputAction.triggered.connect(self.HandleNewOutput)
def CreateFitCanvas(self):
self.fitGroup = QGroupBox("Calibration Fit", self.plotTab)
fitLayout = QVBoxLayout()
self.fitCanvas = MPLCanvas(self.fitGroup, width=6, height=6, dpi=100)
self.fitOptionGroup = QGroupBox("Fit options", self.fitGroup)
fitOptionLayout = QHBoxLayout()
self.fitButton = QPushButton("Run Fit", self.fitOptionGroup)
self.fitButton.clicked.connect(self.HandleRunFit)
self.fitTypeBox = QComboBox(self.fitOptionGroup)
self.fitTypeBox.addItem("linear")
self.fitTypeBox.addItem("quadratic")
self.fitTypeBox.addItem("cubic")
fitOptionLayout.addWidget(QLabel("Fit type", self.fitOptionGroup))
fitOptionLayout.addWidget(self.fitTypeBox)
fitOptionLayout.addWidget(self.fitButton)
self.fitOptionGroup.setLayout(fitOptionLayout)
fitLayout.addWidget(self.fitCanvas)
fitLayout.addWidget(self.fitOptionGroup)
self.fitGroup.setLayout(fitLayout)
self.plotlayout.addWidget(self.fitGroup,0,0)
def CreateResidualCanvas(self):
self.residGroup = QGroupBox("Fit Residuals", self.plotTab)
residLayout = QVBoxLayout()
self.residCanvas = MPLCanvas(self.residGroup, width=6, height=6, dpi=100)
self.residOptionGroup = QGroupBox("Fit options", self.residGroup)
residOptionLayout = QHBoxLayout()
self.residButton = QPushButton("Run Resids.", self.residOptionGroup)
self.residButton.clicked.connect(self.HandleRunResids)
residOptionLayout.addWidget(self.residButton)
self.residOptionGroup.setLayout(residOptionLayout)
residLayout.addWidget(self.residCanvas)
residLayout.addWidget(self.residOptionGroup)
self.residGroup.setLayout(residLayout)
self.plotlayout.addWidget(self.residGroup,1,0)
def CreateTargetTable(self):
self.targetGroup = QGroupBox("Targets", self.tableTab)
targetLayout = QVBoxLayout()
self.targetTable = QTableWidget(self.targetGroup)
self.targetTable.setColumnCount(6)
self.targetTable.setHorizontalHeaderLabels(["Layer1 Thickness(ug/cm^2", "Layer1 (Z, A, S)","Layer2 Thickness(ug/cm^2", "Layer2 (Z, A, S)","Layer3 Thickness(ug/cm^2", "Layer3 (Z, A, S)"])
targetLayout.addWidget(self.targetTable)
self.targetGroup.setLayout(targetLayout)
self.tablelayout.addWidget(self.targetGroup)
self.targetTable.resizeColumnsToContents()
self.targetTable.cellDoubleClicked.connect(self.HandleUpdateTarget)
def CreateReactionTable(self):
self.rxnGroup = QGroupBox("Reactions", self.tableTab)
rxnLayout = QVBoxLayout()
self.reactionTable = QTableWidget(self.rxnGroup)
self.reactionTable.setColumnCount(12)
self.reactionTable.setHorizontalHeaderLabels(["Target","ZT","AT","ZP","AP","ZE","AE","ZR","AR","Beam KE(MeV)","BField(kG)","Angle(deg)"])
rxnLayout.addWidget(self.reactionTable)
self.rxnGroup.setLayout(rxnLayout)
self.tablelayout.addWidget(self.rxnGroup)
self.reactionTable.resizeColumnsToContents()
self.reactionTable.cellDoubleClicked.connect(self.HandleUpdateReaction)
def CreateCalibrationTable(self):
self.calGroup = QGroupBox("Calibration Peaks", self.tableTab)
calLayout = QVBoxLayout()
self.calibrationTable = QTableWidget(self.calGroup)
self.calibrationTable.setColumnCount(8)
self.calibrationTable.setHorizontalHeaderLabels(["Reaction","x(mm)","ux stat.(mm)","ux sys.(mm)","rho(cm)","urho(cm)","Ex(MeV)","uEx(MeV)"])
calLayout.addWidget(self.calibrationTable)
self.calGroup.setLayout(calLayout)
self.tablelayout.addWidget(self.calGroup)
self.calibrationTable.resizeColumnsToContents()
self.calibrationTable.cellDoubleClicked.connect(self.HandleUpdateCalibration)
def CreateOutputTable(self):
self.outGroup = QGroupBox("Output Peaks", self.tableTab)
outLayout = QVBoxLayout()
self.outputTable = QTableWidget(self.outGroup)
self.outputTable.setColumnCount(12)
self.outputTable.setHorizontalHeaderLabels(["Reaction","x(mm)","ux stat.(mm)","ux sys.(mm)","rho(cm)","urho(cm)","Ex(MeV)","uEx(MeV)","FWHM(mm)","uFWHM(mm)","FWHM(MeV)","uFWHM(MeV)"])
outLayout.addWidget(self.outputTable)
self.outGroup.setLayout(outLayout)
self.tablelayout.addWidget(self.outGroup)
self.outputTable.resizeColumnsToContents()
self.outputTable.cellDoubleClicked.connect(self.HandleUpdateOutput)
def CreateFitTable(self):
self.ftableGroup = QGroupBox("Fit Results", self.plotTab)
ftableLayout = QVBoxLayout()
self.fitTable = QTableWidget(3, 9, self.ftableGroup)
self.fitTable.setHorizontalHeaderLabels(["a0","ua0","a1","ua1","a2","ua2","a3","ua3","Chi Sq./NDF"])
self.fitTable.setVerticalHeaderLabels(["linear","quadratic","cubic"])
ftableLayout.addWidget(self.fitTable)
self.ftableGroup.setLayout(ftableLayout)
self.plotlayout.addWidget(self.ftableGroup,0,1)
self.fitTable.resizeColumnsToContents()
def CreateResidualTable(self):
self.rtableGroup = QGroupBox("Residual Results", self.plotTab)
rtableLayout = QVBoxLayout()
self.residualTable = QTableWidget(self.rtableGroup)
self.residualTable.setColumnCount(5)
self.residualTable.setHorizontalHeaderLabels(["x(mm)","rho calc(cm)","rho fit(cm)","residual(cm)","studentized residual"])
rtableLayout.addWidget(self.residualTable)
self.rtableGroup.setLayout(rtableLayout)
self.plotlayout.addWidget(self.rtableGroup,1,1)
self.residualTable.resizeColumnsToContents()
def HandleSave(self):
fileName = QFileDialog.getSaveFileName(self, "Save Input","./","Text Files (*.pickle)")
if fileName[0]:
#self.spanc.WriteConfig(fileName[0])
with open(fileName[0], "wb") as savefile:
pickle.dump(self.spanc, savefile, pickle.HIGHEST_PROTOCOL)
savefile.close()
def HandleOpen(self):
fileName = QFileDialog.getOpenFileName(self, "Open Input","./","Text Files (*.pickle)")
if fileName[0]:
with open(fileName[0], "rb") as openfile:
self.spanc = pickle.load(openfile)
self.UpdateTargetTable()
self.UpdateReactionTable()
self.UpdateCalibrationTable()
self.UpdateOutputTable()
openfile.close()
def HandleNewTarget(self):
targDia = TargetDialog(self)
targDia.new_target.connect(self.AddTarget)
targDia.exec()
return
def HandleUpdateTarget(self, row, col):
targName = self.targetTable.verticalHeaderItem(row).text()
targDia = TargetDialog(self, target=self.spanc.targets[targName])
targDia.new_target.connect(self.UpdateTarget)
targDia.exec()
return
def HandleNewReaction(self):
rxnDia = ReactionDialog(self)
rxnDia.new_reaction.connect(self.AddReaction)
rxnDia.exec()
return
def HandleUpdateReaction(self, row, col):
rxnName = self.reactionTable.verticalHeaderItem(row).text()
rxnDia = ReactionDialog(self, rxn=self.spanc.reactions[rxnName], rxnKey=rxnName)
rxnDia.update_reaction.connect(self.UpdateReaction)
rxnDia.exec()
return
def HandleNewCalibration(self):
calDia = PeakDialog("Calibration", self)
calDia.new_calibration.connect(self.AddCalibration)
calDia.exec()
return
def HandleUpdateCalibration(self, row, col):
peakName = self.calibrationTable.verticalHeaderItem(row).text()
calDia = PeakDialog("Calibration", self, peak=self.spanc.calib_peaks[peakName], peakKey=peakName)
calDia.update_calibration.connect(self.UpdateCalibration)
calDia.exec()
return
def HandleNewOutput(self):
outDia = PeakDialog("Output", self)
outDia.new_output.connect(self.AddOutput)
outDia.exec()
return
def HandleUpdateOutput(self, row, col):
peakName = self.outputTable.verticalHeaderItem(row).text()
outDia = PeakDialog("Output",self,peak=self.spanc.output_peaks[peakName],peakKey=peakName)
outDia.update_output.connect(self.UpdateOutput)
outDia.exec()
return
def HandleRunFit(self):
fit_type = self.fitTypeBox.currentText()
npoints = len(self.spanc.calib_peaks)
if npoints < 3 and fit_type == "linear":
print("Warning! Too few points to properly fit a linear function. Results are invalid.")
elif npoints < 4 and fit_type == "quadratic":
print("Warning! Too few points to properly fit a quadratic function. Results are invalid")
elif npoints < 5 and fit_type == "cubic":
print("Warning! Too few points to properly fit a cubic function. Results are invalid")
xarray, yarray, uxarray, uyarray = self.spanc.PerformFits()
fitarray = np.linspace(np.amin(xarray), np.amax(xarray), 1000)
self.fitCanvas.axes.cla()
self.fitCanvas.axes.errorbar(xarray, yarray, yerr=uyarray, xerr=uxarray, marker="o", linestyle="None")
self.fitCanvas.axes.plot(fitarray, self.spanc.fitters[fit_type].EvaluateFunction(fitarray))
self.fitCanvas.axes.set_xlabel(r"$x$ (mm)")
self.fitCanvas.axes.set_ylabel(r"$\rho$ (cm)")
self.fitCanvas.draw()
self.UpdateFitTable()
self.spanc.CalculateOutputs(fit_type)
self.UpdateOutputTable()
self.fitFlag = True
def HandleRunResids(self):
fit_type = self.fitTypeBox.currentText()
npoints = len(self.spanc.calib_peaks)
if npoints < 3 and fit_type == "linear":
print("Warning! Too few points to properly fit a linear function. Results are invalid.")
elif npoints < 4 and fit_type == "quadratic":
print("Warning! Too few points to properly fit a quadratic function. Results are invalid")
elif npoints < 5 and fit_type == "cubic":
print("Warning! Too few points to properly fit a cubic function. Results are invalid")
xarray, resid_array, student_resids = self.spanc.CalculateResiduals(fit_type)
self.residCanvas.axes.cla()
self.residCanvas.axes.plot(xarray, resid_array, marker="o", linestyle="None")
self.residCanvas.axes.set_xlabel(r"$x$ (mm)")
self.residCanvas.axes.set_ylabel(r"Residual (cm)")
self.residCanvas.draw()
self.UpdateResidualTable(resid_array, student_resids)
def AddTarget(self, layers, name):
target = LayeredTarget()
target.name = name
for layer in layers:
target.AddLayer(layer)
self.spanc.targets[target.name] = target
self.UpdateTargetTable()
def UpdateTarget(self, layers, name):
target = LayeredTarget()
target.name = name
for layer in layers:
target.AddLayer(layer)
self.spanc.targets[target.name] = target
for rxn in self.spanc.reactions.values():
if rxn.target_data.name == name:
rxn.target_data = target
self.UpdateTargetTable()
self.spanc.CalculateCalibrations()
self.UpdateReactionTable()
self.UpdateCalibrationTable()
if self.fitFlag is True:
self.spanc.CalculateOutputs(self.fitTypeBox.currentText())
self.UpdateOutputTable()
def UpdateTargetTable(self):
self.targetTable.setRowCount(len(self.spanc.targets))
self.targetTable.setVerticalHeaderLabels(self.spanc.targets.keys())
cur_row = 0
for key in self.spanc.targets:
for i in range(len(self.spanc.targets[key].targets)) :
self.targetTable.setItem(cur_row, 0+i*2, QTableWidgetItem(str(self.spanc.targets[key].targets[i].thickness)))
self.targetTable.setItem(cur_row, 1+i*2, QTableWidgetItem(self.spanc.targets[key].targets[i].GetComposition()))
cur_row += 1
self.targetTable.resizeColumnsToContents()
self.targetTable.resizeRowsToContents()
def AddReaction(self, zt, at, zp, ap, ze, ae, bke, theta, bfield, name):
targ = self.spanc.targets[name]
rxn = Reaction(zt, at, zp, ap, ze, ae, bke, theta, bfield, targ)
count=0
for key in self.spanc.reactions:
if key == rxn.Name:
count += 1
rxn.Name = rxn.Name + "_" + str(count)
self.spanc.reactions[rxn.Name] = rxn
self.UpdateReactionTable()
def UpdateReaction(self, bke, theta, bfield, key):
self.spanc.reactions[key].ChangeReactionParameters(bke, theta, bfield)
self.UpdateReactionTable()
self.spanc.CalculateCalibrations()
self.UpdateCalibrationTable()
if self.fitFlag is True:
self.spanc.CalculateOutputs(self.fitTypeBox.currentText())
self.UpdateOutputTable()
def UpdateReactionTable(self):
self.reactionTable.setRowCount(len(self.spanc.reactions))
self.reactionTable.setVerticalHeaderLabels(self.spanc.reactions.keys())
cur_row = 0
for key in self.spanc.reactions:
self.reactionTable.setItem(cur_row, 0, QTableWidgetItem(str(self.spanc.reactions[key].target_data.name)))
self.reactionTable.setItem(cur_row, 1, QTableWidgetItem(str(self.spanc.reactions[key].Target.Z)))
self.reactionTable.setItem(cur_row, 2, QTableWidgetItem(str(self.spanc.reactions[key].Target.A)))
self.reactionTable.setItem(cur_row, 3, QTableWidgetItem(str(self.spanc.reactions[key].Projectile.Z)))
self.reactionTable.setItem(cur_row, 4, QTableWidgetItem(str(self.spanc.reactions[key].Projectile.A)))
self.reactionTable.setItem(cur_row, 5, QTableWidgetItem(str(self.spanc.reactions[key].Ejectile.Z)))
self.reactionTable.setItem(cur_row, 6, QTableWidgetItem(str(self.spanc.reactions[key].Ejectile.A)))
self.reactionTable.setItem(cur_row, 7, QTableWidgetItem(str(self.spanc.reactions[key].Residual.Z)))
self.reactionTable.setItem(cur_row, 8, QTableWidgetItem(str(self.spanc.reactions[key].Residual.A)))
self.reactionTable.setItem(cur_row, 9, QTableWidgetItem(str(self.spanc.reactions[key].BKE)))
self.reactionTable.setItem(cur_row, 10, QTableWidgetItem(str(self.spanc.reactions[key].Bfield)))
self.reactionTable.setItem(cur_row, 11, QTableWidgetItem(str(self.spanc.reactions[key].Theta/self.spanc.reactions[key].DEG2RAD)))
cur_row += 1
self.reactionTable.resizeColumnsToContents()
self.reactionTable.resizeRowsToContents()
def AddCalibration(self, x, uxstat, uxsys, ex, uex, rxnname):
peak_name = "Cal" + str(len(self.spanc.calib_peaks))
self.spanc.AddCalibrationPeak(rxnname, peak_name, x, uxstat, uxsys, ex, uex)
self.UpdateCalibrationTable()
def UpdateCalibration(self, x, uxstat, uxsys, ex, uex, rxnname, peakname):
self.spanc.AddCalibrationPeak(rxnname, peakname, x, uxstat, uxsys, ex, uex)
self.UpdateCalibrationTable()
def UpdateCalibrationTable(self):
self.calibrationTable.setRowCount(len(self.spanc.calib_peaks))
self.calibrationTable.setVerticalHeaderLabels(self.spanc.calib_peaks.keys())
cur_row = 0
for key in self.spanc.calib_peaks:
self.calibrationTable.setItem(cur_row, 0, QTableWidgetItem(self.spanc.calib_peaks[key].reaction))
self.calibrationTable.setItem(cur_row, 1, QTableWidgetItem(str(self.spanc.calib_peaks[key].x)))
self.calibrationTable.setItem(cur_row, 2, QTableWidgetItem(str(self.spanc.calib_peaks[key].ux_stat)))
self.calibrationTable.setItem(cur_row, 3, QTableWidgetItem(str(self.spanc.calib_peaks[key].ux_sys)))
self.calibrationTable.setItem(cur_row, 4, QTableWidgetItem(str(self.spanc.calib_peaks[key].rho)))
self.calibrationTable.setItem(cur_row, 5, QTableWidgetItem(str(self.spanc.calib_peaks[key].urho)))
self.calibrationTable.setItem(cur_row, 6, QTableWidgetItem(str(self.spanc.calib_peaks[key].Ex)))
self.calibrationTable.setItem(cur_row, 7, QTableWidgetItem(str(self.spanc.calib_peaks[key].uEx)))
cur_row += 1
self.calibrationTable.resizeColumnsToContents()
self.calibrationTable.resizeRowsToContents()
def AddOutput(self, x, uxstat, uxsys, fwhm, ufwhm, rxnname):
peak_name = "Out" + str(len(self.spanc.output_peaks))
self.spanc.AddOutputPeak(rxnname, peak_name, x, uxstat, uxsys, fwhm, ufwhm)
self.UpdateOutputTable()
def UpdateOutput(self, x, uxstat, uxsys, fwhm, ufwhm, rxnname, peakname):
self.spanc.AddOutputPeak(rxnname, peakname, x, uxstat, uxsys, fwhm, ufwhm)
self.UpdateOutputTable()
def UpdateOutputTable(self):
self.outputTable.setRowCount(len(self.spanc.output_peaks))
self.outputTable.setVerticalHeaderLabels(self.spanc.output_peaks.keys())
cur_row = 0
for key in self.spanc.output_peaks:
self.outputTable.setItem(cur_row, 0, QTableWidgetItem(self.spanc.output_peaks[key].reaction))
self.outputTable.setItem(cur_row, 1, QTableWidgetItem(str(self.spanc.output_peaks[key].x)))
self.outputTable.setItem(cur_row, 2, QTableWidgetItem(str(self.spanc.output_peaks[key].ux_stat)))
self.outputTable.setItem(cur_row, 3, QTableWidgetItem(str(self.spanc.output_peaks[key].ux_sys)))
self.outputTable.setItem(cur_row, 4, QTableWidgetItem(str(self.spanc.output_peaks[key].rho)))
self.outputTable.setItem(cur_row, 5, QTableWidgetItem(str(self.spanc.output_peaks[key].urho)))
self.outputTable.setItem(cur_row, 6, QTableWidgetItem(str(self.spanc.output_peaks[key].Ex)))
self.outputTable.setItem(cur_row, 7, QTableWidgetItem(str(self.spanc.output_peaks[key].uEx)))
self.outputTable.setItem(cur_row, 8, QTableWidgetItem(str(self.spanc.output_peaks[key].fwhm_x)))
self.outputTable.setItem(cur_row, 9, QTableWidgetItem(str(self.spanc.output_peaks[key].ufwhm_x)))
self.outputTable.setItem(cur_row, 10, QTableWidgetItem(str(self.spanc.output_peaks[key].fwhm_Ex)))
self.outputTable.setItem(cur_row, 11, QTableWidgetItem(str(self.spanc.output_peaks[key].ufwhm_Ex)))
cur_row += 1
self.outputTable.resizeColumnsToContents()
self.outputTable.resizeRowsToContents()
def UpdateFitTable(self):
cur_row=0
for key in self.spanc.fitters:
for i in range(len(self.spanc.fitters[key].parameters)):
self.fitTable.setItem(cur_row, 0+i*2, QTableWidgetItem(str(self.spanc.fitters[key].parameters[i])))
self.fitTable.setItem(cur_row, 1+i*2, QTableWidgetItem(str(self.spanc.fitters[key].GetParameterError(i))))
#self.fitTable.setItem(cur_row, 8, QTableWidgetItem(str(self.spanc.fitters[key].redChiSq)))
self.fitTable.setItem(cur_row, 8, QTableWidgetItem(str(self.spanc.fitters[key].ReducedChiSquare())))
cur_row += 1
self.fitTable.resizeColumnsToContents()
self.fitTable.resizeRowsToContents()
def UpdateResidualTable(self, resids, student_resids):
self.residualTable.setRowCount(len(self.spanc.calib_peaks))
self.residualTable.setVerticalHeaderLabels(self.spanc.calib_peaks.keys())
cur_row=0
for key in self.spanc.calib_peaks:
self.residualTable.setItem(cur_row, 0, QTableWidgetItem(str(self.spanc.calib_peaks[key].x)))
self.residualTable.setItem(cur_row, 1, QTableWidgetItem(str(self.spanc.calib_peaks[key].rho)))
self.residualTable.setItem(cur_row, 2, QTableWidgetItem(str(self.spanc.calib_peaks[key].rho + resids[cur_row])))
self.residualTable.setItem(cur_row, 3, QTableWidgetItem(str(resids[cur_row])))
self.residualTable.setItem(cur_row, 4, QTableWidgetItem(str(student_resids[cur_row])))
cur_row += 1
self.residualTable.resizeColumnsToContents()
self.residualTable.resizeRowsToContents()
def main() :
mpl.use("Qt5Agg")
myapp = QApplication(sys.argv)
window = SpancGUI()
sys.exit(myapp.exec_())
if __name__ == '__main__':
main()

70
spsplot/NucData.py Executable file
View File

@ -0,0 +1,70 @@
#!/usr/bin/env python3
import numpy as np
import requests
import lxml.html as xhtml
class MassTable:
def __init__(self):
file = open("./etc/mass.txt","r")
self.mtable = {}
u2mev = 931.4940954
me = 0.000548579909 #MeV
self.etable = {}
file.readline()
file.readline()
for line in file:
entries = line.split()
n = entries[0]
z = entries[1]
a = entries[2]
element = entries[3]
massBig = float(entries[4])
massSmall = float(entries[5])
key = '('+z+','+a+')'
value = (massBig+massSmall*1e-6)*u2mev - float(z)*me
self.mtable[key] = value
self.etable[key] = element
file.close()
def GetMass(self, z, a):
key = '('+str(z)+','+str(a)+')'
if key in self.mtable:
return self.mtable[key]
else:
return 0
def GetSymbol(self, z, a):
key = '('+str(z)+','+str(a)+')'
if key in self.etable:
return str(a)+self.etable[key]
else:
return 'none'
Masses = MassTable()
def GetExcitations(symbol):
levels = np.array(np.empty(0))
text = ''
site = requests.get("https://www.nndc.bnl.gov/nudat2/getdatasetClassic.jsp?nucleus="+symbol+"&unc=nds")
contents = xhtml.fromstring(site.content)
tables = contents.xpath("//table")
rows = tables[2].xpath("./tr")
for row in rows[1:-2]:
entries = row.xpath("./td")
if len(entries) != 0:
entry = entries[0]
data = entry.xpath("./a")
if len(data) == 0:
text = entry.text
else:
text = data[0].text
text = text.replace('?', '')
text = text.replace('\xa0\xa0','')
levels = np.append(levels, float(text)/1000.0)
return levels

83
spsplot/NuclearRxn.py Normal file
View File

@ -0,0 +1,83 @@
#!/usr/bin/env python3
import numpy as np
import NucData
class Nucleus:
def __init__(self, z, a):
self.Z = z
self.A = a
self.Symbol = NucData.Masses.GetSymbol(self.Z, self.A)
self.GSMass = NucData.Masses.GetMass(self.Z, self.A)
def Minus(self, rhs):
final_Z = self.Z - rhs.Z
final_A = self.A - rhs.A
if final_A < 0 or final_Z < 0:
print("Illegal minus operation on Nuclei!")
return Nucleus(0,0)
else:
return Nucleus(final_Z, final_A)
def Plus(self, rhs):
return Nucleus(self.Z + rhs.Z, self.A + rhs.A)
class Reaction:
DEG2RAD = np.pi/180.0 #degrees to radians
C = 299792458 #speed of light m/s
QBRHO2P = 1.0E-9*C #Converts qbrho to p (kG*cm -> MeV/c)
def __init__(self, zt, at, zp, ap, ze, ae, beamKE, theta, bfield):
self.Target = Nucleus(zt, at)
self.Projectile = Nucleus(zp, ap)
self.Ejectile = Nucleus(ze, ae)
self.Residual = (self.Target.Plus(self.Projectile)).Minus(self.Ejectile)
self.BKE = beamKE
self.Theta = theta * self.DEG2RAD
self.Bfield = bfield
self.Name = self.Target.Symbol +"("+ self.Projectile.Symbol +","+ self.Ejectile.Symbol +")"+ self.Residual.Symbol
self.residLevels = NucData.GetExcitations(self.Residual.Symbol)
self.ejectKEvals = np.array(np.empty(len(self.residLevels)))
self.ejectRhovals = np.array(np.empty(len(self.residLevels)))
self.SetEjectileData()
def GetEjectileKineticEnergy(self, Elevel) :
Q = self.Target.GSMass + self.Projectile.GSMass - (self.Ejectile.GSMass + self.Residual.GSMass + Elevel)
Ethresh = -Q*(self.Ejectile.GSMass+self.Residual.GSMass)/(self.Ejectile.GSMass + self.Residual.GSMass - self.Projectile.GSMass)
if self.BKE < Ethresh:
return 0.0
term1 = np.sqrt(self.Projectile.GSMass*self.Ejectile.GSMass*self.BKE)/(self.Ejectile.GSMass + self.Residual.GSMass)*np.cos(self.Theta)
term2 = (self.BKE*(self.Residual.GSMass - self.Projectile.GSMass) + self.Residual.GSMass*Q)/(self.Ejectile.GSMass + self.Residual.GSMass)
ke1 = term1 + np.sqrt(term1**2.0 + term2)
ke2 = term1 - np.sqrt(term1**2.0 + term2)
if ke1 > 0:
return ke1**2.0
else :
return ke2**2.0
def GetEjectileRho(self, ke):
p = np.sqrt(ke*(ke + 2.0*self.Ejectile.GSMass))
return p/(self.QBRHO2P*self.Bfield*self.Ejectile.Z)
def SetEjectileData(self):
for index in range(len(self.residLevels)):
self.ejectKEvals[index] = self.GetEjectileKineticEnergy(self.residLevels[index])
self.ejectRhovals[index] = self.GetEjectileRho(self.ejectKEvals[index])
def ChangeReactionParameters(self, bke, theta, bf) :
self.BKE = bke
self.Theta = theta*self.DEG2RAD
self.Bfield = bf
self.SetEjectileData()
def AddLevel(self, Elevel):
ke = self.GetEjectileKineticEnergy(Elevel)
rho = self.GetEjectileRho(ke)
self.residLevels = np.append(self.residLevels, Elevel)
self.ejectKEvals = np.append(self.ejectKEvals, ke)
self.ejectRhovals = np.append(self.ejectRhovals, rho)

76
spsplot/SPSPlot.py Normal file
View File

@ -0,0 +1,76 @@
#!/usr/bin/env python3
import NuclearRxn as rxn
class SPSPlot:
def __init__(self) :
self.reactions = {}
self.configfile = ""
self.rhoMin = 0
self.rhoMax = 99
self.beamKE = 0
self.Bfield = 0
self.angle = 0
def ReadConfig(self, filename) :
self.reactions.clear()
self.configfile = filename
file = open(filename, "r")
line = file.readline()
entries = line.split()
self.beamKE = float(entries[1])
line = file.readline()
entries = line.split()
self.Bfield = float(entries[1])
line = file.readline()
entries = line.split()
self.angle = float(entries[1])
line = file.readline()
entries = line.split()
self.rhoMin = float(entries[1])
self.rhoMax = float(entries[3])
line = file.readline()
for line in file:
entries = line.split()
reac = rxn.Reaction(int(entries[0]), int(entries[1]), int(entries[2]), int(entries[3]), int(entries[4]), int(entries[5]), self.beamKE, self.angle, self.Bfield)
self.reactions[reac.Name] = reac
file.close()
def WriteConfig(self, filename) :
file = open(filename, "w")
line = "BeamEnergy(MeV): "+str(self.beamKE)+"\n"
file.write(line)
line = "B-field(kG): "+str(self.Bfield)+"\n"
file.write(line)
line = "Angle(deg): "+str(self.angle)+"\n"
file.write(line)
line = "RhoMin: "+str(self.rhoMin)+" RhoMax: "+str(self.rhoMax)+"\n"
file.write(line)
line = "ZT AT ZP AP ZE AE\n"
file.write(line)
for rxnName in self.reactions :
reaction = self.reactions[rxnName]
line = str(reaction.Target.Z)+" "+str(reaction.Target.A)+" "+str(reaction.Projectile.Z)+" "+str(reaction.Projectile.A)+" "+str(reaction.Ejectile.Z)+" "+str(reaction.Ejectile.A)+"\n"
file.write(line)
file.close()
def ChangeReactionParameters(self, bke, theta, bf) :
self.beamKE = bke
self.Bfield = bf
self.angle = theta
for rxnName in self.reactions :
self.reactions[rxnName].ChangeReactionParameters(bke, theta, bf)
def AddReaction(self, zt, at, zp, ap, ze, ae) :
reac = rxn.Reaction(zt, at, zp, ap, ze, ae, self.beamKE, self.Bfield, self.angle)
self.reactions[reac.Name] = reac
def AddLevel(self, name, level) :
self.reactions[name].AddLevel(level)

295
spsplot/SPSPlotGUI.py Executable file
View File

@ -0,0 +1,295 @@
#!/usr/bin/env python3
import SPSPlot as spsplt
import sys
from qtpy.QtWidgets import QApplication, QWidget, QMainWindow
from qtpy.QtWidgets import QLabel, QMenuBar, QAction
from qtpy.QtWidgets import QHBoxLayout, QVBoxLayout, QGroupBox
from qtpy.QtWidgets import QPushButton, QButtonGroup, QRadioButton
from qtpy.QtWidgets import QSpinBox, QDoubleSpinBox, QComboBox
from qtpy.QtWidgets import QDialog, QFileDialog, QDialogButtonBox
from qtpy.QtCore import Signal
import matplotlib as mpl
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
from matplotlib.figure import Figure
class MPLCanvas(FigureCanvasQTAgg):
def __init__(self, parent=None, width=5, height=4, dpi=100):
self.fig = Figure(figsize=(width, height), dpi=dpi, edgecolor="black",linewidth=0.5)
self.axes = self.fig.add_subplot(111)
self.axes.spines['top'].set_visible(False)
super(MPLCanvas, self).__init__(self.fig)
class ReactionDialog(QDialog):
new_reaction = Signal(int, int, int, int, int, int)
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("Add A Reaction")
QBtn = QDialogButtonBox.Ok | QDialogButtonBox.Cancel
self.buttonBox = QDialogButtonBox(QBtn)
self.buttonBox.accepted.connect(self.accept)
self.buttonBox.accepted.connect(self.SendReaction)
self.buttonBox.rejected.connect(self.reject)
self.layout = QVBoxLayout()
self.setLayout(self.layout)
self.CreateReactionInputs()
self.layout.addWidget(self.buttonBox)
def SendReaction(self) :
self.new_reaction.emit(self.ztInput.value(),self.atInput.value(),self.zpInput.value(),self.apInput.value(),self.zeInput.value(),self.aeInput.value())
def CreateReactionInputs(self) :
self.nucleiGroupBox = QGroupBox("Reaction Nuclei",self)
inputLayout = QVBoxLayout()
ztLabel = QLabel("ZT",self.nucleiGroupBox)
self.ztInput = QSpinBox(self.nucleiGroupBox)
self.ztInput.setRange(1, 110)
atLabel = QLabel("AT",self.nucleiGroupBox)
self.atInput = QSpinBox(self.nucleiGroupBox)
self.atInput.setRange(1,270)
zpLabel = QLabel("ZP",self.nucleiGroupBox)
self.zpInput = QSpinBox(self.nucleiGroupBox)
self.zpInput.setRange(1, 110)
apLabel = QLabel("AP",self.nucleiGroupBox)
self.apInput = QSpinBox(self.nucleiGroupBox)
self.apInput.setRange(1,270)
zeLabel = QLabel("ZE",self.nucleiGroupBox)
self.zeInput = QSpinBox(self.nucleiGroupBox)
self.zeInput.setRange(1, 110)
aeLabel = QLabel("AE",self.nucleiGroupBox)
self.aeInput = QSpinBox(self.nucleiGroupBox)
self.aeInput.setRange(1,270)
inputLayout.addWidget(ztLabel)
inputLayout.addWidget(self.ztInput)
inputLayout.addWidget(atLabel)
inputLayout.addWidget(self.atInput)
inputLayout.addWidget(zpLabel)
inputLayout.addWidget(self.zpInput)
inputLayout.addWidget(apLabel)
inputLayout.addWidget(self.apInput)
inputLayout.addWidget(zeLabel)
inputLayout.addWidget(self.zeInput)
inputLayout.addWidget(aeLabel)
inputLayout.addWidget(self.aeInput)
self.nucleiGroupBox.setLayout(inputLayout)
self.layout.addWidget(self.nucleiGroupBox)
class LevelDialog(QDialog):
new_level = Signal(str,float)
def __init__(self, parent) :
super().__init__(parent)
self.setWindowTitle("Add a Level")
QBtn = QDialogButtonBox.Ok | QDialogButtonBox.Cancel
self.buttonBox = QDialogButtonBox(QBtn)
self.buttonBox.accepted.connect(self.accept)
self.buttonBox.accepted.connect(self.SendLevel)
self.buttonBox.rejected.connect(self.reject)
self.layout = QVBoxLayout()
self.setLayout(self.layout)
rxnLabel = QLabel("Choose a reaction",self)
self.reactionList = QComboBox(self)
for rxnName in parent.sps.reactions:
self.reactionList.addItem(rxnName)
stateLabel = QLabel("New state energy",self)
self.stateInput = QDoubleSpinBox(self)
self.stateInput.setRange(0.0,40.0)
self.stateInput.setSuffix(" MeV")
self.layout.addWidget(rxnLabel)
self.layout.addWidget(self.reactionList)
self.layout.addWidget(stateLabel)
self.layout.addWidget(self.stateInput)
self.layout.addWidget(self.buttonBox)
def SendLevel(self):
self.new_level.emit(self.reactionList.currentText(),self.stateInput.value())
class SPSPlotGUI(QMainWindow):
def __init__(self, parent=None) :
super().__init__(parent)
self.setWindowTitle("SPSPlot")
self.sps = spsplt.SPSPlot()
self.generalLayout = QVBoxLayout()
self.centralWidget = QWidget(self)
self.setCentralWidget(self.centralWidget)
self.centralWidget.setLayout(self.generalLayout)
self.energyFlag = True #True = ex False = ke
self.CreateCanvas()
self.CreateMenus()
self.CreateInputs()
self.show()
def CreateCanvas(self):
self.canvas = MPLCanvas(self, width=14, height=5, dpi=100)
self.generalLayout.addWidget(self.canvas, 5)
def CreateMenus(self):
self.fileMenu = self.menuBar().addMenu("&File")
saveAction = QAction("&Save...",self)
openAction = QAction("&Open...",self)
self.fileMenu.addAction(saveAction)
self.fileMenu.addAction(openAction)
self.fileMenu.addAction("&Exit", self.close)
saveAction.triggered.connect(self.HandleSave)
openAction.triggered.connect(self.HandleOpen)
self.addMenu = self.menuBar().addMenu("&New")
newStateAction = QAction("New state...", self)
newReactionAction = QAction("New reaction...", self)
self.addMenu.addAction(newStateAction)
self.addMenu.addAction(newReactionAction)
newStateAction.triggered.connect(self.HandleNewState)
newReactionAction.triggered.connect(self.HandleNewReaction)
def CreateInputs(self):
inputLayout = QHBoxLayout()
self.inputGroupBox = QGroupBox("Adjustable Inputs", self)
rhoMinLabel = QLabel("Rho Min", self.inputGroupBox)
self.rhoMinInput = QDoubleSpinBox(self.inputGroupBox)
self.rhoMinInput.setRange(0.0, 150.0)
self.rhoMinInput.setSuffix(" cm")
rhoMaxLabel = QLabel("RhoMax", self.inputGroupBox)
self.rhoMaxInput = QDoubleSpinBox(self.inputGroupBox)
self.rhoMaxInput.setRange(0.0,150.0)
self.rhoMaxInput.setSuffix(" cm")
bkeLabel = QLabel("Beam KE", self.inputGroupBox)
self.bkeInput = QDoubleSpinBox(self.inputGroupBox)
self.bkeInput.setRange(0.0, 500.0)
self.bkeInput.setSuffix(" MeV")
bfieldLabel = QLabel("B-field", self.inputGroupBox)
self.bfieldInput = QDoubleSpinBox(self.inputGroupBox)
self.bfieldInput.setRange(0.0, 17.0)
self.bfieldInput.setSuffix(" kG")
angleLabel = QLabel("Angle", self.inputGroupBox)
self.angleInput = QDoubleSpinBox(self.inputGroupBox)
self.angleInput.setRange(0.0, 180.0)
self.angleInput.setSuffix(" deg")
self.runButton = QPushButton("Run", self.inputGroupBox)
self.runButton.clicked.connect(self.HandleRun)
self.energyButtonGroup = QGroupBox("Ex/KE switch",self)
buttonLayout = QHBoxLayout()
self.exButton = QRadioButton("Excitation energy", self.energyButtonGroup)
self.exButton.toggled.connect(self.HandleExSwitch)
self.keButton = QRadioButton("Ejectile Kinetic energy", self.energyButtonGroup)
self.keButton.toggled.connect(self.HandleKESwitch)
buttonLayout.addWidget(self.exButton)
buttonLayout.addWidget(self.keButton)
self.energyButtonGroup.setLayout(buttonLayout)
inputLayout.addWidget(rhoMinLabel)
inputLayout.addWidget(self.rhoMinInput)
inputLayout.addWidget(rhoMaxLabel)
inputLayout.addWidget(self.rhoMaxInput)
inputLayout.addWidget(bkeLabel)
inputLayout.addWidget(self.bkeInput)
inputLayout.addWidget(bfieldLabel)
inputLayout.addWidget(self.bfieldInput)
inputLayout.addWidget(angleLabel)
inputLayout.addWidget(self.angleInput)
inputLayout.addWidget(self.runButton)
self.inputGroupBox.setLayout(inputLayout)
inputLayout.addWidget(self.energyButtonGroup)
self.generalLayout.addWidget(self.inputGroupBox, 1)
def HandleSave(self):
fileName = QFileDialog.getSaveFileName(self, "Save Input","./","Text Files (*.txt *.inp)")
if fileName[0]:
self.sps.WriteConfig(fileName[0])
def HandleOpen(self):
fileName = QFileDialog.getOpenFileName(self, "Open Input","./","Text Files (*.txt *.inp)")
if fileName[0]:
self.sps.ReadConfig(fileName[0])
self.UpdateInputs()
self.UpdatePlot()
def HandleNewState(self):
stDlg = LevelDialog(self)
stDlg.new_level.connect(self.sps.AddLevel)
if stDlg.exec():
self.UpdatePlot()
def HandleNewReaction(self):
rxnDlg = ReactionDialog(self)
rxnDlg.new_reaction.connect(self.sps.AddReaction)
if rxnDlg.exec():
self.UpdatePlot()
def HandleRun(self):
self.sps.ChangeReactionParameters(self.bkeInput.value(), self.angleInput.value(), self.bfieldInput.value())
self.sps.rhoMin = self.rhoMinInput.value()
self.sps.rhoMax = self.rhoMaxInput.value()
self.UpdatePlot()
def HandleExSwitch(self):
if self.exButton.isChecked() and (not self.energyFlag):
self.energyFlag = True
self.UpdatePlot()
def HandleKESwitch(self):
if self.keButton.isChecked() and self.energyFlag:
self.energyFlag = False
self.UpdatePlot()
def UpdatePlot(self):
rxnNumber = 0
rhos = []
exs = []
kes = []
rxns = []
for rxnName in self.sps.reactions:
rxnNumber += 1
rxn = self.sps.reactions[rxnName]
for i in range(len(rxn.residLevels)):
rxns.append(rxnNumber)
rhos.append(rxn.ejectRhovals[i])
exs.append(rxn.residLevels[i])
kes.append(rxn.ejectKEvals[i])
self.canvas.axes.cla()
self.canvas.axes.plot(rhos, rxns, marker="o", linestyle="None")
for i in range(len(rxns)):
y = rxns[i]
x = rhos[i]
label = ''
if self.energyFlag:
label = "{:.2f}".format(exs[i])
else:
label = "{:.2f}".format(kes[i])
self.canvas.axes.annotate(label, (x,y), textcoords="offset points",xytext=(0,10),ha="center",rotation="90")
self.canvas.axes.set_xlim(self.sps.rhoMin, self.sps.rhoMax)
self.canvas.axes.set_yticks(range(1,rxnNumber+1))
self.canvas.axes.set_yticklabels(self.sps.reactions)
self.canvas.draw()
def UpdateInputs(self):
self.rhoMinInput.setValue(self.sps.rhoMin)
self.rhoMaxInput.setValue(self.sps.rhoMax)
self.bfieldInput.setValue(self.sps.Bfield)
self.bkeInput.setValue(self.sps.beamKE)
self.angleInput.setValue(self.sps.angle)
def main() :
mpl.use("Qt5Agg")
myapp = QApplication(sys.argv)
window = SPSPlotGUI()
sys.exit(myapp.exec_())
if __name__ == '__main__':
main()

8
test_config.txt Normal file
View File

@ -0,0 +1,8 @@
BeamEnergy(Mev): 16.0
B-field(kG): 7.8
Angle(deg): 27.5
RhoMin: 69.5 RhoMax: 83.5
ZT AT ZP AP ZE AE
6 12 1 2 1 1
6 12 1 2 1 2
8 16 1 2 1 1