@torch.no_grad()
def euler_sample(field, x, steps, record=False):
trajectory = [x]
for k in range(steps):
x = x + field(x, torch.full((len(x),), k / steps)) / steps
trajectory.append(x)
return (x, torch.stack(trajectory)) if record else x
def summarize(x):
d2 = ((x[:, None, :] - mix.means[None]) ** 2).sum(-1)
weights = [round(w, 3) for w in
(torch.bincount(d2.argmin(1), minlength=3) / len(x)).tolist()]
nearest = d2.min(1).values
inside = nearest < (3 * mix.std) ** 2
return (f'mode weights {weights}, spread {nearest[inside].mean():.2f}, '
f'stray {1 - inside.float().mean():.3f}')
def curvature(trajectory): # path length over chord length, minus one
length = (trajectory[1:] - trajectory[:-1]).norm(dim=2).sum(0)
chord = (trajectory[-1] - trajectory[0]).norm(dim=1)
return (length / chord - 1).mean()
torch.manual_seed(3)
x0 = torch.randn(2000, 2)
print(f'target (2000 samples of the mixture): {summarize(data)}')
for steps in (100, 10, 4, 2):
print(f'learned field, {steps:>3d} Euler steps: {summarize(euler_sample(velocity, x0, steps))}')
exact = lambda x, t: true_velocity(x, max(t[0].item(), 1e-3)) # avoid t = 0
print(f'exact field, 100 Euler steps: {summarize(euler_sample(exact, x0, 100))}')
_, path = euler_sample(velocity, x0, 100, record=True)
print(f'curvature of the learned flow: {curvature(path):.3f}')