class ToyMonteCarlo(object): def main(self): global weights, densities, weighted_densities plt.figure() cluster = clusters.SingleStation() self.station = cluster.stations[0] R = np.linspace(0, 100, 100) densities = [] weights = [] for E in np.linspace(1e13, 1e17, 10000): relative_flux = E ** -2.7 Ne = 10 ** (np.log10(E) - 15 + 4.8) self.ldf = KascadeLdf(Ne) min_dens = self.calculate_minimum_density_for_station_at_R(R) weights.append(relative_flux) densities.append(min_dens) weights = np.array(weights) densities = np.array(densities).T weighted_densities = (np.sum(weights * densities, axis=1) / np.sum(weights)) plt.plot(R, weighted_densities) plt.yscale('log') plt.ylabel("Min. density [m^{-2}]") plt.xlabel("Core distance [m]") plt.axvline(5.77) plt.show() def calculate_minimum_density_for_station_at_R(self, R): densities = self.calculate_densities_for_station_at_R(R) return np.min(densities, axis=0) def calculate_densities_for_station_at_R(self, R): densities = [] for detector in self.station.detectors: densities.append(self.calculate_densities_for_detector_at_R( detector, R)) return np.array(densities) def calculate_densities_for_detector_at_R(self, detector, R): x = 0 y = R x0, y0 = detector.get_xy_coordinates() r = np.sqrt((x - x0) ** 2 + (y - y0) ** 2) return self.ldf.calculate_ldf_value(r)
class EnergySensitivity(object): def __init__(self): # Detectors stations = [501, 503, 506] self.cluster = ScienceParkCluster(stations=stations) # Conditions self.detection_probability = 0.5 self.min_detectors = 2 self.min_stations = 3 # Shower parameters self.ldf = KascadeLdf() self.grid_points = 2500 self.max_radius = 1000 self.min_energy = 1e12 self.max_energy = 1e21 self.start_energy = 1e17 self.bisections = 11 # Throw showers in a regular grid around center mass of the station xc, yc, _ = self.cluster.calc_center_of_mass_coordinates() self.xx = np.linspace(-self.max_radius + xc, self.max_radius + xc, np.sqrt(self.grid_points)) self.yy = np.linspace(-self.max_radius + yc, self.max_radius + yc, np.sqrt(self.grid_points)) def main(self): # Cache detector positions for station in self.cluster.stations: for detector in station.detectors: detector.xy_coordinates = detector.get_xy_coordinates() # Results self.results = self.get_min_energy_per_bin() def show_restults(self): self.plot_scintillators_in_cluster() self.plot_energy_acceptance() # self.draw_background_map() def get_area_energy(self, energy): n_bins = np.sum(self.results < energy) bin_area = abs((self.xx[1] - self.xx[0]) * (self.yy[1] - self.yy[0])) area = n_bins * bin_area return area def get_min_energy_per_bin(self): worker_pool = Pool() temp_multi_find_min_energy = partial(multi_find_min_energy, self) results = worker_pool.map(temp_multi_find_min_energy, product(self.xx, self.yy)) worker_pool.close() worker_pool.join() # results = [temp_multi_find_min_energy(xy) for xy in product(self.xx, self.yy)] results = np.array(results).reshape((len(self.xx), len(self.yy))).T return results def find_min_energy(self, xc, yc): # Use bisection to quickly get the final energy energy = self.start_energy lo = self.min_energy hi = self.max_energy for _ in range(self.bisections): n_electrons = 10**(np.log10(energy) - 15 + 4.8) station_densities = self.calculate_densities_for_cluster( xc, yc, n_electrons) p_cluster = self.detection_probability_for_cluster( station_densities) if p_cluster == self.detection_probability: break elif p_cluster < self.detection_probability: lo = energy else: hi = energy energy = 10**((np.log10(lo) + np.log10(hi)) / 2.0) return energy def detection_probability_for_cluster(self, station_densities): """Determine the probability of 'coincidence' Calculate the probability of the requested coincidence using statistics. Fist the probability of a good detection in each station is determined. Then it looks for the probability that at least a given number of stations detects the shower. :param station_densities: list of densities at each detectors in each of the stations. :return: probability of a coicidence. """ if len(station_densities) < self.min_stations: # To few stations return 0 p_stations = [ self.detection_probability_for_station(detector_densities) for detector_densities in station_densities ] p0_stations = [1. - p for p in p_stations] p_cluster = self.calculate_p(p_stations, p0_stations, self.min_stations) return p_cluster def detection_probability_for_station(self, detector_densities): """Determine the probability of 'trigger' Calculate the probability of the requested detection using Poisson statistics. Each detector will be marked as hit when it has at least one particle. At least `min_detectors` need to be hit to count towards the probability of a good detection. :param detector_densities: list of densities at each detectors in the station. :return: list of probabilities for each station. """ if len(detector_densities) < self.min_detectors: # To few detectors return 0 p0_detectors = [self.p0(density) for density in detector_densities] p_detectors = [1. - p0 for p0 in p0_detectors] p_station = self.calculate_p(p_detectors, p0_detectors, self.min_detectors) return p_station def calculate_p(self, p, p0, min_n): n_p = len(p) p_total = 0 for n in range(min_n, n_p + 1): for i in combinations(range(n_p), n): p_combination = 1. for j in range(n_p): if j in i: # Probability of trigger p_combination *= p[j] else: # Probability of no trigger p_combination *= p0[j] p_total += p_combination return p_total def p(self, detector_density): """Chance of at least one particle in detector""" return 1.0 - self.p0(detector_density) def p0(self, detector_density): """Chance of detecting no particle in a detector""" return np.exp(-detector_density / 2.) def calculate_densities_for_cluster(self, x, y, n_electrons): densities = [ self.calculate_densities_for_station(station, x, y, n_electrons) for station in self.cluster.stations ] return densities def calculate_densities_for_station(self, station, x, y, n_electrons): densities = [ self.calculate_densities_for_detector(detector, x, y, n_electrons) for detector in station.detectors ] return densities def calculate_densities_for_detector(self, detector, x, y, n_electrons): r = self.calculate_detector_core_distance(detector, x, y) density = self.ldf.calculate_ldf_value(r, n_electrons) return density def calculate_detector_core_distance(self, detector, x, y): x0, y0 = detector.xy_coordinates r = np.sqrt((x - x0)**2 + (y - y0)**2) return r def plot_scintillators_in_cluster(self): # Draw station locations on a map for station in self.cluster.stations: for detector in station.detectors: detector_x, detector_y = detector.get_xy_coordinates() plt.scatter(detector_x, detector_y, marker=',', c='r', edgecolor='none', s=6) station_x, station_y, station_a = station.get_xyalpha_coordinates() plt.scatter(station_x, station_y, marker=',', c='b', edgecolor='none', s=3) def plot_energy_acceptance(self): # Grid min_energy = np.log10(self.min_energy) max_energy = np.log10(self.max_energy) levels = (max_energy - min_energy) * 3 + 1 label_levels = (max_energy - min_energy) + 1 contour = plt.contour(self.xx, self.yy, self.results, np.logspace(min_energy, max_energy, levels)) plt.clabel(contour, np.logspace(min_energy, max_energy, label_levels), inline=1, fontsize=8, fmt='%.0e') def draw_background_map(self): self_path = os.path.dirname(__file__) map_path = os.path.join(self_path, "backgrounds/ScienceParkMap_1.092.png") # Draw Science Park Map on 1:1 scale (1 meter = 1 pixel) background = plt.imread(map_path) # determine pixel:meter ratio for different OSM zoom levels at Science Park.. bg_scale = 1.092 bg_width = background.shape[1] * bg_scale bg_height = background.shape[0] * bg_scale plt.imshow(background, aspect='equal', alpha=0.5, extent=[-bg_width, bg_width, -bg_height, bg_height])