from pathlib import Path
import os
from collections import defaultdict
from IPython.display import HTML, Image
from phi.torch.flow import *
from tqdm.notebook import trange
import matplotlib.pyplot as plt
import torch
import numpy as np
import warnings
warnings.filterwarnings('ignore')
ROOT = Path("./assets/img/")
if not os.path.exists(ROOT):
os.makedirs(ROOT)
PhiFlow is a differentiable fluid simulation in python. Let’s see a basic example inspired by this.
def clamp_field(f, lo=0.0, hi=1.0):
v = f.values # backend tensor
v = math.maximum(lo, math.minimum(hi, v))
return f.with_values(v)
def smoke_plume():
domain = Box(x=100, y=100)
inflow = Sphere(x=50, y=9.5, radius=5)
inflow_rate = 0.2
def step(v, s, p, dt):
s = advect.mac_cormack(s, v, dt) + inflow_rate * resample(inflow, to=s, soft=True)
buoyancy = resample(s * (0, 0.1), to=v)
v = advect.semi_lagrangian(v, v, dt) + buoyancy * dt
v, p = fluid.make_incompressible(v, (), Solve('auto', 1e-3, x0=p))
return v, s, p
v0 = StaggeredGrid(0, 0, domain, x=64, y=64)
smoke0 = CenteredGrid(0, ZERO_GRADIENT, domain, x=200, y=200)
v_trj, s_trj, p_trj = iterate(step, batch(time=150), v0, smoke0, None, dt=.5, substeps=3, range=trange)
anim = plot(s_trj, animate='time', frame_time=80,)
gif_path = ROOT / "smoke_plume.gif"
anim.save(gif_path, writer="pillow", fps=30)
return Image(url=gif_path)
smoke_plume()
0%| | 0/150 [00:00<?, ?it/s]

<Figure size 640x480 with 0 Axes>
Let’s try what happens to a smoke ring.
def smoke_phi():
domain = Box(x=100, y=100)
def step(v, s, p, dt):
s = advect.mac_cormack(s, v, dt)
buoyancy = resample(s * (0, 0.1), to=v)
v = advect.semi_lagrangian(v, v, dt) + buoyancy * dt
v, p = fluid.make_incompressible(v, (), Solve('auto', 1e-3, x0=p))
return v, s, p
v0 = StaggeredGrid(0, 0, domain, x=64, y=64)
smoke0 = CenteredGrid(0, ZERO_GRADIENT, domain, x=256, y=256)
center = vec(x=50, y=20)
R_out, R_in = 15., 10.
outer = Sphere(center, radius=R_out)
inner = Sphere(center, radius=R_in)
outer_mask = resample(outer, to=smoke0, soft=True)
inner_mask = resample(inner, to=smoke0, soft=True)
ring_mask = outer_mask * (1.0 - inner_mask)
smoke0 = smoke0 + ring_mask
v_trj, s_trj, p_trj = iterate(step, batch(time=150), v0, smoke0, None, dt=.5, range=trange, substeps=3)
anim = plot(s_trj, animate='time', frame_time=80)
plt.close()
gif_path = ROOT / "smoke_phi.gif"
anim.save(gif_path, writer="pillow", fps=30)
return Image(url=gif_path)
smoke_phi()
0%| | 0/150 [00:00<?, ?it/s]

