Skip to content

Commit ecdda4e

Browse files
committed
init inv free
1 parent d42c729 commit ecdda4e

11 files changed

Lines changed: 481 additions & 0 deletions

inv_free/BarrierEnergy.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
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+
# floor:
10+
for i in range(0, len(x)):
11+
d = n.dot(x[i] - o)
12+
if d < dhat:
13+
s = d / dhat
14+
sum += contact_area[i] * dhat * kappa / 2 * (s - 1) * math.log(s)
15+
# ceil:
16+
n = np.array([0.0, -1.0])
17+
for i in range(0, len(x) - 1):
18+
d = n.dot(x[i] - x[-1])
19+
if d < dhat:
20+
s = d / dhat
21+
sum += contact_area[i] * dhat * kappa / 2 * (s - 1) * math.log(s)
22+
return sum
23+
24+
def grad(x, n, o, contact_area):
25+
g = np.array([[0.0, 0.0]] * len(x))
26+
# floor:
27+
for i in range(0, len(x)):
28+
d = n.dot(x[i] - o)
29+
if d < dhat:
30+
s = d / dhat
31+
g[i] = contact_area[i] * dhat * (kappa / 2 * (math.log(s) / dhat + (s - 1) / d)) * n
32+
# ceil:
33+
n = np.array([0.0, -1.0])
34+
for i in range(0, len(x) - 1):
35+
d = n.dot(x[i] - x[-1])
36+
if d < dhat:
37+
s = d / dhat
38+
local_grad = contact_area[i] * dhat * (kappa / 2 * (math.log(s) / dhat + (s - 1) / d)) * n
39+
g[i] += local_grad
40+
g[-1] -= local_grad
41+
return g
42+
43+
def hess(x, n, o, contact_area):
44+
IJV = [[0] * 0, [0] * 0, np.array([0.0] * 0)]
45+
# floor:
46+
for i in range(0, len(x)):
47+
d = n.dot(x[i] - o)
48+
if d < dhat:
49+
local_hess = contact_area[i] * dhat * kappa / (2 * d * d * dhat) * (d + dhat) * np.outer(n, n)
50+
for c in range(0, 2):
51+
for r in range(0, 2):
52+
IJV[0].append(i * 2 + r)
53+
IJV[1].append(i * 2 + c)
54+
IJV[2] = np.append(IJV[2], local_hess[r, c])
55+
# ceil:
56+
n = np.array([0.0, -1.0])
57+
for i in range(0, len(x) - 1):
58+
d = n.dot(x[i] - x[-1])
59+
if d < dhat:
60+
local_hess = contact_area[i] * dhat * kappa / (2 * d * d * dhat) * (d + dhat) * np.outer(n, n)
61+
index = [i, len(x) - 1]
62+
for nI in range(0, 2):
63+
for nJ in range(0, 2):
64+
for c in range(0, 2):
65+
for r in range(0, 2):
66+
IJV[0].append(index[nI] * 2 + r)
67+
IJV[1].append(index[nJ] * 2 + c)
68+
IJV[2] = np.append(IJV[2], ((-1) ** (nI != nJ)) * local_hess[r, c])
69+
return IJV
70+
71+
def init_step_size(x, n, o, p):
72+
alpha = 1
73+
# floor:
74+
for i in range(0, len(x)):
75+
p_n = p[i].dot(n)
76+
if p_n < 0:
77+
alpha = min(alpha, 0.9 * n.dot(x[i] - o) / -p_n)
78+
# ceil:
79+
n = np.array([0.0, -1.0])
80+
for i in range(0, len(x) - 1):
81+
p_n = (p[i] - p[-1]).dot(n)
82+
if p_n < 0:
83+
alpha = min(alpha, 0.9 * n.dot(x[i] - x[-1]) / -p_n)
84+
return alpha
85+
86+
def compute_mu_lambda(x, n, o, contact_area, mu):
87+
mu_lambda = np.array([0.0] * len(x))
88+
for i in range(0, len(x)):
89+
d = n.dot(x[i] - o)
90+
if d < dhat:
91+
s = d / dhat
92+
mu_lambda[i] = mu * -contact_area[i] * dhat * (kappa / 2 * (math.log(s) / dhat + (s - 1) / d))
93+
return mu_lambda

inv_free/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

inv_free/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

inv_free/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

inv_free/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

