def test_full_likelihood(): """ Simple test of likelihood, test against known values for high and low signal cases. Check That full calculation and the gaussian approx become equal at high signal """ spe = 0.5 # Single photo-electron width pedestal = 1 # width of the pedestal distribution image_small = [0,1,2] expectation_small = [1,1,1] full_like_small = poisson_likelihood_full(image_small, expectation_small, spe, pedestal) # Check against known values assert np.sum(np.abs(full_like_small - np.asarray([2.75630505, 2.62168656, 3.39248449]))) < 1e-6 image_large = [40,50,60] expectation_large = [50,50,50] full_like_large = poisson_likelihood_full(image_large, expectation_large, spe, pedestal) # Check against known values assert np.sum(np.abs(full_like_large - np.asarray([7.45489137, 5.99305388, 7.66226007]))) < 1e-6 gaus_like_large = poisson_likelihood_gaussian(image_large, expectation_large, spe, pedestal) # Check thats in large signal case the full expectation is equal to the gaussian approximation (to 5%) assert np.all(np.abs((full_like_large-gaus_like_large)/full_like_large) < 0.05)
def test_full_likelihood(): """ Simple test of likelihood, test against known values for high and low signal cases. Check that full calculation and the gaussian approx become equal at high signal. """ spe = 0.5 # Single photo-electron width pedestal = 1 # width of the pedestal distribution image_small = [0, 1, 2] expectation_small = [1, 1, 1] full_like_small = poisson_likelihood_full(image_small, expectation_small, spe, pedestal) # Check against known values assert np.sum( np.abs(full_like_small - np.asarray([2.75630505, 2.62168656, 3.39248449]))) < 1e-6 image_large = [40, 50, 60] expectation_large = [50, 50, 50] full_like_large = poisson_likelihood_full(image_large, expectation_large, spe, pedestal) # Check against known values assert np.sum( np.abs(full_like_large - np.asarray([7.45489137, 5.99305388, 7.66226007]))) < 1e-6 gaus_like_large = poisson_likelihood_gaussian(image_large, expectation_large, spe, pedestal) # Check thats in large signal case the full expectation is equal to the # gaussian approximation (to 5%) assert np.all( np.abs((full_like_large - gaus_like_large) / full_like_large) < 0.05)
def get_likelihood(self, source_x, source_y, core_x, core_y, energy, x_max_scale, goodness_of_fit=False): """Get the likelihood that the image predicted at the given test position matches the camera image. Parameters ---------- source_x: float Source position of shower in the nominal system (in deg) source_y: float Source position of shower in the nominal system (in deg) core_x: float Core position of shower in tilted telescope system (in m) core_y: float Core position of shower in tilted telescope system (in m) energy: float Shower energy (in TeV) x_max_scale: float Scaling factor applied to geometrically calculated Xmax goodness_of_fit: boolean Determines whether expected likelihood should be subtracted from result Returns ------- float: Likelihood the model represents the camera image at this position """ # First we add units back onto everything. Currently not # handled very well, maybe in future we could just put # everything in the correct units when loading in the class # and ignore them from then on zenith = (np.pi / 2) - self.array_direction.alt.to(u.rad).value azimuth = self.array_direction.az # Geometrically calculate the depth of maximum given this test position x_max = self.get_shower_max(source_x, source_y, core_x, core_y, zenith) x_max *= x_max_scale # Calculate expected Xmax given this energy x_max_exp = guess_shower_depth(energy) # / np.cos(20*u.deg) # Convert to binning of Xmax x_max_bin = x_max - x_max_exp # Check for range if x_max_bin > 200: x_max_bin = 200 if x_max_bin < -100: x_max_bin = -100 # Calculate impact distance for all telescopes impact = np.sqrt(np.power(self.tel_pos_x - core_x, 2) + np.power(self.tel_pos_y - core_y, 2)) # And the expected rotation angle phi = np.arctan2((self.tel_pos_x - core_x), (self.tel_pos_y - core_y)) * u.rad # Rotate and translate all pixels such that they match the # template orientation pix_y_rot, pix_x_rot = self.rotate_translate( self.pixel_x, self.pixel_y, source_x, source_y, phi ) # In the interpolator class we can gain speed advantages by using masked arrays # so we need to make sure here everything is masked prediction = ma.zeros(self.image.shape) prediction.mask = ma.getmask(self.image) time_gradients = np.zeros((self.image.shape[0],2)) # Loop over all telescope types and get prediction for tel_type in np.unique(self.tel_types).tolist(): type_mask = self.tel_types == tel_type prediction[type_mask] = \ self.image_prediction(tel_type, energy * np.ones_like(impact[type_mask]), impact[type_mask], x_max_bin * np.ones_like(impact[type_mask]), pix_x_rot[type_mask] * (180 / math.pi) * -1, pix_y_rot[type_mask] * (180 / math.pi)) if self.use_time_gradient: time_gradients[type_mask] = \ self.predict_time(tel_type, energy * np.ones_like(impact[type_mask]), impact[type_mask], x_max_bin * np.ones_like(impact[type_mask])) if self.use_time_gradient: time_mask = np.logical_and(np.invert(ma.getmask(self.image)), self.time > 0) weight = np.sqrt(self.image) * time_mask rv = norm() sx = pix_x_rot * weight sxx = pix_x_rot * pix_x_rot * weight sy = self.time * weight sxy = self.time * pix_x_rot * weight d = weight.sum(axis=1) * sxx.sum(axis=1) - sx.sum(axis=1) * sx.sum(axis=1) time_fit = (weight.sum(axis=1) * sxy.sum(axis=1) - sx.sum(axis=1) * sy.sum( axis=1)) / d time_fit /= -1 * (180 / math.pi) chi2 = -2 * np.log(rv.pdf((time_fit - time_gradients.T[0])/ time_gradients.T[1])) # Likelihood function will break if we find a NaN or a 0 prediction[np.isnan(prediction)] = 1e-8 prediction[prediction < 1e-8] = 1e-8 prediction *= self.template_scale # Get likelihood that the prediction matched the camera image like = poisson_likelihood_gaussian(self.image, prediction, self.spe, self.ped) like[np.isnan(like)] = 1e9 like *= np.invert(ma.getmask(self.image)) like = ma.MaskedArray(like, mask=ma.getmask(self.image)) array_like = like if goodness_of_fit: return np.sum(like - mean_poisson_likelihood_gaussian(prediction, self.spe, self.ped)) prior_pen = 0 # Add prior penalities if we have them array_like += 1e-8 if "energy" in self.priors: prior_pen += energy_prior(energy, index=-1) if "xmax" in self.priors: prior_pen += xmax_prior(energy, x_max) array_like += prior_pen / float(len(array_like)) if self.array_return: array_like = array_like.ravel() return array_like[np.invert(ma.getmask(array_like))] final_sum = array_like.sum() if self.use_time_gradient: final_sum += chi2.sum() #* np.sum(ma.getmask(self.image)) return final_sum
def get_likelihood(self, source_x, source_y, core_x, core_y, energy, x_max_scale): """Get the likelihood that the image predicted at the given test position matches the camera image. Parameters ---------- source_x: float Source position of shower in the nominal system (in deg) source_y: float Source position of shower in the nominal system (in deg) core_x: float Core position of shower in tilted telescope system (in m) core_y: float Core position of shower in tilted telescope system (in m) energy: float Shower energy (in TeV) x_max_scale: float Scaling factor applied to geometrically calculated Xmax Returns ------- float: Likelihood the model represents the camera image at this position """ # First we add units back onto everything. Currently not # handled very well, maybe in future we could just put # everything in the correct units when loading in the class # and ignore them from then on zenith = 90 * u.deg - self.array_direction.alt azimuth = self.array_direction.az # Geometrically calculate the depth of maximum given this test position x_max = self.get_shower_max(source_x, source_y, core_x, core_y, zenith.to(u.rad).value) * x_max_scale # Calculate expected Xmax given this energy x_max_exp = guess_shower_depth(energy * u.TeV) # Convert to binning of Xmax, addition of 100 can probably be removed x_max_bin = x_max - x_max_exp # Check for range if x_max_bin > 250 * (u.g * u.cm**-2): x_max_bin = 250 * (u.g * u.cm**-2) if x_max_bin < -250 * (u.g * u.cm**-2): x_max_bin = -250 * (u.g * u.cm**-2) x_max_bin = x_max_bin.value array_like = None for tel_count in self.image: # Loop over all telescopes # Calculate impact distance for all telescopes impact = np.sqrt( pow(self.tel_pos_x[tel_count] - core_x, 2) + pow(self.tel_pos_y[tel_count] - core_y, 2)) # And the expected rotation angle phi = np.arctan2( (self.tel_pos_y[tel_count] - core_y), (self.tel_pos_x[tel_count] - core_x)) # - (math.pi/2.) # Rotate and translate all pixels such that they match the # template orientation pix_x_rot, pix_y_rot = self.rotate_translate( self.pixel_x[tel_count] * -1, self.pixel_y[tel_count], source_x, source_y, phi) # Then get the predicted image, convert pixel positions to deg prediction = self.image_prediction(self.type[tel_count], zenith, azimuth, energy, impact, x_max_bin, pix_x_rot * (180 / math.pi), pix_y_rot * (180 / math.pi)) prediction[np.isnan(prediction)] = 0 prediction[prediction < 1e-6] = 1e-6 # Scale templates to match simulations prediction *= self.scale[self.type[tel_count]] # prediction *= self.pixel_area[tel_count] # Get likelihood that the prediction matched the camera image like = poisson_likelihood_gaussian(self.image[tel_count], prediction, self.spe, self.ped[tel_count]) if np.any(prediction == np.inf): print("inf found at ", self.type[tel_count], zenith, azimuth, energy, impact, x_max_bin) like[np.isnan(like)] = 1e9 if array_like is None: array_like = like else: array_like = np.append(array_like, like) prior_pen = 0 # Add prior penalities if we have them array_like += 1e-8 if "energy" in self.priors: prior_pen += energy_prior(energy, index=-2) if "xmax" in self.priors: prior_pen += xmax_prior(energy, x_max) array_like += prior_pen / float(len(array_like)) if self.array_return: return array_like return np.sum(array_like)
def get_likelihood(self, source_x, source_y, core_x, core_y, energy, x_max_scale): """Get the likelihood that the image predicted at the given test position matches the camera image. Parameters ---------- source_x: float Source position of shower in the nominal system (in deg) source_y: float Source position of shower in the nominal system (in deg) core_x: float Core position of shower in tilted telescope system (in m) core_y: float Core position of shower in tilted telescope system (in m) energy: float Shower energy (in TeV) x_max_scale: float Scaling factor applied to geometrically calculated Xmax Returns ------- float: Likelihood the model represents the camera image at this position """ # First we add units back onto everything. Currently not # handled very well, maybe in future we could just put # everything in the correct units when loading in the class # and ignore them from then on zenith = 90 * u.deg - self.array_direction[0] azimuth = self.array_direction[1] # Geometrically calculate the depth of maximum given this test position if self.fit_xmax: x_max = self.get_shower_max(source_x, source_y, core_x, core_y, zenith.to(u.rad).value) * x_max_scale # Calculate expected Xmax given this energy x_max_exp = 300 + 93 * np.log10(energy) # Convert to binning of Xmax, addition of 100 can probably be # removed x_max_bin = x_max.value - x_max_exp # Check for range if x_max_bin > 100: x_max_bin = 100 if x_max_bin < -100: x_max_bin = -100 else: x_max_bin = x_max_scale * 37.8 if x_max_bin < 13: x_max_bin = 13 sum_like = 0 for tel_count in self.image: # Loop over all telescopes # Calculate impact distance for all telescopes impact = np.sqrt( pow(self.tel_pos_x[tel_count] - core_x, 2) + pow(self.tel_pos_y[tel_count] - core_y, 2)) # And the expected rotation angle phi = np.arctan2( (self.tel_pos_y[tel_count] - core_y), (self.tel_pos_x[tel_count] - core_x)) # - (math.pi/2.) # Rotate and translate all pixels such that they match the # template orientation pix_x_rot, pix_y_rot = self.rotate_translate( self.pixel_x[tel_count] * -1, self.pixel_y[tel_count], source_x, source_y, phi) # Then get the predicted image, convert pixel positions to deg prediction = self.image_prediction(self.type[tel_count], zenith, azimuth, energy, impact, x_max_bin, pix_x_rot * (180 / math.pi), pix_y_rot * (180 / math.pi)) prediction[np.isnan(prediction)] = 0 prediction[prediction < 1e-6] = 1e-6 # Scale templates to match simulations prediction *= self.scale[self.type[tel_count]] # Get likelihood that the prediction matched the camera image like = poisson_likelihood_gaussian(self.image[tel_count], prediction, self.spe, self.ped[tel_count]) if np.any(prediction == np.inf): print("inf found at ", self.type[tel_count], zenith, azimuth, energy, impact, x_max_bin) like[np.isnan(like)] = 1e9 sum_like += np.sum(like) if np.sum(prediction) is 0: sum_like += 1e9 return sum_like
def get_likelihood(self, source_x, source_y, core_x, core_y, energy, x_max_scale): """Get the likelihood that the image predicted at the given test position matches the camera image. Parameters ---------- source_x: float Source position of shower in the nominal system (in deg) source_y: float Source position of shower in the nominal system (in deg) core_x: float Core position of shower in tilted telescope system (in m) core_y: float Core position of shower in tilted telescope system (in m) energy: float Shower energy (in TeV) x_max_scale: float Scaling factor applied to geometrically calculated Xmax Returns ------- float: Likelihood the model represents the camera image at this position """ # First we add units back onto everything. Currently not # handled very well, maybe in future we could just put # everything in the correct units when loading in the class # and ignore them from then on zenith = 90*u.deg - self.array_direction.alt azimuth = self.array_direction.az # Geometrically calculate the depth of maximum given this test position x_max = self.get_shower_max(source_x, source_y, core_x, core_y, zenith.to(u.rad).value) * x_max_scale # Calculate expected Xmax given this energy x_max_exp = guess_shower_depth(energy*u.TeV) # Convert to binning of Xmax, addition of 100 can probably be removed x_max_bin = x_max - x_max_exp # Check for range if x_max_bin > 250 * (u.g*u.cm**-2): x_max_bin = 250 * (u.g*u.cm**-2) if x_max_bin < -250 * (u.g*u.cm**-2): x_max_bin = -250 * (u.g*u.cm**-2) x_max_bin = x_max_bin.value array_like = None for tel_count in self.image: # Loop over all telescopes # Calculate impact distance for all telescopes impact = np.sqrt(pow(self.tel_pos_x[tel_count] - core_x, 2) + pow(self.tel_pos_y[tel_count] - core_y, 2)) # And the expected rotation angle phi = np.arctan2((self.tel_pos_y[tel_count] - core_y), (self.tel_pos_x[tel_count] - core_x)) # - (math.pi/2.) # Rotate and translate all pixels such that they match the # template orientation pix_x_rot, pix_y_rot = self.rotate_translate( self.pixel_x[tel_count] * -1, self.pixel_y[tel_count], source_x, source_y, phi ) # Then get the predicted image, convert pixel positions to deg prediction = self.image_prediction( self.type[tel_count], zenith, azimuth, energy, impact, x_max_bin, pix_x_rot * (180 / math.pi), pix_y_rot * (180 / math.pi) ) prediction[np.isnan(prediction)] = 0 prediction[prediction < 1e-6] = 1e-6 # Scale templates to match simulations prediction *= self.scale[self.type[tel_count]] #prediction *= self.pixel_area[tel_count] # Get likelihood that the prediction matched the camera image like = poisson_likelihood_gaussian(self.image[tel_count], prediction, self.spe, self.ped[tel_count]) if np.any(prediction == np.inf): print("inf found at ", self.type[tel_count], zenith, azimuth, energy, impact, x_max_bin) like[np.isnan(like)] = 1e9 if array_like is None: array_like = like else: array_like = np.append(array_like,like) prior_pen = 0 # Add prior penalities if we have them array_like += 1e-8 if "energy" in self.priors: prior_pen += energy_prior(energy, index=-2) if "xmax" in self.priors: prior_pen += xmax_prior(energy, x_max) array_like += prior_pen/float(len(array_like)) if self.array_return: return array_like return np.sum(array_like)
def get_likelihood(self, source_x, source_y, core_x, core_y, energy, x_max_scale): """Get the likelihood that the image predicted at the given test position matches the camera image. Parameters ---------- source_x: float Source position of shower in the nominal system (in deg) source_y: float Source position of shower in the nominal system (in deg) core_x: float Core position of shower in tilted telescope system (in m) core_y: float Core position of shower in tilted telescope system (in m) energy: float Shower energy (in TeV) x_max_scale: float Scaling factor applied to geometrically calculated Xmax Returns ------- float: Likelihood the model represents the camera image at this position """ # First we add units back onto everything. Currently not # handled very well, maybe in future we could just put # everything in the correct units when loading in the class # and ignore them from then on zenith = 90 * u.deg - self.array_direction[0] azimuth = self.array_direction[1] # Geometrically calculate the depth of maximum given this test position if self.fit_xmax: x_max = self.get_shower_max(source_x, source_y, core_x, core_y, zenith.to(u.rad).value) * x_max_scale # Calculate expected Xmax given this energy x_max_exp = 300 + 93 * np.log10(energy) # Convert to binning of Xmax, addition of 100 can probably be # removed x_max_bin = x_max.value - x_max_exp # Check for range if x_max_bin > 100: x_max_bin = 100 if x_max_bin < -100: x_max_bin = -100 else: x_max_bin = x_max_scale * 37.8 if x_max_bin < 13: x_max_bin = 13 sum_like = 0 for tel_count in self.image: # Loop over all telescopes # Calculate impact distance for all telescopes impact = np.sqrt(pow(self.tel_pos_x[tel_count] - core_x, 2) + pow(self.tel_pos_y[tel_count] - core_y, 2)) # And the expected rotation angle phi = np.arctan2((self.tel_pos_y[tel_count] - core_y), (self.tel_pos_x[tel_count] - core_x)) # - (math.pi/2.) # Rotate and translate all pixels such that they match the # template orientation pix_x_rot, pix_y_rot = self.rotate_translate( self.pixel_x[tel_count] * -1, self.pixel_y[tel_count], source_x, source_y, phi ) # Then get the predicted image, convert pixel positions to deg prediction = self.image_prediction( self.type[tel_count], zenith, azimuth, energy, impact, x_max_bin, pix_x_rot * (180 / math.pi), pix_y_rot * (180 / math.pi) ) prediction[np.isnan(prediction)] = 0 prediction[prediction < 1e-6] = 1e-6 # Scale templates to match simulations prediction *= self.scale[self.type[tel_count]] # Get likelihood that the prediction matched the camera image like = poisson_likelihood_gaussian(self.image[tel_count], prediction, self.spe, self.ped[tel_count]) if np.any(prediction == np.inf): print("inf found at ", self.type[tel_count], zenith, azimuth, energy, impact, x_max_bin) like[np.isnan(like)] = 1e9 sum_like += np.sum(like) if np.sum(prediction) is 0: sum_like += 1e9 return sum_like