commit f11c611c18a5d7c533334372ac9d074b32245932 Author: Gordon McCann Date: Mon Dec 6 15:37:47 2021 -0500 First commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..760d137 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +*.pickle +*.txt +!test_config.txt +__pycache__ +*.sublime-project +*.sublime-workspace +!.gitignore \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..c57a1ef --- /dev/null +++ b/README.md @@ -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. diff --git a/bin/spanc b/bin/spanc new file mode 100755 index 0000000..aa5f8e6 --- /dev/null +++ b/bin/spanc @@ -0,0 +1,3 @@ +#!/bin/bash + +./spanc/SpancGUI.py \ No newline at end of file diff --git a/bin/spsplot b/bin/spsplot new file mode 100755 index 0000000..50deff8 --- /dev/null +++ b/bin/spsplot @@ -0,0 +1,3 @@ +#!/bin/bash + +./spsplot/SPSPlotGUI.py diff --git a/spanc/EnergyLoss.py b/spanc/EnergyLoss.py new file mode 100755 index 0000000..6e068df --- /dev/null +++ b/spanc/EnergyLoss.py @@ -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() diff --git a/spanc/EnergyLossData.py b/spanc/EnergyLossData.py new file mode 100644 index 0000000..b258bf0 --- /dev/null +++ b/spanc/EnergyLossData.py @@ -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] + ]) + \ No newline at end of file diff --git a/spanc/Fitter.py b/spanc/Fitter.py new file mode 100755 index 0000000..4c8e348 --- /dev/null +++ b/spanc/Fitter.py @@ -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() \ No newline at end of file diff --git a/spanc/IngoFit.py b/spanc/IngoFit.py new file mode 100755 index 0000000..62316e1 --- /dev/null +++ b/spanc/IngoFit.py @@ -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() + + diff --git a/spanc/LayeredTarget.py b/spanc/LayeredTarget.py new file mode 100644 index 0000000..2c73504 --- /dev/null +++ b/spanc/LayeredTarget.py @@ -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 + diff --git a/spanc/NucData.py b/spanc/NucData.py new file mode 100755 index 0000000..bcf563f --- /dev/null +++ b/spanc/NucData.py @@ -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 diff --git a/spanc/Reaction.py b/spanc/Reaction.py new file mode 100644 index 0000000..22f9e3f --- /dev/null +++ b/spanc/Reaction.py @@ -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 \ No newline at end of file diff --git a/spanc/Spanc.py b/spanc/Spanc.py new file mode 100644 index 0000000..bb2fabc --- /dev/null +++ b/spanc/Spanc.py @@ -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) + + diff --git a/spanc/SpancGUI.py b/spanc/SpancGUI.py new file mode 100755 index 0000000..0b885b5 --- /dev/null +++ b/spanc/SpancGUI.py @@ -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() \ No newline at end of file diff --git a/spsplot/NucData.py b/spsplot/NucData.py new file mode 100755 index 0000000..a145212 --- /dev/null +++ b/spsplot/NucData.py @@ -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 diff --git a/spsplot/NuclearRxn.py b/spsplot/NuclearRxn.py new file mode 100644 index 0000000..a8bd122 --- /dev/null +++ b/spsplot/NuclearRxn.py @@ -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) + + diff --git a/spsplot/SPSPlot.py b/spsplot/SPSPlot.py new file mode 100644 index 0000000..c2d68b6 --- /dev/null +++ b/spsplot/SPSPlot.py @@ -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) \ No newline at end of file diff --git a/spsplot/SPSPlotGUI.py b/spsplot/SPSPlotGUI.py new file mode 100755 index 0000000..8de1a4f --- /dev/null +++ b/spsplot/SPSPlotGUI.py @@ -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() \ No newline at end of file diff --git a/test_config.txt b/test_config.txt new file mode 100644 index 0000000..770022c --- /dev/null +++ b/test_config.txt @@ -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 \ No newline at end of file