Skip to content

Commit 8cba306

Browse files
committed
switched to NH
1 parent c3ff981 commit 8cba306

6 files changed

Lines changed: 292 additions & 112 deletions

File tree

9_reduced_DOF/MassSpringEnergy.py

Lines changed: 0 additions & 35 deletions
This file was deleted.

9_reduced_DOF/NeoHookeanEnergy.py

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
# ANCHOR: helper_func
2+
import utils
3+
import numpy as np
4+
import math
5+
6+
def polar_svd(F):
7+
[U, s, VT] = np.linalg.svd(F)
8+
if np.linalg.det(U) < 0:
9+
U[:, 1] = -U[:, 1]
10+
s[1] = -s[1]
11+
if np.linalg.det(VT) < 0:
12+
VT[1, :] = -VT[1, :]
13+
s[1] = -s[1]
14+
return [U, s, VT]
15+
16+
def dPsi_div_dsigma(s, mu, lam):
17+
ln_sigma_prod = math.log(s[0] * s[1])
18+
inv0 = 1.0 / s[0]
19+
dPsi_dsigma_0 = mu * (s[0] - inv0) + lam * inv0 * ln_sigma_prod
20+
inv1 = 1.0 / s[1]
21+
dPsi_dsigma_1 = mu * (s[1] - inv1) + lam * inv1 * ln_sigma_prod
22+
return [dPsi_dsigma_0, dPsi_dsigma_1]
23+
24+
def d2Psi_div_dsigma2(s, mu, lam):
25+
ln_sigma_prod = math.log(s[0] * s[1])
26+
inv2_0 = 1 / (s[0] * s[0])
27+
d2Psi_dsigma2_00 = mu * (1 + inv2_0) - lam * inv2_0 * (ln_sigma_prod - 1)
28+
inv2_1 = 1 / (s[1] * s[1])
29+
d2Psi_dsigma2_11 = mu * (1 + inv2_1) - lam * inv2_1 * (ln_sigma_prod - 1)
30+
d2Psi_dsigma2_01 = lam / (s[0] * s[1])
31+
return [[d2Psi_dsigma2_00, d2Psi_dsigma2_01], [d2Psi_dsigma2_01, d2Psi_dsigma2_11]]
32+
33+
def B_left_coef(s, mu, lam):
34+
sigma_prod = s[0] * s[1]
35+
return (mu + (mu - lam * math.log(sigma_prod)) / sigma_prod) / 2
36+
37+
def Psi(F, mu, lam):
38+
J = np.linalg.det(F)
39+
lnJ = math.log(J)
40+
return mu / 2 * (np.trace(np.transpose(F).dot(F)) - 2) - mu * lnJ + lam / 2 * lnJ * lnJ
41+
42+
def dPsi_div_dF(F, mu, lam):
43+
FinvT = np.transpose(np.linalg.inv(F))
44+
return mu * (F - FinvT) + lam * math.log(np.linalg.det(F)) * FinvT
45+
46+
def d2Psi_div_dF2(F, mu, lam, project_PSD = True):
47+
[U, sigma, VT] = polar_svd(F)
48+
49+
Psi_sigma_sigma = np.array(d2Psi_div_dsigma2(sigma, mu, lam))
50+
if project_PSD:
51+
Psi_sigma_sigma = utils.make_PSD(Psi_sigma_sigma)
52+
53+
B_left = B_left_coef(sigma, mu, lam)
54+
Psi_sigma = dPsi_div_dsigma(sigma, mu, lam)
55+
B_right = (Psi_sigma[0] + Psi_sigma[1]) / (2 * max(sigma[0] + sigma[1], 1e-6))
56+
B = np.array([[B_left + B_right, B_left - B_right], [B_left - B_right, B_left + B_right]])
57+
if project_PSD:
58+
B = utils.make_PSD(B)
59+
60+
M = np.array([[0, 0, 0, 0]] * 4)
61+
M[0, 0] = Psi_sigma_sigma[0, 0]
62+
M[0, 3] = Psi_sigma_sigma[0, 1]
63+
M[1, 1] = B[0, 0]
64+
M[1, 2] = B[0, 1]
65+
M[2, 1] = B[1, 0]
66+
M[2, 2] = B[1, 1]
67+
M[3, 0] = Psi_sigma_sigma[1, 0]
68+
M[3, 3] = Psi_sigma_sigma[1, 1]
69+
70+
dP_div_dF = np.array([[0, 0, 0, 0]] * 4)
71+
for j in range(0, 2):
72+
for i in range(0, 2):
73+
ij = j * 2 + i
74+
for s in range(0, 2):
75+
for r in range(0, 2):
76+
rs = s * 2 + r
77+
dP_div_dF[ij, rs] = M[0, 0] * U[i, 0] * VT[0, j] * U[r, 0] * VT[0, s] \
78+
+ M[0, 3] * U[i, 0] * VT[0, j] * U[r, 1] * VT[1, s] \
79+
+ M[1, 1] * U[i, 1] * VT[0, j] * U[r, 1] * VT[0, s] \
80+
+ M[1, 2] * U[i, 1] * VT[0, j] * U[r, 0] * VT[1, s] \
81+
+ M[2, 1] * U[i, 0] * VT[1, j] * U[r, 1] * VT[0, s] \
82+
+ M[2, 2] * U[i, 0] * VT[1, j] * U[r, 0] * VT[1, s] \
83+
+ M[3, 0] * U[i, 1] * VT[1, j] * U[r, 0] * VT[0, s] \
84+
+ M[3, 3] * U[i, 1] * VT[1, j] * U[r, 1] * VT[1, s]
85+
return dP_div_dF
86+
# ANCHOR_END: helper_func
87+
88+
# ANCHOR: stress_deriv
89+
def deformation_grad(x, elemVInd, IB):
90+
F = [x[elemVInd[1]] - x[elemVInd[0]], x[elemVInd[2]] - x[elemVInd[0]]]
91+
return np.transpose(F).dot(IB)
92+
93+
def dPsi_div_dx(P, IB): # applying chain-rule, dPsi_div_dx = dPsi_div_dF * dF_div_dx
94+
dPsi_dx_2 = P[0, 0] * IB[0, 0] + P[0, 1] * IB[0, 1]
95+
dPsi_dx_3 = P[1, 0] * IB[0, 0] + P[1, 1] * IB[0, 1]
96+
dPsi_dx_4 = P[0, 0] * IB[1, 0] + P[0, 1] * IB[1, 1]
97+
dPsi_dx_5 = P[1, 0] * IB[1, 0] + P[1, 1] * IB[1, 1]
98+
return [np.array([-dPsi_dx_2 - dPsi_dx_4, -dPsi_dx_3 - dPsi_dx_5]), np.array([dPsi_dx_2, dPsi_dx_3]), np.array([dPsi_dx_4, dPsi_dx_5])]
99+
100+
def d2Psi_div_dx2(dP_div_dF, IB): # applying chain-rule, d2Psi_div_dx2 = dF_div_dx^T * d2Psi_div_dF2 * dF_div_dx (note that d2F_div_dx2 = 0)
101+
intermediate = np.array([[0.0, 0.0, 0.0, 0.0]] * 6)
102+
for colI in range(0, 4):
103+
_000 = dP_div_dF[0, colI] * IB[0, 0]
104+
_010 = dP_div_dF[0, colI] * IB[1, 0]
105+
_101 = dP_div_dF[2, colI] * IB[0, 1]
106+
_111 = dP_div_dF[2, colI] * IB[1, 1]
107+
_200 = dP_div_dF[1, colI] * IB[0, 0]
108+
_210 = dP_div_dF[1, colI] * IB[1, 0]
109+
_301 = dP_div_dF[3, colI] * IB[0, 1]
110+
_311 = dP_div_dF[3, colI] * IB[1, 1]
111+
intermediate[2, colI] = _000 + _101
112+
intermediate[3, colI] = _200 + _301
113+
intermediate[4, colI] = _010 + _111
114+
intermediate[5, colI] = _210 + _311
115+
intermediate[0, colI] = -intermediate[2, colI] - intermediate[4, colI]
116+
intermediate[1, colI] = -intermediate[3, colI] - intermediate[5, colI]
117+
result = np.array([[0.0, 0.0, 0.0, 0.0, 0.0, 0.0]] * 6)
118+
for colI in range(0, 6):
119+
_000 = intermediate[colI, 0] * IB[0, 0]
120+
_010 = intermediate[colI, 0] * IB[1, 0]
121+
_101 = intermediate[colI, 2] * IB[0, 1]
122+
_111 = intermediate[colI, 2] * IB[1, 1]
123+
_200 = intermediate[colI, 1] * IB[0, 0]
124+
_210 = intermediate[colI, 1] * IB[1, 0]
125+
_301 = intermediate[colI, 3] * IB[0, 1]
126+
_311 = intermediate[colI, 3] * IB[1, 1]
127+
result[2, colI] = _000 + _101
128+
result[3, colI] = _200 + _301
129+
result[4, colI] = _010 + _111
130+
result[5, colI] = _210 + _311
131+
result[0, colI] = -_000 - _101 - _010 - _111
132+
result[1, colI] = -_200 - _301 - _210 - _311
133+
return result
134+
# ANCHOR_END: stress_deriv
135+
136+
# ANCHOR: val_grad_hess
137+
def val(x, e, vol, IB, mu, lam):
138+
sum = 0.0
139+
for i in range(0, len(e)):
140+
F = deformation_grad(x, e[i], IB[i])
141+
sum += vol[i] * Psi(F, mu[i], lam[i])
142+
return sum
143+
144+
def grad(x, e, vol, IB, mu, lam):
145+
g = np.array([[0.0, 0.0]] * len(x))
146+
for i in range(0, len(e)):
147+
F = deformation_grad(x, e[i], IB[i])
148+
P = vol[i] * dPsi_div_dF(F, mu[i], lam[i])
149+
g_local = dPsi_div_dx(P, IB[i])
150+
for j in range(0, 3):
151+
g[e[i][j]] += g_local[j]
152+
return g
153+
154+
def hess(x, e, vol, IB, mu, lam, project_PSD = True):
155+
IJV = [[0] * (len(e) * 36), [0] * (len(e) * 36), np.array([0.0] * (len(e) * 36))]
156+
for i in range(0, len(e)):
157+
F = deformation_grad(x, e[i], IB[i])
158+
dP_div_dF = vol[i] * d2Psi_div_dF2(F, mu[i], lam[i], project_PSD)
159+
local_hess = d2Psi_div_dx2(dP_div_dF, IB[i])
160+
for xI in range(0, 3):
161+
for xJ in range(0, 3):
162+
for dI in range(0, 2):
163+
for dJ in range(0, 2):
164+
ind = i * 36 + (xI * 3 + xJ) * 4 + dI * 2 + dJ
165+
IJV[0][ind] = e[i][xI] * 2 + dI
166+
IJV[1][ind] = e[i][xJ] * 2 + dJ
167+
IJV[2][ind] = local_hess[xI * 2 + dI, xJ * 2 + dJ]
168+
return IJV
169+
# ANCHOR_END: val_grad_hess
170+
171+
# ANCHOR: filter_line_search
172+
def init_step_size(x, e, p):
173+
alpha = 1
174+
for i in range(0, len(e)):
175+
x21 = x[e[i][1]] - x[e[i][0]]
176+
x31 = x[e[i][2]] - x[e[i][0]]
177+
p21 = p[e[i][1]] - p[e[i][0]]
178+
p31 = p[e[i][2]] - p[e[i][0]]
179+
detT = np.linalg.det(np.transpose([x21, x31]))
180+
a = np.linalg.det(np.transpose([p21, p31])) / detT
181+
b = (np.linalg.det(np.transpose([x21, p31])) + np.linalg.det(np.transpose([p21, x31]))) / detT
182+
c = 0.9 # solve for alpha that first brings the new volume to 0.1x the old volume for slackness
183+
critical_alpha = utils.smallest_positive_real_root_quad(a, b, c)
184+
if critical_alpha > 0:
185+
alpha = min(alpha, critical_alpha)
186+
return alpha
187+
# ANCHOR_END: filter_line_search

