Stats for Y = q * round(X / q) where X ~ N(mu, sigma)
| 178 | |
| 179 | |
| 180 | class qnormal_gen: |
| 181 | """Stats for Y = q * round(X / q) where X ~ N(mu, sigma)""" |
| 182 | |
| 183 | def __init__(self, mu, sigma, q): |
| 184 | self.mu, self.sigma = list(map(float, (mu, sigma))) |
| 185 | self.q = q |
| 186 | # -- distfn for using the CDF |
| 187 | self._norm_logcdf = scipy.stats.norm(loc=mu, scale=sigma).logcdf |
| 188 | |
| 189 | def in_domain(self, x): |
| 190 | return np.isclose(x, safe_int_cast(np.round(old_div(x, self.q))) * self.q) |
| 191 | |
| 192 | def pmf(self, x): |
| 193 | return np.exp(self.logpmf(x)) |
| 194 | |
| 195 | def logpmf(self, x): |
| 196 | x1 = np.atleast_1d(x) |
| 197 | in_domain = self.in_domain(x1) |
| 198 | rval = np.zeros_like(x1, dtype=float) - np.inf |
| 199 | x_in_domain = x1[in_domain] |
| 200 | |
| 201 | ubound = x_in_domain + self.q * 0.5 |
| 202 | lbound = x_in_domain - self.q * 0.5 |
| 203 | # -- reflect intervals right of mu to other side |
| 204 | # for more accurate calculation |
| 205 | flip = lbound > self.mu |
| 206 | tmp = lbound[flip].copy() |
| 207 | lbound[flip] = self.mu - (ubound[flip] - self.mu) |
| 208 | ubound[flip] = self.mu - (tmp - self.mu) |
| 209 | |
| 210 | assert np.all(ubound > lbound) |
| 211 | a = self._norm_logcdf(ubound) |
| 212 | b = self._norm_logcdf(lbound) |
| 213 | rval[in_domain] = a + np.log1p(-np.exp(b - a)) |
| 214 | if isinstance(x, np.ndarray): |
| 215 | return rval |
| 216 | return float(rval) |
| 217 | |
| 218 | def rvs(self, size=()): |
| 219 | x = mtrand.normal(loc=self.mu, scale=self.sigma, size=size) |
| 220 | rval = safe_int_cast(np.round(old_div(x, self.q))) * self.q |
| 221 | return rval |
| 222 | |
| 223 | |
| 224 | class qlognormal_gen: |
no outgoing calls