Weakly Compressible SPH 模拟代码的问题。

大家好,我借鉴这位同学的代码(Homework0: weakly-compressible SPH) 试着写了下WCSPH,尽管我在调试期间找到很多错误, 但是我的模拟当前跑出来的结果还是非常的奇怪,能不能烦请各位看一看我的这个step() 函数是不是有什么理解上的错误。代码很短(20行),我用的是最暴力的算法,没做任何优化,其中的4个for循环分别对应下面的一组公式:

1).get density Screenshot from 2020-08-02 21-52-11
2).get pressure from densityScreenshot from 2020-08-02 21-53-50
3). get change in pressureScreenshot from 2020-08-02 21-54-02
4). propagate dynamicsScreenshot from 2020-08-02 21-54-19
Screenshot from 2020-08-02 21-54-28

    @ti.kernel
    def step(self):
        #1).get density
        for p_i in self.positions:
            self.density[p_i][0] = 0
            for p_j in range(self.num_particles):
                if p_i == p_j: continue
                r = self.positions[p_i] - self.positions[p_j]
                r_ij = r.norm()
                self.density[p_i][0] += self.mass * self.cubic_kernel(r_ij, self.h)

        #2).get pressure from density
        for p_i in self.positions:
            self.pressure[p_i][0] = self.pressure_equation_of_state(p_i)

        #3). get change in pressure
        for p_i in self.positions:
            self.d_pressure[p_i] = [0, 0]
            for p_j in range(self.num_particles):
                if p_i == p_j: continue
                r = self.positions[p_i] - self.positions[p_j]
                r_ij = r.norm()
                self.d_pressure[p_i] += self.mass * (self.pressure[p_i][0] / self.density[p_i][0]**2 + self.pressure[p_j][0] / self.density[p_j][0] ** 2) * self.cubic_kernel_derivative(r_ij, self.h) * r/r_ij
            self.d_pressure[p_i] *= self.density[p_i][0]
        
        #4). propagate dynamics
        for p_i in self.positions:
            self.velocities[p_i] += self.dt * ( self.d_pressure[p_i] * (-1/self.density[p_i][0]) + self.g)
            self.positions[p_i] += self.dt * self.velocities[p_i]

在此先提前谢谢各位的帮助。

完整版的代码如下,运行就可以看到当前的模拟效果。

import numpy as np
import matplotlib.pyplot as plt
import time
from itertools import count, product
import taichi as ti

ti.init(arch=ti.cpu)

RIGHT = TOP = 0.95
LEFT = BOTTOM = 0.05
# PARTICAL_SIZE = 0.01
PARTICAL_SIZE = 0.1
DIM = 1000
#particle_start_positions = np.array(list(product(np.arange(LEFT + PARTICAL_SIZE, RIGHT, PARTICAL_SIZE), np.arange(0.5, 0.7, PARTICAL_SIZE))))
particle_start_positions = np.array([[0.2,0.3], [0.3,0.3]])
# particle_start_positions = np.array([[0.7,0.7]])

num_particles = len(particle_start_positions)

