|
3 | 3 | from typing import Callable, Dict, Tuple |
4 | 4 |
|
5 | 5 | import numpy as np |
6 | | -from scipy.special import erf |
7 | | - |
8 | | - |
9 | | -def logpdf(x, mu, sigma): |
10 | | - # We do not need second term as it does not depend on parameters |
11 | | - return -(((x - mu) / sigma) ** 2) / 2 # - np.log(np.sqrt(2*np.pi) * sigma) |
12 | | - |
13 | | - |
14 | | -def barrier(x): |
15 | | - res = np.where(x > 0, 1 / x, np.inf) # FIXME: naive barrier function |
16 | | - |
17 | | - return res |
18 | | - |
19 | | - |
20 | | -def logcdf(x): |
21 | | - # TODO: faster (maybe not so accurate, as we do not need it) implementation |
22 | | - # return norm.logcdf(x) |
23 | | - |
24 | | - result = np.zeros(len(x)) |
25 | | - |
26 | | - idx = x < -5 |
27 | | - result[idx] = -x[idx] ** 2 / 2 - 1 / x[idx] ** 2 - 0.9189385336 - np.log(-x[idx]) |
28 | | - result[~idx] = np.log(0.5) + np.log1p(erf(x[~idx] / np.sqrt(2))) |
29 | | - |
30 | | - return result |
31 | | - |
32 | 6 |
|
33 | 7 | try: |
34 | 8 | from iminuit import Minuit |
| 9 | + from scipy.special import erf |
35 | 10 | except ImportError: |
36 | 11 | MaximumLikelihood = None |
37 | 12 | else: |
@@ -63,21 +38,47 @@ def __call__(self, *par): |
63 | 38 | ym = self.model(self.x, *par) |
64 | 39 |
|
65 | 40 | if self.upper_mask is None: |
66 | | - result = -np.sum(logpdf(self.y, ym, self.yerror)) |
| 41 | + result = -np.sum(self.logpdf(self.y, ym, self.yerror)) |
67 | 42 | else: |
68 | 43 | # Measurements |
69 | | - result = -np.sum(logpdf(self.y[~self.upper_mask], ym[~self.upper_mask], self.yerror[~self.upper_mask])) |
| 44 | + result = -np.sum( |
| 45 | + self.logpdf(self.y[~self.upper_mask], ym[~self.upper_mask], self.yerror[~self.upper_mask]) |
| 46 | + ) |
70 | 47 | # Upper limits, Tobit model |
71 | 48 | # https://stats.stackexchange.com/questions/49443/how-to-model-this-odd-shaped-distribution-almost-a-reverse-j |
72 | 49 | result += -np.sum( |
73 | | - logcdf((self.y[self.upper_mask] - ym[self.upper_mask]) / self.yerror[self.upper_mask]) |
| 50 | + self.logcdf((self.y[self.upper_mask] - ym[self.upper_mask]) / self.yerror[self.upper_mask]) |
74 | 51 | ) |
75 | 52 |
|
76 | 53 | # Barriers around parameter ranges |
77 | 54 | # Scale is selected so that for the most of the range it is much smaller |
78 | 55 | # than 0.5 which corresponds to 1-sigma errors |
79 | | - result += 0.0001 * np.sum(barrier((par - self.limits0) / self.limits_scale)) |
80 | | - result += 0.0001 * np.sum(barrier((self.limits1 - par) / self.limits_scale)) |
| 56 | + result += 0.0001 * np.sum(self.barrier((par - self.limits0) / self.limits_scale)) |
| 57 | + result += 0.0001 * np.sum(self.barrier((self.limits1 - par) / self.limits_scale)) |
| 58 | + |
| 59 | + return result |
| 60 | + |
| 61 | + @staticmethod |
| 62 | + def logpdf(x, mu, sigma): |
| 63 | + # We do not need the second term as it does not depend on parameters |
| 64 | + return -(((x - mu) / sigma) ** 2) / 2 # - np.log(np.sqrt(2*np.pi) * sigma) |
| 65 | + |
| 66 | + @staticmethod |
| 67 | + def barrier(x): |
| 68 | + res = np.where(x > 0, 1 / x, np.inf) # FIXME: naive barrier function |
| 69 | + |
| 70 | + return res |
| 71 | + |
| 72 | + @staticmethod |
| 73 | + def logcdf(x): |
| 74 | + # TODO: faster (maybe not so accurate, as we do not need it) implementation |
| 75 | + # return norm.logcdf(x) |
| 76 | + |
| 77 | + result = np.zeros(len(x)) |
| 78 | + |
| 79 | + idx = x < -5 |
| 80 | + result[idx] = -x[idx] ** 2 / 2 - 1 / x[idx] ** 2 - 0.9189385336 - np.log(-x[idx]) |
| 81 | + result[~idx] = np.log(0.5) + np.log1p(erf(x[~idx] / np.sqrt(2))) |
81 | 82 |
|
82 | 83 | return result |
83 | 84 |
|
|
0 commit comments