def test_input_validation(self): match = r"`order` must be either 1, 3, or 5." with pytest.raises(ValueError, match=match): NumericalInverseHermite(StandardNormal(), order=2) match = "`cdf` required but not found" with pytest.raises(ValueError, match=match): stats.NumericalInverseHermite("norm") match = "could not convert string to float" with pytest.raises(ValueError, match=match): stats.NumericalInverseHermite(StandardNormal(), u_resolution='ekki') match = "`max_intervals' must be..." with pytest.raises(ValueError, match=match): stats.NumericalInverseHermite(StandardNormal(), max_intervals=-1) match = "`qmc_engine` must be an instance of..." with pytest.raises(ValueError, match=match): fni = stats.NumericalInverseHermite(StandardNormal()) fni.qrvs(qmc_engine=0) if NumpyVersion(np.__version__) >= '1.18.0': # issues with QMCEngines and old NumPy fni = stats.NumericalInverseHermite(StandardNormal()) match = "`d` must be consistent with dimension of `qmc_engine`." with pytest.raises(ValueError, match=match): fni.qrvs(d=3, qmc_engine=stats.qmc.Halton(2))
def test_custom_distribution(self): dist1 = StandardNormal() fni1 = stats.NumericalInverseHermite(dist1) dist2 = stats.norm() fni2 = stats.NumericalInverseHermite(dist2) assert_allclose(fni1.rvs(random_state=0), fni2.rvs(random_state=0))
def test_inaccurate_CDF(self): # CDF function with inaccurate tail cannot be inverted; see gh-13319 # https://github.com/scipy/scipy/pull/13319#discussion_r626188955 shapes = (2.3098496451481823, 0.6268795430096368) match = ("98 : one or more intervals very short; possibly due to " "numerical problems with a pole or very flat tail") # fails with default tol with pytest.warns(RuntimeWarning, match=match): stats.NumericalInverseHermite(stats.beta(*shapes)) # no error with coarser tol stats.NumericalInverseHermite(stats.beta(*shapes), u_resolution=1e-8)
def test_basic_all_scipy_dists(self, distname, shapes): slow_dists = {'ksone', 'kstwo', 'levy_stable', 'skewnorm'} fail_dists = { 'beta', 'gausshyper', 'geninvgauss', 'ncf', 'nct', 'norminvgauss', 'genhyperbolic', 'studentized_range', 'vonmises', 'kappa4', 'invgauss', 'wald' } if distname in slow_dists: pytest.skip("Distribution is too slow") if distname in fail_dists: # specific reasons documented in gh-13319 # https://github.com/scipy/scipy/pull/13319#discussion_r626188955 pytest.xfail("Fails - usually due to inaccurate CDF/PDF") np.random.seed(0) dist = getattr(stats, distname)(*shapes) with np.testing.suppress_warnings() as sup: sup.filter(RuntimeWarning, "overflow encountered") sup.filter(RuntimeWarning, "divide by zero") sup.filter(RuntimeWarning, "invalid value encountered") fni = stats.NumericalInverseHermite(dist) x = np.random.rand(10) p_tol = np.max(np.abs(dist.ppf(x) - fni.ppf(x)) / np.abs(dist.ppf(x))) u_tol = np.max(np.abs(dist.cdf(fni.ppf(x)) - x)) assert p_tol < 1e-8 assert u_tol < 1e-12
def test_QRVS_size_tuple(self): # QMCEngine samples are always of shape (n, d). When `size` is a tuple, # we set `n = prod(size)` in the call to qmc_engine.random, transform # the sample, and reshape it to the final dimensions. When we reshape, # we need to be careful, because the _columns_ of the sample returned # by a QMCEngine are "independent"-ish, but the elements within the # columns are not. We need to make sure that this doesn't get mixed up # by reshaping: qrvs[..., i] should remain "independent"-ish of # qrvs[..., i+1], but the elements within qrvs[..., i] should be # transformed from the same low-discrepancy sequence. if NumpyVersion(np.__version__) <= '1.18.0': pytest.skip("QMC doesn't play well with old NumPy") dist = StandardNormal() fni = stats.NumericalInverseHermite(dist) size = (3, 4) d = 5 qrng = stats.qmc.Halton(d, seed=0) qrng2 = stats.qmc.Halton(d, seed=0) uniform = qrng2.random(np.prod(size)) qrvs = fni.qrvs(size=size, d=d, qmc_engine=qrng) qrvs2 = stats.norm.ppf(uniform) for i in range(d): sample = qrvs[..., i] sample2 = qrvs2[:, i].reshape(size) assert_allclose(sample, sample2, atol=1e-12)
def test_QRVS(self, qrng, size_in, size_out, d_in, d_out): dist = StandardNormal() fni = stats.NumericalInverseHermite(dist) # If d and qrng.d are inconsistent, an error is raised if d_in is not None and qrng is not None and qrng.d != d_in: match = "`d` must be consistent with dimension of `qmc_engine`." with pytest.raises(ValueError, match=match): fni.qrvs(size_in, d=d_in, qmc_engine=qrng) return # Sometimes d is really determined by qrng if d_in is None and qrng is not None and qrng.d != 1: d_out = (qrng.d, ) shape_expected = size_out + d_out qrng2 = deepcopy(qrng) qrvs = fni.qrvs(size=size_in, d=d_in, qmc_engine=qrng) if size_in is not None: assert (qrvs.shape == shape_expected) if qrng2 is not None: uniform = qrng2.random(np.prod(size_in) or 1) qrvs2 = stats.norm.ppf(uniform).reshape(shape_expected) assert_allclose(qrvs, qrvs2, atol=1e-12)
def setup(self, dist, order): self.urng = np.random.default_rng(0xb235b58c1f616c59c18d8568f77d44d1) with np.testing.suppress_warnings() as sup: sup.filter(RuntimeWarning) try: self.rng = stats.NumericalInverseHermite( dist, order=order, random_state=self.urng) except stats.UNURANError: raise NotImplementedError(f"setup failed for {dist}")
def test_RVS(self, rng, size_in, size_out): dist = StandardNormal() fni = stats.NumericalInverseHermite(dist) rng2 = deepcopy(rng) rvs = fni.rvs(size=size_in, random_state=rng) if size_in is not None: assert (rvs.shape == size_out) if rng2 is not None: rng2 = check_random_state(rng2) uniform = rng2.uniform(size=size_in) rvs2 = stats.norm.ppf(uniform) assert_allclose(rvs, rvs2)
def time_fni(self, distcase): distname, shapes = distcase slow_dists = {'ksone', 'kstwo', 'levy_stable', 'skewnorm'} fail_dists = {'beta', 'gausshyper', 'geninvgauss', 'ncf', 'nct', 'norminvgauss', 'genhyperbolic', 'studentized_range'} if distname in slow_dists or distname in fail_dists: raise NotImplementedError("skipped") dist = getattr(stats, distname)(*shapes) with np.testing.suppress_warnings() as sup: sup.filter(RuntimeWarning, "overflow encountered") sup.filter(RuntimeWarning, "divide by zero") sup.filter(RuntimeWarning, "invalid value encountered") stats.NumericalInverseHermite(dist)
def time_hinv_setup(self, dist, order): with np.testing.suppress_warnings() as sup: sup.filter(RuntimeWarning) stats.NumericalInverseHermite(dist, order=order, random_state=self.urng)