Download tests/test_lecture_7.py from ChatterjeeLab/CIS6270: direct link, hf CLI and curl.
- Browser
- Download file 34.6 kB
-
https://huggingface.co/ChatterjeeLab/CIS6270/resolve/main/tests/test_lecture_7.py
- Command line
-
hf download hf://ChatterjeeLab/CIS6270/tests/test_lecture_7.py
-
curl -L -o test_lecture_7.py https://huggingface.co/ChatterjeeLab/CIS6270/resolve/main/tests/test_lecture_7.py
34.6 kB
| """Lecture 7: every number the chapter works by hand, asserted against the file that prints it. | |
| Chapter 7 computes almost all of its results on paper, so these tests are the chapter itself read | |
| back. The tolerances are the number of digits the chapter quotes. | |
| """ | |
| from __future__ import annotations | |
| import importlib.util | |
| import math | |
| import sys | |
| from pathlib import Path | |
| import pytest | |
| import torch | |
| LECTURE = Path(__file__).resolve().parent.parent / "lecture_7" | |
| def load(name: str): | |
| """Import one lecture file by path, since ``lecture_7`` is a folder of scripts, not a package.""" | |
| spec = importlib.util.spec_from_file_location(f"lecture_7_{name}", LECTURE / f"{name}.py") | |
| module = importlib.util.module_from_spec(spec) | |
| sys.modules[spec.name] = module | |
| spec.loader.exec_module(module) | |
| return module | |
| def tensor(*values): | |
| return torch.tensor(values, dtype=torch.float64) | |
| # %% Sections 7.1 to 7.6, optimal_transport.py | |
| def test_four_points_and_two_pairings(): | |
| ot = load("optimal_transport") | |
| pairings = ot.two_pairings() | |
| assert pairings["arrows_ordered"] == pytest.approx(2.0) | |
| assert pairings["arrows_crossed"] == pytest.approx(10.0) | |
| assert pairings["mass_ordered"] == pytest.approx(1.0) | |
| assert pairings["mass_crossed"] == pytest.approx(5.0) | |
| assert pairings["mass_independent"] == pytest.approx(3.0) | |
| assert pairings["C"].tolist() == [[1.0, 9.0], [1.0, 1.0]] | |
| def test_two_by_two_plan_and_prices(): | |
| ot = load("optimal_transport") | |
| assert ot.feasible_interval() == (0.0, 0.3) | |
| C = ot.cost_matrix(ot.SOURCE_X, ot.TARGET_Y) | |
| assert ot.cost_along_the_segment(0.0, C) == pytest.approx(5.8) | |
| assert ot.cost_along_the_segment(0.3, C) == pytest.approx(3.4) | |
| plan = ot.feasible_plan(0.3) | |
| assert plan.tolist() == [[0.3, 0.3], [0.0, 0.4]] | |
| assert ot.is_coupling(plan, ot.SOURCE_A, ot.TARGET_B) | |
| f, g = ot.prices_from_loaded_routes(C, plan) | |
| assert f.tolist() == [0.0, -8.0] and g.tolist() == [1.0, 9.0] | |
| assert ot.slack(C, f, g).tolist() == [[0.0, 0.0], [8.0, 0.0]] | |
| assert ot.dual_value(ot.SOURCE_A, f, ot.TARGET_B, g) == pytest.approx(3.4) | |
| assert ot.plan_cost(C, plan) ** 0.5 == pytest.approx(1.844, abs=5e-4) | |
| def test_no_crossing_and_three_points(): | |
| ot = load("optimal_transport") | |
| expanded, collected = ot.crossing_difference(0.0, 2.0, 1.0, 3.0) | |
| assert expanded == pytest.approx(collected) and collected == pytest.approx(-8.0) | |
| x = tensor(4.0, 1.0, 6.0) | |
| y = tensor(9.0, 2.0, 3.0) | |
| costs = [c for c, _ in ot.monge_permutations(x.sort().values, y.sort().values)] | |
| assert costs == pytest.approx([11 / 3, 17 / 3, 35 / 3, 45 / 3, 77 / 3, 81 / 3]) | |
| assert ot.wasserstein_on_the_line(x, y, 2.0, 3072) == pytest.approx(11 / 3) | |
| def test_gaussian_closed_form(): | |
| ot = load("optimal_transport") | |
| assert ot.gaussian_w2_squared(0.0, 1.0, 3.0, 2.0) == pytest.approx(10.0) | |
| assert math.sqrt(10.0) == pytest.approx(3.162, abs=5e-4) | |
| points = tensor(-1.0, 0.0, 1.0) | |
| assert ot.brenier_map(points, 0.0, 1.0, 3.0, 2.0).tolist() == [1.0, 3.0, 5.0] | |
| source = torch.special.ndtri(ot.quantile_levels(3072)) | |
| mapped = ot.brenier_map(source, 0.0, 1.0, 3.0, 2.0) | |
| assert float(((mapped - source) ** 2).mean()) == pytest.approx(10.0, abs=1e-3) | |
| # %% Sections 7.9 and 7.10, sinkhorn.py | |
| def test_entropies_of_the_three_couplings(): | |
| sk = load("sinkhorn") | |
| independent = sk.independent_coupling(sk.SOURCE_A, sk.TARGET_B) | |
| assert independent.flatten().tolist() == pytest.approx([0.18, 0.42, 0.12, 0.28]) | |
| assert sk.entropy(sk.EXACT_PLAN) == pytest.approx(1.089, abs=5e-4) | |
| assert sk.entropy(independent) == pytest.approx(1.284, abs=5e-4) | |
| assert sk.entropy(sk.SOURCE_A) == pytest.approx(0.673, abs=5e-4) | |
| assert sk.entropy(sk.TARGET_B) == pytest.approx(0.611, abs=5e-4) | |
| assert sk.relative_entropy(sk.EXACT_PLAN, independent) == pytest.approx(0.195, abs=5e-4) | |
| def test_sinkhorn_first_sweep_by_hand(): | |
| sk = load("sinkhorn") | |
| K = sk.gibbs_kernel(sk.COST, 2.0) | |
| assert K.flatten().tolist() == pytest.approx([0.606531, 0.011109, 0.606531, 0.606531], | |
| abs=5e-7) | |
| v = torch.ones(2, dtype=torch.float64) | |
| assert (K @ v).tolist() == pytest.approx([0.617640, 1.213061], abs=5e-7) | |
| u = sk.SOURCE_A / (K @ v) | |
| assert u.tolist() == pytest.approx([0.971440, 0.329744], abs=5e-7) | |
| assert (K.T @ u).tolist() == pytest.approx([0.789208, 0.210792], abs=5e-7) | |
| v = sk.TARGET_B / (K.T @ u) | |
| assert v.tolist() == pytest.approx([0.38013, 3.32081], abs=5e-6) | |
| plan = sk.scaling_form(u, K, v) | |
| assert plan.flatten().tolist() == pytest.approx([0.2240, 0.0358, 0.0760, 0.6642], abs=5e-5) | |
| assert plan.sum(dim=0).tolist() == pytest.approx([0.3, 0.7]) | |
| assert plan.sum(dim=1).tolist() == pytest.approx([0.2598, 0.7402], abs=5e-5) | |
| after = sk.project_rows(plan, sk.SOURCE_A) | |
| assert after.sum(dim=0).tolist() == pytest.approx([0.558, 0.442], abs=5e-4) | |
| def test_sinkhorn_convergence_and_epsilon(): | |
| sk = load("sinkhorn") | |
| K = sk.gibbs_kernel(sk.COST, 2.0) | |
| u, v, errors = sk.sinkhorn_sweeps(K, sk.SOURCE_A, sk.TARGET_B, 40) | |
| assert errors[:5] == pytest.approx([0.680, 0.382, 0.155, 0.047, 0.012], abs=5e-4) | |
| assert sk.sweeps_to_tolerance(K, sk.SOURCE_A, sk.TARGET_B) == 17 | |
| plan = sk.scaling_form(u, K, v) | |
| assert plan.flatten().tolist() == pytest.approx( | |
| [0.293122, 0.306878, 0.006878, 0.393122], abs=5e-7) | |
| assert float((sk.COST * plan).sum()) == pytest.approx(3.45502, abs=5e-6) | |
| assert sk.entropy(plan) == pytest.approx(1.124, abs=5e-4) | |
| independent = sk.independent_coupling(sk.SOURCE_A, sk.TARGET_B) | |
| assert sk.entropic_objective(plan, sk.COST, 2.0) == pytest.approx(1.208, abs=5e-4) | |
| assert sk.entropic_objective(sk.EXACT_PLAN, sk.COST, 2.0) == pytest.approx(1.222, abs=5e-4) | |
| assert sk.entropic_objective(independent, sk.COST, 2.0) == pytest.approx(1.792, abs=5e-4) | |
| assert sk.relative_entropy(plan, independent) == pytest.approx(0.160, abs=5e-4) | |
| f = sk.multipliers_from_scalings(u, 2.0) | |
| g = sk.multipliers_from_scalings(v, 2.0) | |
| assert f.tolist() == pytest.approx([3.766, -3.739], abs=5e-4) | |
| assert g.tolist() == pytest.approx([-3.220, 4.872], abs=5e-4) | |
| expected = {10.0: (4.0540165, 7), 2.0: (3.4550204, 17), 0.5: (3.4000004, 28), | |
| 0.1: (3.4000000, 80)} | |
| for epsilon, (cost, sweeps) in expected.items(): | |
| kernel = sk.gibbs_kernel(sk.COST, epsilon) | |
| su, sv, _ = sk.sinkhorn_sweeps(kernel, sk.SOURCE_A, sk.TARGET_B, 400) | |
| assert float((sk.COST * sk.scaling_form(su, kernel, sv)).sum()) == pytest.approx( | |
| cost, abs=5e-8) | |
| assert sk.sweeps_to_tolerance(kernel, sk.SOURCE_A, sk.TARGET_B) == sweeps | |
| # %% Sections 7.7 to 7.7.5, displacement.py | |
| def test_one_particle_and_the_averaged_bound(): | |
| dp = load("displacement") | |
| times = torch.linspace(0.0, 1.0, 1025, dtype=torch.float64) | |
| assert dp.action_of_a_path(dp.steady_particle(times), times) == pytest.approx(4.0) | |
| assert dp.action_of_a_path(dp.dawdling_particle(times), times) == pytest.approx(8.0) | |
| x, y = tensor(0.0, 2.0), tensor(1.0, 3.0) | |
| ordered = torch.tensor([[0.5, 0.0], [0.0, 0.5]], dtype=torch.float64) | |
| crossed = torch.tensor([[0.0, 0.5], [0.5, 0.0]], dtype=torch.float64) | |
| independent = torch.full((2, 2), 0.25, dtype=torch.float64) | |
| assert dp.average_squared_displacement(x, y, ordered) == pytest.approx(1.0) | |
| assert dp.average_squared_displacement(x, y, independent) == pytest.approx(3.0) | |
| assert dp.average_squared_displacement(x, y, crossed) == pytest.approx(5.0) | |
| def test_displacement_interpolation_and_its_velocity(): | |
| dp = load("displacement") | |
| mean, std = dp.displacement_law(0.5) | |
| assert (mean, std) == pytest.approx((1.5, 1.5)) | |
| assert dp.gaussian_w2(0.0, 1.0, mean, std) == pytest.approx(math.sqrt(2.5)) | |
| assert math.sqrt(2.5) == pytest.approx(1.581, abs=5e-4) | |
| assert dp.gaussian_w2(0.0, 1.0, 3.0, 2.0) == pytest.approx(3.162, abs=5e-4) | |
| grid = torch.linspace(-6.0, 9.0, 15001, dtype=torch.float64) | |
| peak = float(grid[int(dp.mixture_density(grid).argmax())]) | |
| assert peak == pytest.approx(0.13, abs=5e-3) | |
| half = torch.tensor(0.5, dtype=torch.float64) | |
| assert float(dp.velocity_field(torch.tensor(1.5, dtype=torch.float64), half)) == pytest.approx(3.0) | |
| assert float(dp.velocity_field(torch.tensor(3.0, dtype=torch.float64), half)) == pytest.approx(4.0) | |
| for t in (0.0, 0.25, 0.5, 0.75, 1.0): | |
| assert dp.action_density(t, 4096) == pytest.approx(10.0, abs=1e-3) | |
| assert abs(dp.continuity_residual(1.5, 0.5)) < 1e-12 | |
| # %% Sections 7.8 and 7.8.1, ot_flow_matching.py | |
| def test_batch_coupling_costs(): | |
| fm = load("ot_flow_matching") | |
| x = torch.tensor([[0.0], [2.0]]) | |
| y = torch.tensor([[1.0], [3.0]]) | |
| C = fm.batch_cost_matrix(x, y) | |
| assert C.flatten().tolist() == pytest.approx([1.0, 9.0, 1.0, 1.0]) | |
| assert float(C.mean()) == pytest.approx(3.0) | |
| pairing = fm.optimal_pairing(x, y) | |
| assert pairing.tolist() == [0, 1] | |
| assert float(C[torch.arange(2), pairing].mean()) == pytest.approx(1.0) | |
| assert fm.vertex_count(256, 256) == {"unknowns": 65536, "constraints": 512, | |
| "loaded_routes": 511} | |
| assert fm.conditional_velocity_energy(x, fm.couple(x, y, "optimal")) == pytest.approx(1.0) | |
| def test_flow_matching_pipeline_runs(): | |
| fm = load("ot_flow_matching") | |
| from cis6270.nets import VelocityMLP | |
| torch.manual_seed(0) | |
| model = VelocityMLP(dim=2, width=16) | |
| x0 = torch.randn(8, 2) | |
| x1 = torch.randn(8, 2) | |
| assert float(fm.flow_matching_loss(model, x0, fm.couple(x0, x1, "optimal"))) > 0.0 | |
| samples, straightness = fm.integrate(model, x0, 5) | |
| assert samples.shape == (8, 2) and straightness >= 0.0 | |
| # %% Sections 7.12 to 7.15.3, static_bridge.py | |
| def test_eight_paths_and_the_chain_rule(): | |
| sb = load("static_bridge") | |
| law = sb.path_law(sb.PI_0, [sb.A, sb.A]) | |
| assert law[(0, 0, 1)] == pytest.approx(0.105) | |
| assert law[(0, 0, 0)] == pytest.approx(0.245) | |
| assert law[(1, 1, 1)] == pytest.approx(0.180) | |
| assert sum(law.values()) == pytest.approx(1.0) | |
| table = torch.tensor([[0.5, 0.1], [0.1, 0.3]], dtype=torch.float64) | |
| uniform = torch.full((2, 2), 0.25, dtype=torch.float64) | |
| total, marginal, conditional = sb.chain_rule_terms(table, uniform) | |
| assert total == pytest.approx(0.2180, abs=5e-5) | |
| assert marginal == pytest.approx(0.0201, abs=5e-5) | |
| assert conditional == pytest.approx(0.1979, abs=5e-5) | |
| assert marginal + conditional == pytest.approx(total) | |
| def test_proportional_fitting_on_the_chain(): | |
| sb = load("static_bridge") | |
| R01 = sb.reference_endpoint_law(sb.PI_0, sb.A, 2) | |
| assert R01.flatten().tolist() == pytest.approx([0.305, 0.195, 0.26, 0.24]) | |
| assert float((R01.sum(dim=0) - sb.PI_1).abs().sum()) == pytest.approx(0.73) | |
| f, g, errors = sb.proportional_fitting(R01, sb.PI_0, sb.PI_1, 4) | |
| assert errors == pytest.approx([3.933e-3, 2.098e-5, 1.119e-7, 5.966e-10], rel=1e-3) | |
| assert f.tolist() == pytest.approx([1.072002, 0.937019], abs=5e-7) | |
| assert g.tolist() == pytest.approx([0.350517, 1.843637], abs=5e-7) | |
| plan = f[:, None] * R01 * g[None, :] | |
| assert plan.flatten().tolist() == pytest.approx( | |
| [0.114605, 0.385395, 0.085395, 0.414605], abs=5e-7) | |
| assert sb.kl_vectors(plan.flatten(), R01.flatten()) == pytest.approx(0.2820, abs=5e-5) | |
| independent = sb.PI_0[:, None] * sb.PI_1[None, :] | |
| assert sb.kl_vectors(independent.flatten(), R01.flatten()) == pytest.approx(0.2847, abs=5e-5) | |
| def test_h_transform_and_the_bridge_path_law(): | |
| sb = load("static_bridge") | |
| R01 = sb.reference_endpoint_law(sb.PI_0, sb.A, 2) | |
| f, g, _ = sb.proportional_fitting(R01, sb.PI_0, sb.PI_1, 40) | |
| potentials = sb.backward_potential(sb.A, g, 2) | |
| assert potentials[2].tolist() == pytest.approx([0.350517, 1.843637], abs=5e-7) | |
| assert potentials[1].tolist() == pytest.approx([0.798453, 1.246389], abs=5e-7) | |
| assert potentials[0].tolist() == pytest.approx([0.932834, 1.067215], abs=5e-7) | |
| tilted = [sb.tilted_matrix(sb.A, potentials[k], potentials[k + 1]) for k in range(2)] | |
| assert tilted[0].flatten().tolist() == pytest.approx( | |
| [0.599160, 0.400840, 0.299266, 0.700734], abs=5e-7) | |
| assert tilted[1].flatten().tolist() == pytest.approx( | |
| [0.307297, 0.692703, 0.112490, 0.887510], abs=5e-7) | |
| assert tilted[0].sum(dim=1).tolist() == pytest.approx([1.0, 1.0]) | |
| middle = sb.PI_0 @ tilted[0] | |
| assert middle.tolist() == pytest.approx([0.449213, 0.550787], abs=5e-7) | |
| assert (middle @ tilted[1]).tolist() == pytest.approx([0.2, 0.8]) | |
| assert (1.0 / potentials[0]).tolist() == pytest.approx(f.tolist(), abs=1e-9) | |
| plan = f[:, None] * R01 * g[None, :] | |
| bridge = sb.fill_with_reference_bridges(plan, sb.A) | |
| reference = sb.path_law(sb.PI_0, [sb.A, sb.A]) | |
| assert bridge[(0, 0, 0)] == pytest.approx(0.092060, abs=5e-7) | |
| assert bridge[(0, 0, 1)] == pytest.approx(0.207520, abs=5e-7) | |
| assert bridge[(1, 1, 1)] == pytest.approx(0.310954, abs=5e-7) | |
| ratios = {round(bridge[p] / reference[p], 6) for p in reference} | |
| assert ratios == {0.375755, 1.976383, 0.328441, 1.727522} | |
| assert sb.relative_entropy(bridge, reference) == pytest.approx(0.281961, abs=5e-7) | |
| assert sum(v for k, v in bridge.items() if k[1] == 0) == pytest.approx(0.449213, abs=5e-7) | |
| def test_brownian_reference_recovers_the_entropic_plan(): | |
| sb = load("static_bridge") | |
| plan = sb.brownian_reference_plan(1.0) | |
| assert plan.flatten().tolist() == pytest.approx([0.2931, 0.3069, 0.0069, 0.3931], abs=5e-5) | |
| # %% Sections 7.16 to 7.18, dynamic_bridge.py | |
| def test_constant_control_two_ways(): | |
| db = load("dynamic_bridge") | |
| assert db.control_energy_entropy(2.0, 1.0) == pytest.approx(2.0) | |
| assert db.gaussian_entropy_gap(2.0, 1.0) == pytest.approx(2.0) | |
| generator = torch.Generator().manual_seed(0) | |
| _, log_ratio = db.simulate_controlled(2.0, 200, 8192, 1.0, generator) | |
| assert float(log_ratio.mean()) == pytest.approx(2.0, abs=0.1) | |
| def test_the_bridge_potential_solves_both_equations(): | |
| db = load("dynamic_bridge") | |
| value = db.hopf_cole(db.bridge_potential) | |
| for x, t in ((-0.5, 0.2), (0.5, 0.5), (2.5, 0.8)): | |
| assert abs(db.backward_residual(db.bridge_potential, x, t)) < 1e-12 | |
| assert abs(db.hjb_residual(value, x, t)) < 1e-12 | |
| drift = float(db.bridge_drift(torch.tensor(x, dtype=torch.float64), | |
| torch.tensor(t, dtype=torch.float64))) | |
| assert db.optimal_control(value, x, t) == pytest.approx(drift, abs=1e-10) | |
| def test_brownian_bridge_numbers(): | |
| db = load("dynamic_bridge") | |
| mean, variance = db.bridge_law(0.25) | |
| assert mean == pytest.approx(0.5) | |
| assert variance == pytest.approx(0.1875) | |
| assert math.sqrt(variance) == pytest.approx(0.4330, abs=5e-5) | |
| drift = float(db.bridge_drift(torch.tensor(0.6732, dtype=torch.float64), | |
| torch.tensor(0.25, dtype=torch.float64))) | |
| assert drift == pytest.approx(1.7691, abs=5e-5) | |
| assert db.bridge_law(0.5)[1] == pytest.approx(0.25) | |
| for epsilon, spread in ((0.25, 0.25), (1.0, 0.5), (4.0, 1.0)): | |
| assert math.sqrt(db.bridge_law(0.5, epsilon=epsilon)[1]) == pytest.approx(spread) | |
| # %% Section 7.19.1, dsb.py | |
| def test_dsb_half_steps_on_the_chain(): | |
| dsb = load("dsb") | |
| reference = dsb.reference_path_law(dsb.PI_0, dsb.A, 2) | |
| assert dsb.marginal(reference, 1).tolist() == pytest.approx([0.55, 0.45]) | |
| assert dsb.marginal(reference, 2).tolist() == pytest.approx([0.565, 0.435]) | |
| B2 = dsb.reverse_step(reference, 1) | |
| assert B2.flatten().tolist() == pytest.approx( | |
| [0.681416, 0.318584, 0.379310, 0.620690], abs=5e-7) | |
| assert float(dsb.reverse_step(reference, 0)[0, 0]) == pytest.approx(0.636364, abs=5e-7) | |
| half = dsb.fix_terminal(reference, dsb.PI_1) | |
| assert dsb.marginal(half, 2).tolist() == pytest.approx([0.2, 0.8]) | |
| assert dsb.marginal(half, 1).tolist() == pytest.approx([0.439731, 0.560269], abs=5e-7) | |
| assert dsb.marginal(half, 0).tolist() == pytest.approx([0.466585, 0.533415], abs=5e-7) | |
| assert float((dsb.marginal(half, 0) - dsb.PI_0).abs().sum()) == pytest.approx(0.0668, abs=5e-5) | |
| full = dsb.fix_initial(half, dsb.PI_0) | |
| assert dsb.one_step_conditional(full, 0).flatten().tolist() == pytest.approx( | |
| [0.599738, 0.400262, 0.299771, 0.700229], abs=5e-7) | |
| assert dsb.one_step_conditional(full, 1).flatten().tolist() == pytest.approx( | |
| [0.309924, 0.690076, 0.113725, 0.886275], abs=5e-7) | |
| assert dsb.marginal(full, 2).tolist() == pytest.approx([0.201967, 0.798033], abs=5e-7) | |
| gap = max(float((dsb.one_step_conditional(full, 0) - dsb.TILTED_0).abs().max()), | |
| float((dsb.one_step_conditional(full, 1) - dsb.TILTED_1).abs().max())) | |
| assert gap < 0.003 | |
| _, errors, _ = dsb.proportional_fitting(reference, dsb.PI_0, dsb.PI_1, 4) | |
| assert errors == pytest.approx([3.933e-3, 2.098e-5, 1.119e-7, 5.966e-10], rel=1e-3) | |
| assert errors[0] / errors[1] == pytest.approx(187.5, abs=0.1) | |
| # %% Section 7.19.2, dsbm.py | |
| def test_markov_drift_at_one_point(): | |
| dm = load("dsbm") | |
| assert dm.bridge_drift(3.0, 1.0, 0.5) == pytest.approx(4.0) | |
| assert dm.bridge_drift(0.0, 1.0, 0.5) == pytest.approx(-2.0) | |
| endpoints = tensor(3.0, 0.0) | |
| weights = tensor(0.5, 0.5) | |
| assert dm.markov_drift(endpoints, weights, 1.0, 0.5) == pytest.approx(1.0) | |
| assert dm.least_squares_derivative(1.0, endpoints, weights, 1.0, 0.5) == pytest.approx(0.0) | |
| assert dm.least_squares_derivative(0.0, endpoints, weights, 1.0, 0.5) == pytest.approx(-2.0) | |
| def test_one_imf_round_on_the_chain(): | |
| dm = load("dsbm") | |
| bridges = dm.reference_bridge_table(dm.A) | |
| assert float(bridges[0, 0, 0]) == pytest.approx(0.803279, abs=5e-7) | |
| assert float(bridges[0, 0, 1]) == pytest.approx(0.538462, abs=5e-7) | |
| target = dm.bridge_coupling(dm.A, dm.PI_0, dm.PI_1) | |
| independent = dm.PI_0[:, None] * dm.PI_1[None, :] | |
| assert independent.flatten().tolist() == pytest.approx([0.1, 0.4, 0.1, 0.4]) | |
| assert float((independent - target).abs().sum()) == pytest.approx(0.0584, abs=5e-5) | |
| coupling, gaps, first = dm.imf_rounds(independent, dm.A, 9, target) | |
| assert first[0].flatten().tolist() == pytest.approx( | |
| [0.591425, 0.408575, 0.307692, 0.692308], abs=5e-7) | |
| assert first[1].flatten().tolist() == pytest.approx( | |
| [0.298457, 0.701543, 0.119588, 0.880412], abs=5e-7) | |
| projected = torch.diag(dm.PI_0) @ first[0] @ first[1] | |
| assert projected.flatten().tolist() == pytest.approx( | |
| [0.112688, 0.387312, 0.087312, 0.412688], abs=5e-7) | |
| assert projected.sum(dim=0).tolist() == pytest.approx([0.2, 0.8]) | |
| assert projected.sum(dim=1).tolist() == pytest.approx([0.5, 0.5]) | |
| assert gaps[0] == pytest.approx(0.0077, abs=5e-5) | |
| assert gaps[3] / gaps[4] == pytest.approx(7.37, abs=5e-3) | |
| assert gaps[8] < 1e-9 | |
| # %% Section 7.19.3, sf2m.py | |
| def test_the_two_regression_targets(): | |
| sm = load("sf2m") | |
| x0, x1 = tensor(0.0)[0], tensor(2.0)[0] | |
| z = tensor(0.4)[0] | |
| for t_value, mean, sample, drift, flow, coefficient in ( | |
| (0.25, 0.5, 0.6732, 1.7691, 2.2309, 4 / 3), | |
| (0.75, 1.5, 1.6732, 1.3072, 1.7691, -4 / 3), | |
| ): | |
| t = torch.tensor(t_value, dtype=torch.float64) | |
| assert float(sm.bridge_mean(x0, x1, t)) == pytest.approx(mean) | |
| assert float(sm.bridge_std(t)) == pytest.approx(0.4330, abs=5e-5) | |
| x = sm.bridge_sample(x0, x1, t, z) | |
| assert float(x) == pytest.approx(sample, abs=5e-5) | |
| assert float(sm.conditional_drift(x1, x, t)) == pytest.approx(drift, abs=5e-5) | |
| assert float(sm.conditional_score(x0, x1, x, t)) == pytest.approx(-0.9238, abs=5e-5) | |
| assert float(sm.flow_velocity(x0, x1, x, t)) == pytest.approx(flow, abs=5e-5) | |
| assert float(sm.flow_velocity_collected(x0, x1, x, t)) == pytest.approx(flow, abs=5e-5) | |
| assert float(sm.flow_coefficient(t)) == pytest.approx(coefficient) | |
| assert float(sm.scaled_score(x0, x1, x, t)) == pytest.approx(-0.4, abs=5e-7) | |
| def test_the_batch_plan_of_sf2m(): | |
| sm = load("sf2m") | |
| x, y = tensor(0.0, 2.0), tensor(1.0, 3.0) | |
| uniform = tensor(0.5, 0.5) | |
| plan = sm.entropic_plan(x, y, uniform, uniform, 1.0) | |
| assert plan.sum(dim=0).tolist() == pytest.approx([0.5, 0.5]) | |
| assert float(plan[0, 0] + plan[1, 1]) > float(plan[0, 1] + plan[1, 0]) | |
| # %% Sections 7.21 to 7.23.3, discrete_bridge.py | |
| def test_rate_error_is_not_symmetric(): | |
| db = load("discrete_bridge") | |
| assert db.scaling_cost(0.5) == pytest.approx(0.1534, abs=5e-5) | |
| assert db.scaling_cost(2.0) == pytest.approx(0.3863, abs=5e-5) | |
| assert float(db.rate_error(tensor(1.0)[0], tensor(1.0)[0])) == pytest.approx(0.0) | |
| def test_two_state_continuous_time_bridge(): | |
| db = load("discrete_bridge") | |
| assert torch.matrix_exp(db.Q).flatten().tolist() == pytest.approx( | |
| [0.6328, 0.3672, 0.5507, 0.4493], abs=5e-5) | |
| R01 = db.endpoint_law(db.Q, db.PI_0) | |
| assert R01.flatten().tolist() == pytest.approx([0.3164, 0.1836, 0.2754, 0.2246], abs=5e-5) | |
| assert R01.sum(dim=0).tolist() == pytest.approx([0.5918, 0.4082], abs=5e-5) | |
| f, g, plan = db.sinkhorn(R01, db.PI_0, db.PI_1) | |
| assert f.tolist() == pytest.approx([1.0716, 0.9373], abs=5e-5) | |
| assert g.tolist() == pytest.approx([0.3349, 1.9643], abs=5e-5) | |
| assert float(g[1] / g[0]) == pytest.approx(5.865, abs=5e-4) | |
| assert plan.flatten().tolist() == pytest.approx([0.1136, 0.3864, 0.0864, 0.4136], abs=5e-5) | |
| assert torch.matrix_exp(db.Q / 2).flatten().tolist() == pytest.approx( | |
| [0.7146, 0.2854, 0.4281, 0.5719], abs=5e-5) | |
| phi = db.backward_potential(db.Q, g, 0.5) | |
| assert phi.tolist() == pytest.approx([0.7999, 1.2667], abs=5e-5) | |
| tilted = db.tilted_generator(db.Q, phi) | |
| assert tilted.flatten().tolist() == pytest.approx( | |
| [-1.5836, 1.5836, 0.9472, -0.9472], abs=5e-5) | |
| assert db.backward_potential(db.Q, g, 0.0).tolist() == pytest.approx([0.9331, 1.0669], abs=5e-5) | |
| at_zero = db.tilted_generator(db.Q, db.backward_potential(db.Q, g, 0.0)) | |
| assert float(at_zero[0, 1]) == pytest.approx(1.1433, abs=5e-5) | |
| assert float(at_zero[1, 0]) == pytest.approx(1.3120, abs=5e-5) | |
| at_one = db.tilted_generator(db.Q, g) | |
| assert float(at_one[0, 1]) == pytest.approx(5.8652, abs=5e-5) | |
| assert float(at_one[1, 0]) == pytest.approx(0.2557, abs=5e-5) | |
| marginal = db.bridge_marginal(db.Q, db.PI_0, f, g, 0.5) | |
| assert marginal.tolist() == pytest.approx([0.4668, 0.5332], abs=5e-5) | |
| assert db.bridge_marginal(db.Q, db.PI_0, f, g, 1.0).tolist() == pytest.approx([0.2, 0.8]) | |
| reverse = db.reverse_rates(tilted, marginal) | |
| assert float(reverse[1, 0]) == pytest.approx(1.3862, abs=5e-5) | |
| assert float(reverse[0, 1]) == pytest.approx(1.0821, abs=5e-5) | |
| assert float(marginal[0] * tilted[0, 1]) == pytest.approx( | |
| float(marginal[1] * reverse[1, 0]), abs=5e-5) | |
| def test_euler_sampler_and_the_cost_computed_twice(): | |
| db = load("discrete_bridge") | |
| R01 = db.endpoint_law(db.Q, db.PI_0) | |
| f, g, plan = db.sinkhorn(R01, db.PI_0, db.PI_1) | |
| errors = {} | |
| for steps, expected in ((10, 0.0223), (20, 0.0106), (40, 0.0051), (100, 0.0021)): | |
| terminal, leaving = db.euler_marginal(db.Q, db.PI_0, f, g, steps) | |
| errors[steps] = float((terminal - db.PI_1).abs().sum() / 2) | |
| assert errors[steps] == pytest.approx(expected, abs=1e-4) | |
| if steps == 10: | |
| assert leaving[:3] == pytest.approx([0.1143, 0.1187, 0.1245], abs=5e-5) | |
| assert leaving[-1] == pytest.approx(0.3649, abs=5e-5) | |
| assert db.relative_entropy(plan, R01) == pytest.approx(0.3235, abs=5e-5) | |
| integral, at_half = db.path_cost(db.Q, db.PI_0, f, g) | |
| assert at_half == pytest.approx(0.1300, abs=5e-5) | |
| assert integral == pytest.approx(0.3235, abs=5e-5) | |
| expected = {0.5: 0.3148, 1.0: 0.3235, 2.0: 0.3337, 4.0: 0.3348} | |
| for lam, value in expected.items(): | |
| law = db.endpoint_law(lam * db.Q, db.PI_0) | |
| _, _, scaled = db.sinkhorn(law, db.PI_0, db.PI_1) | |
| assert db.relative_entropy(scaled, law) == pytest.approx(value, abs=5e-5) | |
| assert db.relative_entropy(db.PI_1, db.STATIONARY) == pytest.approx(0.3348, abs=5e-5) | |
| # %% Section 7.24, ddsbm.py | |
| def test_ddsbm_single_bond(): | |
| dd = load("ddsbm") | |
| kernel = dd.replacement_kernel(0.4, dd.PRIOR) | |
| assert kernel.flatten().tolist() == pytest.approx( | |
| [0.6, 0.2, 0.2, 0.2, 0.6, 0.2, 0.2, 0.2, 0.6]) | |
| reference = torch.diag(dd.BOND_0) @ kernel | |
| assert reference.sum(dim=0).tolist() == pytest.approx([0.48, 0.28, 0.24]) | |
| assert (dd.BOND_1 / reference.sum(dim=0)).tolist() == pytest.approx( | |
| [0.2083, 0.7143, 2.9167], abs=5e-5) | |
| f, g, plan, errors = dd.sinkhorn(reference, dd.BOND_0, dd.BOND_1, 200) | |
| assert f.tolist() == pytest.approx([1.1779, 0.9506, 0.5062], abs=5e-5) | |
| assert g.tolist() == pytest.approx([0.1842, 0.6918, 3.0004], abs=5e-5) | |
| assert errors[-1] < 1e-12 | |
| assert float(plan[0, 2]) == pytest.approx(0.495, abs=5e-4) | |
| assert dd.held_mass(plan) == pytest.approx(0.261, abs=5e-4) | |
| assert float((dd.BOND_0 * dd.BOND_1).sum()) == pytest.approx(0.18) | |
| assert float(torch.minimum(dd.BOND_0, dd.BOND_1).sum()) == pytest.approx(0.4) | |
| expected = {0.2: 0.2250, 0.4: 0.2612, 0.6: 0.2926, 0.8: 0.3248, 0.99: 0.3812} | |
| for r, value in expected.items(): | |
| held = dd.sinkhorn(torch.diag(dd.BOND_0) @ dd.replacement_kernel(r, dd.PRIOR), | |
| dd.BOND_0, dd.BOND_1, 200)[2] | |
| assert dd.held_mass(held) == pytest.approx(value, abs=5e-5) | |
| def test_ddsbm_pinned_rates_and_the_posterior_average(): | |
| dd = load("ddsbm") | |
| beta = dd.resampling_rate(0.4) | |
| assert beta == pytest.approx(0.9163, abs=5e-5) | |
| rates = dd.replacement_generator(beta, dd.PRIOR) | |
| assert float(rates[0, 1]) == pytest.approx(0.3054, abs=5e-5) | |
| assert rates.sum(dim=1).abs().max() < 1e-12 | |
| assert dd.half_survival_time(0.4) == pytest.approx(math.log(0.8) / math.log(0.4)) | |
| assert dd.half_survival_time(0.4) == pytest.approx(0.2435, abs=5e-5) | |
| pinned = dd.replacement_kernel(0.5, dd.PRIOR) | |
| assert float(pinned[0, 0]) == pytest.approx(2 / 3, abs=5e-5) | |
| assert float(pinned[0, 1]) == pytest.approx(1 / 6, abs=5e-5) | |
| toward = dd.pinned_rate(rates, pinned, 0, 2, 2) | |
| away = dd.pinned_rate(rates, pinned, 2, 0, 2) | |
| between = dd.pinned_rate(rates, pinned, 0, 1, 2) | |
| assert toward == pytest.approx(1.2217, abs=5e-5) | |
| assert away == pytest.approx(0.0764, abs=5e-5) | |
| assert between == pytest.approx(0.3054, abs=5e-5) | |
| assert toward / away == pytest.approx(16.0, abs=5e-4) | |
| assert dd.rate_error(toward, 1.0) == pytest.approx(0.0229, abs=5e-5) | |
| posterior = 0.6 * toward + 0.4 * between | |
| assert posterior == pytest.approx(0.8552, abs=5e-5) | |
| rates_two = [toward, between] | |
| weights = [0.6, 0.4] | |
| assert dd.posterior_loss(rates_two, weights, posterior) == pytest.approx(0.1357, abs=5e-5) | |
| assert dd.posterior_loss(rates_two, weights, 1.0) == pytest.approx(0.1467, abs=5e-5) | |
| assert dd.posterior_loss(rates_two, weights, 0.5) == pytest.approx(0.2395, abs=5e-5) | |
| def test_ddsbm_factorization_counts(): | |
| dd = load("ddsbm") | |
| assert dd.feature_count(9) == (9, 36, 45) | |
| assert dd.state_space_size(9, 5, 4) == pytest.approx(9.2e27, rel=5e-3) | |
| assert dd.total_jump_rate(36, dd.resampling_rate(0.4), 4) == pytest.approx(24.74, abs=5e-3) | |
| # %% Section 7.25, csbm.py | |
| def test_csbm_references(): | |
| cs = load("csbm") | |
| A = cs.uniform_reference(0.35, 2) | |
| assert A.flatten().tolist() == pytest.approx([0.65, 0.35, 0.35, 0.65]) | |
| assert torch.linalg.matrix_power(A, 3).flatten().tolist() == pytest.approx( | |
| [0.5135, 0.4865, 0.4865, 0.5135], abs=5e-5) | |
| assert cs.uniform_reference(0.35, 5).sum(dim=1).tolist() == pytest.approx([1.0] * 5) | |
| ordered = cs.ordered_reference(1.0, 5) | |
| assert ordered[2].tolist() == pytest.approx( | |
| [0.0545, 0.2442, 0.4026, 0.2442, 0.0545], abs=5e-5) | |
| assert cs.transition_loss(tensor(0.3160, 0.6840), tensor(0.25, 0.75)) == pytest.approx( | |
| 0.0110, abs=5e-5) | |
| def test_csbm_dimf_on_sixteen_paths(): | |
| cs = load("csbm") | |
| A = cs.uniform_reference(cs.ALPHA, 2) | |
| plan = cs.sinkhorn(torch.diag(cs.PI_0) @ torch.linalg.matrix_power(A, 3), cs.PI_0, cs.PI_1) | |
| assert plan.flatten().tolist() == pytest.approx( | |
| [0.10432, 0.39568, 0.09568, 0.40432], abs=5e-6) | |
| assert float(cs.bridge_table(A, 3)[0, 0, 0, 0]) == pytest.approx(0.5348, abs=5e-5) | |
| gap, start, target = cs.starting_gap(A, 3, cs.PI_0, cs.PI_1) | |
| assert float(target[0, 0, 0, 0]) == pytest.approx(0.0558, abs=5e-5) | |
| assert float(target[1, 1, 1, 1]) == pytest.approx(0.2162, abs=5e-5) | |
| assert float(start[0, 0, 0, 0]) == pytest.approx(0.0535, abs=5e-5) | |
| assert float(start[1, 1, 1, 1]) == pytest.approx(0.2139, abs=5e-5) | |
| assert gap == pytest.approx(0.0173, abs=5e-5) | |
| for axis, value in ((1, [0.4754, 0.5246]), (2, [0.4107, 0.5893])): | |
| marginal = start.sum(dim=[d for d in range(4) if d != axis]) | |
| assert marginal.tolist() == pytest.approx(value, abs=5e-5) | |
| gaps, conditionals = cs.d_imf(cs.PI_0[:, None] * cs.PI_1[None, :], A, 3, 5, target) | |
| assert conditionals[0].flatten().tolist() == pytest.approx( | |
| [0.6243, 0.3757, 0.3265, 0.6735], abs=5e-5) | |
| assert conditionals[1].flatten().tolist() == pytest.approx( | |
| [0.5626, 0.4374, 0.2729, 0.7271], abs=5e-5) | |
| assert conditionals[2].flatten().tolist() == pytest.approx( | |
| [0.3160, 0.6840, 0.1192, 0.8808], abs=5e-5) | |
| assert gaps == pytest.approx([2.9e-4, 5.0e-6, 8.4e-8, 1.4e-9, 2.4e-11], rel=0.05) | |
| assert gaps[0] / gaps[1] == pytest.approx(59.0, abs=1.0) | |
| def test_csbm_mixing_and_length(): | |
| cs = load("csbm") | |
| independent = cs.PI_0[:, None] * cs.PI_1[None, :] | |
| expected = {0.2: (0.13589, 5.0), 0.4: (0.00512, 284.3)} | |
| for alpha, (start, ratio) in expected.items(): | |
| A = cs.uniform_reference(alpha, 2) | |
| gap, _, target = cs.starting_gap(A, 3, cs.PI_0, cs.PI_1) | |
| assert gap == pytest.approx(start, abs=5e-5) | |
| later, _ = cs.d_imf(independent, A, 3, 2, target) | |
| assert later[0] / later[1] == pytest.approx(ratio, rel=0.01) | |
| A = cs.uniform_reference(cs.ALPHA, 2) | |
| for steps, (start, ratio) in {2: (0.0574, 7.3), 5: (0.00156, 4674.0)}.items(): | |
| gap, _, target = cs.starting_gap(A, steps, cs.PI_0, cs.PI_1) | |
| assert gap == pytest.approx(start, abs=5e-5) | |
| later, _ = cs.d_imf(independent, A, steps, 2, target) | |
| assert later[0] / later[1] == pytest.approx(ratio, rel=0.01) | |
| # %% Sections 7.26 and 7.27, branched.py | |
| def test_two_branches_worked_out(): | |
| br = load("branched") | |
| linear = lambda t: 0.3 * t | |
| late = lambda t: 0.3 * t ** 2 | |
| assert float(br.branch_weights(linear, torch.tensor(0.5))) == pytest.approx(0.15) | |
| assert float(br.branch_weights(linear, torch.tensor(1.0))) == pytest.approx(0.3) | |
| near_zero = br.weighted_energy(2.0, lambda t: 1.0 - linear(t)) | |
| near_one = br.weighted_energy(-2.0, linear) | |
| assert near_zero == pytest.approx(1.7, abs=5e-5) | |
| assert near_one == pytest.approx(0.3, abs=5e-5) | |
| assert near_zero + near_one == pytest.approx(2.0, abs=5e-5) | |
| far_linear = near_zero + br.weighted_energy(-4.0, linear) | |
| far_late = br.weighted_energy(2.0, lambda t: 1.0 - late(t)) + br.weighted_energy(-4.0, late) | |
| assert far_linear == pytest.approx(2.9, abs=5e-5) | |
| assert far_late == pytest.approx(2.6, abs=5e-5) | |
| def test_the_interpolant(): | |
| br = load("branched") | |
| grid = torch.tensor([0.0, 0.5, 1.0]) | |
| assert br.interpolant(0.0, 2.0, 1.0, grid).tolist() == pytest.approx([0.0, 1.25, 2.0]) | |
| assert br.interpolant_velocity(0.0, 2.0, 1.0, grid).tolist() == pytest.approx([3.0, 2.0, 1.0]) | |
| assert br.interpolant_energy(0.0, 2.0, 1.0) == pytest.approx(2.1667, abs=5e-5) | |
| assert br.interpolant_energy(0.0, 2.0, 0.0) == pytest.approx(2.0, abs=5e-5) | |
| def test_entangled_tilt_and_parameterization(): | |
| br = load("branched") | |
| normalizer = 0.1 * 1.0 + 0.9 * 0.01 | |
| assert normalizer == pytest.approx(0.109) | |
| tilted = 0.1 / normalizer | |
| assert tilted == pytest.approx(0.917, abs=5e-4) | |
| assert tilted / 0.1 == pytest.approx(9.17, abs=5e-3) | |
| reward = (1.0 - tilted) * math.log(0.01) | |
| assert reward == pytest.approx(-0.380, abs=5e-4) | |
| assert reward - math.log(normalizer) == pytest.approx(1.836, abs=5e-4) | |
| assert -math.log(normalizer) == pytest.approx(2.216, abs=5e-4) | |
| assert br.NOISE == pytest.approx(1.4142, abs=5e-5) | |
| step = (-0.3 + 0.5) * 0.01 - 1.0 * 0.8 * 0.01 + br.NOISE * math.sqrt(0.01) * 0.4 | |
| assert step == pytest.approx(0.0506, abs=5e-5) | |
| assert 0.5 / br.NOISE == pytest.approx(0.3536, abs=5e-5) | |
| assert 0.5 * (0.5 / br.NOISE) ** 2 * 0.01 == pytest.approx(0.000625, abs=5e-7) | |
| force = br.compose_bias(torch.tensor([[0.5]]), torch.tensor([[[0.3, -0.4]]]), | |
| torch.tensor([[[1.0, 0.0]]]))[0, 0] | |
| assert force.tolist() == pytest.approx([0.5, -0.4]) | |
| assert float(force.norm()) == pytest.approx(0.640, abs=5e-4) | |
| weights = torch.softmax(torch.tensor([-1.0, 0.0, 1.0]), dim=0) | |
| assert weights.tolist() == pytest.approx([0.090, 0.245, 0.665], abs=5e-4) | |
| def test_entangled_training_lowers_the_loss(): | |
| br = load("branched") | |
| torch.manual_seed(0) | |
| generator = torch.Generator().manual_seed(0) | |
| force = br.BiasForce(width=16) | |
| rollout = br.roll_out(force, 16, 8, torch.device("cpu"), generator) | |
| weights = br.importance_weights(rollout) | |
| assert float(weights.sum()) == pytest.approx(1.0) | |
| assert br.force_check(rollout["positions"][-1]) < 1e-5 | |
| loss = br.cross_entropy_loss(force, rollout, weights) | |
| assert torch.isfinite(loss) | |
| # %% Every file runs end to end | |
| def test_the_script_runs(name, flags, monkeypatch, capsys): | |
| module = load(name) | |
| monkeypatch.setattr(sys, "argv", [name] + flags + ["--quiet"]) | |
| module.main() | |
| assert capsys.readouterr().out.strip() | |