Skip to content

Commit e535859

Browse files
committed
Use non-linear Newton solver in non-linear elasticity restriction operator
1 parent b5a8036 commit e535859

File tree

1 file changed

+83
-47
lines changed

1 file changed

+83
-47
lines changed

python/examples/multigrid/linear_restriction_prolongation.py

Lines changed: 83 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,6 @@ def min_max_eigs(A, n=10):
1616
return lmin, lmax
1717

1818

19-
def rest_pose_hessian(mesh, Y, nu):
20-
x = mesh.X.reshape(math.prod(mesh.X.shape), order='f')
21-
energy = pbat.fem.HyperElasticEnergy.StableNeoHookean
22-
hep, detJeU, GNeU = pbat.fem.hyper_elastic_potential(
23-
mesh, Y, nu, energy=energy)
24-
hep.compute_element_elasticity(x)
25-
HU = hep.hessian()
26-
return HU
27-
28-
2919
def laplacian_energy(mesh, k=1, dims=1):
3020
L = -pbat.fem.laplacian(mesh, dims=dims)[0]
3121
M = None if k == 1 else pbat.fem.mass_matrix(mesh, dims=1, lump=True)
@@ -43,8 +33,9 @@ def display_quadrature(mesh, Xg):
4333
nelems = mesh.E.shape[1]
4434
efree = np.setdiff1d(list(range(nelems)), np.unique(e))
4535
if len(efree) > 0:
46-
ps.register_volume_mesh(f"Invalid mesh nelems={
47-
nelems}", V.T, C[:, efree].T)
36+
ps.register_volume_mesh(
37+
f"Invalid mesh nelems={nelems}", V.T, C[:, efree].T
38+
)
4839

4940

