def mcllh_eff(actual_values, expected_values): """Compute the log-likelihood (llh) based on eq. 3.16 - https://doi.org/10.1007/JHEP06(2019)030 accounting for finite MC statistics. This is the most recommended likelihood in the paper. Parameters ---------- actual_values, expected_values : numpy.ndarrays of same shape Returns ------- llh : numpy.ndarray of same shape as the inputs llh corresponding to each pair of elements in `actual_values` and `expected_values`. Notes ----- * """ assert actual_values.shape == expected_values.shape # Convert to simple numpy arrays containing floats actual_values = unp.nominal_values(actual_values).ravel() sigma = unp.std_devs(expected_values).ravel() expected_values = unp.nominal_values(expected_values).ravel() with np.errstate(invalid='ignore'): # Mask off any nan expected values (these are assumed to be ok) actual_values = np.ma.masked_invalid(actual_values) expected_values = np.ma.masked_invalid(expected_values) # TODO: How should we handle nan / masked values in the "data" # (actual_values) distribution? How about negative numbers? # Make sure actual values (aka "data") are valid -- no infs, no nans, # etc. if np.any((actual_values < 0) | ~np.isfinite(actual_values)): msg = ( '`actual_values` must be >= 0 and neither inf nor nan...\n' + maperror_logmsg(actual_values)) raise ValueError(msg) # Check that new array contains all valid entries if np.any(expected_values < 0.0): msg = ('`expected_values` must all be >= 0...\n' + maperror_logmsg(expected_values)) raise ValueError(msg) # Replace 0's with small positive numbers to avoid inf in log np.clip(expected_values, a_min=SMALL_POS, a_max=np.inf, out=expected_values) llh_val = likelihood_functions.poisson_gamma(actual_values, expected_values, sigma**2, a=1, b=0) return llh_val
def thorsten_llh(actual_values, expected_values): """Compute the log-likelihood (llh) based on eq. 20 - https://doi.org/10.1140/epjp/i2018-12042-x accounting for finite MC statistics. It is a good analytical approximation to the convolutional approach described later in the paper. Parameters ---------- actual_values, expected_values : numpy.ndarrays of same shape Returns ------- llh : numpy.ndarray of same shape as the inputs llh corresponding to each pair of elements in `actual_values` and `expected_values`. Notes ----- * """ assert actual_values.shape == expected_values.shape # Convert to simple numpy arrays containing floats actual_values = unp.nominal_values(actual_values).ravel() sigma = unp.std_devs(expected_values).ravel() expected_values = unp.nominal_values(expected_values).ravel() with np.errstate(invalid='ignore'): # Mask off any nan expected values (these are assumed to be ok) actual_values = np.ma.masked_invalid(actual_values) expected_values = np.ma.masked_invalid(expected_values) # Check that new array contains all valid entries if np.any(actual_values < 0): msg = ('`actual_values` must all be >= 0...\n' + maperror_logmsg(actual_values)) raise ValueError(msg) # TODO: How should we handle nan / masked values in the "data" # (actual_values) distribution? How about negative numbers? # Make sure actual values (aka "data") are valid -- no infs, no nans, # etc. if np.any((actual_values < 0) | ~np.isfinite(actual_values)): msg = ( '`actual_values` must be >= 0 and neither inf nor nan...\n' + maperror_logmsg(actual_values)) raise ValueError(msg) # Check that new array contains all valid entries if np.any(expected_values < 0.0): msg = ('`expected_values` must all be >= 0...\n' + maperror_logmsg(expected_values)) raise ValueError(msg) llh_val = likelihood_functions.poisson_gamma(actual_values, expected_values, sigma**2, a=0, b=0) return llh_val