๐Ÿ๐Ÿ๐Ÿ
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

switch to better divergence clearing method

autumn 76118bc9 12c29e72

+59 -69
+59 -69
fluid_clean.py
··· 1 - # pytorch adaptation of the method described here: 2 - # https://www.dgp.toronto.edu/public_user/stam/reality/Research/pdf/GDC03.pdf 1 + # pytorch adaptation of the methods described here: 2 + # https://www.dgp.toronto.edu/public_user/stam/reality/Research/pdf/GDC03.pdf (overall design) 3 + # https://www.karlsims.com/fluid-flow.html (divergence clearing step) 3 4 4 5 # only dependencies are pillow and pytorch 5 6 ··· 18 19 Path(result_directory + "/velocities").mkdir(parents=True, exist_ok=True) 19 20 Path(result_directory + "/densities").mkdir(parents=True, exist_ok=True) 20 21 21 - scale = 2 22 - w = 1920 // scale 23 - h = 1080 // scale 22 + scale = 10 23 + w = 2**scale 24 + h = 2**scale 24 25 25 26 max_iterations = 100000 26 - save_every = 60 27 + save_every = 1 27 28 28 - velocity_diffusion_rate = 10#100 29 - density_diffusion_rate = 1 29 + velocity_diffusion_rate = 20 30 + density_diffusion_rate = None 30 31 density_timescale = 0.005 31 - timescale = 0.005 32 + timescale = 0.01 32 33 33 34 torch.set_default_device("cuda") 34 35 35 - diffusion_solver_steps = 1 36 - conservation_solver_steps = 50 36 + diffusion_solver_steps = 10 37 + divergence_clearing_steps = 20 37 38 38 39 39 40 # save images ··· 67 68 # skipping points where the window would hang off the edge of the field. thus, we end up with 68 69 # a result that's slightly smaller than the field 69 70 70 - # the paper this was based on doesn't mention convolution, but many of its deeply nested for loops are doing just that 71 - 72 71 def convolve(field, kernel): 73 72 # conv2d works in batches & we only ever need to do a 1-element batch 74 73 # "unsqueeze" is pytorch's absolutely bizarre term for wrapping a tensor with another dimension. like going from [1,2] to [[1,2]] ··· 83 82 [0, 1, 0] 84 83 ]]], dtype=torch.float) 85 84 86 - # difference of values of horizontal neighbors 87 - horizontal_projection_kernel = torch.tensor([[[ 88 - [0, 0, 0], 89 - [1, 0, -1], 90 - [0, 0, 0] 85 + # gradient of divergence in the x direction 86 + x_div_kernel = torch.tensor([[[ 87 + [ 1, 2, 1], 88 + [-2,-4,-2], 89 + [ 1, 2, 1] 91 90 ]]], dtype=torch.float) 92 91 93 - # difference of values of vertical neighbors 94 - vertical_projection_kernel = torch.tensor([[[ 95 - [0, 1, 0], 96 - [0, 0, 0], 97 - [0, -1, 0] 92 + div_opp_kernel = torch.tensor([[[ 93 + [ 1, 0,-1], 94 + [ 0, 0, 0], 95 + [-1, 0, 1] 98 96 ]]], dtype=torch.float) 99 97 100 - 101 - 102 - 98 + # gradient of divergence in the y direction 99 + y_div_kernel = torch.tensor([[[ 100 + [ 1,-2, 1], 101 + [ 2,-4, 2], 102 + [ 1,-2, 1] 103 + ]]], dtype=torch.float) 103 104 104 105 # various boundary conditions 105 106 ··· 157 158 158 159 159 160 # advection 160 - # this is where i deviate from the original paper. 161 + # this is a point where i deviate from the original paper. 161 162 # i take the velocity at every point, and add a scaled version of that to the index at each point 162 163 # to find where that velocity will carry whatever is being advected along it in one timestep. 163 164 # that will be a point that i decompose into the integer component & fractional component. like (3.4, 1.2) -> (3,1) + (0.4, 0.2) ··· 193 194 opposed_vertical_boundary(res[0]) 194 195 return res 195 196 196 - 197 - 198 - def enforce_conservation(field): 199 - divergence = convolve(field[1], horizontal_projection_kernel) 200 - divergence += convolve(field[0], vertical_projection_kernel) 201 - divergence *= 0.5 202 - continuous_boundary(divergence) 203 - p = torch.zeros_like(field[0]) 204 - for i in range(conservation_solver_steps): 205 - p[1:h-1,1:w-1] = (divergence + convolve(p, diffusion_kernel)) / 4 206 - continuous_boundary(p) 207 - field[1,1:h-1,1:w-1] += 0.5 * convolve(p, horizontal_projection_kernel) 208 - field[0,1:h-1,1:w-1] += 0.5 * convolve(p, vertical_projection_kernel) 209 - #field[1,1:h-1,1:w-1] += 0.5 * convolve(field[1], horizontal_projection_kernel) 210 - #field[0,1:h-1,1:w-1] += 0.5 * convolve(field[0], vertical_projection_kernel) 211 - opposed_horizontal_boundary(field[1]) 212 - opposed_vertical_boundary(field[0]) 213 - 197 + def clear_divergence(field): 198 + opposed_horizontal_boundary(field[0]) 199 + opposed_vertical_boundary(field[1]) 200 + for i in range(divergence_clearing_steps): 201 + x_op = convolve(field[0], div_opp_kernel) 202 + y_op = convolve(field[1], div_opp_kernel) 203 + field[1,1:h-1,1:w-1] += (convolve(field[1], y_div_kernel) + x_op) / 8 204 + field[0,1:h-1,1:w-1] += (convolve(field[0], x_div_kernel) + y_op) / 8 205 + opposed_horizontal_boundary(field[0]) 206 + opposed_vertical_boundary(field[1]) 214 207 215 208 216 209 ··· 218 211 # initialize a small field of random velocities, then upscale it interpolating between those velocities, 219 212 # so that the variation in velocity is somewhat smooth instead of pure per-pixel noise, 220 213 # which would lead to a less interesting simulation 221 - velocities = torch.randn([2, h//128, w//128]) * 30 214 + velocities = torch.randn([2, h//32, w//32]) * 120 222 215 upsample = torch.nn.Upsample(size=[h, w], mode='bilinear') 223 216 velocities = upsample(velocities.unsqueeze(0))[0] 224 217 225 - # initialize "dye" densities to a uniform field of 0.1 226 - densities = torch.ones([1, h, w]) * 0.1 218 + # initialize "dye" densities to a uniform field 219 + densities = torch.ones([1, h, w]) * 0.3 227 220 # add lines 228 - for x in range(20): 229 - densities[0,:,x*w//20] += 0.8 230 - for y in range(20): 231 - densities[0,y*h//20,:] += 0.8 221 + #for x in range(20): 222 + # densities[0,:,x*w//20] += 0.8 223 + #for y in range(20): 224 + # densities[0,y*h//20,:] += 0.8 232 225 233 226 frame_index = 0 234 227 235 228 last_frame = time.perf_counter() 236 229 237 - limit = 1 / (math.sqrt(2) * timescale) 230 + limit = 1 / (timescale * 2 * math.sqrt(2)) 238 231 239 232 # core loop of the simulation 240 233 for iteration in range(max_iterations): 241 234 # add a tiny bit of "dye" to the fluid at all points 242 - densities += 0.003 243 - if iteration % (30 * save_every) == 0: 244 - for x in range(20): 245 - densities[0,:,x*w//20] += 0.5 246 - for y in range(20): 247 - densities[0,y*h//20,:] += 0.5 235 + densities.add_(0.003) 236 + #if iteration % (30 * save_every) == 0: 237 + # for x in range(20): 238 + # densities[0,:,x*w//20] += 0.3 239 + # for y in range(20): 240 + # densities[0,y*h//20,:] += 0.3 248 241 249 242 250 - velocities[1,h//12+h//3:h//12+2*h//3,w//8] += 50 + 50 * math.sin(iteration * 0.005) 243 + #velocities[1,h//12+h//3:h//12+2*h//3,w//8] += 50 + 50 * math.sin(iteration * 0.005) 251 244 #velocities[1,h//3-h//12:2*h//3-h//12,7*w//8] -= 20 + 70 * math.sin(iteration * 0.01) 252 245 #velocities[0,7*h//8,4*w//6:5*w//6] -= 60 + 50 * math.sin(iteration * 0.03) 253 246 254 247 # diffuse the velocities in both directions, enforcing a boundary 255 248 # condition that maintains a constant inward velocity matching the outward velocity along the edges. that is, a wall 256 - velocities.clamp_(-limit, limit) 257 249 velocities[0] = diffuse(velocities[0], velocity_diffusion_rate, opposed_horizontal_boundary, timescale) 258 250 velocities[1] = diffuse(velocities[1], velocity_diffusion_rate, opposed_vertical_boundary, timescale) 259 - enforce_conservation(velocities) 251 + clear_divergence(velocities) 252 + velocities.clamp_(-limit, limit) 260 253 261 254 # let the velocity field flow along itself 262 255 velocities = advect(velocities, velocities, timescale) 263 - enforce_conservation(velocities) 256 + clear_divergence(velocities) 264 257 265 258 # diffuse the densities & let them flow along the velocity field 266 - # in the source paper this is done at the same timescale as the fluid simulation itself, but i got better results 267 - # with a separate timescale 268 - #densities = diffuse(densities[0], density_diffusion_rate, continuous_boundary, density_timescale).unsqueeze(0) 259 + if density_diffusion_rate is not None: 260 + densities = diffuse(densities[0], density_diffusion_rate, continuous_boundary, density_timescale).unsqueeze(0) 269 261 densities = advect(densities, velocities, density_timescale) 270 262 271 263 # remove a little density 272 - densities -= 0.003 264 + densities.sub_(0.003) 273 265 densities.clamp_(0,1) 274 266 275 267 if iteration % save_every == 0: 276 268 frame_index += 1 277 269 frame_time = time.perf_counter() - last_frame 278 270 image = torch.cat((0.5 + 0.5 * velocities / torch.sqrt(velocities[0]**2 + velocities[1]**2), torch.zeros((1,h,w))), dim=0) 279 - #save(image, f"velocities/{iteration}") 280 271 save(image, f"velocities/{frame_index:06d}") 281 - #monochrome_save(densities[0], f"densities/{iteration}") 282 272 monochrome_save(densities[0], f"densities/{frame_index:06d}") 283 273 284 274 print(f"frame {frame_index:06d}: {frame_time:06f}")