Skip to content

Commit 28d9d14

Browse files
committed
init mov_dirichlet
1 parent 1af750b commit 28d9d14

10 files changed

Lines changed: 382 additions & 0 deletions

mov_dirichlet/BarrierEnergy.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import math
2+
import numpy as np
3+
4+
dhat = 0.01
5+
kappa = 1e5
6+
7+
def val(x, n, o, contact_area):
8+
sum = 0.0
9+
for i in range(0, len(x)):
10+
d = n.dot(x[i] - o)
11+
if d < dhat:
12+
s = d / dhat
13+
sum += contact_area[i] * dhat * kappa / 2 * (s - 1) * math.log(s)
14+
return sum
15+
16+
def grad(x, n, o, contact_area):
17+
g = np.array([[0.0, 0.0]] * len(x))
18+
for i in range(0, len(x)):
19+
d = n.dot(x[i] - o)
20+
if d < dhat:
21+
s = d / dhat
22+
g[i] = contact_area[i] * dhat * (kappa / 2 * (math.log(s) / dhat + (s - 1) / d)) * n
23+
return g
24+
25+
def hess(x, n, o, contact_area):
26+
IJV = [[0] * 0, [0] * 0, np.array([0.0] * 0)]
27+
for i in range(0, len(x)):
28+
d = n.dot(x[i] - o)
29+
if d < dhat:
30+
local_hess = contact_area[i] * dhat * kappa / (2 * d * d * dhat) * (d + dhat) * np.outer(n, n)
31+
for c in range(0, 2):
32+
for r in range(0, 2):
33+
IJV[0].append(i * 2 + r)
34+
IJV[1].append(i * 2 + c)
35+
IJV[2] = np.append(IJV[2], local_hess[r, c])
36+
return IJV
37+
38+
def init_step_size(x, n, o, p):
39+
alpha = 1
40+
for i in range(0, len(x)):
41+
p_n = p[i].dot(n)
42+
if p_n < 0:
43+
alpha = min(alpha, 0.9 * n.dot(x[i] - o) / -p_n)
44+
return alpha
45+
46+
def compute_mu_lambda(x, n, o, contact_area, mu):
47+
mu_lambda = np.array([0.0] * len(x))
48+
for i in range(0, len(x)):
49+
d = n.dot(x[i] - o)
50+
if d < dhat:
51+
s = d / dhat
52+
mu_lambda[i] = mu * -contact_area[i] * dhat * (kappa / 2 * (math.log(s) / dhat + (s - 1) / d))
53+
return mu_lambda

