def euler_sample(field, x, steps, record=False):
trajectory = [x]
for k in range(steps):
x = x + field(x, jnp.full((len(x),), k / steps)) / steps
trajectory.append(x)
return (x, jnp.stack(trajectory)) if record else x
def summarize(x):
d2 = ((x[:, None, :] - mix.means[None]) ** 2).sum(-1)
weights = [round(float(w), 3) for w in
jnp.bincount(d2.argmin(1), length=3) / len(x)]
nearest = d2.min(1)
inside = nearest < (3 * mix.std) ** 2
return (f'mode weights {weights}, spread {nearest[inside].mean():.2f}, '
f'stray {1 - inside.mean():.3f}')
def curvature(trajectory): # path length over chord length, minus one
length = jnp.linalg.norm(trajectory[1:] - trajectory[:-1], axis=2).sum(0)
chord = jnp.linalg.norm(trajectory[-1] - trajectory[0], axis=1)
return (length / chord - 1).mean()
key, subkey = jax.random.split(key)
x0 = jax.random.normal(subkey, (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, jnp.maximum(t[0], 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}')