求助 taichi field赋值失败

问题描述

通过taichi实现基于PBD的固液耦合模拟,在实现过程中出现了,一个kernel内对field赋值失败的情况,


代码的561~575实现了一个基于网格的近邻搜索,为了调试打印出了邻居大于0的粒子的编号。
运行结果如下

在561~575行,fr_neighbors_num[23]应该被赋值为2,但是循环结束后,fr_neighbors_num[23]变为了0,请问这可能是由什么原因导致的呢?

源代码如下

import math
import numpy as np
import taichi as ti
from taichi.lang.matrix import MatrixType
from utils import Cube, compute_cube_sdf, compute_cube_sdf_grident

ti.init(arch=ti.cuda)

fluid_color = 0x068587
rigid_color = 0xebaca2

screen_res = (400, 400)
screen_to_world_ratio = 10.0
boundary = (screen_res[0] / screen_to_world_ratio,
            screen_res[1] / screen_to_world_ratio)
cell_size = 2.51
cell_recpr = 1.0 / cell_size

def round_up(f, s):
    return (math.floor(f * cell_recpr / s) + 1) * s

grid_size = (round_up(boundary[0], 1), round_up(boundary[1], 1))
max_neighbors_num = 100
particle_radius = 3.0
particle_radius_in_world = particle_radius / screen_to_world_ratio
num_particles_x = 60
num_particles = num_particles_x * 20

h = 1.1
neighbor_radius = h * 1.05
dim = 2
epsilon = 1e-5
g = 9.8
fps = 20
time_delta = 1 / fps

# fluid

poly6_factor = 315.0 / 64.0 / math.pi
spiky_grad_factor = -45.0 / math.pi
fluid_mass = 1.0
rho0 = 1.0
lambda_eps = 100.0
corrK = 0.001
corrN = 4
corr_deltaQ_coeff = 0.3
solver_iterations = 5

cs = 1.0
gamma = 1.0
B = rho0 * cs * cs / gamma

# rigid
rigid_mass = 1.0
damping = 0.99
coef = 1.5

vec2: MatrixType = ti.types.vector(2, ti.f32)
vec3: MatrixType = ti.types.vector(3, ti.f32)
mat2x2: MatrixType = ti.types.matrix(2, 2, ti.f32)
mat3x3: MatrixType = ti.types.matrix(3, 3, ti.f32)


@ti.data_oriented
class ParticleSystem:
    
    def __init__(self, max_particls_num: int) -> None:
        n = max_particls_num

        self.max_particls_num = n
        self.particles_num = ti.field(ti.i32, shape=())
        self.x = vec2.field(shape=(n,))
        self.v = vec2.field(shape=(n,))
        self.f = vec2.field(shape=(n,))  # F / m
        self.phase = ti.field(ti.i32, shape=(n,))
    
    def dump(self):
        n = self.particles_num[None]
        x = self.x.to_numpy()
        return x[:n, :]

    def reset(self):
        self.particles_num[None] = 0

    @ti.func
    def poly6_value(self, s, h):
        result = 0.0
        if 0 < s and s < h:
            x = (h * h - s * s) / (h * h * h)
            result = poly6_factor * x * x * x
        return result

    @ti.func
    def spiky_gradient(self, r, h):
        result = ti.Vector([0.0, 0.0])
        r_len = r.norm()
        if 0 < r_len and r_len < h:
            x = (h - r_len) / (h * h * h)
            g_factor = spiky_grad_factor * x * x
            result = r * g_factor / r_len
        return result


