def calculate_flux(energies, veffs, stations=1, years=1): """Calculate flux (m^-2 s^-1 sr^-1 GeV^-1) for energies in GeV and veffs in km3sr and livetime in years""" energies = np.asarray(energies) veffs = np.asarray(veffs) # Get number of energy bins per decade log_energy = np.log10(energies) d_log_energy = np.diff(log_energy) for d_log in d_log_energy: if not np.isclose(d_log, d_log_energy[0]): raise ValueError("Energies should be evenly spaced in log-10-space") bins_per_decade = 1/d_log_energy[0] # Get average interaction lengths (harmonic mean, since average should be in cross section) int_len = np.zeros(len(energies)) for i, e in enumerate(energies): nu = pyrex.Particle('nu_e', (0, 0, 0), (0, 0, -1), e) nubar = pyrex.Particle('nu_e_bar', (0, 0, 0), (0, 0, -1), e) avg_int = 2/((1/nu.interaction.total_interaction_length)+(1/nubar.interaction.total_interaction_length)) int_len[i] = avg_int # cmwe # Get effective area ice_density = 0.92 # g/cm^3 ice_density *= 1e15 # convert to g/km^3 = nucleons/km^3 aeffs = veffs * ice_density / int_len # cm^2 sr aeffs *= 1e-4 # convert to m^2 sr aeff_tots = aeffs * stations * (years * 365.25 * 24 * 60 * 60) # Upper limit on events # 2.3 for Neyman UL w/ 0 background, # 2.44 for F-C UL w/ 0 background, etc upper_limit = 2.44 return upper_limit / aeff_tots * bins_per_decade / np.log(10) / energies
def test_filter_attenuation(): # filtered_spectrum = self.spectrum # responses = np.array(freq_response(self.frequencies)) # filtered_spectrum *= responses # self.values = np.real(scipy.fftpack.ifft(filtered_spectrum)) # freq_response = pf.attenuation # self = AskaryanSignal(times=np.linspace(-20e-9, 80e-9, 2048, endpoint=False), # energy=p.energy*1e-3, theta=psi, n=n) t = 0 pf = pyrex.PathFinder(pyrex.IceModel(), (0,0,-2800), (5000,5000,-200)) while not(pf.exists): z = np.random.random()*-2800 pf = pyrex.PathFinder(pyrex.IceModel(), (0,0,z), (5000,5000,-200)) p = pyrex.Particle(vertex=pf.from_point, direction=(1/np.sqrt(2),1/np.sqrt(2),0), energy=1e6) k = pf.emitted_ray epol = np.vdot(k, p.direction) * k - p.direction epol = epol / np.linalg.norm(epol) psi = np.arccos(np.vdot(p.direction, k)) n = pyrex.IceModel.index(p.vertex[2]) pulse = pyrex.AskaryanSignal(times=np.linspace(-20e-9, 80e-9, 2048, endpoint=False), energy=p.energy, theta=psi, n=n) t += performance_test("filtered_spectrum = pulse.spectrum", number=1000, use_globals={"pulse": pulse}) t += performance_test("fs = pulse.frequencies", number=1000, use_globals={"pulse": pulse}) fs = pulse.frequencies # performance_test("alen = ice.attenuation_length(-1000, fa*1e-6)", number=100, # use_globals={"ice": pyrex.IceModel(), "fa": np.abs(fs)}) t += performance_test("responses = freq_response(fs)", number=1000, use_globals={"freq_response": pf.attenuation, "fs": fs}) # t += performance_test("responses = np.array(freq_response(pulse.frequencies))", # number = 100, setup="import numpy as np", # use_globals={"freq_response": pf.attenuation, # "pulse": pulse}) filtered_spectrum = pulse.spectrum * np.array(pf.attenuation(pulse.frequencies)) t += performance_test("np.real(scipy.fftpack.ifft(filtered_spectrum))", number=1000, setup="import numpy as np; import scipy.fftpack", use_globals={"filtered_spectrum": filtered_spectrum}) print("Total time:", round(t*1000, 1), "milliseconds for", len(fs), "frequencies") print(" ", round(t*1e6/len(fs), 1), "microseconds per frequency")
def get_good_path(): z = np.random.random() * -2800 p = pyrex.Particle(vertex=(0,0,z), direction=(0,0,1), energy=energy) pf = pyrex.PathFinder(ice, p.vertex, ant.position) k = pf.emitted_ray epol = np.vdot(k, p.direction) * k - p.direction epol = epol / np.linalg.norm(epol) psi = np.arccos(np.vdot(p.direction, k)) if pf.exists and psi<np.pi/2: return p, pf, psi else: return get_good_path()
def test_PathFinder_propagate(): t = 0 pf = pyrex.PathFinder(pyrex.IceModel(), (0,0,-2800), (5000,5000,-200)) while not(pf.exists): z = np.random.random()*-2800 pf = pyrex.PathFinder(pyrex.IceModel(), (0,0,z), (5000,5000,-200)) p = pyrex.Particle(vertex=pf.from_point, direction=(1/np.sqrt(2),1/np.sqrt(2),0), energy=1e6) k = pf.emitted_ray epol = np.vdot(k, p.direction) * k - p.direction epol = epol / np.linalg.norm(epol) psi = np.arccos(np.vdot(p.direction, k)) n = pyrex.IceModel.index(p.vertex[2]) pulse = pyrex.AskaryanSignal(times=np.linspace(-20e-9, 80e-9, 2048, endpoint=False), energy=p.energy, theta=psi, n=n) t += performance_test("signal.values *= 1 / pf.path_length", repeats=100, setup="import pyrex;"+ "signal = pyrex.Signal(pulse.times, pulse.values)", use_globals={"pf": pf, "pulse": pulse}) pulse.values *= 1 / pf.path_length t += performance_test("signal.filter_frequencies(pf.attenuation)", repeats=100, setup="import pyrex;"+ "signal = pyrex.Signal(pulse.times, pulse.values)", use_globals={"pf": pf, "pulse": pulse}) # pulse.filter_frequencies(pf.attenuation) t += performance_test("signal.times += pf.tof", repeats=100, setup="import pyrex;"+ "signal = pyrex.Signal(pulse.times, pulse.values)", use_globals={"pf": pf, "pulse": pulse}) print("Total time:", round(t*1000, 1), "milliseconds per signal")
plt.xlabel("Neutrino Energy (GeV)") plt.ylabel("Effective Volume (m^3)") plt.tight_layout() plt.show() # Then from the effective volumes, we can calculate the effective areas. # The effective area is given by the effective volume divided by the neutrino # interaction length in the ice. The interaction length given by a PyREx # Particle object is the water-equivalent interaction length, so it needs to # be scaled by the relative density of ice. The interaction length used will # be the harmonic mean of the neutrino and antineutrino interaction lengths # (since the cross sections are what should be averaged). int_lens = np.zeros(len(energies)) for i, energy in enumerate(energies): nu = pyrex.Particle(particle_id="nu_e", vertex=(0, 0, 0), direction=(0, 0, 1), energy=energy) nu_bar = pyrex.Particle(particle_id="nu_e_bar", vertex=(0, 0, 0), direction=(0, 0, 1), energy=energy) int_lens[i] = 2 / ((1 / nu.interaction.total_interaction_length) + (1 / nu_bar.interaction.total_interaction_length)) int_lens *= 1e-2 # convert from cm to m (water-equivalent) ice_density = 0.92 # g/cm^3, relative to 1 g/cm^3 for water effective_areas = effective_volumes * ice_density / int_lens area_errors = volume_errors * ice_density / int_lens plt.errorbar(energies, effective_areas, yerr=area_errors,
import numpy as np import matplotlib.pyplot as plt import pyrex # First, set up a neutrino source and find the index of refraction at its depth. # Then use that index of refraction to calculate the Cherenkov angle. source = pyrex.Particle("nu_e", vertex=(0, 0, -1000), direction=(0, 0, -1), energy=1e8) n = pyrex.ice.index(source.vertex[2]) ch_angle = np.arccos(1/n) # Now, for a range of dthetas, generate an Askaryan pulse dtheta away from the # Chereknov angle and plot its frequency spectrum. for dtheta in np.radians(np.logspace(-1, 1, 5)): n_pts = 10001 pulse = pyrex.AskaryanSignal(times=np.linspace(-20e-9, 80e-9, n_pts), particle=source, viewing_angle=ch_angle+dtheta, viewing_distance=1000) plt.plot(pulse.frequencies[:int(n_pts/2)] * 1e-6, # Convert from Hz to MHz np.abs(pulse.spectrum)[:int(n_pts/2)], label=f"$\\Delta\\theta = {np.degrees(dtheta):.2f}$") plt.legend(loc='upper right') plt.title("Frequency Spectrum of Askaryan Pulse\n"+ "For Different Off-Cone Angles") plt.xlabel("Frequency (MHz)") plt.xlim(0, 3000) plt.tight_layout() plt.show() # Actually, we probably really want to see the frequency content after the
ax[0].set_title("Detector Top View") ax[0].set_xlabel("x-position") ax[0].set_ylabel("y-position") ax[1].scatter([ant.position[0] for ant in det], [ant.position[2] for ant in det], c='k') ax[1].set_title("Detector Side View") ax[1].set_xlabel("x-position") ax[1].set_ylabel("z-position") plt.tight_layout() plt.show() # Now set up a particle generator that will just throw the one event we're # interested in, and create an event kernel with our detector and our generator. p = pyrex.Particle(particle_id=pyrex.Particle.Type.electron_neutrino, vertex=[1002.65674195, -421.95118348, -486.0953201], direction=[-0.90615395, -0.41800062, -0.06450191], energy=3e9) p.interaction.kind = p.interaction.Type.neutral_current p.interaction.em_frac = 0 p.interaction.had_frac = 1 gen = pyrex.ListGenerator(pyrex.Event(p)) kern = pyrex.EventKernel(antennas=det, generator=gen) # Then make sure our detector is cleared out and throw the event! # reset_noise will make sure we get new noise waveforms every time. det.clear(reset_noise=True) kern.event() # Now let's take a look at the waveforms of the event. Since each event has a # first and second ray, plot their waveforms side-by-side for each antenna. for i, ant in enumerate(det):