-
Notifications
You must be signed in to change notification settings - Fork 912
/
fluidsim.py
149 lines (121 loc) · 4.68 KB
/
fluidsim.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
import os
import matplotlib
import matplotlib.pyplot as plt
from scipy.misc import imread
from scipy.optimize import minimize
import autograd.numpy as np
from autograd import value_and_grad
# Fluid simulation code based on
# "Real-Time Fluid Dynamics for Games" by Jos Stam
# https://www.josstam.com/_files/ugd/cf1fd6_9989229efbd34a26ba5ccd913721a2ac.pdf
def project(vx, vy):
"""Project the velocity field to be approximately mass-conserving,
using a few iterations of Gauss-Seidel."""
p = np.zeros(vx.shape)
h = 1.0 / vx.shape[0]
div = (
-0.5
* h
* (
np.roll(vx, -1, axis=0)
- np.roll(vx, 1, axis=0)
+ np.roll(vy, -1, axis=1)
- np.roll(vy, 1, axis=1)
)
)
for k in range(10):
p = (
div
+ np.roll(p, 1, axis=0)
+ np.roll(p, -1, axis=0)
+ np.roll(p, 1, axis=1)
+ np.roll(p, -1, axis=1)
) / 4.0
vx -= 0.5 * (np.roll(p, -1, axis=0) - np.roll(p, 1, axis=0)) / h
vy -= 0.5 * (np.roll(p, -1, axis=1) - np.roll(p, 1, axis=1)) / h
return vx, vy
def advect(f, vx, vy):
"""Move field f according to x and y velocities (u and v)
using an implicit Euler integrator."""
rows, cols = f.shape
cell_ys, cell_xs = np.meshgrid(np.arange(rows), np.arange(cols))
center_xs = (cell_xs - vx).ravel()
center_ys = (cell_ys - vy).ravel()
# Compute indices of source cells.
left_ix = np.floor(center_xs).astype(int)
top_ix = np.floor(center_ys).astype(int)
rw = center_xs - left_ix # Relative weight of right-hand cells.
bw = center_ys - top_ix # Relative weight of bottom cells.
left_ix = np.mod(left_ix, rows) # Wrap around edges of simulation.
right_ix = np.mod(left_ix + 1, rows)
top_ix = np.mod(top_ix, cols)
bot_ix = np.mod(top_ix + 1, cols)
# A linearly-weighted sum of the 4 surrounding cells.
flat_f = (1 - rw) * ((1 - bw) * f[left_ix, top_ix] + bw * f[left_ix, bot_ix]) + rw * (
(1 - bw) * f[right_ix, top_ix] + bw * f[right_ix, bot_ix]
)
return np.reshape(flat_f, (rows, cols))
def simulate(vx, vy, smoke, num_time_steps, ax=None, render=False):
print("Running simulation...")
for t in range(num_time_steps):
if ax:
plot_matrix(ax, smoke, t, render)
vx_updated = advect(vx, vx, vy)
vy_updated = advect(vy, vx, vy)
vx, vy = project(vx_updated, vy_updated)
smoke = advect(smoke, vx, vy)
if ax:
plot_matrix(ax, smoke, num_time_steps, render)
return smoke
def plot_matrix(ax, mat, t, render=False):
plt.cla()
ax.matshow(mat)
ax.set_xticks([])
ax.set_yticks([])
plt.draw()
if render:
matplotlib.image.imsave(f"step{t:03d}.png", mat)
plt.pause(0.001)
if __name__ == "__main__":
simulation_timesteps = 100
basepath = os.path.dirname(__file__)
print("Loading initial and target states...")
init_smoke = imread(os.path.join(basepath, "init_smoke.png"))[:, :, 0]
# target = imread('peace.png')[::2,::2,3]
target = imread(os.path.join(basepath, "skull.png"))[::2, ::2]
rows, cols = target.shape
init_dx_and_dy = np.zeros((2, rows, cols)).ravel()
def distance_from_target_image(smoke):
return np.mean((target - smoke) ** 2)
def convert_param_vector_to_matrices(params):
vx = np.reshape(params[: (rows * cols)], (rows, cols))
vy = np.reshape(params[(rows * cols) :], (rows, cols))
return vx, vy
def objective(params):
init_vx, init_vy = convert_param_vector_to_matrices(params)
final_smoke = simulate(init_vx, init_vy, init_smoke, simulation_timesteps)
return distance_from_target_image(final_smoke)
# Specify gradient of objective function using autograd.
objective_with_grad = value_and_grad(objective)
fig = plt.figure(figsize=(8, 8))
ax = fig.add_subplot(111, frameon=False)
def callback(params):
init_vx, init_vy = convert_param_vector_to_matrices(params)
simulate(init_vx, init_vy, init_smoke, simulation_timesteps, ax)
print("Optimizing initial conditions...")
result = minimize(
objective_with_grad,
init_dx_and_dy,
jac=True,
method="CG",
options={"maxiter": 25, "disp": True},
callback=callback,
)
print("Rendering optimized flow...")
init_vx, init_vy = convert_param_vector_to_matrices(result.x)
simulate(init_vx, init_vy, init_smoke, simulation_timesteps, ax, render=True)
print("Converting frames to an animated GIF...")
os.system(
"convert -delay 5 -loop 0 step*.png" " -delay 250 step100.png surprise.gif"
) # Using imagemagick.
os.system("rm step*.png")