GitHub • Documentation • API • Demos
This notebook shows various animations created with ΦFlow.
To animate a plot, simply pass sequence data to vis.plot()
and specify the time dimension using the animate
argument.
All animations are rendered with Matplotlib and ffmpeg.
# !pip install --quiet phiflow
from phi.flow import *
np.seterr(all="ignore");
The Julia set for the function f(z) = z² + c where we animate c = 0.7885 exp(2πi t) over time. A point z' belongs to the Julia set if the sequence obtained by iterating f(z) starting with z' does not diverge. In the below animation, the plot shows the number of iterations until the sequence reaches |z| ≥ 2. For a more detailed explanation, see the related video showcase of the Mandelbrot set.
def julia(re, im, a=math.linspace(0, 2*PI, batch(t=100))):
return iterate(lambda z, k: (z ** 2 + 0.7885 * math.exp(1j*a), k + (abs(z)<2)), 50, re + im*1j, 0)[1]
plot(CenteredGrid(julia, re=256, im=256, bounds=Box(re=(-2, 2), im=(-2, 2))), animate='t')
/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/phi/field/_field.py:142: FutureWarning: Instance checks on Grid are deprecated and will be removed in version 3.0. Use the methods instance.is_grid, instance.is_point_cloud, instance.is_centered and instance.is_staggered instead. return isinstance(self, Grid) /opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/phi/field/_field.py:148: FutureWarning: Instance checks on PointCloud are deprecated and will be removed in version 3.0. Use the methods instance.is_grid, instance.is_point_cloud, instance.is_centered and instance.is_staggered instead. return isinstance(self, PointCloud)
We define the sine waves $\sin(x - t)$ and $\sin(x + t)$ and sample them on a grid from $x=0$ to $x=2\pi$ with resolution $R_x = 100$. This is done for 60 values of $t$, linearly spaced between $0$ and $4\pi$. These curves are animated in the left plot and their sum, a standing wave, is plotted on the right.
curves = CenteredGrid(lambda x, t: stack([math.sin(x - t), math.cos(x + t)], channel('c')), x=100, t=60, bounds=Box(x=2*PI, t=4*PI)).t[:-1]
plot({"Curves": curves, "Sum": sum(curves.c)}, animate='t')
/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/phi/field/_field.py:142: FutureWarning: Instance checks on Grid are deprecated and will be removed in version 3.0. Use the methods instance.is_grid, instance.is_point_cloud, instance.is_centered and instance.is_staggered instead. return isinstance(self, Grid)
Geometric primitives like Sphere
and Box
can be plotted directly. Instance dimensions denote collections of objects.
plot(Sphere(x=wrap([0, 2], instance('s')), y=0, radius=math.linspace(0, 1, batch(t=50))**.5), animate='t')
/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/phi/field/_field.py:142: FutureWarning: Instance checks on Grid are deprecated and will be removed in version 3.0. Use the methods instance.is_grid, instance.is_point_cloud, instance.is_centered and instance.is_staggered instead. return isinstance(self, Grid)
x = math.range(instance(boxes=10))
plot(Box(x=(x, x+1), y=(0, 2 * math.sin(math.linspace(0, 2*PI, batch(t=30)) + x*.5))), animate='t')
/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/phi/field/_field.py:142: FutureWarning: Instance checks on Grid are deprecated and will be removed in version 3.0. Use the methods instance.is_grid, instance.is_point_cloud, instance.is_centered and instance.is_staggered instead. return isinstance(self, Grid)
In addition to the point locations, PointCloud
s can store per-point values, such as vectors.
grid = flatten(CenteredGrid(x=12, y=10).elements).center
direction = math.rotate_vector(vec(x=0, y=1), angle=math.linspace(0, 2*PI, batch(time=50)))
plot(PointCloud(grid, direction), animate='time')
/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/phi/field/_field.py:142: FutureWarning: Instance checks on Grid are deprecated and will be removed in version 3.0. Use the methods instance.is_grid, instance.is_point_cloud, instance.is_centered and instance.is_staggered instead. return isinstance(self, Grid)
Here we visualize the built-in class Noise
, sampling it on a $64^3$ grid ranging from 0 to 10 along each axis. We plot all $x$-$y$ slices over time, yielding a scanning animation.
The left plot shows noise with a smoothness of 1.0 and the right plot shows the same random noise (equal seed) with smoothness of 1.3.
noise = Noise(smoothness=stack({"Default Noise": 1.0, "Smooth Noise": 1.3}, batch('c')))
grid = CenteredGrid(noise, x=64, y=64, z=64, bounds=Box(x=10, y=10, z=10))
plot(grid, animate='z', show_color_bar=False)
/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/phi/field/_field.py:142: FutureWarning: Instance checks on Grid are deprecated and will be removed in version 3.0. Use the methods instance.is_grid, instance.is_point_cloud, instance.is_centered and instance.is_staggered instead. return isinstance(self, Grid) /opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/phi/field/_field.py:148: FutureWarning: Instance checks on PointCloud are deprecated and will be removed in version 3.0. Use the methods instance.is_grid, instance.is_point_cloud, instance.is_centered and instance.is_staggered instead. return isinstance(self, PointCloud)
This animation shows two planets circling the sun, using a PointCloud
with spherical elements for visualization.
PLANETS = instance(planets='Sun,Earth,Mars')
x = tensor([(0, 0), (9, 0), (0, 12)], PLANETS, channel(vector='x,y'))
x = math.rotate_vector(x, math.linspace(0, wrap([0, 5, 3], PLANETS), batch(time=130)))
plot(PointCloud(Sphere(x, radius=wrap([1, .4, .2], PLANETS))), animate='time')
/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/phi/field/_field.py:142: FutureWarning: Instance checks on Grid are deprecated and will be removed in version 3.0. Use the methods instance.is_grid, instance.is_point_cloud, instance.is_centered and instance.is_staggered instead. return isinstance(self, Grid)
Two spheres are placed in a $32^3$ domain, at positions (16, 16, 0) and (16, 16, 32). Their radii grow linearly in time. These spheres are then sampled on a regular grid and plotted as voxels. Additionally, we plot the cross section $y=16$ as a 2D heat map.
sphere = Sphere(x=16, y=16, z=0, radius=math.linspace(0, 16, batch(time=17)))
grid = CenteredGrid(union(sphere, sphere.shifted((0, 0, 32))), x=32, y=32, z=32)
plot({"3D": grid, "2D Slice": grid.y[16]}, animate='time', frame_time=300)
/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/phi/field/_field.py:142: FutureWarning: Instance checks on Grid are deprecated and will be removed in version 3.0. Use the methods instance.is_grid, instance.is_point_cloud, instance.is_centered and instance.is_staggered instead. return isinstance(self, Grid) /opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/phi/field/_field.py:148: FutureWarning: Instance checks on PointCloud are deprecated and will be removed in version 3.0. Use the methods instance.is_grid, instance.is_point_cloud, instance.is_centered and instance.is_staggered instead. return isinstance(self, PointCloud)
For this animated spiral, we plot 200 points whose distance increases linearly from the origin and whose angle increases linearly from 0 to $\alpha = 20 \frac{t}{T}$ where $t$ denotes the current frame and $T$ the number of frames.
When no geometric shape is specified, PointCloud
s are plotted as x
.
Varying the parameters can produce vastly different patterns.
dst = math.linspace(0, 1, instance(points=200))
angle = math.linspace(0, math.linspace(PI*200, 1.1*PI*200, batch(t=200)), dst.shape)
plot(PointCloud(dst * vec(x=math.cos(angle), y=math.sin(angle))), animate='t')
/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/phi/field/_field.py:142: FutureWarning: Instance checks on Grid are deprecated and will be removed in version 3.0. Use the methods instance.is_grid, instance.is_point_cloud, instance.is_centered and instance.is_staggered instead. return isinstance(self, Grid)
This demo visualizes the evolution of a PointCloud
as a 3D scatter plot animation.
Thirty balls are placed at random locations. The initial velocities are sampled from a normal distribution with standard deviations $\sigma_x = \sigma_y = 1$ and $\sigma_z = 2$.
A simulation is than run for 100 frames, performing the following operations at each step:
x0 = math.random_uniform(instance(balls=30), channel(vector='x,y,z')) + 5
balls = PointCloud(Sphere(x0, radius=.02), math.random_normal(x0.shape) * (1, 1, 2))
def step(balls, dt=.1):
balls *= math.where(balls.points.vector['z'] < 0, (1, 1, -1), 1) * 0.7 ** dt
return advect.points(balls, balls, dt) + (0, 0, -9.81 * dt)
plot(field.mask(iterate(step, batch(t=100), balls)), animate='t')
/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/phi/field/_field.py:142: FutureWarning: Instance checks on Grid are deprecated and will be removed in version 3.0. Use the methods instance.is_grid, instance.is_point_cloud, instance.is_centered and instance.is_staggered instead. return isinstance(self, Grid)
Burgers' equation is a partial differential equation consisting of an advection term and a diffusion term acting on a vector field $v$ (velocity). It reads
$$\frac{\partial v}{\partial t} = \nu \frac{\partial^2 v}{\partial x^2} - v \frac{\partial v}{\partial x}.$$Here, we simulate Burgers' equation on a $64^2$ grid for 100 time steps with $\Delta t = 0.5$, starting with a randomly generated initial condition. The evolution is plotted as a vector field. A standalone demo of Burgers' equation is also available here.
velocity = CenteredGrid(Noise(smoothness=1.5, vector='x,y'), extrapolation.PERIODIC, x=64, y=64) * 2
def burgers(v, dt=.5):
return diffuse.explicit(advect.semi_lagrangian(v, v, dt), .08, dt)
vis.plot(iterate(burgers, batch(time=100), velocity), animate='time')
/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/phi/field/_field.py:142: FutureWarning: Instance checks on Grid are deprecated and will be removed in version 3.0. Use the methods instance.is_grid, instance.is_point_cloud, instance.is_centered and instance.is_staggered instead. return isinstance(self, Grid) /opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/phi/field/_field.py:148: FutureWarning: Instance checks on PointCloud are deprecated and will be removed in version 3.0. Use the methods instance.is_grid, instance.is_point_cloud, instance.is_centered and instance.is_staggered instead. return isinstance(self, PointCloud) /opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/phi/field/_field.py:154: FutureWarning: Instance checks on StaggeredGrid are deprecated and will be removed in version 3.0. Use the methods instance.is_grid, instance.is_point_cloud, instance.is_centered and instance.is_staggered instead. return isinstance(self, StaggeredGrid)
Next, we simulate an incompressible fluid with moderate diffusion. We split the PDE
$$\frac{\partial v}{\partial t} = \nu \frac{\partial^2 v}{\partial x^2} - v \frac{\partial v}{\partial x} - \nabla p \quad \mathrm{s.t.} \quad \nabla \cdot v = 0$$into advection, diffusion and pressure projection but will rely purely on numerical diffusion in this example. Starting from a random initial conditions, the fluid is simulated for 40 time steps and the vorticity $w = \nabla \times v$ and the pressure $p$ are shown. Also check out the tutorial notebook or the standalone Python scripts.
def incompressible_fluid_step(v: StaggeredGrid, p: CenteredGrid, dt=.5):
return fluid.make_incompressible(advect.advect(v, v, dt), (), Solve('auto', 1e-5, 1e-5, x0=p))
trj = iterate(incompressible_fluid_step, batch(time=40), *fluid.make_incompressible(StaggeredGrid(Noise(), 0, x=64, y=64)))
plot({"Vorticity": field.curl(trj[0]), "Pressure": trj[1]}, animate='time', same_scale=False)
/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/phi/physics/advect.py:72: FutureWarning: Instance checks on PointCloud are deprecated and will be removed in version 3.0. Use the methods instance.is_grid, instance.is_point_cloud, instance.is_centered and instance.is_staggered instead. if isinstance(field, PointCloud): /opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/phi/physics/advect.py:74: FutureWarning: Instance checks on Grid are deprecated and will be removed in version 3.0. Use the methods instance.is_grid, instance.is_point_cloud, instance.is_centered and instance.is_staggered instead. elif isinstance(field, Grid): /opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/phi/field/_field.py:142: FutureWarning: Instance checks on Grid are deprecated and will be removed in version 3.0. Use the methods instance.is_grid, instance.is_point_cloud, instance.is_centered and instance.is_staggered instead. return isinstance(self, Grid) /opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/phi/field/_field.py:148: FutureWarning: Instance checks on PointCloud are deprecated and will be removed in version 3.0. Use the methods instance.is_grid, instance.is_point_cloud, instance.is_centered and instance.is_staggered instead. return isinstance(self, PointCloud)
The incompressibility constraint $\nabla \cdot v$ in the Navier-Stokes equations is numerically achieved by solving the linear system of equations
$$\nabla p = \nabla \cdot v'$$which yields the pseudo-pressure $p$. This is typically done with a conjugate gradient solver using a laplace stencil (5-point in 2D, 7-point in 3D). This demo visualizes how the pressure optimization progresses internally for a tentative velocity $v' = (1, 1)$ inside a circle at the center of the $100^2$ domain and $v' = 0$ outside.
with math.SolveTape(record_trajectories=True) as solves:
fluid.make_incompressible(StaggeredGrid(Sphere(x=50, y=50, radius=20), 0, x=100, y=100))
plot(solves[0].x, animate='trajectory', frame_time=50)
/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/phi/field/_field.py:142: FutureWarning: Instance checks on Grid are deprecated and will be removed in version 3.0. Use the methods instance.is_grid, instance.is_point_cloud, instance.is_centered and instance.is_staggered instead. return isinstance(self, Grid) /opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/phi/field/_field.py:148: FutureWarning: Instance checks on PointCloud are deprecated and will be removed in version 3.0. Use the methods instance.is_grid, instance.is_point_cloud, instance.is_centered and instance.is_staggered instead. return isinstance(self, PointCloud)
This simulation consists of two quantities $u$ and $v$ that interact via a non-linear partial differential equation (PDE) involving diffusion terms, $\nabla^2 u$ and $\nabla^2 v$. Depending on the exact form and parameters of the PDE, a myriad of resulting patterns can be achieved. The simulation is run for 1000 frames but we only plot every 10th since small time steps must be chosen for stability.
def reaction_diffusion(u, v, du=.19, dv=.05, f=.06, k=.062, dt=1.):
return u + dt * du * field.laplace(u) - u * v**2 + f * (1 - u), v + dt * dv * field.laplace(v) + u * v**2 - (f + k) * v
trj_u, trj_v = iterate(reaction_diffusion, batch(time=1000), *[CenteredGrid(Noise(scale=20, smoothness=1.3), x=100, y=100) * .2 + .5]*2)
plot(trj_u.time[::10], animate='time')
/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/phi/field/_field.py:142: FutureWarning: Instance checks on Grid are deprecated and will be removed in version 3.0. Use the methods instance.is_grid, instance.is_point_cloud, instance.is_centered and instance.is_staggered instead. return isinstance(self, Grid) /opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/phi/field/_field.py:148: FutureWarning: Instance checks on PointCloud are deprecated and will be removed in version 3.0. Use the methods instance.is_grid, instance.is_point_cloud, instance.is_centered and instance.is_staggered instead. return isinstance(self, PointCloud)