CIS6270 / tests /test_examples.py
pranamanam's picture
Update course index, dependencies, and Lecture 3 test paths
faed495 verified
Raw History Blame
4.74 kB
"""Offline unit checks for the Lecture 3 examples."""
import csv
import itertools
from pathlib import Path
import sys
from types import SimpleNamespace
import unittest
import torch
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "lecture_3"))
import esm2_flow_guidance as flow
import esm2_diffusion_guidance as diffusion
class GuidanceTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
torch.set_num_threads(2)
def test_example_annotations(self):
with (ROOT / "lecture_3" / "esm2_example.csv").open(newline="") as handle:
rows = list(csv.DictReader(handle))
self.assertEqual(len(rows), 64)
self.assertEqual({len(row["sequence"]) for row in rows}, {24})
labels = torch.tensor([[float(row["r1"]), float(row["r2"])] for row in rows])
for module in (flow, diffusion):
computed = module.composition_proxies([row["sequence"] for row in rows])
torch.testing.assert_close(computed, labels, atol=1e-7, rtol=0)
for row in rows:
self.assertEqual(int(row["c"]), int(float(row["r1"]) > 0))
def test_scalarization_weights(self):
for module in (flow, diffusion):
with self.subTest(module=module.__name__):
weights = module.normalize_weights((7, 3)).cpu()
torch.testing.assert_close(weights, torch.tensor([0.7, 0.3]))
for invalid in ((0, 0), (-1, 2), (1,), (float("nan"), 1)):
with self.assertRaises(ValueError):
module.normalize_weights(invalid)
def test_reward_gradient_without_weight_updates(self):
for module in (flow, diffusion):
with self.subTest(module=module.__name__):
torch.manual_seed(3)
reward = module.RewardModel(4, 5).to(module.DEVICE)
reward.eval().requires_grad_(False)
state = torch.randn(2, 4, 5, device=module.DEVICE)
time = torch.tensor([0.3, 0.7], device=module.DEVICE)
weights = module.normalize_weights((0.7, 0.3))
gradient = module.reward_gradient(reward, state, time, weights)
delta = torch.zeros_like(state)
delta[0, 1, 2] = 1e-3
difference = reward(state + delta, time) - reward(state - delta, time)
estimate = (difference * weights).sum() / (2e-3)
torch.testing.assert_close(gradient[0, 1, 2], estimate, atol=3e-5, rtol=3e-3)
self.assertFalse(gradient.requires_grad)
self.assertTrue(torch.isfinite(gradient).all())
self.assertTrue(all(p.grad is None for p in reward.parameters()))
def test_ddpm_schedule(self):
self.assertEqual(diffusion.alpha_bars[0].item(), 1.0)
self.assertLess(diffusion.alpha_bars[-1].item(), 1e-4)
self.assertEqual(diffusion.posterior_variances[1].item(), 0.0)
self.assertTrue((diffusion.posterior_variances >= 0).all())
self.assertTrue(torch.isfinite(diffusion.posterior_variances).all())
def test_constrained_decoder_against_exhaustive_search(self):
torch.manual_seed(11)
scores = torch.randn(3, 4, 20)
tokenizer = SimpleNamespace(convert_tokens_to_ids=lambda tokens: list(range(20)))
esm = SimpleNamespace(lm_head=torch.nn.Identity())
stats = {"z_mean": torch.zeros(1, 1, 20), "z_std": torch.ones(1, 1, 20)}
for module in (flow, diffusion):
polar = [i for i, a in enumerate(module.AMINO_ACIDS) if a in module.POLAR_RESIDUES]
nonpolar = [i for i in range(20) if i not in polar]
for minimum in range(5):
sequences = module.decode(scores, esm, tokenizer, stats, min_polar=minimum)
for batch, sequence in enumerate(sequences):
count = sum(a in module.POLAR_RESIDUES for a in sequence)
self.assertGreaterEqual(count, minimum)
actual = sum(float(scores[batch, j, module.AMINO_ACIDS.index(a)])
for j, a in enumerate(sequence))
optimal = max(
sum(float(scores[batch, j, polar if flag else nonpolar].max())
for j, flag in enumerate(assignment))
for assignment in itertools.product((0, 1), repeat=4)
if sum(assignment) >= minimum
)
self.assertAlmostEqual(actual, optimal, places=5)
with self.assertRaises(ValueError):
module.decode(scores, esm, tokenizer, stats, min_polar=5)
if __name__ == "__main__":
unittest.main()