Skip to content

Commit 981217f

Browse files
committed
square on slope with friction
1 parent 7de3da8 commit 981217f

5 files changed

Lines changed: 94 additions & 19 deletions

File tree

friction/BarrierEnergy.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,13 @@ def init_step_size(x, n, o, p):
4141
p_n = p[i].dot(n)
4242
if p_n < 0:
4343
alpha = min(alpha, 0.9 * n.dot(x[i] - o) / -p_n)
44-
return alpha
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

friction/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(vhatnorm, epsv, hhat):
7+
if vhatnorm >= epsv:
8+
return vhatnorm * hhat
9+
else:
10+
vhatnormhhat = vhatnorm * hhat
11+
epsvhhat = epsv * hhat
12+
return vhatnormhhat * vhatnormhhat * (-vhatnormhhat / 3.0 + epsvhhat) / (epsvhhat * epsvhhat) + epsvhhat / 3.0
13+
14+
def f1_div_vhatnorm(vhatnorm, epsv):
15+
if vhatnorm >= epsv:
16+
return 1.0 / vhatnorm
17+
else:
18+
return (-vhatnorm + 2.0 * epsv) / (epsv * epsv)
19+
20+
def f_hess_term(vhatnorm, epsv):
21+
if vhatnorm >= epsv:
22+
return -1.0 / (vhatnorm * vhatnorm)
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+
vhat = np.transpose(T).dot(v[i])
32+
sum += mu_lambda[i] * f0(np.linalg.norm(vhat), 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+
vhat = np.transpose(T).dot(v[i])
41+
g[i] = mu_lambda[i] * f1_div_vhatnorm(np.linalg.norm(vhat), epsv) * T.dot(vhat)
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+
vhat = np.transpose(T).dot(v[i])
50+
vhatnorm = np.linalg.norm(vhat)
51+
inner_term = f1_div_vhatnorm(vhatnorm, epsv) * np.identity(2)
52+
if vhatnorm != 0:
53+
inner_term += f_hess_term(vhatnorm, epsv) / vhatnorm * np.outer(vhat, vhat)
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

friction/readme.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Mass-Spring Solids Simulation
22

3-
A square falling onto the ground under gravity is simulated with mass-spring elasticity potential and implicit Euler time integration.
3+
A square sliding/residing on a slope under gravity is simulated with mass-spring elasticity potential and implicit Euler time integration.
44
Each time step is solved by minimizing the Incremental Potential with the projected Newton method.
55

66
## Dependencies

friction/simulator.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@
1414
n_seg = 4 # num of segments per side of the square
1515
h = 0.01 # time step size in s
1616
DBC = [] # no nodes need to be fixed
17-
ground_n = np.array([0.0, 1.0]) # normal of the slope
17+
ground_n = np.array([0.1, 1.0]) # normal of the slope
1818
ground_o = np.array([0.0, -1.0]) # a point on the slope
19+
mu = 0.11 # friction coefficient of the slope
1920

2021
# initialize simulation
2122
[x, e] = square_mesh.generate(side_len, n_seg) # node positions and edge node indices
@@ -64,7 +65,7 @@ def screen_projection(x):
6465
pygame.display.flip() # flip the display
6566

6667
# step forward simulation and wait for screen refresh
67-
[x, v] = time_integrator.step_forward(x, e, v, m, l2, k, ground_n, ground_o, contact_area, is_DBC, h, 1e-2)
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)
6869
time_step += 1
6970
pygame.time.wait(int(h * 1000))
7071

friction/time_integrator.py

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,53 +10,58 @@
1010
import MassSpringEnergy
1111
import GravityEnergy
1212
import BarrierEnergy
13+
import FrictionEnergy
1314

14-
def step_forward(x, e, v, m, l2, k, n, o, contact_area, is_DBC, h, tol):
15+
def step_forward(x, e, v, m, l2, k, n, o, contact_area, mu, is_DBC, h, tol):
1516
x_tilde = x + v * h # implicit Euler predictive position
1617
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
1719

1820
# Newton loop
1921
iter = 0
20-
E_last = IP_val(x, e, x_tilde, m, l2, k, n, o, contact_area, h)
21-
p = search_dir(x, e, x_tilde, m, l2, k, n, o, contact_area, is_DBC, h)
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)
2224
while LA.norm(p, inf) / h > tol:
2325
print('Iteration', iter, ':')
2426
print('residual =', LA.norm(p, inf) / h)
2527

2628
# filter line search
2729
alpha = BarrierEnergy.init_step_size(x, n, o, p) # avoid interpenetration and tunneling
28-
while IP_val(x + alpha * p, e, x_tilde, m, l2, k, n, o, contact_area, h) > E_last:
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:
2931
alpha /= 2
3032
print('step size =', alpha)
3133

3234
x += alpha * p
33-
E_last = IP_val(x, e, x_tilde, m, l2, k, n, o, contact_area, h)
34-
p = search_dir(x, e, x_tilde, m, l2, k, n, o, contact_area, is_DBC, h)
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)
3537
iter += 1
3638

3739
v = (x - x_n) / h # implicit Euler velocity update
3840
return [x, v]
3941

40-
def IP_val(x, e, x_tilde, m, l2, k, n, o, contact_area, h):
41-
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)) # implicit Euler
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
4244

43-
def IP_grad(x, e, x_tilde, m, l2, k, n, o, contact_area, h):
44-
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)) # implicit Euler
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
4547

46-
def IP_hess(x, e, x_tilde, m, l2, k, n, o, contact_area, h):
48+
def IP_hess(x, e, x_tilde, m, l2, k, n, o, contact_area, v, mu_lambda, h):
4749
IJV_In = InertiaEnergy.hess(x, x_tilde, m)
4850
IJV_MS = MassSpringEnergy.hess(x, e, l2, k)
4951
IJV_B = BarrierEnergy.hess(x, n, o, contact_area)
52+
IJV_F = FrictionEnergy.hess(v, mu_lambda, h, n)
5053
IJV_MS[2] *= h * h # implicit Euler
5154
IJV_B[2] *= h * h # implicit Euler
55+
IJV_F[2] *= h * h # implicit Euler
5256
IJV_In_MS = np.append(IJV_In, IJV_MS, axis=1)
53-
IJV = np.append(IJV_In_MS, IJV_B, 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)
5459
H = sparse.coo_matrix((IJV[2], (IJV[0], IJV[1])), shape=(len(x) * 2, len(x) * 2)).tocsr()
5560
return H
5661

57-
def search_dir(x, e, x_tilde, m, l2, k, n, o, contact_area, is_DBC, h):
58-
projected_hess = IP_hess(x, e, x_tilde, m, l2, k, n, o, contact_area, h)
59-
reshaped_grad = IP_grad(x, e, x_tilde, m, l2, k, n, o, contact_area, h).reshape(len(x) * 2, 1)
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)
6065
# eliminate DOF by modifying gradient and Hessian for DBC:
6166
for i, j in zip(*projected_hess.nonzero()):
6267
if is_DBC[int(i / 2)] | is_DBC[int(j / 2)]:

0 commit comments

Comments
 (0)