@ti.data_oriented
class WCSPH():
    def __init__(self, particle_start_positions, dim = 2, 
    top = TOP, bottom = BOTTOM, left = LEFT, right = RIGHT):
        self.dim = dim
        self.num_particles = len(particle_start_positions)
        self.particle_start_positions =  particle_start_positions

        self.positions = ti.Vector(self.dim, dt = ti.f32) 
        self.velocities = ti.Vector(self.dim, dt = ti.f32)
        self.density = ti.Vector(1, dt = ti.f32)
        self.pressure = ti.Vector(1, dt = ti.f32)
        self.d_pressure = ti.Vector(self.dim, dt = ti.f32)

        # this allows to give many vector/matrix the right shape at one go, rather than specifying shape per vector/matrix
        ti.root.dense(ti.i, self.num_particles).place(self.positions, self.velocities, self.density, self.pressure, self.d_pressure)

        self.positions.from_numpy(self.particle_start_positions)
        self.dt = 1e-3
        self.mass = 1.0
        self.h = 1
        self.g = ti.Vector([0, -9.0])
        self.left_bound = left
        self.right_bound = right 
        self.top_bound = top
        self.bottom_bound = bottom

    # @ti.kernel
    # def initialise(self):
    #     for i in range(self.num_particles):
    #         self.positions[i] = self.particle_start_positions[i]

    @ti.func
    def pressure_equation_of_state(self, p_i, rho_0=1, gamma=7.0, c_0=20.0):
        # Weakly compressible, tait function
        b = rho_0 * c_0 ** 2 / gamma
        return b * ((self.density[p_i][0] / rho_0) ** gamma - 1.0)

    @ti.kernel
    def step(self):
        for p_i in self.positions:
            self.density[p_i][0] = 0
            for p_j in range(self.num_particles):
                if p_i == p_j: continue
                r = self.positions[p_i] - self.positions[p_j]
                r_ij = r.norm()
                self.density[p_i][0] += self.mass * self.cubic_kernel(r_ij, self.h)
        
        for p_i in self.positions:
            self.pressure[p_i][0] = self.pressure_equation_of_state(p_i)
        
        for p_i in self.positions:
            self.d_pressure[p_i] = [0, 0]
            for p_j in range(self.num_particles):
                if p_i == p_j: continue
                r = self.positions[p_i] - self.positions[p_j]
                r_ij = r.norm()
                self.d_pressure[p_i] += self.mass * (self.pressure[p_i][0] / self.density[p_i][0]**2 + self.pressure[p_j][0] / self.density[p_j][0] ** 2) * self.cubic_kernel_derivative(r_ij, self.h) * r/r_ij
            self.d_pressure[p_i] *= self.density[p_i][0]
        
        for p_i in self.positions:
            self.velocities[p_i] += self.dt * ( self.d_pressure[p_i] * (-1/self.density[p_i][0]) + self.g)
            # self.velocities[p_i] += self.g
            self.positions[p_i] += self.dt * self.velocities[p_i]
        
    @ti.func
    def simualteCollisions(self, p_i, vec, d):
        # Collision factor, assume roughly 50% velocity loss after collision, i.e. m_f /(m_f + m_b)
        c_f = 0.5
        self.positions[p_i] += vec * d
        self.velocities[p_i] -= (1.0+c_f) * self.velocities[p_i].dot(vec) * vec

    @ti.kernel
    def enforce_boundary(self):
        for p_i in self.positions:
            pos = self.positions[p_i]
            if pos[0] < self.left_bound:
                self.simualteCollisions(p_i, ti.Vector([1.0, 0.0]), self.left_bound - pos[0])
            if pos[0] > self.right_bound:
                self.simualteCollisions(p_i, ti.Vector([-1.0, 0.0]), pos[0] - self.right_bound)
            if pos[1] > self.top_bound:
                self.simualteCollisions(p_i, ti.Vector([0.0, -1.0]), pos[1] - self.top_bound)
            if pos[1] < self.bottom_bound:
                self.simualteCollisions(p_i, ti.Vector([0.0, 1.0]), self.bottom_bound - pos[1])


    @ti.func
    def update_density(self, i, j, r_ij):
        self.density[i][0] += self.mass * self.cubic_kernel(r_ij, self.h) 


    @ti.func
    def cubic_kernel(self, r, h):
        # value of cubic spline smoothing kernel
        k = 10. / (7. * np.pi * h ** 2)
        q = r / h
        assert q >= 0.0
        res = ti.cast(0.0, ti.f32)
        if q <= 1.0:
            res = k * (1 - 1.5 * q ** 2 + 0.75 * q ** 3)
        elif q < 2.0:
            res = k * 0.25 * (2 - q) ** 3
        return res

    @ti.func
    def cubic_kernel_derivative(self, r, h):
        # derivative of cubcic spline smoothing kernel
        k = 10. / (7. * np.pi * h ** 2)
        q = r / h
        assert q > 0.0
        res = ti.cast(0.0, ti.f32)
        if q < 1.0:
            res =  (k / h) * (-3 * q + 2.25 * q ** 2)
        elif q < 2.0:
            res = -0.75 * (k / h) * (2 - q) ** 2
        return res
    

def main():
    gui = ti.GUI("WCSPH", (DIM, DIM))
    
    tmp = WCSPH(particle_start_positions)
    # tmp.initialise()

    while True:
        #box
        print("positions: ", tmp.positions)
        print("velocities: ", tmp.velocities)
        print("pressure: ", tmp.pressure)
        print("d_pressure: ", tmp.d_pressure)
        print("density: ", tmp.density)
        print()
        tmp.step()
        tmp.enforce_boundary()
        gui.line(begin=(LEFT, BOTTOM), end=(RIGHT, BOTTOM), color=0xFFFFFF, radius=1)
        gui.line(begin=(LEFT, TOP), end=(RIGHT, TOP), color=0xFFFFFF, radius=1)
        gui.line(begin=(LEFT, TOP), end=(LEFT, BOTTOM), color=0xFFFFFF, radius=1)
        gui.line(begin=(RIGHT, TOP), end=(RIGHT, BOTTOM), color=0xFFFFFF, radius=1)

        gui.circles(tmp.positions.to_numpy(), color = 0xFFFFFF, radius = PARTICAL_SIZE * DIM / 2)

        gui.show()



if __name__ == '__main__':
    main()