mov_dirichlet/FrictionEnergy.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import numpy as np
2+
import utils
3+
4+
epsv = 1e-3
5+
6+
def f0(vbarnorm, epsv, hhat):
7+
if vbarnorm >= epsv:
8+
return vbarnorm * hhat
9+
else:
10+
vbarnormhhat = vbarnorm * hhat
11+
epsvhhat = epsv * hhat
12+
return vbarnormhhat * vbarnormhhat * (-vbarnormhhat / 3.0 + epsvhhat) / (epsvhhat * epsvhhat) + epsvhhat / 3.0
13+
14+
def f1_div_vbarnorm(vbarnorm, epsv):
15+
if vbarnorm >= epsv:
16+
return 1.0 / vbarnorm
17+
else:
18+
return (-vbarnorm + 2.0 * epsv) / (epsv * epsv)
19+
20+
def f_hess_term(vbarnorm, epsv):
21+
if vbarnorm >= epsv:
22+
return -1.0 / (vbarnorm * vbarnorm)
23+
else:
24+
return -1.0 / (epsv * epsv)
25+
26+
def val(v, mu_lambda, hhat, n):
27+
sum = 0.0
28+
T = np.identity(2) - np.outer(n, n) # tangent of slope is constant
29+
for i in range(0, len(v)):
30+
if mu_lambda[i] > 0:
31+
vbar = np.transpose(T).dot(v[i])
32+
sum += mu_lambda[i] * f0(np.linalg.norm(vbar), epsv, hhat)
33+
return sum
34+
35+
def grad(v, mu_lambda, hhat, n):
36+
g = np.array([[0.0, 0.0]] * len(v))
37+
T = np.identity(2) - np.outer(n, n) # tangent of slope is constant
38+
for i in range(0, len(v)):
39+
if mu_lambda[i] > 0:
40+
vbar = np.transpose(T).dot(v[i])
41+
g[i] = mu_lambda[i] * f1_div_vbarnorm(np.linalg.norm(vbar), epsv) * T.dot(vbar)
42+
return g
43+
44+
def hess(v, mu_lambda, hhat, n):
45+
IJV = [[0] * 0, [0] * 0, np.array([0.0] * 0)]
46+
T = np.identity(2) - np.outer(n, n) # tangent of slope is constant
47+
for i in range(0, len(v)):
48+
if mu_lambda[i] > 0:
49+
vbar = np.transpose(T).dot(v[i])
50+
vbarnorm = np.linalg.norm(vbar)
51+
inner_term = f1_div_vbarnorm(vbarnorm, epsv) * np.identity(2)
52+
if vbarnorm != 0:
53+
inner_term += f_hess_term(vbarnorm, epsv) / vbarnorm * np.outer(vbar, vbar)
54+
local_hess = mu_lambda[i] * T.dot(utils.make_PD(inner_term)).dot(np.transpose(T)) / hhat
55+
for c in range(0, 2):
56+
for r in range(0, 2):
57+
IJV[0].append(i * 2 + r)
58+
IJV[1].append(i * 2 + c)
59+
IJV[2] = np.append(IJV[2], local_hess[r, c])
60+
return IJV

mov_dirichlet/GravityEnergy.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import numpy as np
2+
3+
gravity = [0.0, -9.81]
4+
5+
def val(x, m):
6+
sum = 0.0
7+
for i in range(0, len(x)):
8+
sum += -m[i] * x[i].dot(gravity)
9+
return sum
10+
11+
def grad(x, m):
12+
g = np.array([gravity] * len(x))
13+
for i in range(0, len(x)):
14+
g[i] *= -m[i]
15+
return g
16+
17+
# Hessian is 0

mov_dirichlet/InertiaEnergy.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import numpy as np
2+
3+
def val(x, x_tilde, m):
4+
sum = 0.0
5+
for i in range(0, len(x)):
6+
diff = x[i] - x_tilde[i]
7+
sum += 0.5 * m[i] * diff.dot(diff)
8+
return sum
9+
10+
def grad(x, x_tilde, m):
11+
g = np.array([[0.0, 0.0]] * len(x))
12+
for i in range(0, len(x)):
13+
g[i] = m[i] * (x[i] - x_tilde[i])
14+
return g
15+
16+
def hess(x, x_tilde, m):
17+
IJV = [[0] * (len(x) * 2), [0] * (len(x) * 2), np.array([0.0] * (len(x) * 2))]
18+
for i in range(0, len(x)):
19+
for d in range(0, 2):
20+
IJV[0][i * 2 + d] = i * 2 + d
21+
IJV[1][i * 2 + d] = i * 2 + d
22+
IJV[2][i * 2 + d] = m[i]
23+
return IJV

