8
8
import meshio
9
9
import argparse
10
10
import math
11
+ import qpsolvers
11
12
12
13
13
14
def min_max_eigs (A , n = 10 ):
@@ -25,17 +26,43 @@ def laplacian_energy(mesh, k=1, dims=1):
25
26
return U
26
27
27
28
28
- def display_quadrature (mesh , Xg ):
29
- V = mesh .X
30
- C = mesh .E
31
- bvh = pbat .geometry .bvh (V , C , cell = pbat .geometry .Cell .Tetrahedron )
32
- e , d = bvh .nearest_primitives_to_points (Xg , parallelize = True )
33
- nelems = mesh .E .shape [1 ]
34
- efree = np .setdiff1d (list (range (nelems )), np .unique (e ))
35
- if len (efree ) > 0 :
36
- ps .register_volume_mesh (
37
- f"Invalid mesh nelems={ nelems } " , V .T , C [:, efree ].T
38
- )
29
+ def transfer_quadrature (cmesh , wg2 , Xg2 , order = 1 ):
30
+ CV , CC = cmesh .X , cmesh .E
31
+ Xg1 = cmesh .quadrature_points (order )
32
+ e1 = np .linspace (0 , cmesh .E .shape [1 ]- 1 ,
33
+ num = cmesh .E .shape [1 ], dtype = np .int64 )
34
+ e1 = np .repeat (e1 , int (Xg1 .shape [1 ] / cmesh .E .shape [1 ]))
35
+ Xi1 = pbat .fem .reference_positions (cmesh , e1 , Xg1 )
36
+ bvh1 = pbat .geometry .bvh (CV , CC , cell = pbat .geometry .Cell .Tetrahedron )
37
+ e2 = np .array (bvh1 .nearest_primitives_to_points (
38
+ Xg2 , parallelize = True )[0 ])
39
+ Xi2 = pbat .fem .reference_positions (cmesh , e2 , Xg2 )
40
+ wg1 , err = pbat .math .transfer_quadrature (
41
+ e1 , Xi1 , e2 , Xi2 , wg2 , order = order , with_error = True , max_iters = 50 , precision = 1e-10 )
42
+ return wg1 , Xg1 , e1 , err
43
+
44
+
45
+ def patch_quadrature (cmesh , wg1 , Xg1 , e1 , err = 1e-4 , numerical_zero = 1e-10 ):
46
+ wtotal = np .zeros (cmesh .E .shape [1 ])
47
+ np .add .at (wtotal , e1 , wg1 )
48
+ ezero = np .argwhere (wtotal < numerical_zero ).squeeze ()
49
+ ezeroinds = np .in1d (e1 , ezero )
50
+ e1zero = np .copy (e1 [ezeroinds ])
51
+ Xg1zero = np .copy (Xg1 [:, ezeroinds ])
52
+ MM , BM , PM = pbat .math .reference_moment_fitting_systems (
53
+ e1zero , Xg1zero , e1zero , Xg1zero , np .zeros (Xg1zero .shape [1 ]))
54
+ MM = pbat .math .block_diagonalize_moment_fitting (MM , PM )
55
+ n = np .count_nonzero (ezeroinds )
56
+ P = - sp .sparse .eye (n ).asformat ('csc' )
57
+ G = MM .asformat ('csc' )
58
+ h = np .full (G .shape [0 ], err )
59
+ lb = np .full (n , 0. )
60
+ q = np .full (n , 0. )
61
+ wzero = qpsolvers .solve_qp (P , q , G = G , h = h , lb = lb , initvals = np .zeros (
62
+ n ), solver = "cvxopt" )
63
+ wg1p = np .copy (wg1 )
64
+ wg1p [ezeroinds ] = wzero
65
+ return wg1p , ezeroinds
39
66
40
67
41
68
def gradient_operator (mesh , Xg , dims = 1 ):
@@ -85,7 +112,7 @@ def shape_function_matrix(mesh, Xg, dims=1):
85
112
86
113
87
114
class BaseFemFunctionTransferOperator ():
88
- def __init__ (self , MD : pbat .fem .Mesh , MS : pbat .fem .Mesh , MT : pbat .fem .Mesh , H ):
115
+ def __init__ (self , MD : pbat .fem .Mesh , MS : pbat .fem .Mesh , MT : pbat .fem .Mesh , H = None ):
89
116
"""Operator for transferring FEM discretized functions from a source
90
117
mesh MS to a target mesh MT, given the domain MD.
91
118
@@ -141,9 +168,48 @@ def __call__(self, u):
141
168
142
169
143
170
class CholFemFunctionTransferOperator (BaseFemFunctionTransferOperator ):
144
- 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 ):
171
+ def __init__ (self , MD : pbat .fem .Mesh , MS : pbat .fem .Mesh , MT : pbat .fem .Mesh , H , lreg = 5 , hreg = 1 , greg = 1 ):
172
+ super ().__init__ (MD , MS , MT , H )
173
+ n = self .A .shape [0 ]
174
+ self .greg = greg
175
+ A = self .A + lreg * self .U + hreg * self .H + greg * self .GT .T @ self .IG @ self .GT
176
+ lmin , lmax = min_max_eigs (A , n = 1 )
177
+ tau = 0.
178
+ if lmin [0 ] <= 0 :
179
+ # Regularize A (due to positive semi-definiteness)
180
+ tau = abs (lmin [0 ]) + 1e-10
181
+ tau = sp .sparse .diags (np .full (n , tau ))
182
+ AR = A + tau
183
+ solver = pbat .math .linalg .SolverBackend .Eigen
184
+ self .Ainv = pbat .math .linalg .chol (AR , solver = solver )
185
+ self .Ainv .compute (AR )
186
+
187
+ def __matmul__ (self , u ):
188
+ b = self .P @ u + self .greg * self .GT .T @ self .IG @ self .GS @ u
189
+ du = self .Ainv .solve (b ).squeeze ()
190
+ return du
191
+
192
+
193
+ class RankKApproximateFemFunctionTransferOperator (BaseFemFunctionTransferOperator ):
194
+ def __init__ (self , MD : pbat .fem .Mesh , MS : pbat .fem .Mesh , MT : pbat .fem .Mesh , H , lreg = 5 , hreg = 1 , greg = 1 , modes = 30 ):
145
195
super ().__init__ (MD , MS , MT , H )
146
196
self .greg = greg
197
+ A = self .A + lreg * self .U + hreg * self .H + greg * self .GT .T @ self .IG @ self .GT
198
+ l , V = sp .sparse .linalg .eigsh (A , k = modes , sigma = 1e-5 , which = 'LM' )
199
+ keep = np .nonzero (l > 1e-5 )[0 ]
200
+ self .l , self .V = l [keep ], V [:, keep ]
201
+
202
+ def __matmul__ (self , B ):
203
+ B = self .P @ B + self .greg * self .GT .T @ self .IG @ self .GS @ B
204
+ B = self .V .T @ B
205
+ B = B @ sp .sparse .diags (1 / self .l )
206
+ X = self .V @ B
207
+ return X
208
+
209
+
210
+ class NewtonFunctionTransferOperator (BaseFemFunctionTransferOperator ):
211
+ def __init__ (self , MD : pbat .fem .Mesh , MS : pbat .fem .Mesh , MT : pbat .fem .Mesh , hxreg = 1e-4 , Y = 1e6 , nu = 0.45 , rho = 1e3 ):
212
+ super ().__init__ (MD , MS , MT )
147
213
self .M = (self .NT .T @ (rho * self .Ig ) @ self .NT ).asformat ('csc' )
148
214
self .P = (self .NT .T @ (rho * self .Ig ) @ self .NS ).asformat ('csc' )
149
215
self .rho = rho
@@ -153,18 +219,6 @@ def __init__(self, MD: pbat.fem.Mesh, MS: pbat.fem.Mesh, MT: pbat.fem.Mesh, H, l
153
219
self .hxreg = hxreg
154
220
self .X = MT .X
155
221
self .u = np .zeros (math .prod (self .X .shape ), order = "f" )
156
- # n = self.A.shape[0]
157
- # A = self.M + lreg*self.U + hreg*self.H
158
- # lmin, lmax = min_max_eigs(A, n=1)
159
- # tau = 0.
160
- # if lmin[0] <= 0:
161
- # # Regularize A (due to positive semi-definiteness)
162
- # tau = abs(lmin[0]) + 1e-10
163
- # tau = sp.sparse.diags(np.full(n, tau))
164
- # AR = A + tau
165
- # solver = pbat.math.linalg.SolverBackend.Eigen
166
- # self.Ainv = pbat.math.linalg.chol(AR, solver=solver)
167
- # self.Ainv.compute(AR)
168
222
169
223
def __matmul__ (self , u ):
170
224
xr = self .X .reshape (math .prod (self .X .shape ), order = 'f' )
@@ -199,23 +253,6 @@ def __matmul__(self, u):
199
253
return uk
200
254
201
255
202
- class RankKApproximateFemFunctionTransferOperator (BaseFemFunctionTransferOperator ):
203
- def __init__ (self , MD : pbat .fem .Mesh , MS : pbat .fem .Mesh , MT : pbat .fem .Mesh , H , lreg = 5 , hreg = 1 , greg = 1 , modes = 30 ):
204
- super ().__init__ (MD , MS , MT , H )
205
- self .greg = greg
206
- A = self .A + lreg * self .U + hreg * self .H + greg * self .GT .T @ self .IG @ self .GT
207
- l , V = sp .sparse .linalg .eigsh (A , k = modes , sigma = 1e-5 , which = 'LM' )
208
- keep = np .nonzero (l > 1e-5 )[0 ]
209
- self .l , self .V = l [keep ], V [:, keep ]
210
-
211
- def __matmul__ (self , B ):
212
- B = self .P @ B + self .greg * self .GT .T @ self .IG @ self .GS @ B
213
- B = self .V .T @ B
214
- B = B @ sp .sparse .diags (1 / self .l )
215
- X = self .V @ B
216
- return X
217
-
218
-
219
256
def rest_pose_hessian (mesh , Y , nu ):
220
257
x = mesh .X .reshape (math .prod (mesh .X .shape ), order = 'f' )
221
258
energy = pbat .fem .HyperElasticEnergy .StableNeoHookean
@@ -282,9 +319,11 @@ def signal(w: float, v: np.ndarray, t: float, c: float, k: float):
282
319
w , L = linear_elastic_deformation_modes (
283
320
mesh , args .rho , args .Y , args .nu , args .modes )
284
321
HC = rest_pose_hessian (cmesh , args .Y , args .nu )
285
- lreg , hreg , greg , hxreg = 0 , 1e-2 , 1e-2 , 1e-4
286
- Fldl = CholFemFunctionTransferOperator (
287
- mesh , mesh , cmesh , HC , lreg = lreg , hreg = hreg , greg = greg , hxreg = hxreg )
322
+ lreg , hreg , greg , hxreg = 1e-2 , 0 , 1 , 1e-4
323
+ # Fldl = CholFemFunctionTransferOperator(
324
+ # mesh, mesh, cmesh, HC, lreg=lreg, hreg=hreg, greg=greg)
325
+ Fldl = NewtonFunctionTransferOperator (
326
+ mesh , mesh , cmesh , hxreg = hxreg )
288
327
Krestrict = 30
289
328
Frank = RankKApproximateFemFunctionTransferOperator (
290
329
mesh , mesh , cmesh , HC , lreg = lreg , hreg = hreg , greg = greg , modes = Krestrict )
@@ -309,14 +348,40 @@ def signal(w: float, v: np.ndarray, t: float, c: float, k: float):
309
348
dtheta = np .pi / 120
310
349
animate = False
311
350
step = False
351
+ screenshot = False
352
+
353
+ wg2 = pbat .fem .inner_product_weights (mesh , 1 ).flatten (order = "F" )
354
+ Xg2 = mesh .quadrature_points (1 )
355
+
356
+ pg2 = ps .register_point_cloud ("Fine quad" , Xg2 .T )
357
+ pg2 .add_scalar_quantity ("weights" , wg2 , enabled = True )
358
+ pg2 .set_point_radius_quantity ("weights" )
359
+
360
+ wg1 , Xg1 , e1 , err = transfer_quadrature (cmesh , wg2 , Xg2 , order = 1 )
361
+ pg1 = ps .register_point_cloud ("Cage quad" , Xg1 .T )
362
+ pg1 .add_scalar_quantity ("weights" , wg1 , enabled = True )
363
+ pg1 .set_point_radius_quantity ("weights" )
364
+
365
+ wg1p , ezeroinds = patch_quadrature (cmesh , wg1 , Xg1 , e1 , err = 1e-4 )
366
+ ezero = np .unique (e1 [ezeroinds ])
367
+ wgpatched = wg1p [ezeroinds ]
368
+ Xgpatched = Xg1 [:, ezeroinds ]
369
+ pp = ps .register_point_cloud ("Patched" , Xgpatched .T )
370
+ pp .add_scalar_quantity ("weights" , wgpatched , enabled = True )
371
+ pp .set_point_radius_quantity ("weights" )
372
+ ps .register_volume_mesh ("Bad coarse" , CV , CC [ezero , :])
373
+ pg1p = ps .register_point_cloud ("Cage quad patched" , Xg1 .T )
374
+ pg1p .add_scalar_quantity ("weights" , wg1p , enabled = True )
375
+ pg1p .set_point_radius_quantity ("weights" )
312
376
313
377
def callback ():
314
378
global mode , c , k , theta , dtheta
315
- global animate , step
379
+ global animate , step , screenshot
316
380
changed , mode = imgui .InputInt ("Mode" , mode )
317
381
changed , c = imgui .InputFloat ("Wave amplitude" , c )
318
382
changed , k = imgui .InputFloat ("Wave frequency" , k )
319
383
changed , animate = imgui .Checkbox ("animate" , animate )
384
+ changed , screenshot = imgui .Checkbox ("screenshot" , screenshot )
320
385
step = imgui .Button ("step" )
321
386
322
387
if animate or step :
@@ -339,5 +404,8 @@ def callback():
339
404
if theta > 2 * np .pi :
340
405
theta = 0
341
406
407
+ if screenshot :
408
+ ps .screenshot ()
409
+
342
410
ps .set_user_callback (callback )
343
411
ps .show ()
0 commit comments