9_reduced_DOF/simulator.py

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,32 +10,38 @@
1010

1111
# simulation setup
1212
side_len = 1
13-
rho = 1000 # density of square
14-
k = 2e5 # spring stiffness
15-
n_seg = 4 # num of segments per side of the square
16-
h = 0.005 # time step size in s
13+
rho = 2000 # density of square
14+
E = 1e4 # Young's modulus
15+
nu = 0.4 # Poisson's ratio
16+
n_seg = 10 # num of segments per side of the square
17+
h = 0.04 # time step size in s
1718
DBC = [] # no nodes need to be fixed
1819
y_ground = -1 # height of the planar ground
1920

2021
# initialize simulation
2122
[x, e] = square_mesh.generate(side_len, n_seg) # node positions and edge node indices
2223
v = np.array([[0.0, 0.0]] * len(x)) # velocity
2324
m = [rho * side_len * side_len / ((n_seg + 1) * (n_seg + 1))] * len(x) # calculate node mass evenly
24-
# rest length squared
25-
l2 = []
25+
# ANCHOR: elem_precomp
26+
# rest shape basis, volume, and lame parameters
27+
vol = [0.0] * len(e)
28+
IB = [np.array([[0.0, 0.0]] * 2)] * len(e)
2629
for i in range(0, len(e)):
27-
diff = x[e[i][0]] - x[e[i][1]]
28-
l2.append(diff.dot(diff))
29-
k = [k] * len(e) # spring stiffness
30+
TB = [x[e[i][1]] - x[e[i][0]], x[e[i][2]] - x[e[i][0]]]
31+
vol[i] = np.linalg.det(np.transpose(TB)) / 2
32+
IB[i] = np.linalg.inv(np.transpose(TB))
33+
mu_lame = [0.5 * E / (1 + nu)] * len(e)
34+
lam = [E * nu / ((1 + nu) * (1 - 2 * nu))] * len(e)
35+
# ANCHOR_END: elem_precomp
3036
# identify whether a node is Dirichlet
3137
is_DBC = [False] * len(x)
3238
for i in DBC:
3339
is_DBC[i] = True
3440
# ANCHOR: contact_area
3541
contact_area = [side_len / n_seg] * len(x) # perimeter split to each node
3642
# ANCHOR_END: contact_area
37-
# compute reduced basis using 0: polynomial functions or 1: modal reduction
38-
reduced_basis = utils.compute_reduced_basis(x, e, l2, k, is_DBC, method=0, order=3)
43+
# compute reduced basis using 0: no reduction; 1: polynomial functions; 2: modal reduction
44+
reduced_basis = utils.compute_reduced_basis(x, e, vol, IB, mu_lame, lam, method=1, order=2)
3945

