Download tests/test_lecture_3.py from ChatterjeeLab/CIS6270: direct link, hf CLI and curl.
- Browser
- Download file 20.8 kB
-
https://huggingface.co/ChatterjeeLab/CIS6270/resolve/main/tests/test_lecture_3.py
- Command line
-
hf download hf://ChatterjeeLab/CIS6270/tests/test_lecture_3.py
-
curl -L -o test_lecture_3.py https://huggingface.co/ChatterjeeLab/CIS6270/resolve/main/tests/test_lecture_3.py
20.8 kB
| """Lecture 3: the numbers Chapter 3 works by hand, and the invariants the code must keep. | |
| Every assertion here is either a quantity the chapter computes in the text or a property the | |
| derivation guarantees. Nothing trains, so the whole file runs in a few seconds. | |
| """ | |
| import math | |
| import pytest | |
| import torch | |
| from cis6270.metrics import mode_report | |
| from lecture_3 import ( | |
| ddpm, forward_diffusion, guidance, latent_diffusion, samplers, score_matching, sde, | |
| ) | |
| # %% The forward process, Sections 3.2 through 3.10 | |
| def test_euler_maruyama_step_matches_the_worked_numbers(): | |
| landed, drift, noise = sde.euler_maruyama_step(2.0, -1.0, 0.5, 0.04, 1.5) | |
| assert landed == pytest.approx(2.11) | |
| assert (drift, noise) == pytest.approx((-0.04, 0.15)) | |
| def test_three_draws_average_above_the_drift_prediction(): | |
| endpoints = [sde.euler_maruyama_step(2.0, -1.0, 0.5, 0.04, e)[0] for e in (1.5, -0.5, 0.2)] | |
| assert endpoints == pytest.approx([2.11, 1.91, 1.98]) | |
| assert sum(endpoints) / 3.0 == pytest.approx(2.00, abs=1e-6) # The drift alone reaches 1.96. | |
| def test_increment_scales_as_the_square_root_of_the_step(): | |
| assert sde.increment_scale(0.04)["std"] == pytest.approx(0.2) | |
| assert sde.increment_scale(0.02)["std"] == pytest.approx(0.1414, abs=1e-4) | |
| assert sde.increment_scale(0.04, -1.0, 0.5)["noise_over_drift"] == pytest.approx(2.5) | |
| def test_ornstein_uhlenbeck_law_at_log_two(): | |
| law = sde.ornstein_uhlenbeck_law(2.0, 1.0, math.sqrt(2.0), math.log(2.0)) | |
| assert law["mean"] == pytest.approx(1.0) | |
| assert law["variance"] == pytest.approx(0.75) | |
| assert law["std"] == pytest.approx(0.8660, abs=1e-4) | |
| assert law["stationary_variance"] == pytest.approx(1.0) | |
| def test_ito_recovers_the_variance_and_the_chain_rule_alone_does_not(): | |
| generator = torch.Generator().manual_seed(0) | |
| check = sde.check_ito_on_square(8192, 200, 0.25, generator) | |
| assert check["ito_mean"] == pytest.approx(0.25, abs=0.02) | |
| assert check["realized_mean_square"] == pytest.approx(0.25, abs=0.02) | |
| assert abs(check["no_curvature_mean"]) < 0.02 # The curvature term carries all the growth. | |
| def test_fokker_planck_holds_on_the_spreading_gaussian(): | |
| grid = torch.linspace(-3.0, 3.0, 41) | |
| for t in (0.0, 0.5, 2.0): | |
| residual = sde.fokker_planck_residual(grid, torch.full_like(grid, t)) | |
| assert float(residual.abs().max()) < 1e-6 | |
| def test_peak_of_the_gaussian_falls_at_the_rate_the_equation_gives(): | |
| peak = sde.peak_extrapolation(0.1) | |
| assert peak["p_0"] == pytest.approx(0.3989, abs=1e-4) | |
| assert peak["rate"] == pytest.approx(-0.1995, abs=1e-4) | |
| assert peak["linear"] == pytest.approx(0.3790, abs=1e-4) | |
| assert peak["exact"] == pytest.approx(0.3804, abs=1e-4) | |
| assert peak["gap"] == pytest.approx(0.0014, abs=1e-4) | |
| # %% Score matching, Sections 3.14 through 3.17 | |
| def test_posterior_average_at_the_running_observation(): | |
| report = score_matching.posterior_report(7.4, (10.0, 9.0), (0.3, 0.7), epsilon_theta=-0.8) | |
| assert report["weights"] == pytest.approx([0.2156, 0.7844], abs=1e-4) | |
| assert report["clean_mean"] == pytest.approx(9.2156, abs=1e-4) | |
| assert report["conditional_scores"] == pytest.approx([5 / 3, -5 / 9], abs=1e-4) | |
| assert report["marginal"] == pytest.approx([-0.0765, -0.0765], abs=1e-4) | |
| assert report["residual"] == pytest.approx(0.835, abs=1e-3) | |
| assert report["score_hat"] == pytest.approx(4 / 3, abs=1e-4) | |
| assert report["x0"] == pytest.approx([9.85, 9.85, 9.2156], abs=1e-4) | |
| def test_one_parameter_fit_is_minimized_at_the_inverse_variance(): | |
| assert score_matching.one_parameter_loss(0.5, 1.0) == pytest.approx(-0.375) | |
| assert score_matching.one_parameter_loss(1.0, 1.0) == pytest.approx(-0.5) | |
| assert score_matching.one_parameter_loss(0.25, 4.0) == pytest.approx(-0.125) | |
| grid = [0.05 * i for i in range(1, 41)] | |
| best = min(grid, key=lambda a: score_matching.one_parameter_loss(a, 4.0)) | |
| assert best == pytest.approx(0.25) # a = 1 / v with v = 4. | |
| def test_the_three_objectives_share_one_minimizer(): | |
| generator = torch.Generator().manual_seed(1) | |
| clean = score_matching.sample_clean(32768, generator) | |
| epsilon = torch.randn(clean.shape, generator=generator) | |
| found = score_matching.minimizers(clean, epsilon) | |
| assert found["explicit"] == pytest.approx(1.0) | |
| assert found["implicit"] == pytest.approx(1.0, abs=0.03) | |
| assert found["denoising"] == pytest.approx(1.0, abs=0.03) | |
| def test_integration_by_parts_holds_on_the_exact_field(): | |
| generator = torch.Generator().manual_seed(2) | |
| clean = score_matching.sample_clean(32768, generator) | |
| points = score_matching.A_T * clean + score_matching.SIGMA_T * torch.randn( | |
| clean.shape, generator=generator, | |
| ) | |
| identity = score_matching.integration_by_parts_gap(score_matching.exact_score, points) | |
| assert identity["gap"] < 0.05 * abs(identity["left"]) | |
| def test_hutchinson_estimates_the_trace_the_exact_loop_computes(): | |
| generator = torch.Generator().manual_seed(3) | |
| means = torch.randn(2, score_matching.TRACE_DIM, generator=generator) * 1.5 | |
| points = torch.randn(8, score_matching.TRACE_DIM, generator=generator) | |
| def field(z): | |
| return score_matching.high_dimensional_score(z, means, 1.0) | |
| exact, passes = score_matching.exact_trace(field, points) | |
| assert passes == score_matching.TRACE_DIM # One backward pass per coordinate. | |
| estimate, draws = score_matching.hutchinson_trace(field, points, 256, generator) | |
| error = float(draws.std(dim=0).mean()) / math.sqrt(256) | |
| assert float((estimate - exact).mean().abs()) < 4.0 * error | |
| # %% The schedule, the corruption and DDPM, Sections 3.11, 3.12, 3.19 and 3.24 through 3.28 | |
| def test_schedule_matches_the_continuous_curve(): | |
| assert float(forward_diffusion.alphas_cumprod[207]) == pytest.approx(0.6402, abs=1e-4) | |
| assert float(forward_diffusion.a[207]) == pytest.approx(0.8001, abs=1e-4) | |
| assert float(forward_diffusion.sigma[207]) == pytest.approx(0.5999, abs=1e-4) | |
| assert forward_diffusion.continuous_signal(0.207) == pytest.approx(0.7997, abs=1e-4) | |
| assert float(forward_diffusion.alphas_cumprod[1000]) == pytest.approx(4.036e-5, rel=1e-3) | |
| assert float(forward_diffusion.a[1000]) == pytest.approx(0.0064, abs=1e-4) | |
| assert forward_diffusion.continuous_signal(1.0) == pytest.approx(0.0066, abs=1e-4) | |
| assert float(forward_diffusion.a[500]) == pytest.approx(0.2803, abs=1e-4) | |
| def test_every_copy_of_the_schedule_agrees(): | |
| """No lecture file imports another, so each rebuilds the schedule. The copies must match.""" | |
| for name in ("betas", "alphas", "alphas_cumprod"): | |
| for other in (ddpm, latent_diffusion): | |
| assert torch.equal(getattr(forward_diffusion, name), getattr(other, name)), name | |
| assert torch.equal(ddpm.posterior_variance[1:], latent_diffusion.posterior_variance[1:]) | |
| # The samplers read the same path off the continuous curve, and the two agree at a fine grid. | |
| for grid in (samplers.make_schedule(1000), guidance.schedule_tensors(1000)): | |
| assert float(grid["alphas_cumprod"][207].sqrt()) == pytest.approx(0.8, abs=5e-3) | |
| assert torch.allclose(grid["alphas_cumprod"], ddpm.alphas_cumprod, atol=1e-3) | |
| def test_noise_levels_of_the_continuous_schedule(): | |
| for signal, time, rate in ((0.98, 0.0589, 1.2721), (0.8, 0.2068, 4.2157), | |
| (0.35, 0.4544, 9.1420), (0.04, 0.7994, 16.0073)): | |
| located = forward_diffusion.level_time(signal) | |
| assert located["t"] == pytest.approx(time, abs=1e-4) | |
| assert located["beta_t"] == pytest.approx(rate, abs=1e-3) | |
| assert located["sigma_t"] == pytest.approx(math.sqrt(1.0 - signal ** 2), abs=1e-9) | |
| assert forward_diffusion.level_time(0.8)["sigma_t"] == pytest.approx(0.6, abs=1e-4) | |
| def test_dropped_elbo_weight_at_step_five_hundred(): | |
| weight = ddpm.elbo_weight( | |
| ddpm.betas[500], ddpm.posterior_variance[500], | |
| ddpm.alphas[500], ddpm.alphas_cumprod[500], | |
| ) | |
| assert float(ddpm.posterior_variance[500]) == pytest.approx(0.01003, abs=1e-5) | |
| assert float(weight) == pytest.approx(0.005508, abs=1e-6) | |
| assert ddpm.elbo_weight(0.2, 1 / 9, 0.8, 0.64) == pytest.approx(0.625, abs=1e-6) | |
| def test_one_pixel_through_the_forward_code(): | |
| a_k = float(forward_diffusion.a[207]) | |
| sigma_k = float(forward_diffusion.sigma[207]) | |
| assert a_k * 0.5 + sigma_k * (-1.0) == pytest.approx(-0.1998, abs=1e-4) | |
| def test_q_sample_uses_the_supplied_noise_exactly(): | |
| x_0 = torch.randn(4, 1, 28, 28) | |
| noise = torch.randn_like(x_0) | |
| k = torch.tensor([1, 207, 500, 1000]) | |
| for module in (forward_diffusion, ddpm): | |
| x_k, returned = module.q_sample(x_0, k, noise) | |
| abar = module.alphas_cumprod[k].view(-1, 1, 1, 1) | |
| assert torch.allclose(x_k, abar.sqrt() * x_0 + (1 - abar).sqrt() * noise, atol=1e-6) | |
| assert returned is noise | |
| def test_corruption_law_matches_the_gaussian_it_is_drawn_from(): | |
| generator = torch.Generator().manual_seed(0) | |
| x_0 = torch.randn(1, 1, 28, 28, generator=generator) | |
| levels = [1, 207, 500, 1000] | |
| law = forward_diffusion.measure_law(x_0, levels, 1024, generator) | |
| assert law["mean_gap"] < 0.05 # Centered on a_k x_0, to one over the square root of 1024. | |
| assert law["variance_gap"] < 0.05 # Spread sigma_k^2. | |
| # Four times the draws halves the gap, and a systematic error in a coefficient would not. | |
| finer = forward_diffusion.measure_law(x_0, levels, 4096, generator) | |
| assert finer["mean_gap"] < 0.6 * law["mean_gap"] | |
| def test_worked_training_batch_at_abar_point_eight(): | |
| batch = forward_diffusion.worked_batch( | |
| 0.8, (2.0, -1.0, 0.5), (0.5, -0.2, 1.0), (0.4, -0.1, 0.8), | |
| ) | |
| assert batch["a_k"] == pytest.approx(0.8944, abs=1e-4) | |
| assert batch["sigma_k"] == pytest.approx(0.4472, abs=1e-4) | |
| assert batch["x_k"] == pytest.approx([2.0125, -0.9839, 0.8944], abs=1e-3) | |
| assert batch["loss"] == pytest.approx(0.02, abs=1e-6) | |
| assert batch["score_targets"] == pytest.approx([-1.118, 0.447, -2.236], abs=1e-3) | |
| assert batch["x_0_hat"] == pytest.approx([2.05, -1.05, 0.60], abs=1e-2) | |
| def test_two_step_reverse_transition_agrees_in_both_forms(): | |
| step = ddpm.two_step_transition(0.2, 7.4, -1.0, 0.3) | |
| assert step["x_0_hat"] == pytest.approx(10.0, abs=1e-6) | |
| assert step["mean_from_noise"] == pytest.approx(8.646, abs=1e-3) | |
| assert step["mean_from_clean"] == pytest.approx(step["mean_from_noise"], abs=1e-6) | |
| assert step["weights"] == pytest.approx([0.4969, 0.4969], abs=1e-4) | |
| assert step["variance"] == pytest.approx(1 / 9) | |
| assert step["x_{k-1}"] == pytest.approx(8.746, abs=1e-3) | |
| assert step["elbo_weight"] == pytest.approx(0.625, abs=1e-6) | |
| def test_unet_keeps_the_image_shape_and_the_last_reverse_step_adds_no_noise(): | |
| torch.manual_seed(0) | |
| model = ddpm.UNet(in_channels=1, base_channels=8, time_embed_dim=32) | |
| x = torch.randn(2, 1, 28, 28) | |
| k = torch.tensor([1, 1]) | |
| assert model(x, k).shape == x.shape | |
| first, second = ddpm.p_sample(model, x, k), ddpm.p_sample(model, x, k) | |
| assert torch.allclose(first, second) # keep = 0 at k = 1, so the step is the mean. | |
| # %% The three samplers, Sections 3.22, 3.26, 3.32, 3.34 and 3.35 | |
| def test_one_backward_step_under_both_dynamics(): | |
| step = samplers.one_step_comparison(1.2, 2.0, 0.5, 0.8, 0.1) | |
| assert step["score"] == pytest.approx(-1.6) | |
| assert step["velocity"] == pytest.approx(0.4) | |
| assert step["ode_next"] == pytest.approx(1.16) | |
| assert step["sde_drift"] == pytest.approx(2.0) | |
| assert step["sde_mean"] == pytest.approx(1.0) | |
| assert step["sde_noise_std"] == pytest.approx(0.4472, abs=1e-4) | |
| def test_langevin_step_from_the_worked_state(): | |
| assert samplers.langevin_step(2.0, -2.0, 0.01, 0.5) == pytest.approx(2.0507, abs=1e-4) | |
| def test_schedule_stays_a_valid_discretization_at_any_grid_size(): | |
| for num_steps in (20, 200, 1000): | |
| schedule = samplers.make_schedule(num_steps) | |
| assert float(schedule["betas"][1:].max()) < 1.0 | |
| assert float(schedule["alphas_cumprod"][0]) == pytest.approx(1.0) | |
| assert float(schedule["alphas_cumprod"][num_steps]) < 1e-4 # Terminal law is Gaussian. | |
| thousand = samplers.make_schedule(1000) | |
| assert float(thousand["alphas_cumprod"][207].sqrt()) == pytest.approx(0.8, abs=5e-3) | |
| def test_every_sampler_finds_all_four_modes(): | |
| budget = 60 | |
| for name, points in ( | |
| ("ancestral", samplers.ancestral_sample( | |
| samplers.ExactScore(), samplers.make_schedule(budget), 512, | |
| torch.Generator().manual_seed(0))), | |
| ("flow", samplers.probability_flow_sample( | |
| samplers.ExactScore(), 512, budget, torch.Generator().manual_seed(0))), | |
| ("langevin", samplers.annealed_langevin_sample( | |
| samplers.ExactScore(), 512, [0.7 - 0.69 * i / 19 for i in range(20)], 3, 0.15, | |
| torch.Generator().manual_seed(0))), | |
| ): | |
| measured = mode_report(points) | |
| assert measured["modes_found"] == 4, name | |
| assert measured["mode_entropy"] > 1.30, name # log 4 = 1.386 when the modes are even. | |
| def test_each_sampler_reports_the_evaluations_it_spent(): | |
| score = samplers.ExactScore() | |
| samplers.probability_flow_sample(score, 64, 37, torch.Generator().manual_seed(0)) | |
| assert score.calls == 37 | |
| score = samplers.ExactScore() | |
| samplers.annealed_langevin_sample(score, 64, [0.5, 0.2, 0.05], 4, 0.1, | |
| torch.Generator().manual_seed(0)) | |
| assert score.calls == 12 # Three levels, four iterations each. | |
| # %% Guidance, Sections 3.37 through 3.45 | |
| def test_worked_guided_score(): | |
| score, gradient = torch.tensor([[-1.0, -0.1]]), torch.tensor([[1.3, 1.1]]) | |
| at_one = guidance.guided_score(score, gradient, 1.0)[0] | |
| at_two = guidance.guided_score(score, gradient, 2.0)[0] | |
| assert at_one.tolist() == pytest.approx([0.3, 1.0], abs=1e-6) | |
| assert float(at_one.norm()) == pytest.approx(1.0440, abs=1e-4) | |
| assert at_two.tolist() == pytest.approx([1.6, 2.1], abs=1e-6) | |
| assert float(at_two.norm()) == pytest.approx(2.6401, abs=1e-4) | |
| def test_classifier_free_step_and_the_score_it_implies(): | |
| eps = guidance.classifier_free_field(torch.tensor(0.50), torch.tensor(0.10), 2.0) | |
| assert float(eps) == pytest.approx(-0.30, abs=1e-6) | |
| assert float(-eps / 0.5) == pytest.approx(0.6, abs=1e-6) | |
| def test_guided_backward_step_agrees_in_both_parameterizations(): | |
| guided_eps = float(guidance.guided_noise(torch.tensor(0.8), torch.tensor(0.6), 2.0, 0.5)) | |
| assert guided_eps == pytest.approx(0.2, abs=1e-6) | |
| velocity = -0.5 * 2.0 * (1.2 + (-guided_eps / 0.5)) # The collected form of the velocity. | |
| assert velocity == pytest.approx(-0.8, abs=1e-6) | |
| assert 1.2 - 0.1 * velocity == pytest.approx(1.28, abs=1e-6) | |
| def test_classifier_gradient_equals_the_difference_of_the_two_fields(): | |
| points = torch.randn(64, 2, generator=torch.Generator().manual_seed(0)) | |
| gradient = guidance.classifier_gradient(points, 0.8, 0) | |
| difference = (guidance.conditional_score(points, 0.8, 0) | |
| - guidance.unconditional_score(points, 0.8)) | |
| assert torch.allclose(gradient, difference, atol=1e-6) | |
| def test_weights_are_projected_onto_the_simplex(): | |
| weights = guidance.normalize_weights((-1.0, 3.0, 1.0), torch.device("cpu")) | |
| assert float(weights.sum()) == pytest.approx(1.0) | |
| assert float(weights[0]) == 0.0 # A negative weight is clamped away. | |
| def test_scalarized_rewards_reverse_the_ranking_with_the_weights(): | |
| first, second = (1.2, 0.2), (0.5, 1.1) | |
| assert guidance.scalarize(first, (0.7, 0.3)) == pytest.approx(0.90, abs=1e-4) | |
| assert guidance.scalarize(second, (0.7, 0.3)) == pytest.approx(0.68, abs=1e-4) | |
| assert guidance.scalarize(first, (0.3, 0.7)) == pytest.approx(0.50, abs=1e-4) | |
| assert guidance.scalarize(second, (0.3, 0.7)) == pytest.approx(0.92, abs=1e-4) | |
| def test_competing_gradients_cancel_at_the_stated_weights(): | |
| combined = 0.6 * torch.tensor([1.0, 0.8]) + 0.4 * torch.tensor([0.5, -1.2]) | |
| assert combined.tolist() == pytest.approx([0.80, 0.0], abs=1e-6) | |
| def test_selection_weights_and_the_guidance_schedule(): | |
| weights = (2.0 * torch.tensor([1.2, 0.8, 0.3])).softmax(0) | |
| assert weights.tolist() == pytest.approx([0.6193, 0.2783, 0.1024], abs=1e-4) | |
| assert guidance.schedule_value(0.6) == pytest.approx(0.96) | |
| assert guidance.schedule_value(0.0) == 0.0 and guidance.schedule_value(1.0) == 0.0 | |
| def test_guidance_moves_mass_onto_the_target_mode(): | |
| schedule = guidance.schedule_tensors(60) | |
| fractions = [] | |
| for w in (0.0, 0.25, 1.0): | |
| points = guidance.sample_diffusion( | |
| schedule, 512, guidance.TARGET_LABEL, w=w, | |
| generator=torch.Generator().manual_seed(0), | |
| ) | |
| fractions.append(mode_report(points)["mode_fractions"][guidance.TARGET_LABEL]) | |
| assert fractions[0] < 0.35 # Unconditional, so about a quarter. | |
| assert fractions[0] < fractions[1] < fractions[2] | |
| assert fractions[2] > 0.9 | |
| def test_reward_guidance_prefers_the_right_hand_modes(): | |
| schedule = guidance.schedule_tensors(60) | |
| points = guidance.sample_diffusion( | |
| schedule, 512, guidance.TARGET_LABEL, eta=2.0, lambdas=(1.0, 0.0), | |
| generator=torch.Generator().manual_seed(0), | |
| ) | |
| fractions = mode_report(points)["mode_fractions"] | |
| assert fractions[0] + fractions[1] > 0.9 # The two modes at x = +2. | |
| # %% Latent diffusion, Sections 3.21, 3.29 and 3.46 | |
| def test_latent_coordinate_corrupted_and_carried_back(): | |
| abar = float(latent_diffusion.alphas_cumprod[500]) | |
| a_500, sigma_500 = math.sqrt(abar), math.sqrt(1.0 - abar) | |
| assert (a_500, sigma_500) == pytest.approx((0.2803, 0.9599), abs=1e-4) | |
| z_500 = a_500 * 1.2 + sigma_500 * 0.4 | |
| assert z_500 == pytest.approx(0.7204, abs=1e-4) | |
| eps_hat = (z_500 - a_500 * 1.05) / sigma_500 | |
| assert eps_hat == pytest.approx(0.4438, abs=1e-4) | |
| beta_k, alpha_k = float(latent_diffusion.betas[500]), float(latent_diffusion.alphas[500]) | |
| mean = (z_500 - beta_k / sigma_500 * eps_hat) / math.sqrt(alpha_k) | |
| assert mean == pytest.approx(0.7193, abs=1e-4) | |
| variance = float(latent_diffusion.posterior_variance[500]) | |
| assert math.sqrt(variance) == pytest.approx(0.1002, abs=1e-4) | |
| def test_masked_corruption_leaves_padding_at_zero(): | |
| z0 = torch.randn(3, 8, 16) | |
| mask = torch.ones(3, 8) | |
| mask[:, 5:] = 0.0 # The last three positions are padding. | |
| z_k, noise = latent_diffusion.q_sample_latent(z0, torch.tensor([1, 500, 1000]), mask) | |
| assert torch.count_nonzero(z_k[:, 5:, :]) == 0 | |
| assert noise.shape == z0.shape | |
| def test_latent_loss_counts_valid_residues_only(): | |
| z0 = torch.zeros(2, 4, 3) | |
| z0_hat = torch.ones(2, 4, 3) # Unit error at every coordinate. | |
| mask = torch.tensor([[1.0, 1.0, 0.0, 0.0], [1.0, 0.0, 0.0, 0.0]]) | |
| assert float(latent_diffusion.latent_loss(z0_hat, z0, mask)) == pytest.approx(1.0) | |
| z0_hat[:, 2:, :] = 100.0 # A large error, but only on padding. | |
| assert float(latent_diffusion.latent_loss(z0_hat, z0, mask)) == pytest.approx(1.0) | |
| def test_denoiser_returns_a_latent_of_the_same_shape(): | |
| torch.manual_seed(0) | |
| model = latent_diffusion.LatentDenoiser(embed_dim=16, num_layers=1, num_heads=2, max_len=8) | |
| z_k = torch.randn(2, 8, 16) | |
| mask = torch.ones(2, 8) | |
| assert model(z_k, torch.tensor([7, 900]), mask).shape == z_k.shape | |
| def test_composition_proxies_on_a_known_peptide(): | |
| charge, polar = latent_diffusion.composition_proxies("KRDE") | |
| assert charge == pytest.approx(0.0) # Two positive and two negative over four residues. | |
| assert polar == pytest.approx(1.0) # All four are in the polar set. | |
| assert latent_diffusion.composition_proxies("AAAA") == pytest.approx((0.0, 0.0)) | |
| def test_enforce_polar_reaches_the_minimum_at_the_cheapest_positions(): | |
| torch.manual_seed(0) | |
| logits = torch.randn(2, 12, 20) | |
| logits[:, :, latent_diffusion.POLAR_COLUMNS] -= 5.0 # Make the argmax non-polar everywhere. | |
| choices = logits.argmax(dim=-1) | |
| sequences = latent_diffusion.enforce_polar(logits, choices, 5) | |
| for sequence in sequences: | |
| assert len(sequence) == 12 | |
| assert sum(a in latent_diffusion.POLAR_RESIDUES for a in sequence) >= 5 | |
| unchanged = latent_diffusion.enforce_polar(logits, choices, 0) # Nothing is missing. | |
| assert unchanged == ["".join(latent_diffusion.AMINO_ACIDS[int(i)] for i in row) | |
| for row in choices] | |
| def test_the_cheapest_two_substitutions_cost_what_the_notes_says(): | |
| costs = torch.tensor([0.9, 1.4, 2.6, 3.1, 3.3, 4.0, 4.2, 5.5, 6.1]) | |
| cheapest, _ = costs.topk(2, largest=False) | |
| assert cheapest.tolist() == pytest.approx([0.9, 1.4]) | |
| assert float(cheapest.sum()) == pytest.approx(2.3, abs=1e-5) | |