mov_dirichlet/MassSpringEnergy.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import numpy as np
2+
import utils
3+
4+
def val(x, e, l2, k):
5+
sum = 0.0
6+
for i in range(0, len(e)):
7+
diff = x[e[i][0]] - x[e[i][1]]
8+
sum += l2[i] * 0.5 * k[i] * (diff.dot(diff) / l2[i] - 1) ** 2
9+
return sum
10+
11+
def grad(x, e, l2, k):
12+
g = np.array([[0.0, 0.0]] * len(x))
13+
for i in range(0, len(e)):
14+
diff = x[e[i][0]] - x[e[i][1]]
15+
g_diff = 2 * k[i] * (diff.dot(diff) / l2[i] - 1) * diff
16+
g[e[i][0]] += g_diff
17+
g[e[i][1]] -= g_diff
18+
return g
19+
20+
def hess(x, e, l2, k):
21+
IJV = [[0] * (len(e) * 16), [0] * (len(e) * 16), np.array([0.0] * (len(e) * 16))]
22+
for i in range(0, len(e)):
23+
diff = x[e[i][0]] - x[e[i][1]]
24+
H_diff = 2 * k[i] / l2[i] * (2 * np.outer(diff, diff) + (diff.dot(diff) - l2[i]) * np.identity(2))
25+
H_local = utils.make_PD(np.block([[H_diff, -H_diff], [-H_diff, H_diff]]))
26+
# add to global matrix
27+
for nI in range(0, 2):
28+
for nJ in range(0, 2):
29+
indStart = i * 16 + (nI * 2 + nJ) * 4
30+
for r in range(0, 2):
31+
for c in range(0, 2):
32+
IJV[0][indStart + r * 2 + c] = e[i][nI] * 2 + r
33+
IJV[1][indStart + r * 2 + c] = e[i][nJ] * 2 + c
34+
IJV[2][indStart + r * 2 + c] = H_local[nI * 2 + r, nJ * 2 + c]
35+
return IJV

mov_dirichlet/readme.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Mass-Spring Solids Simulation
2+
3+
A square sliding/residing on a slope under gravity is simulated with mass-spring elasticity potential and implicit Euler time integration.
4+
Each time step is solved by minimizing the Incremental Potential with the projected Newton method.
5+
6+
## Dependencies
7+
```
8+
pip install numpy scipy pygame
9+
```
10+
11+
## Run
12+
```
13+
python simulator.py
14+
```

mov_dirichlet/simulator.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# Mass-Spring Solids Simulation
2+
3+
import numpy as np # numpy for linear algebra
4+
import pygame # pygame for visualization
5+
pygame.init()
6+
7+
import square_mesh # square mesh
8+
import time_integrator
9+
10+
# simulation setup
11+
side_len = 1
12+
rho = 1000 # density of square
13+
k = 2e4 # spring stiffness
14+
n_seg = 4 # num of segments per side of the square
15+
h = 0.01 # time step size in s
16+
DBC = [] # no nodes need to be fixed
17+
ground_n = np.array([0.1, 1.0]) # normal of the slope
18+
ground_n /= np.linalg.norm(ground_n) # normalize ground normal vector just in case
19+
ground_o = np.array([0.0, -1.0]) # a point on the slope
20+
mu = 0.11 # friction coefficient of the slope
21+
22+
# initialize simulation
23+
[x, e] = square_mesh.generate(side_len, n_seg) # node positions and edge node indices
24+
v = np.array([[0.0, 0.0]] * len(x)) # velocity
25+
m = [rho * side_len * side_len / ((n_seg + 1) * (n_seg + 1))] * len(x) # calculate node mass evenly
26+
# rest length squared
27+
l2 = []
28+
for i in range(0, len(e)):
29+
diff = x[e[i][0]] - x[e[i][1]]
30+
l2.append(diff.dot(diff))
31+
k = [k] * len(e) # spring stiffness
32+
# identify whether a node is Dirichlet
33+
is_DBC = [False] * len(x)
34+
for i in DBC:
35+
is_DBC[i] = True
36+
contact_area = [side_len / n_seg] * len(x) # perimeter split to each node
37+
38+
# simulation with visualization
39+
resolution = np.array([900, 900])
40+
offset = resolution / 2
41+
scale = 200
42+
def screen_projection(x):
43+
return [offset[0] + scale * x[0], resolution[1] - (offset[1] + scale * x[1])]
44+
45+
time_step = 0
46+
screen = pygame.display.set_mode(resolution)
47+
running = True
48+
while running:
49+
# run until the user asks to quit
50+
for event in pygame.event.get():
51+
if event.type == pygame.QUIT:
52+
running = False
53+
54+
print('### Time step', time_step, '###')
55+
56+
# fill the background and draw the square
57+
screen.fill((255, 255, 255))
58+
pygame.draw.aaline(screen, (0, 0, 255), screen_projection([ground_o[0] - 3.0 * ground_n[1], ground_o[1] + 3.0 * ground_n[0]]),
59+
screen_projection([ground_o[0] + 3.0 * ground_n[1], ground_o[1] - 3.0 * ground_n[0]])) # slope
60+
for eI in e:
61+
pygame.draw.aaline(screen, (0, 0, 255), screen_projection(x[eI[0]]), screen_projection(x[eI[1]]))
62+
for xI in x:
63+
pygame.draw.circle(screen, (0, 0, 255), screen_projection(xI), 0.1 * side_len / n_seg * scale)
64+
65+
pygame.display.flip() # flip the display
66+
67+
# step forward simulation and wait for screen refresh
68+
[x, v] = time_integrator.step_forward(x, e, v, m, l2, k, ground_n, ground_o, contact_area, mu, is_DBC, h, 1e-2)
69+
time_step += 1
70+
pygame.time.wait(int(h * 1000))
71+
72+
pygame.quit()