4046
# simulation with visualization
4147
resolution = np.array([900, 900])
@@ -45,7 +51,7 @@ def screen_projection(x):
4551
return [offset[0] + scale * x[0], resolution[1] - (offset[1] + scale * x[1])]
4652

4753
time_step = 0
48-
square_mesh.write_to_file(time_step, x, n_seg)
54+
square_mesh.write_to_file(time_step, x, e)
4955
screen = pygame.display.set_mode(resolution)
5056
running = True
5157
while running:
@@ -60,16 +66,20 @@ def screen_projection(x):
6066
screen.fill((255, 255, 255))
6167
pygame.draw.aaline(screen, (0, 0, 255), screen_projection([-2, y_ground]), screen_projection([2, y_ground])) # ground
6268
for eI in e:
69+
# ANCHOR: draw_tri
6370
pygame.draw.aaline(screen, (0, 0, 255), screen_projection(x[eI[0]]), screen_projection(x[eI[1]]))
71+
pygame.draw.aaline(screen, (0, 0, 255), screen_projection(x[eI[1]]), screen_projection(x[eI[2]]))
72+
pygame.draw.aaline(screen, (0, 0, 255), screen_projection(x[eI[2]]), screen_projection(x[eI[0]]))
73+
# ANCHOR_END: draw_tri
6474
for xI in x:
6575
pygame.draw.circle(screen, (0, 0, 255), screen_projection(xI), 0.1 * side_len / n_seg * scale)
6676