<Figure size 640x480 with 0 Axes>
What about puffing smoke from the side.
def smoke_puff():
domain = Box(x=100, y=100)
inflow = Box(x=(0, 10), y=(40, 60))
inflow_rate = 0.1
in_velocity = vec(x=1., y=0.)
def step(v, s, p, dt):
s = advect.mac_cormack(s, v, dt)
s = s + inflow_rate*resample(inflow, to=s, soft=True)
buoyancy = resample(s * (0, 0.1), to=v)
v = v + resample(inflow, to=v, soft=True)*in_velocity
v = advect.semi_lagrangian(v, v, dt) + buoyancy * dt
v, p = fluid.make_incompressible(v, (), Solve('auto', 1e-2, x0=p))
return v, s, p
v0 = StaggeredGrid(0, 0, domain, x=64, y=64)
smoke0 = CenteredGrid(0, ZERO_GRADIENT, domain, x=256, y=256)
v_trj, s_trj, p_trj = iterate(step, batch(time=150), v0, smoke0, None, dt=.5, range=trange, substeps=3)
anim = plot(s_trj, animate='time', frame_time=80)
plt.close()
gif_path = ROOT / "smoke_puff.gif"
anim.save(gif_path, writer="pillow", fps=30)
return Image(url=gif_path)
smoke_puff()
0%| | 0/150 [00:00<?, ?it/s]

<Figure size 640x480 with 0 Axes>
Let’s add an obstacle in front of the smoke stream.
def smoke_obstacle():
domain = Box(x=100, y=100)
inflow = Box(x=(0, 10), y=(40, 60))
inflow_rate = 0.1
in_velocity = vec(x=1., y=0.)
geometry = Box(x=(20, 30), y=(45, 55))
def step(v, s, p, dt):
s = advect.mac_cormack(s, v, dt)
s = s + inflow_rate*resample(inflow, to=s, soft=True)
buoyancy = resample(s * (0, 0.1), to=v)
v = v + resample(inflow, to=v, soft=True)*in_velocity
v = advect.semi_lagrangian(v, v, dt) + buoyancy * dt
v, p = fluid.make_incompressible(v, geometry, Solve('auto', 1e-2, x0=p))
return v, s, p
v0 = StaggeredGrid(0, 0, domain, x=64, y=64)
smoke0 = CenteredGrid(0, ZERO_GRADIENT, domain, x=256, y=256)
v_trj, s_trj, p_trj = iterate(step, batch(time=150), v0, smoke0, None, dt=.5, range=trange, substeps=3)
anim = plot(s_trj, animate='time', frame_time=80,)
plt.close()
gif_path = ROOT / "smoke_obstacle.gif"
anim.save(gif_path, writer="pillow", fps=30)
return Image(url=gif_path)
smoke_obstacle()
0%| | 0/150 [00:00<?, ?it/s]

<Figure size 640x480 with 0 Axes>
And two smoke stream colliding.
def smoke_collide():
domain = Box(x=100, y=100)
inflow_left = Box(x=(0, 10), y=(40, 60))
inflow_right = Box(x=(90, 100), y=(40, 60))
inflow_rate = 0.1
in_velocity_left = vec(x=1., y=0.)
in_velocity_right = vec(x=-1., y=0.)
def step(v, s, p, dt):
s = advect.mac_cormack(s, v, dt)
s = s + inflow_rate*resample(inflow_left, to=s, soft=True) + \
inflow_rate*resample(inflow_right, to=s, soft=True)
buoyancy = resample(s * (0, 0.1), to=v)
v = v + resample(inflow_left, to=v, soft=True)*in_velocity_left + \
resample(inflow_right, to=v, soft=True)*in_velocity_right
v = advect.semi_lagrangian(v, v, dt) + buoyancy * dt
v, p = fluid.make_incompressible(v, (), Solve('auto', 1e-2, x0=p))
return v, s, p
v0 = StaggeredGrid(0, 0, domain, x=64, y=64)
smoke0 = CenteredGrid(0, ZERO_GRADIENT, domain, x=256, y=256)
v_trj, s_trj, p_trj = iterate(step, batch(time=150), v0, smoke0, None, dt=.5, range=trange, substeps=3)
anim = plot(s_trj, animate='time', frame_time=80,)
plt.close()
gif_path = ROOT / "smoke_collide.gif"
anim.save(gif_path, writer="pillow", fps=30)
return Image(url=gif_path)
smoke_collide()
0%| | 0/150 [00:00<?, ?it/s]

<Figure size 640x480 with 0 Axes>