Source code for madminer.likelihood.base

import logging
import numpy as np

from abc import abstractmethod
from scipy.stats import poisson, norm

from ..analysis import DataAnalyzer
from ..utils.various import mdot

logger = logging.getLogger(__name__)


[docs]class BaseLikelihood(DataAnalyzer):
[docs] @abstractmethod def create_negative_log_likelihood(self, *args, **kwargs): raise NotImplementedError()
[docs] @abstractmethod def create_expected_negative_log_likelihood(self, *args, **kwargs): raise NotImplementedError()
@abstractmethod def _log_likelihood(self, *args, **kwargs): raise NotImplementedError() @abstractmethod def _log_likelihood_kinematic(self, *args, **kwargs): raise NotImplementedError() def _asimov_data( self, theta, test_split=0.2, sample_only_from_closest_benchmark=True, n_asimov=None, ): # get data start_event, end_event, correction_factor = self._calculate_partition_bounds("test", test_split) x, weights_benchmarks = next( self.event_loader( start=start_event, end=end_event, batch_size=n_asimov, generated_close_to=theta if sample_only_from_closest_benchmark else None, ) ) weights_benchmarks *= correction_factor # morphing theta_matrix = self._get_theta_benchmark_matrix(theta) weights_theta = mdot(theta_matrix, weights_benchmarks) weights_theta /= np.sum(weights_theta) return x, weights_theta def _log_likelihood_poisson( self, n_observed, theta, nu, luminosity=300000.0, weights_benchmarks=None, total_weights=None, ): if total_weights is not None and nu is None: # `histo` mode: Efficient morphing of whole cross section for the case without nuisance parameters theta_matrix = self._get_theta_benchmark_matrix(theta) xsec = mdot(theta_matrix, total_weights) elif total_weights is not None and self.nuisance_morpher is not None: # `histo` mode: Efficient morphing of whole cross section for the case with nuisance parameters logger.debug("Using nuisance interpolation") theta_matrix = self._get_theta_benchmark_matrix(theta) xsec = mdot(theta_matrix, total_weights) nuisance_effects = self.nuisance_morpher.calculate_nuisance_factors( nu, total_weights.reshape((1, -1)) ).flatten() xsec *= nuisance_effects elif weights_benchmarks is not None: # `weighted` mode: Reweights existing events to (theta, nu) -- better than entirely new xsec calculation weights = self._weights([theta], [nu], weights_benchmarks)[0] xsec = sum(weights) elif weights_benchmarks is None: # `sampled` mode: Calculated total cross sections entirely new -- least efficient xsec = self.xsecs(thetas=[theta], nus=[nu], partition="train", generated_close_to=theta)[0][0] n_predicted = xsec * luminosity if xsec < 0: logger.warning("Total cross section is negative (%s pb) at theta=%s)", xsec, theta) n_predicted = 10 ** -5 n_observed_rounded = int(np.round(n_observed, 0)) log_likelihood = poisson.logpmf(k=n_observed_rounded, mu=n_predicted) logger.debug( "Poisson log likelihood: %s (%s expected, %s observed at theta=%s)", log_likelihood, n_predicted, n_observed_rounded, theta, ) return log_likelihood def _log_likelihood_constraint(self, nu): log_p = np.sum(norm.logpdf(nu)) logger.debug("Constraint log likelihood: %s", log_p) return log_p @staticmethod def _clean_nans(array): not_finite = np.any(~np.isfinite(array), axis=0) if np.sum(not_finite) > 0: logger.warning("Removing %s inf / nan results from calculation") array[:, not_finite] = 0.0 return array