mov_dirichlet/square_mesh.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import numpy as np
2+
3+
def generate(side_length, n_seg):
4+
# sample nodes uniformly on a square
5+
x = np.array([[0.0, 0.0]] * ((n_seg + 1) ** 2))
6+
step = side_length / n_seg
7+
for i in range(0, n_seg + 1):
8+
for j in range(0, n_seg + 1):
9+
x[i * (n_seg + 1) + j] = [-side_length / 2 + i * step, -side_length / 2 + j * step]
10+
11+
# connect the nodes with edges
12+
e = []
13+
# horizontal edges
14+
for i in range(0, n_seg):
15+
for j in range(0, n_seg + 1):
16+
e.append([i * (n_seg + 1) + j, (i + 1) * (n_seg + 1) + j])
17+
# vertical edges
18+
for i in range(0, n_seg + 1):
19+
for j in range(0, n_seg):
20+
e.append([i * (n_seg + 1) + j, i * (n_seg + 1) + j + 1])
21+
# diagonals
22+
for i in range(0, n_seg):
23+
for j in range(0, n_seg):
24+
e.append([i * (n_seg + 1) + j, (i + 1) * (n_seg + 1) + j + 1])
25+
e.append([(i + 1) * (n_seg + 1) + j, i * (n_seg + 1) + j + 1])
26+
27+
return [x, e]