5041
def gradient_operator(mesh, Xg, dims=1):
@@ -103,9 +94,10 @@ def __init__(self, MD: pbat.fem.Mesh, MS: pbat.fem.Mesh, MT: pbat.fem.Mesh, H):
10394
MS (pbat.fem.Mesh): Source mesh
10495
MT (pbat.fem.Mesh): Target mesh
10596
"""
97+
nelems = MD.E.shape[1]
10698
quadrature_order = 2*max(MS.order, MT.order)
10799
Xg = MD.quadrature_points(quadrature_order)
108-
wg = np.tile(MD.quadrature_weights(quadrature_order), MD.E.shape[1])
100+
wg = np.tile(MD.quadrature_weights(quadrature_order), nelems)
109101
from scipy.sparse import kron, eye, diags
110102
Ig = diags(wg)
111103
Ig = kron(Ig, eye(MT.dims))
@@ -128,70 +120,111 @@ def __matmul__(self, X):
128120
pass
129121

130122

123+
class NonLinearElasticRestrictionEnergy:
124+
def __init__(self, hep, rho, Ig, NT, y, xr, hxreg):
125+
self.hep = hep
126+
self.rho = rho
127+
self.Ig = Ig
128+
self.NT = NT
129+
self.y = y
130+
self.xr = xr
131+
self.hxreg = hxreg
132+
133+
def __call__(self, u):
134+
x = self.xr + u
135+
self.hep.compute_element_elasticity(x, grad=False, hessian=False)
136+
duku = self.NT @ u - self.y
137+
E = 0.5 * duku.T @ (self.rho * self.Ig) @ duku + \
138+
self.hxreg * self.hep.eval()
139+
return E
140+
141+
131142
class CholFemFunctionTransferOperator(BaseFemFunctionTransferOperator):
132-
def __init__(self, MD: pbat.fem.Mesh, MS: pbat.fem.Mesh, MT: pbat.fem.Mesh, H, lreg=5, hreg=1, greg=1, Y=1e6, nu=0.45, hxreg=1e-4, rho=1e3):
143+
def __init__(self, MD: pbat.fem.Mesh, MS: pbat.fem.Mesh, MT: pbat.fem.Mesh, H, lreg=5, hreg=1, greg=1, hxreg=1e-4, Y=1e6, nu=0.45, rho=1e3):
133144
super().__init__(MD, MS, MT, H)
134-
n = self.A.shape[0]
145+
self.greg = greg
146+
self.M = (self.NT.T @ (rho * self.Ig) @ self.NT).asformat('csc')
147+
self.P = (self.NT.T @ (rho * self.Ig) @ self.NS).asformat('csc')
148+
self.rho = rho
135149
energy = pbat.fem.HyperElasticEnergy.StableNeoHookean
136150
self.hep, self.detJeU, self.GNeU = pbat.fem.hyper_elastic_potential(
137151
MT, Y, nu, energy=energy)
138-
self.MT = MT
139-
self.rho = rho
140-
self.M = self.NT.T @ (rho * self.Ig) @ self.NT # + lreg*self.U + hreg*self.H + greg * self.GT.T @ self.IG @ self.GT
141-
self.P = self.NT.T @ (rho * self.Ig) @ self.NS
142152
self.hxreg = hxreg
153+
self.X = MT.X
154+
self.u = np.zeros(math.prod(self.X.shape), order="f")
155+
# n = self.A.shape[0]
156+
# A = self.M + lreg*self.U + hreg*self.H
143157
# lmin, lmax = min_max_eigs(A, n=1)
144158
# tau = 0.
145159
# if lmin[0] <= 0:
146160
# # Regularize A (due to positive semi-definiteness)
147-
# print("WARNING: A is semi-definite")
148161
# tau = abs(lmin[0]) + 1e-10
149162
# tau = sp.sparse.diags(np.full(n, tau))
150163
# AR = A + tau
151164
# solver = pbat.math.linalg.SolverBackend.Eigen
152165
# self.Ainv = pbat.math.linalg.chol(AR, solver=solver)
153166
# self.Ainv.compute(AR)
154-
self.uk = np.zeros(math.prod(self.MT.X.shape))
155167

156168
def __matmul__(self, u):
157-
self.uk = np.zeros(math.prod(self.MT.X.shape))
158-
xr = self.MT.X.reshape(math.prod(self.MT.X.shape), order='f')
159-
for k in range(1):
160-
x = xr + self.uk
169+
xr = self.X.reshape(math.prod(self.X.shape), order='f')
170+
uk = self.u # np.zeros_like(xr)
171+
y = self.P @ u
172+
f = NonLinearElasticRestrictionEnergy(
173+
self.hep, self.rho, self.Ig, self.NT, self.NS @ u, xr, self.hxreg)
174+
for k in range(5):
175+
x = xr + uk
161176
self.hep.compute_element_elasticity(x)
162-
duku = self.NT @ self.uk - self.NS @ u
163-
E = 0.5 * duku.T @ (self.rho * self.Ig) @ duku + self.hxreg * self.hep.eval()
164-
H = self.M + self.hxreg * self.hep.hessian()
177+
K = self.hep.hessian()
178+
H = self.M + self.hxreg * K
165179
solver = pbat.math.linalg.SolverBackend.Eigen
166180
Hinv = pbat.math.linalg.ldlt(H, solver=solver)
167181
Hinv.compute(H)
168-
# B = self.P @ u + self.GT.T @ self.IG @ self.GS @ u
169-
ng = self.P @ u - self.hxreg * self.hep.gradient()
170-
r = ng.T @ ng
171-
if r < 1e-3:
172-
break
173-
# X = self.Ainv.solve(B)
174-
du = Hinv.solve(ng).squeeze()
175-
self.uk += du
176-
return self.uk
182+
gk = (self.M @ uk - y) + self.hxreg * self.hep.gradient()
183+
du = Hinv.solve(-gk).squeeze()
184+
# Line search
185+
alpha = 1.
186+
Dfk = gk.dot(du)
187+
fk = f(uk)
188+
maxiters = 20
189+
tau = 0.5
190+
for j in range(maxiters):
191+
fx = f(uk + alpha*du)
192+
flinear = fk + alpha * c * Dfk
193+
if fx <= flinear:
194+
break
195+
alpha = tau*alpha
196+
uk += alpha*du
197+
self.u = uk
198+
return uk
177199

178200

179201
class RankKApproximateFemFunctionTransferOperator(BaseFemFunctionTransferOperator):
180202
def __init__(self, MD: pbat.fem.Mesh, MS: pbat.fem.Mesh, MT: pbat.fem.Mesh, H, lreg=5, hreg=1, greg=1, modes=30):
181203
super().__init__(MD, MS, MT, H)
204+
self.greg = greg
182205
A = self.A + lreg*self.U + hreg*self.H + greg * self.GT.T @ self.IG @ self.GT
183206
l, V = sp.sparse.linalg.eigsh(A, k=modes, sigma=1e-5, which='LM')
184207
keep = np.nonzero(l > 1e-5)[0]
185208
self.l, self.V = l[keep], V[:, keep]
186209

187210
def __matmul__(self, B):
188-
B = self.P @ B + self.GT.T @ self.IG @ self.GS @ B
211+
B = self.P @ B + self.greg * self.GT.T @ self.IG @ self.GS @ B
189212
B = self.V.T @ B
190213
B = B @ sp.sparse.diags(1 / self.l)
191214
X = self.V @ B
192215
return X
193216

194217

218+
def rest_pose_hessian(mesh, Y, nu):
219+
x = mesh.X.reshape(math.prod(mesh.X.shape), order='f')
220+
energy = pbat.fem.HyperElasticEnergy.StableNeoHookean
221+
hep, detJeU, GNeU = pbat.fem.hyper_elastic_potential(
222+
mesh, Y, nu, energy=energy)
223+
hep.compute_element_elasticity(x)
224+
HU = hep.hessian()
225+
return HU
226+
227+
195228
def linear_elastic_deformation_modes(mesh, rho, Y, nu, modes=30, sigma=-1e-5):
196229
M, detJeM = pbat.fem.mass_matrix(mesh, rho=rho)
197230
HU = rest_pose_hessian(mesh, Y, nu)
@@ -248,20 +281,24 @@ def signal(w: float, v: np.ndarray, t: float, c: float, k: float):
248281
w, L = linear_elastic_deformation_modes(
249282
mesh, args.rho, args.Y, args.nu, args.modes)
250283
HC = rest_pose_hessian(cmesh, args.Y, args.nu)
251-
lreg, hreg, greg, hxreg = 0, 0, 0, 1e-4
284+
lreg, hreg, greg, hxreg = 0, 1e-2, 0, 1e-4
252285
Fldl = CholFemFunctionTransferOperator(
253-
mesh, mesh, cmesh, HC, lreg=lreg, hreg=hreg, greg=greg, hxreg=hxreg, rho=args.rho)
254-
# Krestrict = 30
255-
# Frank = RankKApproximateFemFunctionTransferOperator(
256-
# mesh, mesh, cmesh, HC, lreg=lreg, hreg=hreg, greg=greg, modes=Krestrict)
286+
mesh, mesh, cmesh, HC, lreg=lreg, hreg=hreg, greg=greg, hxreg=hxreg)
287+
Krestrict = 30
288+
Frank = RankKApproximateFemFunctionTransferOperator(
289+
mesh, mesh, cmesh, HC, lreg=lreg, hreg=hreg, greg=greg, modes=Krestrict)
257290

258291
ps.set_up_dir("z_up")
259292
ps.set_front_dir("neg_y_front")
260293
ps.set_ground_plane_mode("shadow_only")
261294
ps.init()
262295
vm = ps.register_surface_mesh("model", V, F)
263296
ldlvm = ps.register_surface_mesh("LDL cage", CV, CF)
264-
# rankvm = ps.register_surface_mesh("Rank K cage", CV, CF)
297+
ldlvm.set_transparency(0.5)
298+
ldlvm.set_edge_width(1)
299+
rankvm = ps.register_surface_mesh("Rank K cage", CV, CF)
300+
rankvm.set_transparency(0.5)
301+
rankvm.set_edge_width(1)
265302
mode = 6
266303
t0 = time.time()
267304
t = 0
@@ -275,7 +312,6 @@ def signal(w: float, v: np.ndarray, t: float, c: float, k: float):
275312
def callback():
276313
global mode, c, k, theta, dtheta
277314
global animate, step
278-
279315
changed, mode = imgui.InputInt("Mode", mode)
280316
changed, c = imgui.InputFloat("Wave amplitude", c)
281317
changed, k = imgui.InputFloat("Wave frequency", k)
@@ -293,10 +329,10 @@ def callback():
293329
ut = 1
294330
u = uf + ur + ut
295331
XCldl = CV + (Fldl @ u).reshape(CV.shape)
296-
# XCrank = CV + (Frank @ u).reshape(CV.shape)
332+
XCrank = CV + (Frank @ u).reshape(CV.shape)
297333
vm.update_vertex_positions(V + (uf + ur).reshape(V.shape))
298334
ldlvm.update_vertex_positions(XCldl)
299-
# rankvm.update_vertex_positions(XCrank)
335+
rankvm.update_vertex_positions(XCrank)
300336

301337
theta += dtheta
302338
if theta > 2*np.pi:

0 commit comments

Comments
 (0)