Spaces:
Sleeping
Sleeping
| # Copyright (c) 2022 NVIDIA CORPORATION. All rights reserved. | |
| # NVIDIA CORPORATION and its licensors retain all intellectual property | |
| # and proprietary rights in and to this software, related documentation | |
| # and any modifications thereto. Any use, reproduction, disclosure or | |
| # distribution of this software and related documentation without an express | |
| # license agreement from NVIDIA CORPORATION is strictly prohibited. | |
| ########################################################################### | |
| # Example Smoothed Particle Hydrodynamics | |
| # | |
| # Shows how to implement a SPH fluid simulation. | |
| # | |
| # Neighbors are found using the wp.HashGrid class, and | |
| # wp.hash_grid_query(), wp.hash_grid_query_next() kernel methods. | |
| # | |
| # Reference Publication | |
| # Matthias Müller, David Charypar, and Markus H. Gross. | |
| # "Particle-based fluid simulation for interactive applications." | |
| # Symposium on Computer animation. Vol. 2. 2003. | |
| # | |
| ########################################################################### | |
| import os | |
| import numpy as np | |
| import warp as wp | |
| import warp.render | |
| wp.init() | |
| def square(x: float): | |
| return x * x | |
| def cube(x: float): | |
| return x * x * x | |
| def fifth(x: float): | |
| return x * x * x * x * x | |
| def density_kernel(xyz: wp.vec3, smoothing_length: float): | |
| # calculate distance | |
| distance = wp.dot(xyz, xyz) | |
| return wp.max(cube(square(smoothing_length) - distance), 0.0) | |
| def diff_pressure_kernel( | |
| xyz: wp.vec3, pressure: float, neighbor_pressure: float, neighbor_rho: float, smoothing_length: float | |
| ): | |
| # calculate distance | |
| distance = wp.sqrt(wp.dot(xyz, xyz)) | |
| if distance < smoothing_length: | |
| # calculate terms of kernel | |
| term_1 = -xyz / distance | |
| term_2 = (neighbor_pressure + pressure) / (2.0 * neighbor_rho) | |
| term_3 = square(smoothing_length - distance) | |
| return term_1 * term_2 * term_3 | |
| else: | |
| return wp.vec3() | |
| def diff_viscous_kernel(xyz: wp.vec3, v: wp.vec3, neighbor_v: wp.vec3, neighbor_rho: float, smoothing_length: float): | |
| # calculate distance | |
| distance = wp.sqrt(wp.dot(xyz, xyz)) | |
| # calculate terms of kernel | |
| if distance < smoothing_length: | |
| term_1 = (neighbor_v - v) / neighbor_rho | |
| term_2 = smoothing_length - distance | |
| return term_1 * term_2 | |
| else: | |
| return wp.vec3() | |
| def compute_density( | |
| grid: wp.uint64, | |
| particle_x: wp.array(dtype=wp.vec3), | |
| particle_rho: wp.array(dtype=float), | |
| density_normalization: float, | |
| smoothing_length: float, | |
| ): | |
| tid = wp.tid() | |
| # order threads by cell | |
| i = wp.hash_grid_point_id(grid, tid) | |
| # get local particle variables | |
| x = particle_x[i] | |
| # store density | |
| rho = float(0.0) | |
| # particle contact | |
| neighbors = wp.hash_grid_query(grid, x, smoothing_length) | |
| # loop through neighbors to compute density | |
| for index in neighbors: | |
| # compute distance | |
| distance = x - particle_x[index] | |
| # compute kernel derivative | |
| rho += density_kernel(distance, smoothing_length) | |
| # add external potential | |
| particle_rho[i] = density_normalization * rho | |
| def get_acceleration( | |
| grid: wp.uint64, | |
| particle_x: wp.array(dtype=wp.vec3), | |
| particle_v: wp.array(dtype=wp.vec3), | |
| particle_rho: wp.array(dtype=float), | |
| particle_a: wp.array(dtype=wp.vec3), | |
| isotropic_exp: float, | |
| base_density: float, | |
| gravity: float, | |
| pressure_normalization: float, | |
| viscous_normalization: float, | |
| smoothing_length: float, | |
| ): | |
| tid = wp.tid() | |
| # order threads by cell | |
| i = wp.hash_grid_point_id(grid, tid) | |
| # get local particle variables | |
| x = particle_x[i] | |
| v = particle_v[i] | |
| rho = particle_rho[i] | |
| pressure = isotropic_exp * (rho - base_density) | |
| # store forces | |
| pressure_force = wp.vec3() | |
| viscous_force = wp.vec3() | |
| # particle contact | |
| neighbors = wp.hash_grid_query(grid, x, smoothing_length) | |
| # loop through neighbors to compute acceleration | |
| for index in neighbors: | |
| if index != i: | |
| # get neighbor velocity | |
| neighbor_v = particle_v[index] | |
| # get neighbor density and pressures | |
| neighbor_rho = particle_rho[index] | |
| neighbor_pressure = isotropic_exp * (neighbor_rho - base_density) | |
| # compute relative position | |
| relative_position = particle_x[index] - x | |
| # calculate pressure force | |
| pressure_force += diff_pressure_kernel( | |
| relative_position, pressure, neighbor_pressure, neighbor_rho, smoothing_length | |
| ) | |
| # compute kernel derivative | |
| viscous_force += diff_viscous_kernel(relative_position, v, neighbor_v, neighbor_rho, smoothing_length) | |
| # sum all forces | |
| force = pressure_normalization * pressure_force + viscous_normalization * viscous_force | |
| # add external potential | |
| particle_a[i] = force / rho + wp.vec3(0.0, gravity, 0.0) | |
| def apply_bounds( | |
| particle_x: wp.array(dtype=wp.vec3), | |
| particle_v: wp.array(dtype=wp.vec3), | |
| damping_coef: float, | |
| width: float, | |
| height: float, | |
| length: float, | |
| ): | |
| tid = wp.tid() | |
| # get pos and velocity | |
| x = particle_x[tid] | |
| v = particle_v[tid] | |
| # clamp x left | |
| if x[0] < 0.0: | |
| x = wp.vec3(0.0, x[1], x[2]) | |
| v = wp.vec3(v[0] * damping_coef, v[1], v[2]) | |
| # clamp x right | |
| if x[0] > width: | |
| x = wp.vec3(width, x[1], x[2]) | |
| v = wp.vec3(v[0] * damping_coef, v[1], v[2]) | |
| # clamp y bot | |
| if x[1] < 0.0: | |
| x = wp.vec3(x[0], 0.0, x[2]) | |
| v = wp.vec3(v[0], v[1] * damping_coef, v[2]) | |
| # clamp z left | |
| if x[2] < 0.0: | |
| x = wp.vec3(x[0], x[1], 0.0) | |
| v = wp.vec3(v[0], v[1], v[2] * damping_coef) | |
| # clamp z right | |
| if x[2] > length: | |
| x = wp.vec3(x[0], x[1], length) | |
| v = wp.vec3(v[0], v[1], v[2] * damping_coef) | |
| # apply clamps | |
| particle_x[tid] = x | |
| particle_v[tid] = v | |
| def kick(particle_v: wp.array(dtype=wp.vec3), particle_a: wp.array(dtype=wp.vec3), dt: float): | |
| tid = wp.tid() | |
| v = particle_v[tid] | |
| particle_v[tid] = v + particle_a[tid] * dt | |
| def drift(particle_x: wp.array(dtype=wp.vec3), particle_v: wp.array(dtype=wp.vec3), dt: float): | |
| tid = wp.tid() | |
| x = particle_x[tid] | |
| particle_x[tid] = x + particle_v[tid] * dt | |
| def initialize_particles( | |
| particle_x: wp.array(dtype=wp.vec3), smoothing_length: float, width: float, height: float, length: float | |
| ): | |
| tid = wp.tid() | |
| # grid size | |
| nr_x = wp.int32(width / 4.0 / smoothing_length) | |
| nr_y = wp.int32(height / smoothing_length) | |
| nr_z = wp.int32(length / 4.0 / smoothing_length) | |
| # calculate particle position | |
| z = wp.float(tid % nr_z) | |
| y = wp.float((tid // nr_z) % nr_y) | |
| x = wp.float((tid // (nr_z * nr_y)) % nr_x) | |
| pos = smoothing_length * wp.vec3(x, y, z) | |
| # add small jitter | |
| state = wp.rand_init(123, tid) | |
| pos = pos + 0.001 * smoothing_length * wp.vec3(wp.randn(state), wp.randn(state), wp.randn(state)) | |
| # set position | |
| particle_x[tid] = pos | |
| class Example: | |
| def __init__(self, stage): | |
| # render params | |
| self.frame_dt = 1.0 / 60.0 | |
| self.frame_count = 600 | |
| self.renderer = wp.render.UsdRenderer(stage) | |
| self.sim_time = 0.0 | |
| # simulation params | |
| self.smoothing_length = 0.8 # NOTE change this to adjust number of particles | |
| self.width = 80.0 # x | |
| self.height = 80.0 # y | |
| self.length = 80.0 # z | |
| self.isotropic_exp = 20 | |
| self.base_density = 1.0 | |
| self.particle_mass = 0.01 * self.smoothing_length**3 # reduce according to smoothing length | |
| self.dt = 0.01 * self.smoothing_length # decrease sim dt by smoothing length | |
| self.dynamic_visc = 0.025 | |
| self.damping_coef = -0.95 | |
| self.gravity = -0.1 | |
| self.n = int( | |
| self.height * (self.width / 4.0) * (self.height / 4.0) / (self.smoothing_length**3) | |
| ) # number particles (small box in corner) | |
| self.sim_step_to_frame_ratio = int(32 / self.smoothing_length) | |
| # constants | |
| self.density_normalization = (315.0 * self.particle_mass) / ( | |
| 64.0 * np.pi * self.smoothing_length**9 | |
| ) # integrate density kernel | |
| self.pressure_normalization = -(45.0 * self.particle_mass) / (np.pi * self.smoothing_length**6) | |
| self.viscous_normalization = (45.0 * self.dynamic_visc * self.particle_mass) / ( | |
| np.pi * self.smoothing_length**6 | |
| ) | |
| # allocate arrays | |
| self.x = wp.empty(tuple([self.n]), dtype=wp.vec3) | |
| self.v = wp.zeros(tuple([self.n]), dtype=wp.vec3) | |
| self.rho = wp.zeros(tuple([self.n]), dtype=float) | |
| self.a = wp.zeros(tuple([self.n]), dtype=wp.vec3) | |
| # set random positions | |
| wp.launch( | |
| kernel=initialize_particles, | |
| dim=self.n, | |
| inputs=[self.x, self.smoothing_length, self.width, self.height, self.length], | |
| ) # initialize in small area | |
| # create hash array | |
| grid_size = int(self.height / (4.0 * self.smoothing_length)) | |
| self.grid = wp.HashGrid(grid_size, grid_size, grid_size) | |
| def update(self): | |
| with wp.ScopedTimer("simulate", active=True): | |
| for _ in range(self.sim_step_to_frame_ratio): | |
| with wp.ScopedTimer("grid build", active=False): | |
| # build grid | |
| self.grid.build(self.x, self.smoothing_length) | |
| with wp.ScopedTimer("forces", active=False): | |
| # compute density of points | |
| wp.launch( | |
| kernel=compute_density, | |
| dim=self.n, | |
| inputs=[self.grid.id, self.x, self.rho, self.density_normalization, self.smoothing_length], | |
| ) | |
| # get new acceleration | |
| wp.launch( | |
| kernel=get_acceleration, | |
| dim=self.n, | |
| inputs=[ | |
| self.grid.id, | |
| self.x, | |
| self.v, | |
| self.rho, | |
| self.a, | |
| self.isotropic_exp, | |
| self.base_density, | |
| self.gravity, | |
| self.pressure_normalization, | |
| self.viscous_normalization, | |
| self.smoothing_length, | |
| ], | |
| ) | |
| # apply bounds | |
| wp.launch( | |
| kernel=apply_bounds, | |
| dim=self.n, | |
| inputs=[self.x, self.v, self.damping_coef, self.width, self.height, self.length], | |
| ) | |
| # kick | |
| wp.launch(kernel=kick, dim=self.n, inputs=[self.v, self.a, self.dt]) | |
| # drift | |
| wp.launch(kernel=drift, dim=self.n, inputs=[self.x, self.v, self.dt]) | |
| self.sim_time += self.frame_dt | |
| def render(self, is_live=False): | |
| with wp.ScopedTimer("render", active=True): | |
| time = 0.0 if is_live else self.sim_time | |
| self.renderer.begin_frame(time) | |
| self.renderer.render_points(points=self.x.numpy(), radius=self.smoothing_length, name="points") | |
| self.renderer.end_frame() | |
| if __name__ == "__main__": | |
| stage_path = os.path.join(os.path.dirname(__file__), "outputs/example_sph.usd") | |
| example = Example(stage_path) | |
| for i in range(example.frame_count): | |
| example.render() | |
| example.update() | |
| example.renderer.save() | |