class RigidSolver(ParticleSystem):

    def __init__(self, max_particls_num: int, max_rigid_num: int = 10) -> None:
        super().__init__(max_particls_num)
        n = max_particls_num
        self.max_rigid_num = max_rigid_num

        # debug
        self.debug = ti.field(ti.i32, shape=())

        self.n_x = vec2.field(shape=(n,))
        self.o_x = vec2.field(shape=(n,))  # origin x
        self.d_x = vec2.field(shape=(n,))
        self.count_x = ti.field(ti.i32, shape=(n,))
        self.sdf = ti.field(ti.f32, shape=(n,))
        self.sdf_gradient = vec2.field(shape=(n,))

        # grid for search neighbors
        self.ns_shape = grid_size
        self.ns_grid = ti.field(ti.i32, shape=self.ns_shape + (max_neighbors_num,))
        self.ns_grid_num = ti.field(ti.i32, shape=self.ns_shape)
        self.neighbors_num = ti.field(ti.i32, shape=(n,))
        self.neighbors = ti.field(ti.i32, shape=(n, max_neighbors_num))

        # number of rigid-body
        self.rigid_num = ti.field(ti.i32, shape=())
        self.rigid_particls_num = ti.field(ti.i32, shape=(max_rigid_num,))
        self.rigid_particls_offset = ti.field(ti.i32, shape=(max_rigid_num,))

        self.rigidR = mat2x2.field(shape=(max_rigid_num,))

    def reset(self):
        self.debug[None] = 0
        self.particles_num[None] = 0
        self.rigid_num[None] = 0

    @ti.kernel
    def reset_f(self):
        m = self.particles_num[None]
        for i in range(m):
            self.f[i] = ti.Vector([0., -g])

    @ti.kernel
    def add_particle(self, 
        vec_p: ti.types.ndarray(), 
        sdf: ti.types.ndarray(), 
        sdf_grident: ti.types.ndarray(),
        n: int, 
    ):
        rigid_id = self.rigid_num[None]
        m = self.particles_num[None]
        for i in range(n):
            self.o_x[m + i] = ti.Vector([vec_p[i, 0], vec_p[i, 1]])
            self.x[m + i] = ti.Vector([vec_p[i, 0], vec_p[i, 1]])
            self.n_x[m + i] = ti.Vector([vec_p[i, 0], vec_p[i, 1]])
            self.v[m + i] = ti.Vector([0., 0.])
            self.f[m + i] = ti.Vector([0., -g])
            self.sdf[m + i] = sdf[i]
            self.sdf_gradient[m + i] = ti.Vector([sdf_grident[i, 0], sdf_grident[i, 1]])
            self.phase[m + i] = rigid_id
        self.rigid_particls_offset[rigid_id] = m
        self.rigid_particls_num[rigid_id] = n
        self.particles_num[None] += n
        self.rigid_num[None] += 1

    def add_cube(self, center: np.ndarray, W: float, H: float):
        center = center / screen_to_world_ratio
        W /= screen_to_world_ratio
        H /= screen_to_world_ratio
        r = particle_radius_in_world
        nW = int((W - 2 * r) / (2 * r) + 0.5)
        nH = int((H - 2 * r) / (2 * r) + 0.5)
        pos_x = np.linspace(center[0] - W / 2 + r, center[0] + W / 2 - r, nW)
        pos_y = np.linspace(center[1] - H / 2 + r, center[1] + H / 2 - r, nH)
        pos_x, pos_y = np.meshgrid(pos_x, pos_y)

        pos = np.stack([pos_x.ravel(), pos_y.ravel()], axis=-1)
        n = pos.shape[0]
        sdf = np.zeros((n,))
        sdf_grident = np.zeros((n, 2))

        cube = Cube(center, W, H)
        for i in range(n):
            sdf[i] = compute_cube_sdf(cube, pos[i])
            sdf_grident[i] = compute_cube_sdf_grident(cube, pos[i])

        if self.particles_num[None] + n <= self.max_particls_num and self.rigid_num[None] < self.max_rigid_num:
            self.add_particle(pos, sdf, sdf_grident, n)
    
    @ti.func
    def to_grid_index(self, x):  # Vector is Matrix with m = 1
        I = (x * cell_recpr).cast(ti.i32)
        return I

    @ti.func
    def grid_index_is_valid(self, I):
        return (0 <= I[0] < self.ns_grid.shape[0]) and (0 <= I[1] < self.ns_grid.shape[1])

    @ti.kernel
    def find_neighbors(self):
        for I in ti.grouped(self.ns_grid_num):
            self.ns_grid_num[I] = 0
        for p_i in range(self.particles_num[None]):
            I = self.to_grid_index(self.n_x[p_i])
            j = ti.atomic_add(self.ns_grid_num[I], 1)
            self.ns_grid[I, j] = p_i
        for p_i in range(self.particles_num[None]):
            I = self.to_grid_index(self.n_x[p_i])
            j = 0
            for dI in ti.grouped(ti.ndrange((-1, 2), (-1, 2))):
                nI = I + dI
                if self.grid_index_is_valid(nI):
                    for J in range(self.ns_grid_num[nI]):
                        p_j = self.ns_grid[nI, J]
                        dist = (self.n_x[p_i] - self.n_x[p_j]).norm()
                        if p_i != p_j and dist < neighbor_radius and j < max_neighbors_num:
                            self.neighbors[p_i, j] = p_j
                            j += 1
                if j >= max_neighbors_num:
                    break
            self.neighbors_num[p_i] = j

    @ti.kernel
    def predict_position(self, dt: ti.f32):
        for i in range(self.particles_num[None]):
            vel = self.v[i] + dt * self.f[i]
            pos = self.x[i] + dt * vel
            self.n_x[i] = pos
    
    @ti.kernel
    def reset_dx(self):
        for p_i in range(self.particles_num[None]):
            self.d_x[p_i] = ti.Vector([0., 0.])
            self.count_x[p_i] = 0

    @ti.kernel
    def shape_matching(self, r_id: ti.i32):
        offset = self.rigid_particls_offset[r_id]
        nx_cm = ti.Vector([0., 0.])  # mass center
        ox_cm = ti.Vector([0., 0.])
        ti.loop_config(serialize=True)
        for i in range(self.rigid_particls_num[r_id]):
            n = ti.cast(i, ti.f32)
            nx_cm = n / (n + 1) * nx_cm + 1.0 / (n + 1) * self.n_x[offset + i]
            ox_cm = n / (n + 1) * ox_cm + 1.0 / (n + 1) * self.o_x[offset + i]
        Apq = ti.Matrix([[0., 0.], [0., 0.]])
        for i in range(self.rigid_particls_num[r_id]):
            p = self.n_x[offset + i] - nx_cm
            q = self.o_x[offset + i] - ox_cm
            Apq += rigid_mass * (p @ q.transpose())
        R, _ = ti.polar_decompose(Apq)
        self.rigidR[r_id] = R
        for i in range(self.rigid_particls_num[r_id]):
            self.d_x[offset + i] += (R @ (self.o_x[offset + i] - ox_cm)) + nx_cm - self.n_x[offset + i]
            self.count_x[offset + i] += 1
    
    @ti.kernel
    def collision_solving(self):
        for p_i in range(self.particles_num[None]):
            I = self.to_grid_index(self.n_x[p_i])
            for dI in ti.grouped(ti.ndrange((-1, 2), (-1, 2))):
                nI = I + dI
                if self.grid_index_is_valid(nI):
                    for p_j_index in range(self.ns_grid_num[nI]):
                        p_j = self.ns_grid[nI, p_j_index]
                        dist = (self.n_x[p_i] - self.n_x[p_j]).norm()
                        r = particle_radius_in_world
                        if self.phase[p_i] != self.phase[p_j] and dist < 2 * r:
                            sdf_grad_i = self.sdf_gradient[p_i]
                            sdf_grad_j = self.sdf_gradient[p_j]
                            sdf_i = ti.abs(self.sdf[p_i])
                            sdf_j = ti.abs(self.sdf[p_j])
                            d = min(sdf_i, sdf_j)
                            self.count_x[p_i] += 1
                            if sdf_i < sdf_j:
                                r_id = self.phase[p_i]
                                n_ij = self.rigidR[r_id] @ sdf_grad_i
                                self.d_x[p_i] += -0.5 * d * n_ij
                            else:
                                r_id = self.phase[p_j]
                                n_ji = self.rigidR[r_id] @ sdf_grad_j
                                self.d_x[p_i] += 0.5 * d * n_ji

    @ti.kernel
    def apply_dx(self):
        for p_i in range(self.particles_num[None]):
            if self.count_x[p_i] > 0:
                self.n_x[p_i] += self.d_x[p_i] / ti.cast(self.count_x[p_i], ti.f32) * ti.min(coef, 1.0 * self.count_x[p_i])

    @ti.kernel
    def apply_dx_prev(self):
        for p_i in range(self.particles_num[None]):
            if self.count_x[p_i] > 0:
                self.x[p_i] += self.d_x[p_i] / ti.cast(self.count_x[p_i], ti.f32) * ti.min(coef, 1.0 * self.count_x[p_i])

    @ti.kernel
    def boundary_solving(self):
        bmin = ti.Vector([particle_radius_in_world, particle_radius_in_world])
        bmax = ti.Vector([boundary[0], boundary[1]]) - particle_radius_in_world
        for j in range(self.particles_num[None]):
            p = self.n_x[j]
            for i in ti.static(range(dim)):
                # Use randomness to prevent particles from sticking into each other after clamping
                if p[i] <= bmin[i]:
                    p[i] = bmin[i]  # bmin[i] + epsilon * ti.random()
                elif bmax[i] <= p[i]:
                    p[i] = bmax[i]  # bmax[i] - epsilon * ti.random()
            if (p - self.n_x[j]).norm() > 0:
                self.d_x[j] += p - self.n_x[j]
                self.count_x[j] += 1

    def substep(self):
        self.reset_dx()
        for r_id in range(self.rigid_num[None]):
            self.shape_matching(r_id)
        self.apply_dx()
    
        self.reset_dx()
        self.collision_solving()
        self.apply_dx()

        self.reset_dx()
        self.boundary_solving()
        self.apply_dx()

    @ti.kernel
    def update(self, dt: ti.f32):
        for p_i in range(self.particles_num[None]):
            self.v[p_i] = (self.n_x[p_i] - self.x[p_i]) / dt * damping
            if (self.v[p_i]).norm() <= 0.05 * dt:
                self.v[p_i] = ti.Vector([0., 0.])
        for i in range(self.particles_num[None]):
            self.x[i] = self.n_x[i]

    def step(self):
        dt = time_delta
        self.predict_position(dt)
        self.find_neighbors()
        for _ in range(solver_iterations):
            self.substep()
        self.update(dt)


