1+ # Material Point Method Simulation
2+ import numpy as np # numpy for linear algebra
3+ import taichi as ti # taichi for fast and parallelized computation
4+
5+ ti .init (arch = ti .cpu )
6+
7+ # ANCHOR: property_def
8+ # simulation setup
9+ grid_size = 128 # background Eulerian grid's resolution, in 2D is [128, 128]
10+ dx = 1.0 / grid_size # the domain size is [1m, 1m] in 2D, so dx for each cell is (1/128)m
11+ dt = 2e-4 # time step size in second
12+ ppc = 8 # average particles per cell
13+
14+ density = 1000 # mass density, unit: kg / m^3
15+ E , nu = 1e4 , 0.3 # block's Young's modulus and Poisson's ratio
16+ mu , lam = E / (2 * (1 + nu )), E * nu / ((1 + nu ) * (1 - 2 * nu )) # Lame parameters
17+ # ANCHOR_END: property_def
18+
19+ # ANCHOR: setting
20+ # uniformly sampling material particles
21+ def uniform_grid (x0 , y0 , x1 , y1 , dx ):
22+ xx , yy = np .meshgrid (np .arange (x0 , x1 + dx , dx ), np .arange (y0 , y1 + dx , dx ))
23+ return np .column_stack ((xx .ravel (), yy .ravel ()))
24+
25+ box1_samples = uniform_grid (0.2 , 0.4 , 0.4 , 0.6 , dx / np .sqrt (ppc ))
26+ box1_velocities = np .tile (np .array ([10.0 , 0 ]), (len (box1_samples ), 1 ))
27+ box2_samples = uniform_grid (0.6 , 0.4 , 0.8 , 0.6 , dx / np .sqrt (ppc ))
28+ box2_velocities = np .tile (np .array ([- 10.0 , 0 ]), (len (box1_samples ), 1 ))
29+ all_samples = np .concatenate ([box1_samples , box2_samples ], axis = 0 )
30+ all_velocities = np .concatenate ([box1_velocities , box2_velocities ], axis = 0 )
31+ # ANCHOR_END: setting
32+
33+ # ANCHOR: data_def
34+ # material particles data
35+ N_particles = len (all_samples )
36+ x = ti .Vector .field (2 , float , N_particles ) # the position of particles
37+ x .from_numpy (all_samples )
38+ v = ti .Vector .field (2 , float , N_particles ) # the velocity of particles
39+ v .from_numpy (all_velocities )
40+ vol = ti .field (float , N_particles ) # the volume of particle
41+ vol .fill (0.2 * 0.4 / N_particles ) # get the volume of each particle as V_rest / N_particles
42+ m = ti .field (float , N_particles ) # the mass of particle
43+ m .fill (vol [0 ] * density )
44+ F = ti .Matrix .field (2 , 2 , float , N_particles ) # the deformation gradient of particles
45+ F .from_numpy (np .tile (np .eye (2 ), (N_particles , 1 , 1 )))
46+
47+ # grid data
48+ grid_m = ti .field (float , (grid_size , grid_size ))
49+ grid_v = ti .Vector .field (2 , float , (grid_size , grid_size ))
50+ # ANCHOR_END: data_def
51+
52+ # ANCHOR: reset_grid
53+ def reset_grid ():
54+ # after each transfer, the grid is reset
55+ grid_m .fill (0 )
56+ grid_v .fill (0 )
57+ # ANCHOR_END: reset_grid
58+
59+ ################################
60+ # Stvk Hencky Elasticity
61+ ################################
62+ # ANCHOR: stvk
63+ @ti .func
64+ def StVK_Hencky_PK1_2D (F ):
65+ U , sig , V = ti .svd (F )
66+ inv_sig = sig .inverse ()
67+ e = ti .Matrix ([[ti .log (sig [0 , 0 ]), 0 ], [0 , ti .log (sig [1 , 1 ])]])
68+ return U @ (2 * mu * inv_sig @ e + lam * e .trace () * inv_sig ) @ V .transpose ()
69+ # ANCHOR_END: stvk
70+
71+ # Particle-to-Grid (P2G) Transfers
72+ # ANCHOR: p2g
73+ @ti .kernel
74+ def particle_to_grid_transfer ():
75+ for p in range (N_particles ):
76+ base = (x [p ] / dx - 0.5 ).cast (int )
77+ fx = x [p ] / dx - base .cast (float )
78+ # quadratic B-spline interpolating functions (Section 26.2)
79+ w = [0.5 * (1.5 - fx ) ** 2 , 0.75 - (fx - 1 ) ** 2 , 0.5 * (fx - 0.5 ) ** 2 ]
80+ # gradient of the interpolating function (Section 26.2)
81+ dw_dx = [fx - 1.5 , 2 * (1.0 - fx ), fx - 0.5 ]
82+
83+ P = StVK_Hencky_PK1_2D (F [p ])
84+ for i in ti .static (range (3 )):
85+ for j in ti .static (range (3 )):
86+ offset = ti .Vector ([i , j ])
87+ weight = w [i ][0 ] * w [j ][1 ]
88+ grad_weight = ti .Vector ([(1. / dx ) * dw_dx [i ][0 ] * w [j ][1 ],
89+ w [i ][0 ] * (1. / dx ) * dw_dx [j ][1 ]])
90+
91+ grid_m [base + offset ] += weight * m [p ] # mass transfer
92+ grid_v [base + offset ] += weight * m [p ] * v [p ] # momentum Transfer, PIC formulation
93+ # internal force (stress) transfer
94+ fi = - vol [p ] * P @ F [p ].transpose () @ grad_weight
95+ grid_v [base + offset ] += dt * fi
96+ # ANCHOR_END: p2g
97+
98+ # Grid Update
99+ # ANCHOR: grid_update
100+ @ti .kernel
101+ def update_grid ():
102+ for i , j in grid_m :
103+ if grid_m [i , j ] > 0 :
104+ grid_v [i , j ] = grid_v [i , j ] / grid_m [i , j ] # extract updated nodal velocity from transferred nodal momentum
105+
106+ # Dirichlet BC near the bounding box
107+ if i <= 3 or i > grid_size - 3 or j <= 2 or j > grid_size - 3 :
108+ grid_v [i , j ] = 0
109+ # ANCHOR_END: grid_update
110+
111+
112+ # Grid-to-Particle (G2P) Transfers
113+ # ANCHOR: g2p
114+ @ti .kernel
115+ def grid_to_particle_transfer ():
116+ for p in range (N_particles ):
117+ base = (x [p ] / dx - 0.5 ).cast (int )
118+ fx = x [p ] / dx - base .cast (float )
119+ # quadratic B-spline interpolating functions (Section 26.2)
120+ w = [0.5 * (1.5 - fx ) ** 2 , 0.75 - (fx - 1 ) ** 2 , 0.5 * (fx - 0.5 ) ** 2 ]
121+ # gradient of the interpolating function (Section 26.2)
122+ dw_dx = [fx - 1.5 , 2 * (1.0 - fx ), fx - 0.5 ]
123+
124+ new_v = ti .Vector .zero (float , 2 )
125+ v_grad_wT = ti .Matrix .zero (float , 2 , 2 )
126+ for i in ti .static (range (3 )):
127+ for j in ti .static (range (3 )):
128+ offset = ti .Vector ([i , j ])
129+ weight = w [i ][0 ] * w [j ][1 ]
130+ grad_weight = ti .Vector ([(1. / dx ) * dw_dx [i ][0 ] * w [j ][1 ],
131+ w [i ][0 ] * (1. / dx ) * dw_dx [j ][1 ]])
132+
133+ new_v += weight * grid_v [base + offset ]
134+ v_grad_wT += grid_v [base + offset ].outer_product (grad_weight )
135+
136+ v [p ] = new_v
137+ F [p ] = (ti .Matrix .identity (float , 2 ) + dt * v_grad_wT ) @ F [p ]
138+ # ANCHOR_END: g2p
139+
140+ # Deformation Gradient and Particle State Update
141+ # ANCHOR: particle_update
142+ @ti .kernel
143+ def update_particle_state ():
144+ for p in range (N_particles ):
145+ x [p ] += dt * v [p ] # advection
146+ # ANCHOR_END: particle_update
147+
148+ # ANCHOR: time_step
149+ def step ():
150+ # a single time step of the Material Point Method (MPM) simulation
151+ reset_grid ()
152+ particle_to_grid_transfer ()
153+ update_grid ()
154+ grid_to_particle_transfer ()
155+ update_particle_state ()
156+ # ANCHOR_END: time_step
157+
158+ ################################
159+ # Main
160+ ################################
161+ gui = ti .GUI ("2D MPM Elasticity" , res = 512 , background_color = 0xFFFFFF )
162+ while True :
163+ for s in range (50 ): step ()
164+
165+ gui .circles (x .to_numpy ()[:len (box1_samples )], radius = 3 , color = 0xFF0000 )
166+ gui .circles (x .to_numpy ()[len (box1_samples ):], radius = 3 , color = 0x0000FF )
167+ gui .show ()
0 commit comments