1"""Units, bounds and closed forms of the match-value distributions."""2import numpy as np3from scipy import integrate45from src.model import TruncNormal, Exponential, Pareto, Weibull, seller_type_grid678def _check_dist(G, vs):9 for v in vs:10 # H(v) = int_v (1-G) dw (numerical cross-check of the closed form)11 hi = G.hi if np.isfinite(G.hi) else np.inf12 num = integrate.quad(lambda w: float(G.surv(w)), v, hi, limit=300)[0]13 assert abs(float(G.H(v)) - num) < 1e-6, (G.name, v, float(G.H(v)), num)14 assert 0.0 <= float(G.cdf(v)) <= 1.015 assert float(G.pdf(v)) >= 0.016 assert float(G.H(v)) >= 0.017 if float(G.surv(v)) > 1e-8:18 assert float(G.M(v)) >= 0.019 assert float(G.cond_mean(v)) >= v - 1e-1220 # probability mass integrates to one21 lo = G.lo22 hi = G.hi if np.isfinite(G.hi) else np.inf23 mass = integrate.quad(lambda w: float(G.pdf(w)), lo, hi, limit=400)[0]24 assert abs(mass - 1.0) < 1e-5, (G.name, mass)25 # mean consistent26 mean_num = integrate.quad(lambda w: w * float(G.pdf(w)), lo, hi, limit=400)[0]27 assert abs(mean_num - G.mean()) < 1e-5282930def test_truncnorm_closed_forms():31 G = TruncNormal(1.0, 0.1, 0.7, 1.3)32 _check_dist(G, [0.5, 0.7, 0.85, 1.0, 1.1, 1.29, 1.3, 1.5])33 assert G.is_ifr()343536def test_exponential_pareto_weibull():37 _check_dist(Exponential(rate=10.0, lo=0.7), [0.5, 0.7, 0.9, 1.5])38 _check_dist(Pareto(a=3.0, lo=0.7), [0.5, 0.7, 0.9, 1.5])39 _check_dist(Weibull(shape=2.0, scale=0.2, lo=0.7), [0.5, 0.7, 0.9, 1.2])404142def test_ifr_inequality_truncnorm():43 """Under IFR: g H <= (1-G)^2 (fact F2 used in Proposition 2)."""44 G = TruncNormal(1.0, 0.1, 0.7, 1.3)45 v = np.linspace(0.7, 1.29, 200)46 assert np.all(G.pdf(v) * G.H(v) <= G.surv(v) ** 2 + 1e-12)474849def test_pareto_violates_ifr_inequality():50 G = Pareto(a=2.0, lo=0.7)51 v = np.linspace(0.8, 3.0, 50)52 assert np.all(G.pdf(v) * G.H(v) > G.surv(v) ** 2)535455def test_seller_type_grid_mass_and_order():56 c, f = seller_type_grid(0.02, 0.3, 9, 2.0, 4.0)57 assert abs(f.sum() - 1.0) < 1e-1258 assert np.all(np.diff(c) > 0) and c[0] >= 0.02 and c[-1] <= 0.359 # mean of the discretisation close to the Beta mean60 assert abs((c * f).sum() - (0.02 + 0.28 * 2.0 / 6.0)) < 1e-9616263def test_samples_match_cdf():64 rng = np.random.default_rng(1)65 G = TruncNormal(1.0, 0.1, 0.7, 1.3)66 x = G.sample(rng, 200_000)67 assert abs(x.mean() - G.mean()) < 2e-368 assert abs((x >= 1.0).mean() - float(G.surv(1.0))) < 5e-369