def GetDrag(self, h, V): """ Function to compute the drag force, modelled on an Atlas V launch vehicle as a function of Mach no.""" #CONSTANTS: gamma = 1.14 R = 287 T, rho = atmosphere.Atmosphere(h) A = np.sqrt(gamma * R * T) M = V / A if M <= 1.25: Cd = -0.0415 * M**3 + 0.3892 * M**2 - 0.2614 * M + 0.303 elif M > 1.25 and M <= 4: Cd = -0.049 * M**4 + 0.5664 * M**3 - 2.3265 * M**2 + 3.8512 * M - 1.6625 elif M > 4 and M <= 10: Cd = -0.0037 * M**3 + 0.0695 * M**2 - 0.4105 * M + 0.9732 elif M > 10: Cd = 0.255 D = 0.5 * rho * V**2 * self.A * Cd return D
def __init__(self,X_max,N_max,d0,earth_emergence,azimuth=0, ckarray='gg_t_delta_theta_2020_normalized.npz',tel_area = 1): self.atmosphere = at.Atmosphere() self.gga = CherenkovPhotonArray(ckarray) self.tel_area = tel_area self.reset_shower(X_max,N_max,d0,earth_emergence,azimuth=0)
def __init__(self, time_span, rocket): if isinstance(rocket, Rocket.Rocket) is False: print( 'Error <in ModelWorld>: "rocket" must be an object of Rocket!') exit(-1) if time_span[0] < 0 or time_span[1] < 0: print( 'Error <in ModelWorld>: incorrect time interval! Time cannot be negative!' ) exit(-1) self.t_start = time_span[0] self.t_end = time_span[1] self.atmo = Atmo.Atmosphere() self.main_rocket = copy.copy(rocket) self.rocket = copy.copy(rocket)
def initialize_run(self): # ## Create atmosphere: attributes are self.atm.gas, self.atm.cloud and self.atm.layerProperty self.atm = atm.Atmosphere(self.planet, mode=self.mode, config=self.config, log=self.log, **self.kwargs) self.atm.run() # ## Read in absorption modules: to change absorption, edit files under /constituents' self.alpha = alpha.Alpha(mode=self.mode, config=self.config, log=self.log, **self.kwargs) # ## Next compute radiometric properties - initialize bright and return data class self.bright = bright.Brightness(mode=self.mode, log=self.log, **self.kwargs) self.data_return = data_handling.DataReturn() # ## Create fileIO class self.fIO = fileIO.FileIO(self.output_type)
class Shower(): """A class for generating extensive air shower profiles and their Cherenkov outputs. The shower can either be a Gaisser Hillas shower or a Griessen Shower. Parameters: X_max: depth at shower max (g/cm^2) N_max: number of charged particles at X_max h0: height of first interaction (meters) X0: Start depth theta: Polar angle of the shower axis with respect to vertical. Vertical is defined as normal to the Earth's surface at the point where the axis intersects with the surface. phi: azimuthal angle of axis intercept (radians) measured from the x axis. Standard physics spherical coordinate convention. Positive x axis is North, positive y axis is west. profile: Shower type, either 'GN' for Greisen, or GH for Gaisser-Hillas. direction: Shower direction, either 'up' for upward going showers, or 'down' for downward going showers. """ earth_radius = 6.371e6 Lambda = 70 atm = at.Atmosphere() axis_h = np.linspace(0,atm.maximum_height,10000) axis_rho = atm.density(axis_h) axis_delta = atm.delta(axis_h) axis_Moliere = 96. / axis_rho Moliere_data = np.load('lateral.npz') t_Moliere = Moliere_data['t'] AVG_Moliere = Moliere_data['avg'] def __init__(self,X_max,N_max,h0,theta,direction,phi=0,type='GH'): self.type = type self.reset_shower(X_max,N_max,h0,theta,direction,phi,type) def reset_shower(self,X_max,N_max,h0,theta,direction,phi=0,type='GH'): '''Set necessary attributes and perform calculations ''' self.input_X_max = X_max self.N_max = N_max self.h0 = h0 self.direction = direction self.theta = theta self.axis_r, self.axis_start_r = self.set_axis(theta,h0) self.axis_X, self.axis_dr, self.X0 = self.set_depth(self.axis_r, self.axis_start_r) self.X_max = X_max + self.X0 self.axis_nch = self.size(self.axis_X) self.axis_nch[self.axis_nch<1.e3] = 0 self.i_ch = np.nonzero(self.axis_nch) self.shower_X = self.axis_X[self.i_ch] self.shower_r = self.axis_r[self.i_ch] self.shower_Moliere = self.axis_Moliere[self.i_ch] self.shower_t = self.stage(self.shower_X) self.shower_avg_M = np.interp(self.shower_t,self.t_Moliere,self.AVG_Moliere) self.shower_rms_w = self.shower_avg_M * self.shower_Moliere def h_to_axis_R_LOC(self,h,theta): '''Return the length along the shower axis from the point of Earth emergence to the height above the surface specified Parameters: h: array of heights in meters theta: polar angle of shower axis (radians) returns: r (same size as h), an array of distances along the shower axis_sp. ''' cos_EM = np.cos(np.pi-theta) R = self.earth_radius r_CoE= h + R # distance from the center of the earth to the specified height r = R*cos_EM + np.sqrt(R**2*cos_EM**2-R**2+r_CoE**2) return r def set_axis(self,theta,h0): '''Create a table of distances along the shower axis ''' axis_r = self.h_to_axis_R_LOC(self.axis_h, theta) axis_start_r = self.h_to_axis_R_LOC(self.h0, theta) return axis_r, axis_start_r def set_depth(self,axis_r,axis_start_r): '''Integrate atmospheric density over selected direction to create a table of depth values. ''' axis_dr = axis_r[1:] - axis_r[:-1] axis_deltaX = np.sqrt(self.axis_rho[1:]*self.axis_rho[:-1])*axis_dr / 10# converting to g/cm^2 if self.direction == 'up': axis_X = np.concatenate((np.array([0]),np.cumsum(axis_deltaX))) elif self.direction == 'down': axis_X = np.concatenate((np.cumsum(axis_deltaX[::-1])[::-1], np.array([0]))) axis_dr = np.concatenate((np.array([0]),axis_dr)) X0 = np.interp(axis_start_r,axis_r,axis_X) return axis_X, axis_dr, X0 def size(self,X): """Return the size of the shower at a slant-depth X Parameters: X: the slant depth at which to calculate the shower size [g/cm2] Returns: N: the shower size """ if self.type == 'GH': value = self.GaisserHillas(X) elif self.type == 'GN': value = self.Greisen(X) return value def GaisserHillas(self,X): '''Return the size of a GH shower at a given depth. ''' x = (X-self.X0)/self.Lambda g0 = x>0. m = (self.X_max-self.X0)/self.Lambda n = np.zeros_like(x) n[g0] = np.exp( m*(np.log(x[g0])-np.log(m)) - (x[g0]-m) ) return self.N_max * n def Greisen(self,X,p=36.62): '''Return the size of a Greisen shower at a given depth. ''' X[X < self.X0] = self.X0 Delta = X - self.X_max W = self.X_max-self.X0 eps = Delta / W s = (1+eps)/(1+eps/3) i = np.nonzero(s) n = np.zeros_like(X) n[i] = np.exp((eps[i]*(1-1.5*np.log(s[i]))-1.5*np.log(s[i]))*(W/p)) return self.N_max * n def stage(self,X,X0=36.62): """Return the shower stage at a given slant-depth X. This is after Lafebre et al. Parameters: X: atmosphering slant-depth [g/cm2] X0: radiation length of air [g/cm2] Returns: t: shower stage """ return (X-self.X_max)/X0
class UpwardShower(): """A class for generating upward shower profiles and their Cherenkov outputs. The shower can either be a Gaisser Hillas shower or a Griessen Shower. Parameters: X_max: depth at shower max (g/cm^2) N_max: number of charged particles at X_max d0: height of first interaction (meters) earth_emergence: angle the primary particle makes with the tangent line of the Earth's surface as it emerges (radians) azimuth: azimuthal angle of Earth emergence (radians) n_stage: number of depth steps for calculating Cherenkov light ckarray: filename of the Cherenkov distribution table tel_area: surface area of the orbital telescopes (m^2) """ earth_radius = 6.371e6 Lambda = 70 atm = at.Atmosphere() axis_h = np.linspace(0,atm.maximum_height,1000) axis_rho = atm.density(axis_h) axis_delta = atm.delta(axis_h) def __init__(self,X_max,N_max,d0,earth_emergence,azimuth=0, ckarray='gg_t_delta_theta_2020_normalized.npz',tel_area = 1): self.atmosphere = at.Atmosphere() self.gga = CherenkovPhotonArray(ckarray) self.tel_area = tel_area self.reset_shower(X_max,N_max,d0,earth_emergence,azimuth=0) def reset_shower(self,X_max,N_max,d0,earth_emergence,azimuth=0): '''Set necessary attributes and perform calculations ''' self.X_max = X_max self.N_max = N_max self.d0 = d0 self.earth_emergence = earth_emergence self.zenith = np.pi / 2 - earth_emergence self.azimuth = azimuth self.axis_cem = np.cos(np.pi - self.zenith) self.crunch_numbers() def crunch_numbers(self): '''This function performs all the calculations in order. ''' self.set_shower_axis() self.select_shower_steps() self.calculate_gg() self.calculate_yield() def h_to_axis_R_LOC(self,h,cos_EM): '''Return the length along the shower axis from the point of Earth emergence to the height above the surface specified Parameters: h: array of heights in meters cos_EM: cosine of the Earth emergence angle plus 90 degrees returns: r (same size as h), an array of distances along the shower axis_sp. ''' R = self.earth_radius r_CoE= h + R # distance from the center of the earth to the specified height r = R*cos_EM + np.sqrt(R**2*cos_EM**2-R**2+r_CoE**2) return r def flat_earth_difference(self,r,EM,CEM): '''Calculate the difference in height between a flat earth height and the corrected height as as an inclined upward shower goes through the atmosphere. Parameters: r: distance along the track (m) EM: earth emergence angle (rad) CEM: cosine of the earth emergence angle + pi/2 radians returns: the difference in atmospheric height (between flat and round earth) at the given length along the track. ''' calculate the height correction R = self.earth_radius h_flat = r * np.sin(EM) h_true = np.sqrt(r**2 + R**2 -2*r*R*CEM) - R return h_true - h_flat def set_shower_axis(self): '''Create a table of 10000 distances and depths along the shower axis to interpolate into across its whole atmospheric path ''' self.axis_r = self.h_to_axis_R_LOC(self.axis_h, self.axis_cem) self.axis_dr = self.axis_r[1:] - self.axis_r[:-1] axis_deltaX = np.sqrt(self.axis_rho[1:]*self.axis_rho[:-1])*self.axis_dr / 10# converting to g/cm^2 self.axis_X = np.concatenate((np.array([0]),np.cumsum(axis_deltaX))) self.axis_dr = np.concatenate((np.array([0]),self.axis_dr)) def select_shower_steps(self): '''Select the depth indices steps where the shower is producing light, i.e. where there are charged particles, then invoke the orbital counter class to calculate angles and distances to hypothetical telescopes. ''' self.axis_start_r = self.h_to_axis_R_LOC(self.d0, self.axis_cem) self.X0 = np.interp(self.axis_start_r,self.axis_r,self.axis_X) self.X_max += self.X0 self.axis_nch = self.size(self.axis_X) self.axis_nch[self.axis_nch<1.e-1] = 0 self.axis_t = self.stage(self.axis_X) self.i_ch = np.nonzero(self.axis_nch) self.n_stage = np.size(self.i_ch) axis_r = self.axis_r[self.i_ch] - self.axis_start_r tel_r = self.h_to_axis_R_LOC(525.e3, self.axis_cem) - self.axis_start_r self.OC = OrbitalCounters(axis_r,100,100.e3,tel_r) def size(self,X): """Return the size of the shower at a slant-depth X Parameters: X: the slant depth at which to calculate the shower size [g/cm2] Returns: N: the shower size """ x = (X-self.X0)/self.Lambda g0 = x>0. m = (self.X_max-self.X0)/self.Lambda n = np.zeros_like(x) n[g0] = np.exp( m*(np.log(x[g0])-np.log(m)) - (x[g0]-m) ) return self.N_max * n def stage(self,X,X0=36.62): """Return the shower stage at a given slant-depth X. This is after Lafebre et al. Parameters: X: atmosphering slant-depth [g/cm2] X0: radiation length of air [g/cm2] Returns: t: shower stage """ return (X-self.X_max)/X0 def calculate_gg(self): '''This function sets the class attribute gg which is the value of the normalized Cherenkov distribution for each stage going to each telescope. ''' self.gg = np.empty_like(self.OC.travel_length) for i in range(self.OC.travel_length.shape[0]): for j in range(self.OC.travel_length.shape[1]): if self.axis_t[self.i_ch][j]>self.gga.t[0] and self.axis_t[self.i_ch][j]<self.gga.t[-1]: self.gg[i,j] = self.gga.interpolate(self.axis_t[self.i_ch][j],self.axis_delta[self.i_ch][j],self.OC.tel_q[i,j]) else: self.gg[i,j] = 0 def calculate_yield(self): '''This function calculates the number of Cherenkov photons at each telescope using the normalized angular distribution values interpolated from the table. ''' alpha_over_hbarc = 370.e2 # per eV per m, from PDG tel_dE =1.377602193180103 # Energy interval calculated from Cherenkov wavelengths chq = CherenkovPhoton.cherenkov_angle(1.e12,self.axis_delta[self.i_ch]) cy = alpha_over_hbarc*np.sin(chq)**2*tel_dE tel_factor = self.axis_nch[self.i_ch] * self.axis_dr[self.i_ch] * cy self.ng = self.gg * self.OC.tel_omega * tel_factor self.ng_sum = self.ng.sum(axis = 1)
class Shower(): """A class for generating extensive air shower profiles and their Cherenkov outputs. The shower can either be a Gaisser Hillas shower or a Griessen Shower. The Cartesian origin is at the point where the shower axis intersects with the Earth's surface. Parameters: X_max: depth at shower max (g/cm^2) N_max: number of charged particles at X_max h0: height of first interaction above the ground level (meters) X0: Start depth theta: Polar angle of the shower axis with respect to vertical. Vertical is defined as normal to the Earth's surface at the point where the axis intersects with the surface. direction: Shower direction, either 'up' for upward going showers, or 'down' for downward going showers. phi: azimuthal angle of axis intercept (radians) measured from the x axis. Standard physics spherical coordinate convention. Positive x axis is North, positive y axis is west. ground_level: Height above sea level of center of shower footprint (meters) type: Shower type, either 'GN' for Greisen, or GH for Gaisser-Hillas. """ earth_radius = 6.371e6 c = value('speed of light in vacuum') Lambda = 70 atm = at.Atmosphere() Moliere_data = np.load('lateral.npz') t_Moliere = Moliere_data['t'] AVG_Moliere = Moliere_data['avg'] theta_upper_limit = np.pi / 2 theta_lower_limit = 0. def __init__(self, X_max, N_max, h0, theta, direction, phi=0., ground_level=0., type='GH'): if theta < self.theta_lower_limit or theta > self.theta_upper_limit: raise Exception("Theta value out of bounds") self.reset_shower(X_max, N_max, h0, theta, direction, phi, ground_level, type) def reset_shower(self, X_max, N_max, h0, theta, direction, phi, ground_level, type): '''Set necessary attributes and perform calculations ''' self.type = type self.input_X_max = X_max self.N_max = N_max self.h0 = h0 self.direction = direction self.theta = theta self.phi = phi self.axis_h = np.linspace(ground_level, self.atm.maximum_height, 10000) self.axis_rho = self.atm.density(self.axis_h) self.axis_delta = self.atm.delta(self.axis_h) self.axis_h -= ground_level self.axis_Moliere = 96. / self.axis_rho axis_midh = np.sqrt(self.axis_h[1:] * self.axis_h[:-1]) self.axis_dh = np.empty_like(self.axis_h) self.axis_dh[1:-1] = np.abs(axis_midh[:-1] - axis_midh[1:]) self.axis_dh[-1] = self.axis_dh[-2] self.axis_dh[0] = self.axis_dh[1] self.ground_level = ground_level self.earth_radius += ground_level # adjust earth radius self.axis_r = self.h_to_axis_R_LOC(self.axis_h, theta) self.axis_start_r = self.h_to_axis_R_LOC(h0, theta) self.axis_X, self.axis_dr, self.X0 = self.set_depth( self.axis_r, self.axis_start_r) self.X_max = X_max + self.X0 self.axis_nch = self.size(self.axis_X) self.axis_nch[self.axis_nch < 1.e3] = 0 self.i_ch = np.nonzero(self.axis_nch)[0] self.shower_X = self.axis_X[self.i_ch] self.shower_r = self.axis_r[self.i_ch] self.shower_dr = self.axis_dr[self.i_ch] self.shower_nch = self.axis_nch[self.i_ch] self.shower_Moliere = self.axis_Moliere[self.i_ch] self.shower_delta = self.axis_delta[self.i_ch] self.shower_h = self.axis_h[self.i_ch] self.shower_dh = self.axis_dh[self.i_ch] self.shower_t = self.stage(self.shower_X, self.X_max) self.shower_avg_M = np.interp(self.shower_t, self.t_Moliere, self.AVG_Moliere) self.shower_rms_w = self.shower_avg_M * self.shower_Moliere @classmethod def h_to_axis_R_LOC(cls, h, theta): '''Return the length along the shower axis from the point of Earth emergence to the height above the surface specified Parameters: h: array of heights (m above sea level) theta: polar angle of shower axis (radians) returns: r (m) (same size as h), an array of distances along the shower axis_sp. ''' cos_EM = np.cos(np.pi - theta) R = cls.earth_radius r_CoE = h + R # distance from the center of the earth to the specified height r = R * cos_EM + np.sqrt(R**2 * cos_EM**2 - R**2 + r_CoE**2) return r @classmethod def theta_normal(cls, h, theta): ''' Convert a polar angle (at a given height) with respect to the z axis to a polar angle with respect to vertical in the atmosphere (at that height) Parameters: h: array of heights (m above sea level) theta: array of polar angle of shower axis (radians) Returns: The cosine(s) of the corrected angles(s) ''' r = cls.h_to_axis_R_LOC(h, theta) cq = ((cls.earth_radius + h)**2 + r**2 - cls.earth_radius**2) / (2 * r * (cls.earth_radius + h)) return cq def set_depth(self, axis_r, axis_start_r): '''Integrate atmospheric density over selected direction to create a table of depth values. Parameters: axis_r: distances along the shower axis axis_start_r: distance along the axis where the shower starts returns: axis_X: depths at each axis distances (g/cm^2) axis_dr: corresponding spatial distance associated with each depth (m) X0: start depth (g/cm^2) ''' axis_dr = axis_r[1:] - axis_r[:-1] axis_deltaX = np.sqrt( self.axis_rho[1:] * self.axis_rho[:-1]) * axis_dr / 10 # converting to g/cm^2 if self.direction == 'up': axis_X = np.concatenate((np.array([0]), np.cumsum(axis_deltaX))) elif self.direction == 'down': axis_X = np.concatenate( (np.cumsum(axis_deltaX[::-1])[::-1], np.array([0]))) axis_dr = np.concatenate((np.array([0]), axis_dr)) X0 = np.interp(axis_start_r, axis_r, axis_X) return axis_X, axis_dr, X0 def size(self, X): """Return the size of the shower at a slant-depth X Parameters: X: the slant depth at which to calculate the shower size [g/cm2] Returns: N: the shower size (# of charged particles) """ if self.type == 'GH': value = self.GaisserHillas(X) elif self.type == 'GN': value = self.Greisen(X) return value def GaisserHillas(self, X): '''Return the size of a GH shower at a given depth. Parameters: X: depth Returns: # of charged particles ''' x = (self.axis_X - self.X0) / self.Lambda g0 = x > 0. m = (self.X_max - self.X0) / self.Lambda n = np.zeros_like(x) n[g0] = np.exp(m * (np.log(x[g0]) - np.log(m)) - (x[g0] - m)) return self.N_max * n def Greisen(self, X_in, p=36.62): '''Return the size of a Greisen shower at a given depth. Parameters: X_in: depth Returns: # of charged particles ''' X = [x if x > self.X0 else self.X0 for x in X_in] Delta = X - self.X_max W = self.X_max - self.X0 eps = Delta / W s = (1 + eps) / (1 + eps / 3) i = np.nonzero(s) n = np.zeros_like(X) n[i] = np.exp( (eps[i] * (1 - 1.5 * np.log(s[i])) - 1.5 * np.log(s[i])) * (W / p)) return self.N_max * n @classmethod def stage(cls, X, X_max, X0=36.62): """Return the shower stage at a given slant-depth X. This is after Lafebre et al. Parameters: X: atmosphering slant-depth [g/cm2] X0: radiation length of air [g/cm2] Returns: t: shower stage """ return (X - X_max) / X0