@ti.data_oriented
class FluidSolver(ParticleSystem):

    def __init__(self, max_particls_num: int) -> None:
        super().__init__(max_particls_num)
        n = max_particls_num
        self.n_x = vec2.field(shape=(n,))

        # params for pbf
        self.d_x = vec2.field(shape=(n,))
        self.lam = ti.field(ti.f32, shape=(n,))

        # grid for search neighbors
        self.ns_shape = grid_size
        self.ns_grid = ti.field(ti.i32, shape=self.ns_shape + (max_neighbors_num,))
        self.ns_grid_num = ti.field(ti.i32, shape=self.ns_shape)
        self.neighbors_num = ti.field(ti.i32, shape=(n,))
        self.neighbors = ti.field(ti.i32, shape=(n, max_neighbors_num))

    @ti.kernel
    def reset_f(self):
        m = self.particles_num[None]
        for i in range(m):
            self.f[i] = ti.Vector([0., -g])

    @ti.kernel
    def add_particle(self, 
        vec_p: ti.types.ndarray(), 
        n: int, 
    ):
        m = self.particles_num[None]
        for i in range(n):
            self.x[m + i] = ti.Vector([vec_p[i, 0], vec_p[i, 1]])
            self.n_x[m + i] = ti.Vector([vec_p[i, 0], vec_p[i, 1]])
            self.v[m + i] = ti.Vector([0., 0.])
            self.f[m + i] = ti.Vector([0., -g])
            self.phase[m + i] = 0
        self.particles_num[None] += n

    def add_cube(self, center: np.ndarray, W: float, H: float):
        center = center / screen_to_world_ratio
        W /= screen_to_world_ratio
        H /= screen_to_world_ratio
        r = particle_radius_in_world
        nW = int((W - 2 * r) / (2 * r) + 0.5)
        nH = int((H - 2 * r) / (2 * r) + 0.5)
        pos_x = np.linspace(center[0] - W / 2 + r, center[0] + W / 2 - r, nW)
        pos_y = np.linspace(center[1] - H / 2 + r, center[1] + H / 2 - r, nH)
        pos_x, pos_y = np.meshgrid(pos_x, pos_y)
        pos = np.stack([pos_x.ravel(), pos_y.ravel()], axis=-1)
        n = pos.shape[0]
        if self.particles_num[None] + n <= self.max_particls_num:
            self.add_particle(pos, n)

    @ti.func
    def boundary_solving(self, p):
        bmin = particle_radius_in_world
        bmax = ti.Vector([boundary[0], boundary[1]]) - particle_radius_in_world
        for i in ti.static(range(dim)):
            # Use randomness to prevent particles from sticking into each other after clamping
            if p[i] <= bmin:
                p[i] = bmin + epsilon * ti.random()
            elif bmax[i] <= p[i]:
                p[i] = bmax[i] - epsilon * ti.random()
        return p

    @ti.kernel
    def predict_position(self, dt: ti.f32):
        for i in range(self.particles_num[None]):
            vel = self.v[i] + dt * self.f[i]
            pos = self.x[i] + dt * vel
            self.n_x[i] = self.boundary_solving(pos)

    @ti.func
    def to_grid_index(self, x):  # Vector is Matrix with m = 1
        I = (x * cell_recpr).cast(ti.i32)
        return I
    
    @ti.func
    def grid_index_is_valid(self, I):
        return (0 <= I[0] < self.ns_grid.shape[0]) and (0 <= I[1] < self.ns_grid.shape[1])

    @ti.kernel
    def find_neighbors(self):
        for I in ti.grouped(self.ns_grid_num):
            self.ns_grid_num[I] = 0
        for p_i in range(self.particles_num[None]):
            I = self.to_grid_index(self.n_x[p_i])
            j = ti.atomic_add(self.ns_grid_num[I], 1)
            self.ns_grid[I, j] = p_i
        for p_i in range(self.particles_num[None]):
            I = self.to_grid_index(self.n_x[p_i])
            j = 0
            for dI in ti.grouped(ti.ndrange((-1, 2), (-1, 2))):
                nI = I + dI
                if self.grid_index_is_valid(nI):
                    for J in range(self.ns_grid_num[nI]):
                        p_j = self.ns_grid[nI, J]
                        dist = (self.n_x[p_i] - self.n_x[p_j]).norm()
                        if p_i != p_j and dist < neighbor_radius and j < max_neighbors_num:
                            self.neighbors[p_i, j] = p_j
                            j += 1
                if j >= max_neighbors_num:
                    break
            self.neighbors_num[p_i] = j
    
    @ti.func
    def compute_scorr(self, pos_ji):
        # Eq (13)
        x = self.poly6_value(pos_ji.norm(), h) / self.poly6_value(corr_deltaQ_coeff * h, h)
        # pow(x, 4)
        x = x * x
        x = x * x
        return (-corrK) * x

    @ti.kernel
    def substep(self):
        # compute lambdas
        # Eq (8) ~ (11)
        for p_i in range(self.particles_num[None]):
            pos_i = self.n_x[p_i]
            grad_i = ti.Vector([0.0, 0.0])
            sum_gradient_sqr = 0.0
            density_constraint = 0.0
            for j in range(self.neighbors_num[p_i]):
                p_j = self.neighbors[p_i, j]
                pos_ji = pos_i - self.n_x[p_j]
                grad_j = self.spiky_gradient(pos_ji, h)
                grad_i += grad_j
                sum_gradient_sqr += grad_j.norm() ** 2
                # Eq(2)
                density_constraint += self.poly6_value(pos_ji.norm(), h)
            # Eq(1)
            density_constraint = (fluid_mass * density_constraint / rho0) - 1.0
            sum_gradient_sqr += grad_i.norm() ** 2
            self.lam[p_i] = (-density_constraint) / (sum_gradient_sqr + lambda_eps)
        # compute position deltas
        # Eq(12), (14)
        for p_i in range(self.particles_num[None]):
            pos_i = self.n_x[p_i]
            lambda_i = self.lam[p_i]
            pos_delta_i = ti.Vector([0.0, 0.0])
            for j in range(self.neighbors_num[p_i]):
                p_j = self.neighbors[p_i, j]
                lambda_j = self.lam[p_j]
                pos_ji = pos_i - self.n_x[p_j]
                scorr_ij = self.compute_scorr(pos_ji)
                pos_delta_i += (lambda_i + lambda_j + scorr_ij) * \
                    self.spiky_gradient(pos_ji, h)
            pos_delta_i /= rho0
            self.d_x[p_i] = pos_delta_i
        # apply position deltas
        for i in range(self.particles_num[None]):
            self.n_x[i] += self.d_x[i]

    @ti.kernel
    def update(self, dt: ti.f32):
        for i in range(self.particles_num[None]):
            self.n_x[i] = self.boundary_solving(self.n_x[i])
        for p_i in range(self.particles_num[None]):
            self.v[p_i] = (self.n_x[p_i] - self.x[p_i]) / dt
        for i in range(self.particles_num[None]):
            self.x[i] = self.n_x[i]
    
    def step(self):
        dt = time_delta
        self.predict_position(dt)
        self.find_neighbors()
        for _ in range(solver_iterations):
            self.substep()
        self.update(dt)