inv_free/SpringEnergy.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, m, DBC, DBC_target, k):
4+
sum = 0.0
5+
for i in range(0, len(DBC)):
6+
diff = x[DBC[i]] - DBC_target[i]
7+
sum += 0.5 * k * m[DBC[i]] * diff.dot(diff)
8+
return sum
9+
10+
def grad(x, m, DBC, DBC_target, k):
11+
g = np.array([[0.0, 0.0]] * len(x))
12+
for i in range(0, len(DBC)):
13+
g[DBC[i]] = k * m[DBC[i]] * (x[DBC[i]] - DBC_target[i])
14+
return g
15+
16+
def hess(x, m, DBC, DBC_target, k):
17+
IJV = [[0] * 0, [0] * 0, np.array([0.0] * 0)]
18+
for i in range(0, len(DBC)):
19+
for d in range(0, 2):
20+
IJV[0].append(DBC[i] * 2 + d)
21+
IJV[1].append(DBC[i] * 2 + d)
22+
IJV[2] = np.append(IJV[2], k * m[DBC[i]])
23+
return IJV

inv_free/readme.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Inversion-free Hyperelastic Solids Simulation
2+
3+
A square falling onto a ground under gravity and then compressed by a ceiling is simulated with an inversion-free hyperelastic 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+
```

inv_free/simulator.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
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 = [(n_seg + 1) * (n_seg + 1)] # dirichlet node index
17+
DBC_v = [np.array([0.0, -0.5])] # dirichlet node velocity
18+
DBC_limit = [np.array([0.0, -0.6])] # dirichlet node limit position
19+
ground_n = np.array([0.0, 1.0]) # normal of the slope
20+
ground_n /= np.linalg.norm(ground_n) # normalize ground normal vector just in case
21+
ground_o = np.array([0.0, -1.0]) # a point on the slope
22+
mu = 0.11 # friction coefficient of the slope
23+
24+
# initialize simulation
25+
[x, e] = square_mesh.generate(side_len, n_seg) # node positions and edge node indices
26+
x = np.append(x, [[0.0, side_len * 0.6]], axis=0) # ceil origin (with normal [0.0, -1.0])
27+
v = np.array([[0.0, 0.0]] * len(x)) # velocity
28+
m = [rho * side_len * side_len / ((n_seg + 1) * (n_seg + 1))] * len(x) # calculate node mass evenly
29+
# rest length squared
30+
l2 = []
31+
for i in range(0, len(e)):
32+
diff = x[e[i][0]] - x[e[i][1]]
33+
l2.append(diff.dot(diff))
34+
k = [k] * len(e) # spring stiffness
35+
# identify whether a node is Dirichlet
36+
is_DBC = [False] * len(x)
37+
for i in DBC:
38+
is_DBC[i] = True
39+
contact_area = [side_len / n_seg] * len(x) # perimeter split to each node
40+
41+
# simulation with visualization
42+
resolution = np.array([900, 900])
43+
offset = resolution / 2
44+
scale = 200
45+
def screen_projection(x):
46+
return [offset[0] + scale * x[0], resolution[1] - (offset[1] + scale * x[1])]
47+
48+
time_step = 0
49+
screen = pygame.display.set_mode(resolution)
50+
running = True
51+
while running:
52+
# run until the user asks to quit
53+
for event in pygame.event.get():
54+
if event.type == pygame.QUIT:
55+
running = False
56+
57+
print('### Time step', time_step, '###')
58+
59+
# fill the background and draw the square
60+
screen.fill((255, 255, 255))
61+
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]]),
62+
screen_projection([ground_o[0] + 3.0 * ground_n[1], ground_o[1] - 3.0 * ground_n[0]])) # ground
63+
pygame.draw.aaline(screen, (0, 0, 255), screen_projection([x[-1][0] + 3.0, x[-1][1]]),
64+
screen_projection([x[-1][0] - 3.0, x[-1][1]])) # ceil
65+
for eI in e:
66+
pygame.draw.aaline(screen, (0, 0, 255), screen_projection(x[eI[0]]), screen_projection(x[eI[1]]))
67+
for xId in range(0, len(x) - 1):
68+
xI = x[xId]
69+
pygame.draw.circle(screen, (0, 0, 255), screen_projection(xI), 0.1 * side_len / n_seg * scale)
70+
71+
pygame.display.flip() # flip the display
72+
73+
# step forward simulation and wait for screen refresh
74+
[x, v] = time_integrator.step_forward(x, e, v, m, l2, k, ground_n, ground_o, contact_area, mu, is_DBC, DBC, DBC_v, DBC_limit, h, 1e-2)
75+
time_step += 1
76+
pygame.time.wait(int(h * 1000))
77+
78+
pygame.quit()

inv_free/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]

0 commit comments

Comments
 (0)