mov_dirichlet/time_integrator.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import copy
2+
from cmath import inf
3+
4+
import numpy as np
5+
import numpy.linalg as LA
6+
import scipy.sparse as sparse
7+
from scipy.sparse.linalg import spsolve
8+
9+
import InertiaEnergy
10+
import MassSpringEnergy
11+
import GravityEnergy
12+
import BarrierEnergy
13+
import FrictionEnergy
14+
15+
def step_forward(x, e, v, m, l2, k, n, o, contact_area, mu, is_DBC, h, tol):
16+
x_tilde = x + v * h # implicit Euler predictive position
17+
x_n = copy.deepcopy(x)
18+
mu_lambda = BarrierEnergy.compute_mu_lambda(x, n, o, contact_area, mu) # compute mu * lambda for each node using x^n
19+
20+
# Newton loop
21+
iter = 0
22+
E_last = IP_val(x, e, x_tilde, m, l2, k, n, o, contact_area, (x - x_n) / h, mu_lambda, h)
23+
p = search_dir(x, e, x_tilde, m, l2, k, n, o, contact_area, (x - x_n) / h, mu_lambda, is_DBC, h)
24+
while LA.norm(p, inf) / h > tol:
25+
print('Iteration', iter, ':')
26+
print('residual =', LA.norm(p, inf) / h)
27+
28+
# filter line search
29+
alpha = BarrierEnergy.init_step_size(x, n, o, p) # avoid interpenetration and tunneling
30+
while IP_val(x + alpha * p, e, x_tilde, m, l2, k, n, o, contact_area, (x - x_n) / h, mu_lambda, h) > E_last:
31+
alpha /= 2
32+
print('step size =', alpha)
33+
34+
x += alpha * p
35+
E_last = IP_val(x, e, x_tilde, m, l2, k, n, o, contact_area, (x - x_n) / h, mu_lambda, h)
36+
p = search_dir(x, e, x_tilde, m, l2, k, n, o, contact_area, (x - x_n) / h, mu_lambda, is_DBC, h)
37+
iter += 1
38+
39+
v = (x - x_n) / h # implicit Euler velocity update
40+
return [x, v]
41+
42+
def IP_val(x, e, x_tilde, m, l2, k, n, o, contact_area, v, mu_lambda, h):
43+
return InertiaEnergy.val(x, x_tilde, m) + h * h * (MassSpringEnergy.val(x, e, l2, k) + GravityEnergy.val(x, m) + BarrierEnergy.val(x, n, o, contact_area) + FrictionEnergy.val(v, mu_lambda, h, n)) # implicit Euler
44+
45+
def IP_grad(x, e, x_tilde, m, l2, k, n, o, contact_area, v, mu_lambda, h):
46+
return InertiaEnergy.grad(x, x_tilde, m) + h * h * (MassSpringEnergy.grad(x, e, l2, k) + GravityEnergy.grad(x, m) + BarrierEnergy.grad(x, n, o, contact_area) + FrictionEnergy.grad(v, mu_lambda, h, n)) # implicit Euler
47+
48+
def IP_hess(x, e, x_tilde, m, l2, k, n, o, contact_area, v, mu_lambda, h):
49+
IJV_In = InertiaEnergy.hess(x, x_tilde, m)
50+
IJV_MS = MassSpringEnergy.hess(x, e, l2, k)
51+
IJV_B = BarrierEnergy.hess(x, n, o, contact_area)
52+
IJV_F = FrictionEnergy.hess(v, mu_lambda, h, n)
53+
IJV_MS[2] *= h * h # implicit Euler
54+
IJV_B[2] *= h * h # implicit Euler
55+
IJV_F[2] *= h * h # implicit Euler
56+
IJV_In_MS = np.append(IJV_In, IJV_MS, axis=1)
57+
IJV_In_MS_B = np.append(IJV_In_MS, IJV_B, axis=1)
58+
IJV = np.append(IJV_In_MS_B, IJV_F, axis=1)
59+
H = sparse.coo_matrix((IJV[2], (IJV[0], IJV[1])), shape=(len(x) * 2, len(x) * 2)).tocsr()
60+
return H
61+
62+
def search_dir(x, e, x_tilde, m, l2, k, n, o, contact_area, v, mu_lambda, is_DBC, h):
63+
projected_hess = IP_hess(x, e, x_tilde, m, l2, k, n, o, contact_area, v, mu_lambda, h)
64+
reshaped_grad = IP_grad(x, e, x_tilde, m, l2, k, n, o, contact_area, v, mu_lambda, h).reshape(len(x) * 2, 1)
65+
# eliminate DOF by modifying gradient and Hessian for DBC:
66+
for i, j in zip(*projected_hess.nonzero()):
67+
if is_DBC[int(i / 2)] | is_DBC[int(j / 2)]:
68+
projected_hess[i, j] = (i == j)
69+
for i in range(0, len(x)):
70+
if is_DBC[i]:
71+
reshaped_grad[i * 2] = reshaped_grad[i * 2 + 1] = 0.0
72+
return spsolve(projected_hess, -reshaped_grad).reshape(len(x), 2)

0 commit comments

Comments
 (0)