@ti.data_oriented
class CouplingSolver:

    def __init__(self, max_particls_num: int, max_rigid_num: int) -> None:
        self.fluid_solver = FluidSolver(max_particls_num)
        self.rigid_solver = RigidSolver(max_particls_num, max_rigid_num)

        n = max_neighbors_num
        self.ns_shape = grid_size
        self.fr_ns_grid = ti.field(ti.i32, shape=self.ns_shape + (max_neighbors_num,))
        self.fr_ns_grid_num = ti.field(ti.i32, shape=self.ns_shape)
        self.fr_neighbors_num = ti.field(ti.i32, shape=(n,))
        self.fr_neighbors = ti.field(ti.i32, shape=(n, max_neighbors_num))

        self.fluid_p = ti.field(ti.f32, shape=(n,))
        self.fluid_rho = ti.field(ti.f32, shape=(n,))

    def reset(self):
        self.fluid_solver.reset()
        self.rigid_solver.reset()
    
    def add_rigid_cube(self, center: np.ndarray, W: float, H: float):
        self.rigid_solver.add_cube(center, W, H)
    
    def add_fluid_cube(self, center: np.ndarray, W: float, H: float):
        self.fluid_solver.add_cube(center, W, H)
    
    @ti.kernel
    def compute_ext_force(self):
        print("BEGIN-----------------------------------------------------------")
        n = self.fluid_solver.particles_num[None]
        m = self.rigid_solver.particles_num[None]
        ti.loop_config(serialize=True)
        for I in ti.grouped(self.fr_ns_grid_num):
            self.fr_ns_grid_num[I] = 0
        ti.loop_config(serialize=True)
        for b_i in range(m):
            I = self.fluid_solver.to_grid_index(self.rigid_solver.n_x[b_i])
            j = ti.atomic_add(self.fr_ns_grid_num[I], 1)
            self.fr_ns_grid[I, j] = b_i
        ti.loop_config(serialize=True)
        for p_i in range(n):
            I = self.fluid_solver.to_grid_index(self.fluid_solver.n_x[p_i])
            j = 0
            for dI in ti.grouped(ti.ndrange((-1, 2), (-1, 2))):
                nI = I + dI
                if self.fluid_solver.grid_index_is_valid(nI):
                    for J in range(self.fr_ns_grid_num[nI]):
                        b_j = self.fr_ns_grid[nI, J]
                        dist = (self.fluid_solver.n_x[p_i] - self.rigid_solver.n_x[b_j]).norm()
                        if dist < neighbor_radius and j < max_neighbors_num:
                            self.fr_neighbors[p_i, j] = b_j
                            j += 1
                if j >= max_neighbors_num:
                    break
            self.fr_neighbors_num[p_i] = j
            if j > 0:
                print("p_i:", p_i, "num:", self.fr_neighbors_num[p_i])
        print("-----------------------------------------------------------")
        print("p_i:", 23, "num:", self.fr_neighbors_num[23])
        print("-----------------------------------------------------------")

        for p_i in range(n):
            pos_i = self.fluid_solver.n_x[p_i]
            rho_i = 0.0
            for j in range(self.fluid_solver.neighbors_num[p_i]):
                p_j = self.fluid_solver.neighbors[p_i, j]
                pos_j = self.fluid_solver.n_x[p_j]
                pos_ij = pos_i - pos_j
                rho_i += fluid_mass * self.fluid_solver.poly6_value(pos_ij.norm(), h)
            for j in range(self.fr_neighbors_num[p_i]):
                b_j = self.fr_neighbors[p_i, j]
                pos_j = self.rigid_solver.n_x[b_j]
                pos_ij = pos_i - pos_j
                rho_i += rigid_mass * self.rigid_solver.poly6_value(pos_ij.norm(), h)
                # print("p_i:", p_i, "b_j:", b_j, " norm:", pos_ij.norm(), "pos_i:", pos_i, "pos_j:", pos_j)
            self.fluid_rho[p_i] = rho_i

        for p_i in range(n):
            rho_i = self.fluid_rho[p_i]
            self.fluid_p[p_i] = (ti.pow(rho_i / rho0, gamma) - 1)
        
        for p_i in range(n):
            pos_i = self.fluid_solver.n_x[p_i]
            pre_i = self.fluid_p[p_i]  # pressure
            rho_i = self.fluid_rho[p_i]
            for j in range(self.fr_neighbors_num[p_i]):
                b_j = self.fr_neighbors[p_i, j]
                pos_j = self.rigid_solver.n_x[b_j]
                pos_ij = pos_i - pos_j
                Fp_ij = fluid_mass * rigid_mass * (pre_i / ti.pow(rho_i, 2)) * \
                    self.fluid_solver.spiky_gradient(pos_ij, h)
                self.fluid_solver.f[p_i] += -Fp_ij / fluid_mass
                self.rigid_solver.f[b_j] += Fp_ij / rigid_mass

        # for p_i in range(n):
        #     if self.neighbors_num[p_i] > 0:
        #         print("p_i:", p_i, "neighbors_num:", self.neighbors_num[p_i], "p:", self.fluid_p[p_i], "rho:", self.fluid_rho[p_i])


    def step(self):
        self.fluid_solver.reset_f()
        self.rigid_solver.reset_f()

        self.fluid_solver.find_neighbors()
        self.rigid_solver.find_neighbors()

        self.compute_ext_force()

        dt = time_delta
        self.fluid_solver.predict_position(dt)
        self.fluid_solver.find_neighbors()

        self.rigid_solver.predict_position(dt)
        self.rigid_solver.find_neighbors()

        for _ in range(solver_iterations):
            self.fluid_solver.substep()
            self.rigid_solver.substep()

        self.fluid_solver.update(dt)
        self.rigid_solver.update(dt)


    def dump(self):
        return self.fluid_solver.dump(), self.rigid_solver.dump()
    

