def P2G(): # Particle to grid (P2G) for p in x: base = (x[p] * inv_dx - 0.5).cast(int) fx = x[p] * inv_dx - base.cast(float) # Bspline w = [0.5 * (1.5 - fx) ** 2, 0.75 - (fx - 1) ** 2, 0.5 * (fx - 0.5) ** 2] F[p] = (ti.Matrix.identity(float, 2) + dt * C[p]) @ F[p] # deformation gradient update h = max(0.1, min(5, ti.exp(10 * (1.0 - Jp[p])))) # Hardening coefficient mu, la = mu_0 * h, lambda_0 * h mu = 0.0 U, sig, V = ti.svd(F[p]) J = 1.0 for d in ti.static(range(2)): new_sig = sig[d, d] if material[p] == 2: # snow new_sig = min(max(sig[d, d], 1 - 2.5e-2), 1 + 4.5e-3) # plasticity Jp[p] *= sig[d, d] / new_sig sig[d, d] = new_sig J *= new_sig F[p] = ti.Matrix.identity(float, 2) * ti.sqrt(J) # Reset deformation gradient stress = 2 * mu * (F[p] - U @ V.transpose()) @ F[p].transpose() + ti.Matrix.identity(float, 2) * la * J * (J - 1) stress = (-dt * p_vol * 4 * inv_dx * inv_dx) * stress affine = stress + p_mass * C[p] for i, j in ti.static(ti.ndrange(3, 3)): # Loop over 3x3 grid offset = ti.Vector([i, j]) dpos = (offset.cast(float) - fx) * dx weight = w[i][0] * w[j][1] grid_v[base + offset] += weight * (p_mass * v[p] + affine @ dpos) grid_m[base + offset] += weight * p_mass
def affine(self, i): self.system.F[i] = (ti.Matrix.identity(ti.f32, DIM) + self.system.dt * self.system.C[i]) @ self.system.F[i] h = 0.3 if self.system.type[i] == 1 else ti.exp( 10 * (1.0 - self.system.Jp[i])) la = self.lambda_0 * h U, sig, V = ti.svd(self.system.F[i]) J = 1.0 for d in ti.static(range(DIM)): new_sig = min(max(sig[d, d], 1 - 2.5e-2), 1 + 4.5e-3) if self.system.type[i] == 2 else sig[d, d] self.system.Jp[i] *= sig[d, d] / new_sig sig[d, d] = new_sig J *= new_sig if self.system.type[ i] == 0: # Reset deformation gradient to avoid numerical instability self.system.F[i] = ti.Matrix.identity(ti.f32, DIM) self.system.F[i][0, 0] = J elif self.system.type[i] == 2: self.system.F[i] = U @ sig @ V.transpose( ) # Reconstruct elastic deformation gradient after plasticity stress = ti.Matrix.identity(ti.f32, DIM) * la * J * (J - 1) if self.system.type[i] != 0: stress += 2 * self.mu_0 * h * ( self.system.F[i] - U @ V.transpose()) @ self.system.F[i].transpose() stress = (-self.system.dt * self.vol * 4 * self.inv_dx * self.inv_dx) * stress return stress + self.mass * self.system.C[i]
def substep(): for i, j in ti.ndrange(n_grid, n_grid): grid_v[i, j] = [0, 0] grid_m[i, j] = 0 for p in range(n_particles): # Particle state update and scatter to grid (P2G) base = (x[p] * inv_dx - 0.5).cast(int) fx = x[p] * inv_dx - base.cast(float) # Quadratic kernels [http://mpm.graphics Eqn. 123, with x=fx, fx-1,fx-2] w = [0.5 * ti.sqr(1.5 - fx), 0.75 - ti.sqr(fx - 1), 0.5 * ti.sqr(fx - 0.5)] F[p] = (ti.Matrix.identity(ti.f32, 2) + dt * C[p]) @ F[p] # deformation gradient update h = ti.exp(10 * (1.0 - Jp[p])) # Hardening coefficient: snow gets harder when compressed if material[p] == 1: # jelly, make it softer h = 0.3 mu, la = mu_0 * h, lambda_0 * h if material[p] == 0: # liquid mu = 0.0 U, sig, V = ti.svd(F[p]) J = 1.0 for d in ti.static(range(2)): new_sig = sig[d, d] if material[p] == 2: # Snow new_sig = min(max(sig[d, d], 1 - 2.5e-2), 1 + 4.5e-3) # Plasticity Jp[p] *= sig[d, d] / new_sig sig[d, d] = new_sig J *= new_sig if material[p] == 0: # Reset deformation gradient to avoid numerical instability F[p] = ti.Matrix.identity(ti.f32, 2) * ti.sqrt(J) elif material[p] == 2: F[p] = U @ sig @ V.T() # Reconstruct elastic deformation gradient after plasticity stress = 2 * mu * (F[p] - U @ V.T()) @ F[p].T() + ti.Matrix.identity(ti.f32, 2) * la * J * (J - 1) stress = (-dt * p_vol * 4 * inv_dx * inv_dx) * stress affine = stress + p_mass * C[p] for i, j in ti.static(ti.ndrange(3, 3)): # Loop over 3x3 grid node neighborhood offset = ti.Vector([i, j]) dpos = (offset.cast(float) - fx) * dx weight = w[i][0] * w[j][1] grid_v[base + offset] += weight * (p_mass * v[p] + affine @ dpos) grid_m[base + offset] += weight * p_mass for i, j in ti.ndrange(n_grid, n_grid): if grid_m[i, j] > 0: # No need for epsilon here grid_v[i, j] = (1 / grid_m[i, j]) * grid_v[i, j] # Momentum to velocity grid_v[i, j][1] -= dt * 50 # gravity if i < 3 and grid_v[i, j][0] < 0: grid_v[i, j][0] = 0 # Boundary conditions if i > n_grid - 3 and grid_v[i, j][0] > 0: grid_v[i, j][0] = 0 if j < 3 and grid_v[i, j][1] < 0: grid_v[i, j][1] = 0 if j > n_grid - 3 and grid_v[i, j][1] > 0: grid_v[i, j][1] = 0 for p in range(n_particles): # grid to particle (G2P) base = (x[p] * inv_dx - 0.5).cast(int) fx = x[p] * inv_dx - base.cast(float) w = [0.5 * ti.sqr(1.5 - fx), 0.75 - ti.sqr(fx - 1.0), 0.5 * ti.sqr(fx - 0.5)] new_v = ti.Vector.zero(ti.f32, 2) new_C = ti.Matrix.zero(ti.f32, 2, 2) for i, j in ti.static(ti.ndrange(3, 3)): # loop over 3x3 grid node neighborhood dpos = ti.Vector([i, j]).cast(float) - fx g_v = grid_v[base + ti.Vector([i, j])] weight = w[i][0] * w[j][1] new_v += weight * g_v new_C += 4 * inv_dx * weight * ti.outer_product(g_v, dpos) v[p], C[p] = new_v, new_C x[p] += dt * v[p] # advection
def makePD(self, M: ti.template()): U, sigma, V = ti.svd(M) if sigma[0, 0] >= 0: return D = ti.Matrix(self.dim, self.real, self.real) for i in range(self.dim): if sigma[i, i] < 0: D[i, i] = 0 else: D[i, i] = sigma[i, i] M = sigma @ D @ sigma.transpose()
def particle_to_grid(): for k in particle_position: p = particle_position[k] grid = p * inv_dx base = int(grid - 0.5) fx = grid - base w = [0.5 * (1.5 - fx)**2, 0.75 - (fx - 1)**2, 0.5 * (fx - 0.5)**2] # B-spline # stress = -dt * 4 * E * p_vol * (particle_J[k] - 1) * (inv_dx**2) # affine = ti.Matrix([[stress, 0], [0, stress]]) + p_mass * particle_C[k] particle_F[k] = (ti.Matrix.identity(float, 2) + dt * particle_C[k]) @ particle_F[k] # hardening coefficient h = ti.exp(10 * (1.0 - particle_J[k])) if particle_material[k] == 1: # jelly h = 0.3 mu, la = mu_0 * h, lambda_0 * h if particle_material[k] == 0: # fluid mu = 0.0 U, sig, V = ti.svd(particle_F[k]) J = 1.0 for d in ti.static(range(2)): new_sig = sig[d, d] if particle_material[k] == 2: # snow new_sig = min(max(sig[d, d], 1 - 2.5e-2), 1 + 4.5e-3) # plasticity particle_J[k] *= sig[d, d] / new_sig sig[d, d] = new_sig J *= new_sig if particle_material[ k] == 0: # Fluid: Reset deformation gradient to avoid numerical instability particle_F[k] = ti.Matrix.identity(float, 2) * ti.sqrt(J) elif particle_material[k] == 2: particle_F[k] = U @ sig @ V.transpose( ) # Snow: Reconstruct elastic deformation gradient after plasticity stress = 2 * mu * (particle_F[k] - U @ V.transpose()) @ particle_F[ k].transpose() + ti.Matrix.identity(float, 2) * la * J * (J - 1) stress = (-dt * p_vol * 4 * inv_dx * inv_dx) * stress affine = stress + p_mass * particle_C[k] for i in ti.static(range(3)): for j in ti.static(range(3)): offset = ti.Vector([i, j]) weight = w[i][0] * w[j][1] dpos = (offset - fx) * dx grid_velocity[base + offset] += weight * ( p_mass * particle_velocity[k] + affine @ dpos) grid_weight[base + offset] += weight * p_mass
def p2g(f: ti.i32): for p in range(0, n_particles): new_Jp = Jp[f, p] base = ti.cast(x[f, p] * inv_dx - 0.5, ti.i32) fx = x[f, p] * inv_dx - ti.cast(base, ti.f32) w = [ 0.5 * ti.sqr(1.5 - fx), 0.75 - ti.sqr(fx - 1.0), 0.5 * ti.sqr(fx - 0.5) ] # quadratic kernels new_F = (ti.Matrix.diag(dim=dim, val=1) + dt * C[f, p]) @ F[f, p] # deformation gradient update h = max(0.1, min(5, ti.exp(10 * (1.0 - new_Jp)))) if particle_type[p] == 1: # jelly, make it softer h = 0.3 mu, la = mu_0 * h, lambda_0 * h if particle_type[p] == 0: # liquid mu = 0.0 U, sig, V = ti.svd(new_F) J = 1.0 for d in ti.static(range(2)): new_sig = sig[d, d] if particle_type[p] == 2: # Snow new_sig = min(max(sig[d, d], 1 - 2.5e-2), 1 + 4.5e-3) # Plasticity new_Jp *= sig[d, d] / new_sig sig[d, d] = new_sig J *= new_sig if particle_type[ p] == 0: # Reset deformation gradient to avoid numerical instability new_F = ti.Matrix.diag(dim=dim, val=1) * ti.sqrt(J) elif particle_type[p] == 2: new_F = U @ sig @ ti.transposed( V) # Reconstruct elastic deformation gradient after plasticity F[f + 1, p] = new_F Jp[f + 1, p] = new_Jp stress = 2 * mu * (new_F - U @ ti.transposed(V)) @ ti.transposed( new_F) + ti.Matrix.diag(dim=dim, val=1) * la * J * (J - 1) stress = (-dt * p_vol * 4 * inv_dx * inv_dx) * stress affine = stress + p_mass * C[f, p] # loop over 3x3 node neighborhood for i in ti.static(range(3)): for j in ti.static(range(3)): offset = ti.Vector([i, j]) dpos = (ti.cast(ti.Vector([i, j]), ti.f32) - fx) * dx weight = w[i](0) * w[j](1) ti.atomic_add(grid_v_in[base + offset], weight * (p_mass * v[f, p] + affine @ dpos)) ti.atomic_add(grid_m[base + offset], weight * p_mass)
def p2g(self, dt: ti.f32): for p in self.x: base = (self.x[p] * self.inv_dx - 0.5).cast(int) fx = self.x[p] * self.inv_dx - base.cast(float) # Quadratic kernels [http://mpm.graphics Eqn. 123, with x=fx, fx-1,fx-2] w = [ 0.5 * ti.sqr(1.5 - fx), 0.75 - ti.sqr(fx - 1), 0.5 * ti.sqr(fx - 0.5) ] # deformation gradient update self.F[p] = (ti.Matrix.identity(ti.f32, self.dim) + dt * self.C[p]) @ self.F[p] # Hardening coefficient: snow gets harder when compressed h = ti.exp(10 * (1.0 - self.Jp[p])) if self.material[ p] == self.material_elastic: # jelly, make it softer h = 0.3 mu, la = self.mu_0 * h, self.lambda_0 * h if self.material[p] == self.material_water: # liquid mu = 0.0 U, sig, V = ti.svd(self.F[p]) J = 1.0 for d in ti.static(range(self.dim)): new_sig = sig[d, d] #section 7 of snow paper ... dt being too big if self.material[p] == self.material_snow: # Snow new_sig = min(max(sig[d, d], 1 - 2.5e-2), 1 + 4.5e-3) # Plasticity self.Jp[p] *= sig[d, d] / new_sig sig[d, d] = new_sig J *= new_sig if self.material[p] == self.material_water: # Reset deformation gradient to avoid numerical instability new_F = ti.Matrix.identity(ti.f32, self.dim) new_F[0, 0] = J self.F[p] = new_F elif self.material[p] == self.material_snow: # Reconstruct elastic deformation gradient after plasticity self.F[p] = U @ sig @ V.T() stress = 2 * mu * (self.F[p] - U @ V.T()) @ self.F[p].T( ) + ti.Matrix.identity(ti.f32, self.dim) * la * J * (J - 1) stress = (-dt * self.p_vol * 4 * self.inv_dx**2) * stress affine = stress + self.p_mass * self.C[p] # Loop over 3x3 grid node neighborhood for offset in ti.static(ti.grouped(self.stencil_range())): dpos = (offset.cast(float) - fx) * self.dx weight = 1.0 for d in ti.static(range(self.dim)): weight *= w[offset[d]][d] self.grid_v[base + offset] += weight * (self.p_mass * self.v[p] + affine @ dpos) self.grid_m[base + offset] += weight * self.p_mass
def ssvd(F): U, sig, V = ti.svd(F) if U.determinant() < 0: for i in ti.static(range(3)): U[i, 2] *= -1 sig[2, 2] = -sig[2, 2] if V.determinant() < 0: for i in ti.static(range(3)): V[i, 2] *= -1 sig[2, 2] = -sig[2, 2] return U, sig, V
def polar_decompose3d(A, dt): """Perform polar decomposition (A=UP) for 3x3 matrix. Mathematical concept refers to https://en.wikipedia.org/wiki/Polar_decomposition. Args: A (ti.Matrix(3, 3)): input 3x3 matrix `A`. dt (DataType): date type of elements in matrix `A`, typically accepts ti.f32 or ti.f64. Returns: Decomposed 3x3 matrices `U` and `P`. """ U, sig, V = ti.svd(A, dt) return U @ V.transpose(), V @ sig @ V.transpose()
def local_solve_build_bp_for_all_constraints(): for i in range(NF): # Construct strain constraints: # Construct Current F_i: ia, ib, ic = f2v[i] a, b, c = pos_new[ia], pos_new[ib], pos_new[ic] D_i = ti.Matrix.cols([b - a, c - a]) F_i = ti.cast(D_i @ B[i], ti.f64) F[i] = F_i # Use current F_i construct current 'B * p' or Ri U, sigma, V = ti.svd(F_i, ti.f64) Bp[i] = U @ V.transpose() # Construct volume preservation constraints: x, y, max_it, tol = 10.0, 10.0, 80, 1e-6 for t in range(max_it): aa, bb = x + sigma[0, 0], y + sigma[1, 1] f = aa * bb - 1 g1, g2 = bb, aa bot = g1 * g1 + g2 * g2 if abs(bot) < tol: break top = x * g1 + y * g2 - f div = top / bot x0, y0 = x, y x = div * g1 y = div * g2 _dx, _dy = x - x0, y - y0 if _dx * _dx + _dy * _dy < tol * tol: break PP = ti.Matrix.rows([[x + sigma[0, 0], 0.0], [0.0, sigma[1, 1] + y]]) Bp[NF + i] = U @ PP @ V.transpose() # Calculate Phi for all the elements: for i in range(NF): Bp_i_strain = Bp[i] Bp_i_volume = Bp[NF + i] F_i = F[i] energy1 = mu * volume * ((F_i - Bp_i_strain).norm()**2) energy2 = 0.5 * lam * volume * ((F_i - Bp_i_volume).trace()**2) phi[i] = energy1 + energy2
def substep(): for K in ti.static(range(16)): for p in x: base = (x[p] * inv_dx - 0.5).cast(int) fx = x[p] * inv_dx - base.cast(float) w = [ 0.5 * ti.sqr(1.5 - fx), 0.75 - ti.sqr(fx - 1), 0.5 * ti.sqr(fx - 0.5) ] F[p] = (ti.Matrix.identity(ti.f32, 2) + dt * C[p]) @ F[p] h = ti.exp(10 * (1.0 - Jp[p])) if material[p] == 1: h = 0.3 mu, la = mu_0 * h, lambda_0 * h if material[p] == 0: # liquid mu = 0.0 U, sig, V = ti.svd(F[p]) J = 1.0 for d in ti.static(range(2)): new_sig = sig[d, d] if material[p] == 2: # Snow new_sig = min(max(sig[d, d], 1 - 2.5e-2), 1 + 4.5e-3) Jp[p] *= sig[d, d] / new_sig sig[d, d] = new_sig J *= new_sig if material[p] == 0: F[p] = ti.Matrix.identity(ti.f32, 2) * ti.sqrt(J) elif material[p] == 2: F[p] = U @ sig @ V.T() stress = 2 * mu * (F[p] - U @ V.T()) @ F[p].T( ) + ti.Matrix.identity(ti.f32, 2) * la * J * (J - 1) stress = (-dt * p_vol * 4 * inv_dx * inv_dx) * stress affine = stress + p_mass * C[p] for i, j in ti.static(ti.ndrange(3, 3)): offset = ti.Vector([i, j]) dpos = (offset.cast(float) - fx) * dx weight = w[i][0] * w[j][1] grid_v[base + offset] += weight * (p_mass * v[p] + affine @ dpos) grid_m[base + offset] += weight * p_mass
def update_vdv(): for p in range(num_particles[None]): dv[p] = ti.Vector.zero(float, dim) # anisotropic filtering parameters C = ti.Matrix.zero(float, dim, dim) # anisotropic covariance w = 0.0 pf = ti.Vector.zero(float, dim) # total pairwise force for j in range(num_neighbors[p]): q = neighbors[p, j] r = x[p] - x[q] norm = ti.max(r.norm(), 1e-5) # compute distance and it's norm if is_fluid(p) == 1: dv[p] += viscosity_force( p, q, r, norm) # compute Viscosity force contribution dv[p] += pressure_force( p, q, r, norm) # compute pressure force contribution w += W(norm, dh) C += r.outer_product(r) * w pf += pairwise_force( p, q, r, norm) # compute surface tension contribution # add body force and filtered pairwise force if is_fluid(p) == 1: dv[p] += g C /= w U, sig, V = ti.svd(C) for i in ti.static(range(dim)): sig[i, i] = ti.max(sig[i, i], eps_min) G = U @ sig.inverse() @ V.transpose() T = (1 - eta) * ti.Matrix.identity( float, dim) + eta * G / G.determinant() dv[p] += T @ pf for p in range(num_particles[None]): v[p] += (dt / 2) * dv[p]
def update_dt(self): # update velocity for p in self.x: if self.flag[p] == 0: self.v[p] += self.dt * (self.a_G[p] + self.a_other[p] + self.a_lambda[p] + self.a_friction[p]) # update fe for p in self.x: if self.flag[p] == 0: grad_v = ti.Matrix([[0., 0.], [0., 0.]]) for nbr in range(self.nbrs_num[p]): i = self.nbrs_list[p, nbr] if self.flag[i] == 0: grad_v += self.v[i].outer_product(self.dW(i, p)) self.F_E[p] += self.dt * grad_v @ self.F_E[p] U, Sigma, V = ti.svd(self.F_E[p], ti.f32) for n in ti.static(range(self.dim)): Sigma[n, n] = min(1 + self.theta_s, max(1 - self.theta_c, Sigma[n, n])) self.F_E[p] = V @ Sigma @ V.transpose() # update position for p in self.x: if self.flag[p] == 0: self.x[p] += self.dt * self.v[p]
def run(): for i in range(1): U[None], sigma[None], V[None] = ti.svd(A[None], dt) UtU[None] = ti.transposed(U[None]) @ U[None] VtV[None] = ti.transposed(V[None]) @ V[None] A_reconstructed[None] = U[None] @ sigma[None] @ ti.transposed(V[None])
def substep(f: ti.i32): for i, j in grid_m: grid_v[i, j] = [0, 0] grid_m[i, j] = 0 for p in x: # Particle state update and scatter to grid (P2G) base = (x[p] * inv_dx - 0.5).cast(int) fx = x[p] * inv_dx - base.cast(float) # Quadratic kernels [http://mpm.graphics Eqn. 123, with x=fx, fx-1,fx-2] w = [0.5 * (1.5 - fx)**2, 0.75 - (fx - 1)**2, 0.5 * (fx - 0.5)**2] F[p] = (ti.Matrix.identity(ti.f32, 2) + dt * C[p]) @ F[p] # deformation gradient update h = ti.exp( 10 * (1.0 - Jp[p])) # Hardening coefficient: snow gets harder when compressed if material[p] == 1: # jelly, make it softer h = 0.3 mu, la = mu_0 * h, lambda_0 * h U, sig, V = ti.svd(F[p]) J = 1.0 for d in ti.static(range(2)): new_sig = sig[d, d] Jp[p] *= sig[d, d] / new_sig sig[d, d] = new_sig J *= new_sig stress = 2 * mu * (F[p] - U @ V.T()) @ F[p].T() + ti.Matrix.identity( ti.f32, 2) * la * J * (J - 1) ## ADD THE ACTUATION STRESS FOR SOLID MATERIAL if material[p] == 2: # actuated material only act = 0.0 act = ti.sin(actuation_omega * f * dt) A = ti.Matrix.identity(ti.f32, 2) * act * act_strength stress += F[p] @ A @ F[p].T() ## stress = (-dt * p_vol * 4 * inv_dx * inv_dx) * stress affine = stress + p_mass * C[p] for i, j in ti.static(ti.ndrange( 3, 3)): # Loop over 3x3 grid node neighborhood offset = ti.Vector([i, j]) dpos = (offset.cast(float) - fx) * dx weight = w[i][0] * w[j][1] grid_v[base + offset] += weight * (p_mass * v[p] + affine @ dpos) grid_m[base + offset] += weight * p_mass for i, j in grid_m: if grid_m[i, j] > 0: # No need for epsilon here grid_v[i, j] = (1 / grid_m[i, j]) * grid_v[i, j] # Momentum to velocity grid_v[i, j][1] -= dt * 50 # gravity if i < 3 and grid_v[i, j][0] < 0: grid_v[i, j][0] = 0 # Boundary conditions if i > n_grid - 3 and grid_v[i, j][0] > 0: grid_v[i, j][0] = 0 if j < 3 and grid_v[i, j][1] < 0: grid_v[i, j][1] = 0 if j > n_grid - 3 and grid_v[i, j][1] > 0: grid_v[i, j][1] = 0 for p in x: # grid to particle (G2P) base = (x[p] * inv_dx - 0.5).cast(int) fx = x[p] * inv_dx - base.cast(float) w = [0.5 * (1.5 - fx)**2, 0.75 - (fx - 1.0)**2, 0.5 * (fx - 0.5)**2] new_v = ti.Vector.zero(ti.f32, 2) new_C = ti.Matrix.zero(ti.f32, 2, 2) for i, j in ti.static(ti.ndrange( 3, 3)): # loop over 3x3 grid node neighborhood dpos = ti.Vector([i, j]).cast(float) - fx g_v = grid_v[base + ti.Vector([i, j])] weight = w[i][0] * w[j][1] new_v += weight * g_v new_C += 4 * inv_dx * weight * g_v.outer_product(dpos) v[p], C[p] = new_v, new_C x[p] += dt * v[p] # advection
def run(): U[None], sigma[None], V[None] = ti.svd(A[None])
def psi(self, F): # strain energy density function Ψ(F) U, sig, V = ti.svd(F) # fixed corotated model, you can replace it with any constitutive model return self.mu_0 * (F - U @ V.transpose()).norm( )**2 + self.lambda_0 / 2 * (F.determinant() - 1)**2
def substep(): line = ti.Vector([x_le[0] - x_ls[0], x_le[1] - x_ls[1]]).normalized() for p in x_r: x_r[p] = x_r[p] - line * dt * r_v for p in x_rp: x_rp[p] = x_rp[p] - line * dt * r_v # CDF for i, j in grid_A: grid_A[i, j] = 0 grid_T[i, j] = 0 grid_d[i, j] = 0.0 for p in x_rp: ba = x_r[p + 1] - x_r[p] base = (x_rp[p] * inv_dx - 0.5).cast(int) for i, j in ti.static(ti.ndrange( 3, 3)): # Loop over 3x3 grid node neighborhood offset = ti.Vector([i, j]) pa = (offset + base).cast(float) * dx - x_r[p] h = pa.dot(ba) / (ba.dot(ba)) if h <= 1 and h >= 0: grid_d[base + offset] = (pa - h * ba).norm() grid_A[base + offset] = 1 outer = pa[0] * ba[1] - pa[1] * ba[0] #print(grid_d[base + offset]) if outer > 0: grid_T[base + offset] = 1 else: grid_T[base + offset] = -1 for p in x: p_A[p] = 0 p_T[p] = 0 p_d[p] = 0.0 base = (x[p] * inv_dx - 0.5).cast(int) fx = x[p] * inv_dx - base.cast(float) w = [0.5 * (1.5 - fx)**2, 0.75 - (fx - 1)**2, 0.5 * (fx - 0.5)**2] Tpr = 0.0 for i, j in ti.static(ti.ndrange( 3, 3)): # Loop over 3x3 grid node neighborhood offset = ti.Vector([i, j]) if grid_A[base + offset] == 1: p_A[p] = 1 weight = w[i][0] * w[j][1] Tpr += weight * grid_d[base + offset] * grid_T[base + offset] if p_A[p] == 1: if Tpr > 0: p_T[p] = 1 else: p_T[p] = -1 p_d[p] = abs(Tpr) #print(p_d[p]) for i, j in grid_m: grid_v[i, j] = [0, 0] grid_m[i, j] = 0 # P2G for p in x: # Particle state update and scatter to grid (P2G) # p is a scalar base = (x[p] * inv_dx - 0.5).cast(int) fx = x[p] * inv_dx - base.cast(float) # Quadratic kernels [http://mpm.graphics Eqn. 123, with x=fx, fx-1,fx-2] w = [0.5 * (1.5 - fx)**2, 0.75 - (fx - 1)**2, 0.5 * (fx - 0.5)**2] F[p] = (ti.Matrix.identity(float, 2) + dt * C[p]) @ F[p] # deformation gradient update #h = ti.exp(10 * (1.0 - Jp[p])) # Hardening coefficient: snow gets harder when compressed #if material[p] == 1: # jelly, make it softer # h = 0.5 #mu, la = mu_0 * h, lambda_0 * h #if material[p] == 0: # liquid # mu = 0.0 h = 0.5 mu, la = mu_0 * h, lambda_0 * h U, sig, V = ti.svd(F[p]) J = 1.0 for d in ti.static(range(2)): new_sig = sig[d, d] #if material[p] == 2: # Snow # new_sig = min(max(sig[d, d], 1 - 2.5e-2), 1 + 4.5e-3) # Plasticity #Jp[p] *= sig[d, d] / new_sig #sig[d, d] = new_sig J *= new_sig #if material[p] == 0: # Reset deformation gradient to avoid numerical instability # F[p] = ti.Matrix.identity(float, 2) * ti.sqrt(J) #elif material[p] == 2: # F[p] = U @ sig @ V.transpose() # Reconstruct elastic deformation gradient after plasticity stress = 2 * mu * (F[p] - U @ V.transpose()) @ F[p].transpose( ) + ti.Matrix.identity(float, 2) * la * J * (J - 1) stress = (-dt * p_vol * 4 * inv_dx * inv_dx) * stress affine = stress + p_mass * C[p] for i, j in ti.static(ti.ndrange( 3, 3)): # Loop over 3x3 grid node neighborhood offset = ti.Vector([i, j]) if p_T[p] * grid_T[base + offset] == -1: #continue pass else: dpos = (offset.cast(float) - fx) * dx weight = w[i][0] * w[j][1] grid_v[base + offset] += weight * (p_mass * v[p] + affine @ dpos) grid_m[base + offset] += weight * p_mass # grid operation for i, j in grid_m: if grid_m[i, j] > 0: # No need for epsilon here grid_v[i, j] = (1 / grid_m[i, j]) * grid_v[i, j] # Momentum to velocity grid_v[i, j][1] -= dt * gravity # gravity if j > n_grid - 3: grid_v[i, j] = [0, 0] if i < 3 and grid_v[i, j][0] < 0: grid_v[i, j][0] = 0 # Boundary conditions if i > n_grid - 3 and grid_v[i, j][0] > 0: grid_v[i, j][0] = 0 if j < 3 and grid_v[i, j][1] < 0: grid_v[i, j][1] = 0 #if j > n_grid - 3 and grid_v[i, j][1] > 0: grid_v[i, j][1] = 0 # G2P for p in x: # grid to particle (G2P) base = (x[p] * inv_dx - 0.5).cast(int) fx = x[p] * inv_dx - base.cast(float) w = [0.5 * (1.5 - fx)**2, 0.75 - (fx - 1.0)**2, 0.5 * (fx - 0.5)**2] new_v = ti.Vector.zero(float, 2) new_C = ti.Matrix.zero(float, 2, 2) for i, j in ti.static(ti.ndrange( 3, 3)): # loop over 3x3 grid node neighborhood g_v = ti.Vector([0.0, 0.0]) if p_T[p] * grid_T[base + ti.Vector([i, j])] == -1: #slip boundary #line = ti.Vector([x_le[0]-x_ls[0],x_le[1]-x_ls[1]]).normalized() #g_v = v[p].dot(line) * line line = ti.Vector([x_le[0] - x_ls[0], x_le[1] - x_ls[1]]).normalized() pa = ti.Vector([x[p][0] - x_ls[0], x[p][1] - x_ls[1]]) np = (pa - pa.dot(line) * line).normalized() sg = v[p].dot(np) if sg > 0: g_v = v[p] else: g_v = v[p].dot(line) * line else: g_v = grid_v[base + ti.Vector([i, j])] dpos = ti.Vector([i, j]).cast(float) - fx weight = w[i][0] * w[j][1] new_v += weight * g_v new_C += 4 * inv_dx * weight * g_v.outer_product(dpos) v[p], C[p] = new_v, new_C x[p] += dt * v[p] # advection
def run(): U[None], sigma[None], V[None] = ti.svd(A[None], dt) UtU[None] = U[None].transpose() @ U[None] VtV[None] = V[None].transpose() @ V[None] A_reconstructed[None] = U[None] @ sigma[None] @ V[None].transpose()
def dpsi_dF(self, F): # first Piola-Kirchoff stress P(F), i.e. ∂Ψ/∂F U, sig, V = ti.svd(F) J = F.determinant() R = U @ V.transpose() return 2 * self.mu_0 * (F - R) + self.lambda_0 * ( J - 1) * J * F.inverse().transpose()
def polar_decompose3d(A, dt): U, sig, V = ti.svd(A, dt) return U @ V.transpose(), V @ sig @ V.transpose()
def svd(self): for p in range(0, self.n_particles): self.U[p], self.sig[p], self.V[p] = ti.svd(self.F_tmp[p])
def firstPiolaDifferential(self, p, F, dF): U, sig, V = ti.svd(F) D = U.transpose() @ dF @ V K = ti.Matrix.zero(self.real, self.dim, self.dim) self.dPdFOfSigmaContractProjected(p, D, K) return U @ K @ V.transpose()
def substep(g_x: float, g_y: float, g_z: float): for I in ti.grouped(grid_m): grid_v[I] = ti.zero(grid_v[I]) grid_m[I] = 0 ti.block_dim(n_grid) for p in x: if used[p] == 0: continue Xp = x[p] / dx base = int(Xp - 0.5) fx = Xp - base w = [0.5 * (1.5 - fx)**2, 0.75 - (fx - 1)**2, 0.5 * (fx - 0.5)**2] F[p] = (ti.Matrix.identity(float, 3) + dt * C[p]) @ F[p] # deformation gradient update h = ti.exp( 10 * (1.0 - Jp[p])) # Hardening coefficient: snow gets harder when compressed if materials[p] == JELLY: # jelly, make it softer h = 0.3 mu, la = mu_0 * h, lambda_0 * h if materials[p] == WATER: # liquid mu = 0.0 U, sig, V = ti.svd(F[p]) J = 1.0 for d in ti.static(range(3)): new_sig = sig[d, d] if materials[p] == SNOW: # Snow new_sig = min(max(sig[d, d], 1 - 2.5e-2), 1 + 4.5e-3) # Plasticity Jp[p] *= sig[d, d] / new_sig sig[d, d] = new_sig J *= new_sig if materials[ p] == WATER: # Reset deformation gradient to avoid numerical instability new_F = ti.Matrix.identity(float, 3) new_F[0, 0] = J F[p] = new_F elif materials[p] == SNOW: F[p] = U @ sig @ V.transpose( ) # Reconstruct elastic deformation gradient after plasticity stress = 2 * mu * (F[p] - U @ V.transpose()) @ F[p].transpose( ) + ti.Matrix.identity(float, 3) * la * J * (J - 1) stress = (-dt * p_vol * 4) * stress / dx**2 affine = stress + p_mass * C[p] for offset in ti.static(ti.grouped(ti.ndrange(*neighbour))): dpos = (offset - fx) * dx weight = 1.0 for i in ti.static(range(dim)): weight *= w[offset[i]][i] grid_v[base + offset] += weight * (p_mass * v[p] + affine @ dpos) grid_m[base + offset] += weight * p_mass for I in ti.grouped(grid_m): if grid_m[I] > 0: grid_v[I] /= grid_m[I] grid_v[I] += dt * ti.Vector([g_x, g_y, g_z]) cond = I < bound and grid_v[I] < 0 or I > n_grid - bound and grid_v[ I] > 0 grid_v[I] = 0 if cond else grid_v[I] ti.block_dim(n_grid) for p in x: if used[p] == 0: continue Xp = x[p] / dx base = int(Xp - 0.5) fx = Xp - base w = [0.5 * (1.5 - fx)**2, 0.75 - (fx - 1)**2, 0.5 * (fx - 0.5)**2] new_v = ti.zero(v[p]) new_C = ti.zero(C[p]) for offset in ti.static(ti.grouped(ti.ndrange(*neighbour))): dpos = (offset - fx) * dx weight = 1.0 for i in ti.static(range(dim)): weight *= w[offset[i]][i] g_v = grid_v[base + offset] new_v += weight * g_v new_C += 4 * weight * g_v.outer_product(dpos) / dx**2 v[p] = new_v x[p] += dt * v[p] C[p] = new_C
def updateIsotropicHelper(self, p, F): self.reinitializeIsotropicHelper(p) if ti.static(self.dim == 2): U, sigma, V = ti.svd(F) J = sigma[0, 0] * sigma[1, 1] _2mu = self.mu_0 * 2 _lambda = self.lambda_0 * (J - 1) Sprod = ti.Vector([sigma[1, 1], sigma[0, 0]]) self.psi0[p] = _2mu * (sigma[0, 0] - 1) + _lambda * Sprod[0] self.psi1[p] = _2mu * (sigma[1, 1] - 1) + _lambda * Sprod[1] self.psi00[p] = _2mu + self.lambda_0 * Sprod[0] * Sprod[0] self.psi11[p] = _2mu + self.lambda_0 * Sprod[1] * Sprod[1] self.psi01[p] = _lambda + self.lambda_0 * Sprod[0] * Sprod[1] # (psi0-psi1)/(sigma0-sigma1) self.m01[p] = _2mu - _lambda # (psi0+psi1)/(sigma0+sigma1) self.p01[p] = (self.psi0[p] + self.psi1[p]) / self.clamp_small_magnitude( sigma[0, 0] + sigma[1, 1], 1e-6) self.Aij[p] = ti.Matrix([[self.psi00[p], self.psi01[p]], [self.psi01[p], self.psi11[p]]]) self.B01[p] = ti.Matrix([[(self.m01[p] + self.p01[p]) * 0.5, (self.m01[p] - self.p01[p]) * 0.5], [(self.m01[p] - self.p01[p]) * 0.5, (self.m01[p] + self.p01[p]) * 0.5]]) # proj A self.makePD(self.Aij[p]) # proj B self.makePD2d(self.B01[p]) if ti.static(self.dim == 3): U, sigma, V = ti.svd(F) J = sigma[0, 0] * sigma[1, 1] * sigma[2, 2] _2mu = self.mu_0 * 2 _lambda = self.lambda_0 * (J - 1) Sprod = ti.Vector([ sigma[1, 1] * sigma[2, 2], sigma[0, 0] * sigma[2, 2], sigma[0, 0] * sigma[1, 1] ]) self.psi0[p] = _2mu * (sigma[0, 0] - 1) + _lambda * Sprod[0] self.psi1[p] = _2mu * (sigma[1, 1] - 1) + _lambda * Sprod[1] self.psi2[p] = _2mu * (sigma[2, 2] - 1) + _lambda * Sprod[2] self.psi00[p] = _2mu + self.lambda_0 * Sprod[0] * Sprod[0] self.psi11[p] = _2mu + self.lambda_0 * Sprod[1] * Sprod[1] self.psi22[p] = _2mu + self.lambda_0 * Sprod[2] * Sprod[2] self.psi01[p] = _lambda * sigma[ 2, 2] + self.lambda_0 * Sprod[0] * Sprod[1] self.psi02[p] = _lambda * sigma[ 1, 1] + self.lambda_0 * Sprod[0] * Sprod[2] self.psi12[p] = _lambda * sigma[ 0, 0] + self.lambda_0 * Sprod[1] * Sprod[2] # (psiA-psiB)/(sigmaA-sigmaB) self.m01[p] = _2mu - _lambda * sigma[2, 2] # i[p] = 0 self.m02[p] = _2mu - _lambda * sigma[1, 1] # i[p] = 2 self.m12[p] = _2mu - _lambda * sigma[0, 0] # i[p] = 1 # (psiA+psiB)/(sigmaA+sigmaB) self.p01[p] = (self.psi0[p] + self.psi1[p]) / self.clamp_small_magnitude( sigma[0, 0] + sigma[1, 1], 1e-6) self.p02[p] = (self.psi0[p] + self.psi2[p]) / self.clamp_small_magnitude( sigma[0, 0] + sigma[2, 2], 1e-6) self.p12[p] = (self.psi1[p] + self.psi2[p]) / self.clamp_small_magnitude( sigma[1, 1] + sigma[2, 2], 1e-6) self.Aij[p] = ti.Matrix( [[self.psi00[p], self.psi01[p], self.psi02[p]], [self.psi01[p], self.psi11[p], self.psi12[p]], [self.psi02[p], self.psi12[p], self.psi22[p]]]) self.B01[p] = ti.matrix([[(self.m01[p] + self.p01[p]) * 0.5, (self.m01[p] - self.p01[p]) * 0.5], [(self.m01[p] - self.p01[p]) * 0.5, (self.m01[p] + self.p01[p]) * 0.5]]) self.B12[p] = ti.matrix([[(self.m12[p] + self.p12[p]) * 0.5, (self.m12[p] - self.p12[p]) * 0.5], [(self.m12[p] - self.p12[p]) * 0.5, (self.m12[p] + self.p12[p]) * 0.5]]) self.B20[p] = ti.matrix([[(self.m02[p] + self.p02[p]) * 0.5, (self.m02[p] - self.p02[p]) * 0.5], [(self.m02[p] - self.p02[p]) * 0.5, (self.m02[p] + self.p02[p]) * 0.5]]) # proj A self.makePD(self.Aij[p]) # proj B self.makePD2d(self.B01[p]) self.makePD2d(self.B12[p]) self.makePD2d(self.B20[p])
def substep(): # set zero initial state for both water/sand grid for i, j in grid_sm: grid_sv[i, j], grid_wv[i, j] = [0, 0], [0, 0] grid_sm[i, j], grid_wm[i, j] = 0, 0 grid_sf[i, j], grid_wf[i, j] = [0, 0], [0, 0] # P2G (sand's part) for p in range(n_s_particles): base = (x_s[p] * inv_dx - 0.5).cast(int) if base[0] < 0 or base[1] < 0 or base[0] >= n_grid - 2 or base[ 1] >= n_grid - 2: continue fx = x_s[p] * inv_dx - base.cast(float) # Quadratic kernels [http://mpm.graphics Eqn. 123, with x=fx, fx-1,fx-2] w = [0.5 * (1.5 - fx)**2, 0.75 - (fx - 1)**2, 0.5 * (fx - 0.5)**2] U, sig, V = ti.svd(F_s[p]) inv_sig = sig.inverse() e = ti.Matrix([[ti.log(sig[0, 0]), 0], [0, ti.log(sig[1, 1])]]) stress = U @ (2 * mu_s * inv_sig @ e + lambda_s * e.trace() * inv_sig) @ V.transpose() # formula (25) stress = (-p_vol * 4 * inv_dx * inv_dx) * stress @ F_s[p].transpose() # stress *= h(e) # print(h(e)) affine = s_mass * C_s[p] for i, j in ti.static(ti.ndrange(3, 3)): offset = ti.Vector([i, j]) dpos = (offset.cast(float) - fx) * dx weight = w[i][0] * w[j][1] grid_sv[base + offset] += weight * (s_mass * v_s[p] + affine @ dpos) grid_sm[base + offset] += weight * s_mass grid_sf[base + offset] += weight * stress @ dpos # P2G (water's part): for p in range(n_w_particles): base = (x_w[p] * inv_dx - 0.5).cast(int) fx = x_w[p] * inv_dx - base.cast(float) # Quadratic kernels [http://mpm.graphics Eqn. 123, with x=fx, fx-1,fx-2] w = [0.5 * (1.5 - fx)**2, 0.75 - (fx - 1)**2, 0.5 * (fx - 0.5)**2] stress = w_k * (1 - 1 / (J_w[p]**w_gamma)) stress = (-p_vol * 4 * inv_dx * inv_dx) * stress * J_w[p] # stress = -4 * 400 * p_vol * (J_w[p] - 1) / dx ** 2 (special case when gamma equals to 1) affine = w_mass * C_w[p] # affine = ti.Matrix([[stress, 0], [0, stress]]) + w_mass * C_w[p] for i, j in ti.static(ti.ndrange(3, 3)): offset = ti.Vector([i, j]) dpos = (offset.cast(float) - fx) * dx weight = w[i][0] * w[j][1] grid_wv[base + offset] += weight * (w_mass * v_w[p] + affine @ dpos) grid_wm[base + offset] += weight * w_mass grid_wf[base + offset] += weight * stress * dpos # Update Grids Momentum for i, j in grid_sm: if grid_sm[i, j] > 0: grid_sv[i, j] = ( 1 / grid_sm[i, j]) * grid_sv[i, j] # Momentum to velocity if grid_wm[i, j] > 0: grid_wv[i, j] = (1 / grid_wm[i, j]) * grid_wv[i, j] # Momentum exchange cE = (n * n * w_rho * gravity[1]) / k_hat # drag coefficient if grid_sm[i, j] > 0 and grid_wm[i, j] > 0: sm, wm = grid_sm[i, j], grid_wm[i, j] sv, wv = grid_sv[i, j], grid_wv[i, j] d = cE * sm * wm M = ti.Matrix([[sm, 0], [0, wm]]) D = ti.Matrix([[-d, d], [d, -d]]) V = ti.Matrix.rows([grid_sv[i, j], grid_wv[i, j]]) G = ti.Matrix.rows([gravity, gravity]) F = ti.Matrix.rows([grid_sf[i, j], grid_wf[i, j]]) A = M + dt * D B = M @ V + dt * (M @ G + F) X = A.inverse() @ B grid_sv[i, j], grid_wv[i, j] = ti.Vector( [X[0, 0], X[0, 1]]), ti.Vector([X[1, 0], X[1, 1]]) elif grid_sm[i, j] > 0: grid_sv[i, j] += dt * (gravity + grid_sf[i, j] / grid_sm[i, j] ) # Update explicit force elif grid_wm[i, j] > 0: grid_wv[i, j] += dt * (gravity + grid_wf[i, j] / grid_wm[i, j]) normal = ti.Vector.zero(float, 2) if grid_sm[i, j] > 0: if i < 3 and grid_sv[i, j][0] < 0: normal = ti.Vector([1, 0]) if i > n_grid - 3 and grid_sv[i, j][0] > 0: normal = ti.Vector([-1, 0]) if j < 3 and grid_sv[i, j][1] < 0: normal = ti.Vector([0, 1]) if j > n_grid - 3 and grid_sv[i, j][1] > 0: normal = ti.Vector([0, -1]) if not (normal[0] == 0 and normal[1] == 0): # Apply friction s = grid_sv[i, j].dot(normal) if s <= 0: v_normal = s * normal v_tangent = grid_sv[ i, j] - v_normal # divide velocity into normal and tangential parts vt = v_tangent.norm() if vt > 1e-12: grid_sv[i, j] = v_tangent - ( vt if vt < -mu_b * s else -mu_b * s) * ( v_tangent / vt) # The Coulomb friction law if grid_wm[i, j] > 0: if i < 3 and grid_wv[i, j][0] < 0: grid_wv[i, j][0] = 0 # Boundary conditions if i > n_grid - 3 and grid_wv[i, j][0] > 0: grid_wv[i, j][0] = 0 if j < 3 and grid_wv[i, j][1] < 0: grid_wv[i, j][1] = 0 if j > n_grid - 3 and grid_wv[i, j][1] > 0: grid_wv[i, j][1] = 0 # G2P (water's part) for p in range(n_w_particles): base = (x_w[p] * inv_dx - 0.5).cast(int) fx = x_w[p] * inv_dx - base.cast(float) w = [0.5 * (1.5 - fx)**2, 0.75 - (fx - 1.0)**2, 0.5 * (fx - 0.5)**2] new_v = ti.Vector.zero(float, 2) new_C = ti.Matrix.zero(float, 2, 2) for i, j in ti.static(ti.ndrange(3, 3)): dpos = ti.Vector([i, j]).cast(float) - fx g_v = grid_wv[base + ti.Vector([i, j])] weight = w[i][0] * w[j][1] new_v += weight * g_v new_C += 4 * inv_dx * weight * g_v.outer_product(dpos) J_w[p] = (1 + dt * new_C.trace()) * J_w[p] v_w[p], C_w[p] = new_v, new_C x_w[p] += dt * v_w[p] # G2P (sand's part) for p in range(n_s_particles): base = (x_s[p] * inv_dx - 0.5).cast(int) if base[0] < 0 or base[1] < 0 or base[0] >= n_grid - 2 or base[ 1] >= n_grid - 2: continue fx = x_s[p] * inv_dx - base.cast(float) w = [0.5 * (1.5 - fx)**2, 0.75 - (fx - 1.0)**2, 0.5 * (fx - 0.5)**2] new_v = ti.Vector.zero(float, 2) new_C = ti.Matrix.zero(float, 2, 2) phi_s[p] = 0.0 # Saturation for i, j in ti.static(ti.ndrange( 3, 3)): # loop over 3x3 grid node neighborhood dpos = ti.Vector([i, j]).cast(float) - fx g_v = grid_sv[base + ti.Vector([i, j])] weight = w[i][0] * w[j][1] new_v += weight * g_v new_C += 4 * inv_dx * weight * g_v.outer_product(dpos) if grid_sm[base + ti.Vector([i, j])] > 0 and grid_wm[ base + ti.Vector([i, j])] > 0: phi_s[p] += weight # formula (24) F_s[p] = (ti.Matrix.identity(float, 2) + dt * new_C) @ F_s[p] v_s[p], C_s[p] = new_v, new_C x_s[p] += dt * v_s[p] U, sig, V = ti.svd(F_s[p]) e = ti.Matrix([[ti.log(sig[0, 0]), 0], [0, ti.log(sig[1, 1])]]) new_e, dq = project(e, p) hardening(dq, p) new_F = U @ ti.Matrix([[ti.exp(new_e[0, 0]), 0], [0, ti.exp(new_e[1, 1])]]) @ V.transpose() vc_s[p] += -ti.log(new_F.determinant()) + ti.log( F_s[p].determinant()) # formula (26) F_s[p] = new_F
def substep(): #re-initialize grid quantities for i, j in grid_m: grid_v[i, j] = [0, 0] grid_m[i, j] = 0 # Particle state update and scatter to grid (P2G) for p in x: #for particle p, compute base index base = (x[p] * inv_dx - 0.5).cast(int) fx = x[p] * inv_dx - base.cast(float) # Quadratic kernels [http://mpm.graphics Eqn. 123, with x=fx, fx-1,fx-2] w = [0.5 * (1.5 - fx) ** 2, 0.75 - (fx - 1) ** 2, 0.5 * (fx - 0.5) ** 2] dw = [fx - 1.5, -2.0 * (fx - 1), fx - 0.5] mu, la = mu_0, lambda_0 #opportunity here to modify these to model other materials U, sig, V = ti.svd(F[p]) J = 1.0 for d in ti.static(range(2)): new_sig = sig[d, d] Jp[p] *= sig[d, d] / new_sig sig[d, d] = new_sig J *= new_sig #Compute Kirchoff Stress kirchoff = kirchoff_FCR(F[p], [email protected](), J, mu, la) #P2G for velocity and mass AND Force Update! for i, j in ti.static(ti.ndrange(3, 3)): # Loop over 3x3 grid node neighborhood offset = ti.Vector([i, j]) dpos = (offset.cast(float) - fx) * dx weight = w[i][0] * w[j][1] dweight = ti.Vector.zero(float,2) dweight[0] = inv_dx * dw[i][0] * w[j][1] dweight[1] = inv_dx * w[i][0] * dw[j][1] force = -p_vol * kirchoff @ dweight grid_v[base + offset] += p_mass * weight * (v[p] + C[p] @ dpos) #momentum transfer grid_m[base + offset] += weight * p_mass #mass transfer grid_v[base + offset] += dt * force #add force to update velocity, don't divide by mass bc this is actually updating MOMENTUM # Gravity and Boundary Collision for i, j in grid_m: if grid_m[i, j] > 0: # No need for epsilon here grid_v[i, j] = (1 / grid_m[i, j]) * grid_v[i, j] # Momentum to velocity grid_v[i, j] += dt * gravity[None] * 30 # gravity #add force from mouse dist = attractor_pos[None] - dx * ti.Vector([i, j]) grid_v[i, j] += dist / (0.01 + dist.norm()) * attractor_strength[None] * dt * 100 #wall collisions if i < 3 and grid_v[i, j][0] < 0: grid_v[i, j][0] = 0 # Boundary conditions if i > n_grid - 3 and grid_v[i, j][0] > 0: grid_v[i, j][0] = 0 if j < 3 and grid_v[i, j][1] < 0: grid_v[i, j][1] = 0 if j > n_grid - 3 and grid_v[i, j][1] > 0: grid_v[i, j][1] = 0 # grid to particle (G2P) for p in x: base = (x[p] * inv_dx - 0.5).cast(int) fx = x[p] * inv_dx - base.cast(float) w = [0.5 * (1.5 - fx) ** 2, 0.75 - (fx - 1.0) ** 2, 0.5 * (fx - 0.5) ** 2] dw = [fx - 1.5, -2.0 * (fx - 1), fx - 0.5] new_v = ti.Vector.zero(float, 2) new_C = ti.Matrix.zero(float, 2, 2) new_F = ti.Matrix.zero(float, 2, 2) for i, j in ti.static(ti.ndrange(3, 3)): # loop over 3x3 grid node neighborhood dpos = ti.Vector([i, j]).cast(float) - fx g_v = grid_v[base + ti.Vector([i, j])] weight = w[i][0] * w[j][1] dweight = ti.Vector.zero(float,2) dweight[0] = inv_dx * dw[i][0] * w[j][1] dweight[1] = inv_dx * w[i][0] * dw[j][1] new_v += weight * g_v new_C += 4 * inv_dx * weight * g_v.outer_product(dpos) new_F += g_v.outer_product(dweight) v[p], C[p] = new_v, new_C x[p] += dt * v[p] # advection F[p] = (ti.Matrix.identity(float, 2) + (dt * new_F)) @ F[p] #updateF (explicitMPM way)
def substep(): for i, j in grid_m: grid_v[i, j] = [0, 0] grid_m[i, j] = 0 grid_assit[i, j] = [0, 0] for p in x: # Particle state update and scatter to grid (P2G) base = (x[p] * inv_dx - 0.5).cast(int) fx = x[p] * inv_dx - base.cast(float) # Quadratic kernels [http://mpm.graphics Eqn. 123, with x=fx, fx-1,fx-2] w = [0.5 * (1.5 - fx)**2, 0.75 - (fx - 1)**2, 0.5 * (fx - 0.5)**2] F[p] = (ti.Matrix.identity(ti.f32, 2) + dt * C[p]) @ F[p] # deformation gradient update h = ti.exp( 10 * (1.0 - Jp[p])) # Hardening coefficient: snow gets harder when compressed if material[p] == 1: # jelly, make it softer h = 1.0 mu, la = mu_0 * h, lambda_0 * h if material[p] == 0: # liquid mu = 0.0 U, sig, V = ti.svd(F[p]) J = 1.0 for d in ti.static(range(2)): new_sig = sig[d, d] if material[p] == 2: # Snow new_sig = min(max(sig[d, d], 1 - 2.5e-2), 1 + 4.5e-3) # Plasticity Jp[p] *= sig[d, d] / new_sig sig[d, d] = new_sig J *= new_sig if material[ p] == 0: # Reset deformation gradient to avoid numerical instability F[p] = ti.Matrix.identity(ti.f32, 2) * ti.sqrt(J) elif material[p] == 2: F[p] = U @ sig @ V.T( ) # Reconstruct elastic deformation gradient after plasticity stress = 2 * mu * (F[p] - U @ V.T()) @ F[p].T() + ti.Matrix.identity( ti.f32, 2) * la * J * (J - 1) stress = (-dt * p_vol * 4 * inv_dx * inv_dx) * stress affine = stress + p_mass * C[p] for i, j in ti.static(ti.ndrange( 3, 3)): # Loop over 3x3 grid node neighborhood offset = ti.Vector([i, j]) dpos = (offset.cast(float) - fx) * dx weight = w[i][0] * w[j][1] grid_assit[base + offset] += weight * (affine @ dpos) grid_v[base + offset] += weight * (p_mass * v[p] + affine @ dpos) grid_m[base + offset] += weight * p_mass for p in windmill_x: windmill_C = ti.Matrix.zero(ti.f32, 2, 2) windmill_F = ti.Matrix.identity(ti.f32, 2) #ti.Matrix([1., 0.],[0., 1.]) base = (windmill_x[p] * inv_dx - 0.5).cast(int) fx = windmill_x[p] * inv_dx - base.cast(float) # Quadratic kernels [http://mpm.graphics Eqn. 123, with x=fx, fx-1,fx-2] w = [0.5 * (1.5 - fx)**2, 0.75 - (fx - 1)**2, 0.5 * (fx - 0.5)**2] # F[p] = (ti.Matrix.identity(ti.f32, 2) + dt * C[p]) @ F[p] # deformation gradient update h = 1.0 # Hardening coefficient: snow gets harder when compressed mu, la = mu_0 * h, lambda_0 * h U, sig, V = ti.svd(windmill_F) J = 1.0 stress = 2 * mu * (windmill_F - U @ V.T()) @ windmill_F.T( ) + ti.Matrix.identity(ti.f32, 2) * la * J * (J - 1) stress = (-dt * p_vol * 4 * inv_dx * inv_dx) * stress affine = stress + p_mass * windmill_C for i, j in ti.static(ti.ndrange( 3, 3)): # Loop over 3x3 grid node neighborhood offset = ti.Vector([i, j]) dpos = (offset.cast(float) - fx) * dx weight = w[i][0] * w[j][1] wr = ti.Vector([ center[None][1] - windmill_x[p][1], windmill_x[p][0] - center[None][0] ]) wr = wr / wr.norm() wr = wr * omega[None] / 180. * pi grid_v[base + offset] += weight * (p_mass * wr + affine @ dpos) grid_m[base + offset] += weight * p_mass # torque[None] = 0. # for p in windmill_x: # base = (windmill_x[p] * inv_dx - 0.5).cast(int) # fx = windmill_x[p] * inv_dx - base.cast(float) # # Quadratic kernels [http://mpm.graphics Eqn. 123, with x=fx, fx-1,fx-2] # w = [0.5 * (1.5 - fx) ** 2, 0.75 - (fx - 1) ** 2, 0.5 * (fx - 0.5) ** 2] # for i, j in ti.static(ti.ndrange(3, 3)): # Loop over 3x3 grid node neighborhood # offset = ti.Vector([i, j]) # dpos = (offset.cast(float) - fx) * dx # weight = w[i][0] * w[j][1] # torque[None] += weight * grid_assit[base + offset].dot(windmill_x[p]-center[None]) # print(torque[None]) for i, j in grid_m: if grid_m[i, j] > 0: # No need for epsilon here grid_v[i, j] = (1 / grid_m[i, j]) * grid_v[i, j] # Momentum to velocity grid_v[i, j][1] -= dt * 50 # gravity if i < 3 and grid_v[i, j][0] < 0: grid_v[i, j][0] = 0 # Boundary conditions if i > n_grid - 3 and grid_v[i, j][0] > 0: grid_v[i, j][0] = 0 if j < 3 and grid_v[i, j][1] < 0: grid_v[i, j][1] = 0 if j > n_grid - 3 and grid_v[i, j][1] > 0: grid_v[i, j][1] = 0 for p in x: # grid to particle (G2P) base = (x[p] * inv_dx - 0.5).cast(int) fx = x[p] * inv_dx - base.cast(float) w = [0.5 * (1.5 - fx)**2, 0.75 - (fx - 1.0)**2, 0.5 * (fx - 0.5)**2] new_v = ti.Vector.zero(ti.f32, 2) new_C = ti.Matrix.zero(ti.f32, 2, 2) for i, j in ti.static(ti.ndrange( 3, 3)): # loop over 3x3 grid node neighborhood dpos = ti.Vector([i, j]).cast(float) - fx g_v = grid_v[base + ti.Vector([i, j])] weight = w[i][0] * w[j][1] new_v += weight * g_v new_C += 4 * inv_dx * weight * g_v.outer_product(dpos) v[p], C[p] = new_v, new_C x[p] += dt * v[p] # advection torque[None] = 0. for p in windmill_x: base = (x[p] * inv_dx - 0.5).cast(int) fx = x[p] * inv_dx - base.cast(float) w = [0.5 * (1.5 - fx)**2, 0.75 - (fx - 1.0)**2, 0.5 * (fx - 0.5)**2] new_v = ti.Vector.zero(ti.f32, 2) for i, j in ti.static(ti.ndrange( 3, 3)): # loop over 3x3 grid node neighborhood dpos = ti.Vector([i, j]).cast(float) - fx g_v = grid_v[base + ti.Vector([i, j])] weight = w[i][0] * w[j][1] new_v += weight * g_v wr = ti.Vector([ center[None][1] - windmill_x[p][1], windmill_x[p][0] - center[None][0] ]) wr = wr * omega[None] / 180. * pi new_v = new_v - wr torque[None] += new_v.dot(windmill_x[p] - center[None]) print(torque[None])
def p2g(self, dt: ti.f32): ti.no_activate(self.particle) ti.block_dim(256) ti.block_local(*self.grid_v.entries) ti.block_local(self.grid_m) for I in ti.grouped(self.pid): p = self.pid[I] base = ti.floor(self.x[p] * self.inv_dx - 0.5).cast(int) for D in ti.static(range(self.dim)): base[D] = ti.assume_in_range(base[D], I[D], 0, 1) fx = self.x[p] * self.inv_dx - base.cast(float) # Quadratic kernels [http://mpm.graphics Eqn. 123, with x=fx, fx-1,fx-2] w = [0.5 * (1.5 - fx)**2, 0.75 - (fx - 1)**2, 0.5 * (fx - 0.5)**2] # deformation gradient update self.F[p] = (ti.Matrix.identity(ti.f32, self.dim) + dt * self.C[p]) @ self.F[p] # Hardening coefficient: snow gets harder when compressed h = ti.exp(10 * (1.0 - self.Jp[p])) if self.material[ p] == self.material_elastic: # jelly, make it softer h = 0.3 mu, la = self.mu_0 * h, self.lambda_0 * h if self.material[p] == self.material_water: # liquid mu = 0.0 U, sig, V = ti.svd(self.F[p]) J = 1.0 if self.material[p] != self.material_sand: for d in ti.static(range(self.dim)): new_sig = sig[d, d] if self.material[p] == self.material_snow: # Snow new_sig = min(max(sig[d, d], 1 - 2.5e-2), 1 + 4.5e-3) # Plasticity self.Jp[p] *= sig[d, d] / new_sig sig[d, d] = new_sig J *= new_sig if self.material[p] == self.material_water: # Reset deformation gradient to avoid numerical instability new_F = ti.Matrix.identity(ti.f32, self.dim) new_F[0, 0] = J self.F[p] = new_F elif self.material[p] == self.material_snow: # Reconstruct elastic deformation gradient after plasticity self.F[p] = U @ sig @ V.transpose() stress = ti.Matrix.zero(ti.f32, self.dim, self.dim) if self.material[p] != self.material_sand: stress = 2 * mu * ( self.F[p] - U @ V.transpose()) @ self.F[p].transpose( ) + ti.Matrix.identity(ti.f32, self.dim) * la * J * (J - 1) else: sig = self.sand_projection(sig, p) self.F[p] = U @ sig @ V.transpose() log_sig_sum = 0.0 center = ti.Matrix.zero(ti.f32, self.dim, self.dim) for i in ti.static(range(self.dim)): log_sig_sum += ti.log(sig[i, i]) center[i, i] = 2.0 * self.mu_0 * ti.log( sig[i, i]) * (1 / sig[i, i]) for i in ti.static(range(self.dim)): center[i, i] += self.lambda_0 * log_sig_sum * (1 / sig[i, i]) stress = U @ center @ V.transpose() @ self.F[p].transpose() stress = (-dt * self.p_vol * 4 * self.inv_dx**2) * stress affine = stress + self.p_mass * self.C[p] # Loop over 3x3 grid node neighborhood for offset in ti.static(ti.grouped(self.stencil_range())): dpos = (offset.cast(float) - fx) * self.dx weight = 1.0 for d in ti.static(range(self.dim)): weight *= w[offset[d]][d] self.grid_v[base + offset] += weight * (self.p_mass * self.v[p] + affine @ dpos) self.grid_m[base + offset] += weight * self.p_mass
def substep(): for i, j in grid_m: grid_v[i, j] = [0, 0] grid_m[i, j] = 0 for p in x: base = (x[p] * inv_dx - 0.5).cast(int) fx = x[p] * inv_dx - base.cast(float) w = [ 0.5 * ti.sqr(1.5 - fx), 0.75 - ti.sqr(fx - 1), 0.5 * ti.sqr(fx - 0.5) ] F[p] = (ti.Matrix.identity(ti.f32, 2) + dt * C[p]) @ F[p] h = ti.exp(10 * (1.0 - Jp[p])) if material[p] == 0: h = 0.3 mu, la = mu_0 * h, lambda_0 * h U, sig, V = ti.svd(F[p]) J = 1.0 for d in ti.static(range(2)): new_sig = sig[d, d] if material[p] == 1: new_sig = min(max(sig[d, d], 1 - 2.5e-2), 1 + 4.5e-3) Jp[p] *= sig[d, d] / new_sig sig[d, d] = new_sig J *= new_sig if material[p] == 1: F[p] = U @ sig @ V.T() stress = 2 * mu * (F[p] - U @ V.T()) @ F[p].T() + ti.Matrix.identity( ti.f32, 2) * la * J * (J - 1) stress = (-dt * p_vol * 4 * inv_dx * inv_dx) * stress affine = stress + p_mass * C[p] for i, j in ti.static(ti.ndrange(3, 3)): offset = ti.Vector([i, j]) dpos = (offset.cast(float) - fx) * dx weight = w[i][0] * w[j][1] grid_v[base + offset] += weight * (p_mass * v[p] + affine @ dpos) grid_m[base + offset] += weight * p_mass for i, j in grid_m: if grid_m[i, j] > 0: grid_v[i, j] = (1 / grid_m[i, j]) * grid_v[i, j] grid_v[i, j][1] -= dt * 50 if i < 3 and grid_v[i, j][0] < 0: grid_v[i, j][0] = 0 if i > n_grid - 3 and grid_v[i, j][0] > 0: grid_v[i, j][0] = 0 if j < 3 and grid_v[i, j][1] < 0: grid_v[i, j][1] = 0 if j > n_grid - 3 and grid_v[i, j][1] > 0: grid_v[i, j][1] = 0 for p in x: base = (x[p] * inv_dx - 0.5).cast(int) fx = x[p] * inv_dx - base.cast(float) w = [ 0.5 * ti.sqr(1.5 - fx), 0.75 - ti.sqr(fx - 1.0), 0.5 * ti.sqr(fx - 0.5) ] new_v = ti.Vector.zero(ti.f32, 2) new_C = ti.Matrix.zero(ti.f32, 2, 2) for i, j in ti.static(ti.ndrange(3, 3)): dpos = ti.Vector([i, j]).cast(float) - fx g_v = grid_v[base + ti.Vector([i, j])] weight = w[i][0] * w[j][1] new_v += weight * g_v new_C += 4 * inv_dx * weight * ti.outer_product(g_v, dpos) v[p], C[p] = new_v, new_C x[p] += dt * v[p]