def denormalize(self, scale: Scale): denormalized_min = (scale.denormalize_point(self.min) if self.min is not None else None) denormalized_max = (scale.denormalize_point(self.max) if self.max is not None else None) return self.__class__(self.p, denormalized_min, denormalized_max, self.weight)
def test_log_pdf(xscale: Scale): normed_test_loc = 0.5 normed_test_s = 0.1 test_loc = xscale.denormalize_point(normed_test_loc) test_s = normed_test_s * xscale.width ergoLogisticMixture = LogisticMixture( components=[ Logistic( loc=xscale.denormalize_point(0.2), s=0.5 * xscale.width, scale=xscale, ), Logistic(loc=test_loc, s=test_s, scale=xscale), ], probs=[1.8629593e-29, 1.0], ) ergoLogistic = Logistic(loc=test_loc, s=test_s, scale=xscale) ## Test PDF normed_scipydist = scipy.stats.logistic(normed_test_loc, normed_test_s) for x in np.linspace(0, 1, 10): denormalized_x = xscale.denormalize_point(x) assert (normed_scipydist.pdf(x) / xscale.width == pytest.approx( float(ergoLogistic.pdf(denormalized_x)), rel=1e-3) == pytest.approx(float(ergoLogisticMixture.pdf(denormalized_x)), rel=1e-3))
def test_cdf(xscale: Scale): scipydist_normed = scipy.stats.logistic(0.5, 0.05) true_loc = xscale.denormalize_point(0.5) true_s = 0.05 * xscale.width ergodist = Logistic(loc=true_loc, s=true_s, scale=xscale) for x in np.linspace(0, 1, 10): assert scipydist_normed.cdf(x) == pytest.approx(float( ergodist.cdf(xscale.denormalize_point(x))), rel=1e-3) # TODO: consider a better approach for log scale if isinstance(xscale, LogScale): for x in np.linspace(xscale.low, xscale.high, 10): assert scipydist_normed.cdf( xscale.normalize_point(x)) == pytest.approx(float( ergodist.cdf(x)), rel=1e-3) else: scipydist_true = scipy.stats.logistic(true_loc, true_s) for x in np.linspace(xscale.low, xscale.high, 10): assert scipydist_true.cdf(x) == pytest.approx(float( ergodist.cdf(x)), rel=1e-3)
def denormalize(self, scale: Scale): denormed_base_dist = self.base_dist.denormalize(scale) denormed_floor = scale.denormalize_point(self.floor) denormed_ceiling = scale.denormalize_point(self.ceiling) return self.__class__( base_dist=denormed_base_dist, floor=denormed_floor, ceiling=denormed_ceiling, )
def denormalize(self, scale: Scale): denormalized_xs = np.array( [scale.denormalize_point(x) for x in self.xs]) denormalized_densities = np.array( [density / scale.width for density in self.densities]) return self.__class__(denormalized_xs, denormalized_densities, self.weight)
def test_interval_plus_entropy(scale: Scale): conditions = [ IntervalCondition(p=0.5, max=scale.denormalize_point(0.3)), MaxEntropyCondition(weight=0.01), ] fitted_dist = PointDensity.from_conditions( conditions, scale=scale, ) # We expect at most 3 different densities: one for inside the interval, one for outside, # and one between. assert np.unique(fitted_dist.normed_densities).size <= 3
def test_pdf(xscale: Scale): normed_test_loc = 0.5 normed_test_s = 0.1 test_loc = xscale.denormalize_point(normed_test_loc) test_s = normed_test_s * xscale.width ergoLogisticMixture = LogisticMixture( components=[ Logistic( loc=xscale.denormalize_point(0.2), s=0.5 * xscale.width, scale=xscale, ), Logistic(loc=test_loc, s=test_s, scale=xscale), ], probs=[1.8629593e-29, 1.0], ) ergoLogistic = Logistic(loc=test_loc, s=test_s, scale=xscale) ## Make sure it integrates to 1 _xs = xscale.denormalize_points(np.linspace(0, 1, 100)) densities_logistic = np.array([float(ergoLogistic.pdf(x)) for x in _xs]) densities_mixture = np.array( [float(ergoLogisticMixture.pdf(x)) for x in _xs]) auc_logistic = float(trapz(densities_logistic, x=_xs)) auc_mixture = float(trapz(densities_mixture, x=_xs)) assert (1 == pytest.approx(auc_logistic, abs=0.03) == pytest.approx( auc_mixture, abs=0.03)) if not isinstance(xscale, LogScale): scipydist = scipy.stats.logistic(test_loc, test_s) for x in np.linspace(xscale.denormalize_point(0), xscale.denormalize_point(1), 10): assert (scipydist.pdf(x) == pytest.approx( float(ergoLogistic.pdf(x)), rel=1e-3) == pytest.approx( float(ergoLogisticMixture.pdf(x)), rel=1e-3))
def test_density_cdf(scale: Scale): uniform_dist = PointDensity.from_conditions([MaxEntropyCondition()], scale=scale) # Off of scale assert uniform_dist.cdf(scale.denormalize_point(-0.5)) == 0 assert uniform_dist.cdf(scale.denormalize_point(1.5)) == 1 # Edges of scale assert uniform_dist.cdf(scale.denormalize_point(0.005)) < uniform_dist.cdf( scale.denormalize_point(0.015)) assert uniform_dist.cdf(scale.denormalize_point(0.985)) < uniform_dist.cdf( scale.denormalize_point(0.995)) # Denormalized onto a different scale denormalized_dist = uniform_dist.denormalize(Scale(0, 2)) assert denormalized_dist.cdf(1) == pytest.approx(0.5, abs=0.01) assert denormalized_dist.cdf(1.5) != 0 assert denormalized_dist.cdf(2.5) == 1
def denormalize(self, scale: Scale): denormalized_mean = scale.denormalize_point(self.mean) return self.__class__(denormalized_mean, self.weight)
def test_truncated_ppf(xscale: Scale): normed_test_loc = 0.5 normed_test_s = 0.1 test_loc = xscale.denormalize_point(normed_test_loc) test_s = normed_test_s * xscale.width normed_baseline_dist = scipy.stats.logistic(normed_test_loc, normed_test_s) def ppf_through_cdf(dist, q): return scipy.optimize.bisect(lambda x: dist.cdf(x) - q, dist.ppf(0.0001), dist.ppf(0.9999), maxiter=1000) # No bounds dist_w_no_bounds = Truncate(Logistic(loc=test_loc, s=test_s, scale=xscale)) for x in np.linspace(0.01, 0.99, 8): assert dist_w_no_bounds.ppf(x) == pytest.approx(float( xscale.denormalize_point(normed_baseline_dist.ppf(x))), rel=0.001) # Floor dist_w_floor = Truncate( Logistic(loc=test_loc, s=test_s, scale=xscale), floor=xscale.denormalize_point(0.5), ) mix_w_floor = LogisticMixture( components=[ Truncate( # type: ignore Logistic(test_loc, s=test_s, scale=xscale), floor=xscale.denormalize_point(0.5), ) ], probs=[1.0], ) for x in np.linspace(0.01, 0.99, 8): assert dist_w_floor.ppf(x) == pytest.approx(float(mix_w_floor.ppf(x)), rel=0.001) assert dist_w_floor.ppf(x) == pytest.approx(float( ppf_through_cdf(dist_w_floor, x)), rel=0.001) # Ceiling dist_w_ceiling = Truncate( Logistic(loc=test_loc, s=test_s, scale=xscale), ceiling=xscale.denormalize_point(0.8), ) mix_w_ceiling = LogisticMixture( components=[ Truncate( # type: ignore Logistic(test_loc, s=test_s, scale=xscale), ceiling=xscale.denormalize_point(0.8), ) ], probs=[1.0], ) for x in np.linspace(0.01, 0.99, 8): assert dist_w_ceiling.ppf(x) == pytest.approx(float( mix_w_ceiling.ppf(x)), rel=0.001) assert dist_w_ceiling.ppf(x) == pytest.approx(float( ppf_through_cdf(dist_w_ceiling, x)), rel=0.001) # Floor and Ceiling dist_w_floor_and_ceiling = Truncate( Logistic(loc=test_loc, s=test_s, scale=xscale), floor=xscale.denormalize_point(0.2), ceiling=xscale.denormalize_point(0.8), ) mix_w_floor_and_ceiling = LogisticMixture( components=[ Truncate( # type: ignore Logistic(test_loc, s=test_s, scale=xscale), floor=xscale.denormalize_point(0.2), ceiling=xscale.denormalize_point(0.8), ) ], probs=[1.0], ) for x in np.linspace(0.01, 0.99, 8): assert dist_w_floor_and_ceiling.ppf(x) == pytest.approx(float( mix_w_floor_and_ceiling.ppf(x)), rel=0.001) assert dist_w_floor_and_ceiling.ppf(x) == pytest.approx(float( ppf_through_cdf(dist_w_floor_and_ceiling, x)), rel=0.001)
def denormalize(self, scale: Scale): denormalized_outcome = scale.denormalize_point(self.outcome) return self.__class__(denormalized_outcome, self.weight)
def denormalize(self, scale: Scale): denormalized_variance = scale.denormalize_point(self.variance) return self.__class__(denormalized_variance, self.weight)
def test_density_pdf(scale: Scale): uniform_dist = PointDensity.from_conditions([MaxEntropyCondition()], scale=scale) assert uniform_dist.pdf(scale.denormalize_point(0.5)) != 0 assert uniform_dist.pdf(scale.denormalize_point(1.5)) == 0
def test_hist_pdf(scale: Scale): uniform_dist = HistogramDist.from_conditions([MaxEntropyCondition()], scale=scale) assert uniform_dist.pdf(scale.denormalize_point(0.5)) != 0 assert uniform_dist.pdf(scale.denormalize_point(1.5)) == 0
class Logistic(Distribution): loc: float # normalized s: float # normalized scale: Scale metadata: Any = None def __init__( self, loc: float, s: float, scale: Optional[Scale] = None, metadata=None, normalized=False, ): # TODO (#303): Raise ValueError on scale < 0 if normalized: self.loc = loc self.s = np.max([s, 0.0000001]) self.metadata = metadata if scale is not None: self.scale = scale else: self.scale = Scale(0, 1) self.true_s = self.s * self.scale.width self.true_loc = self.scale.denormalize_point(loc) elif scale is None: raise ValueError("Either a Scale or normalized parameters are required") else: self.loc = scale.normalize_point(loc) self.s = np.max([s, 0.0000001]) / scale.width self.scale = scale self.metadata = metadata self.true_s = s # convenience field only used in repr currently self.true_loc = loc # convenience field only used in repr currently def __repr__(self): return ( f"Logistic(scale={self.scale}, true_loc={self.true_loc}, " f"true_s={self.true_s}, normed_loc={self.loc}, normed_s={self.s}," f" metadata={self.metadata})" ) # Distribution def pdf(self, x): y = (self.scale.normalize_point(x) - self.loc) / self.s p = np.exp(scipy.stats.logistic.logpdf(y) - np.log(self.s)) return p / self.scale.width def logpdf(self, x): y = (self.scale.normalize_point(x) - self.loc) / self.s logp = scipy.stats.logistic.logpdf(y) - np.log(self.s) return logp - np.log(self.scale.width) def cdf(self, x): y = (self.scale.normalize_point(x) - self.loc) / self.s return scipy.stats.logistic.cdf(y) def ppf(self, q): return self.scale.denormalize_point( oscipy.stats.logistic(loc=self.loc, scale=self.s).ppf(q) ) def sample(self): return self.scale.denormalize_point( oscipy.stats.logistic.rvs(loc=self.loc, scale=self.s) ) # Scaled def normalize(self): """ Return the normalized condition. :param scale: the true scale :return: the condition normalized to [0,1] """ return self.__class__( self.loc, self.s, Scale(0, 1), self.metadata, normalized=True ) def denormalize(self, scale: Scale): """ Assume that the distribution has been normalized to be over [0,1]. Return the distribution on the true scale :param scale: the true scale """ return self.__class__(self.loc, self.s, scale, self.metadata, normalized=True) # Structured @classmethod def structure(self, params): class_params, numeric_params = params self_class, scale_classes = class_params self_numeric, scale_numeric = numeric_params scale = scale_classes[0].structure((scale_classes, scale_numeric)) return self_class( loc=self_numeric[0], s=self_numeric[1], scale=scale, normalized=True ) def destructure(self): scale_classes, scale_numeric = self.scale.destructure() class_params = (self.__class__, scale_classes) self_numeric = (self.loc, self.s) numeric_params = (self_numeric, scale_numeric) return (class_params, numeric_params)