def main():
    pbd_solver = CouplingSolver(10000, 10)
    pbd_solver.add_fluid_cube(np.array([100, 200]), 80, 300)
    pbd_solver.add_rigid_cube(np.array([300, 200]), 30, 30)

    gui = ti.GUI("PBD", screen_res)
    while gui.running:
        for e in gui.get_events(ti.GUI.PRESS):
            if e.key == 'r':
                pbd_solver.reset()
            if e.key == 'g':
                pbd_solver.step()
        x0, x1 = pbd_solver.dump()
        for j in range(dim):
            x0[:, j] *= screen_to_world_ratio / screen_res[j]
            x1[:, j] *= screen_to_world_ratio / screen_res[j]

        gui.circles(x0, radius=3.0, color=fluid_color)
        gui.circles(x1, radius=3.0, color=rigid_color)
        gui.show()


if __name__ == '__main__':
    main()

我尝试去复现这个问题,但这个代码似乎缺了一些依赖?from utils import Cube, compute_cube_sdf, compute_cube_sdf_grident

十分抱歉 整理代码的时候把这个文件忘掉了

在群里问了一下,我感觉问题可能来自是我在data_oriented class里面创建了其它data_oriented class的实例,似乎不支持这种写法(只是没有报错)?

utils.py的代码如下