6777
pygame.display.flip() # flip the display
6878

6979
# step forward simulation and wait for screen refresh
70-
[x, v] = time_integrator.step_forward(x, e, v, m, l2, k, y_ground, contact_area, is_DBC, reduced_basis, h, 1e-2)
80+
[x, v] = time_integrator.step_forward(x, e, v, m, vol, IB, mu_lame, lam, y_ground, contact_area, is_DBC, reduced_basis, h, 1e-2)
7181
time_step += 1
7282
pygame.time.wait(int(h * 1000))
73-
square_mesh.write_to_file(time_step, x, n_seg)
83+
square_mesh.write_to_file(time_step, x, e)
7484

7585
pygame.quit()

9_reduced_DOF/square_mesh.py

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,23 @@ def generate(side_length, n_seg):
99
for j in range(0, n_seg + 1):
1010
x[i * (n_seg + 1) + j] = [-side_length / 2 + i * step, -side_length / 2 + j * step]
1111

12-
# connect the nodes with edges
12+
# ANCHOR: tri_vert_ind
13+
# connect the nodes with triangle elements
1314
e = []
14-
# horizontal edges
15-
for i in range(0, n_seg):
16-
for j in range(0, n_seg + 1):
17-
e.append([i * (n_seg + 1) + j, (i + 1) * (n_seg + 1) + j])
18-
# vertical edges
19-
for i in range(0, n_seg + 1):
20-
for j in range(0, n_seg):
21-
e.append([i * (n_seg + 1) + j, i * (n_seg + 1) + j + 1])
22-
# diagonals
2315
for i in range(0, n_seg):
2416
for j in range(0, n_seg):
25-
e.append([i * (n_seg + 1) + j, (i + 1) * (n_seg + 1) + j + 1])
26-
e.append([(i + 1) * (n_seg + 1) + j, i * (n_seg + 1) + j + 1])
17+
# triangulate each cell following a symmetric pattern:
18+
if (i % 2)^(j % 2):
19+
e.append([i * (n_seg + 1) + j, (i + 1) * (n_seg + 1) + j, i * (n_seg + 1) + j + 1])
20+
e.append([(i + 1) * (n_seg + 1) + j, (i + 1) * (n_seg + 1) + j + 1, i * (n_seg + 1) + j + 1])
21+
else:
22+
e.append([i * (n_seg + 1) + j, (i + 1) * (n_seg + 1) + j, (i + 1) * (n_seg + 1) + j + 1])
23+
e.append([i * (n_seg + 1) + j, (i + 1) * (n_seg + 1) + j + 1, i * (n_seg + 1) + j + 1])
24+
# ANCHOR_END: tri_vert_ind
2725

2826
return [x, e]
2927

30-
def write_to_file(frameNum, x, n_seg):
28+
def write_to_file(frameNum, x, e):
3129
# Check if 'output' directory exists; if not, create it
3230
if not os.path.exists('output'):
3331
os.makedirs('output')
@@ -39,8 +37,6 @@ def write_to_file(frameNum, x, n_seg):
3937
for row in x:
4038
f.write(f"v {float(row[0]):.6f} {float(row[1]):.6f} 0.0\n")
4139
# write vertex indices for each triangle
42-
for i in range(0, n_seg):
43-
for j in range(0, n_seg):
44-
#NOTE: each cell is exported as 2 triangles for rendering
45-
f.write(f"f {i * (n_seg+1) + j + 1} {(i+1) * (n_seg+1) + j + 1} {(i+1) * (n_seg+1) + j+1 + 1}\n")
46-
f.write(f"f {i * (n_seg+1) + j + 1} {(i+1) * (n_seg+1) + j+1 + 1} {i * (n_seg+1) + j+1 + 1}\n")
40+
for row in e:
41+
#NOTE: vertex indices start from 1 in obj file format
42+
f.write(f"f {row[0] + 1} {row[1] + 1} {row[2] + 1}\n")

0 commit comments

Comments
 (0)