from collections import namedtuple
import math
import numpy as np

Cube = namedtuple('Cube', ['center', 'width', 'height'])

def compute_cube_sdf(cube: Cube, x: np.ndarray) -> float:
    bl = np.array([
        cube.center[0] - cube.width / 2, 
        cube.center[1] - cube.height / 2
    ])
    tr = np.array([
        cube.center[0] + cube.width / 2, 
        cube.center[1] + cube.height / 2
    ])
    if bl[0] <= x[0] <= tr[0] and bl[1] <= x[1] <= tr[1]:
        return -1 * min(
            x[0] - bl[0],
            tr[0] - x[0],
            x[1] - bl[1],
            tr[1] - x[1]
        )
    elif bl[0] <= x[0] <= tr[0]:
        return min(abs(x[1] - bl[1]), abs(tr[1] - x[1]))
    elif bl[1] <= x[1] <= tr[1]:
        return min(abs(x[0] - bl[0]), abs(tr[0] - x[0]))
    else:
        dx = min(abs(x[1] - bl[1]), abs(tr[1] - x[1]))
        dy = min(abs(x[0] - bl[0]), abs(tr[0] - x[0]))
        return math.sqrt(dx * dx + dy * dy)


def compute_cube_sdf_grident(cube: Cube, x: np.ndarray) -> np.ndarray:
    d = 0.05
    grad_x = (compute_cube_sdf(cube, x + np.array([d, 0])) - compute_cube_sdf(cube, x - np.array([d, 0]))) / (2 * d)
    grad_y = (compute_cube_sdf(cube, x + np.array([0, d])) - compute_cube_sdf(cube, x - np.array([0, d]))) / (2 * d)
    return np.array([grad_x, grad_y])