16
16
from NuMPI .Tools import Reduction
17
17
from NuMPI .IO import save_npy
18
18
19
+ from muTopOpt .AimFunction import aim_function_sequential
20
+ from muTopOpt .AimFunction import aim_function_deriv_strain_sequential
21
+ from muTopOpt .AimFunction import aim_function_deriv_phase_sequential
22
+ from muTopOpt .MaterialDensity import node_to_quad_pt_2_quad_pts_sequential
23
+ from muTopOpt .MaterialDensity import df_dphase_2_quad_pts_derivative_sequential
19
24
from muTopOpt .AimFunction import aim_function
20
25
from muTopOpt .AimFunction import aim_function_deriv_strain
21
26
from muTopOpt .AimFunction import aim_function_deriv_phase
22
- from muTopOpt .MaterialDensity import node_to_quad_pt_2_quad_pts_sequential
23
- from muTopOpt .MaterialDensity import df_dphase_2_quad_pts_derivative_sequential
27
+ from muTopOpt .MaterialDensity import node_to_quad_pt_2_quad_pts
28
+ from muTopOpt .MaterialDensity import df_dphase_2_quad_pts_derivative
24
29
25
30
26
31
def calculate_dstress_dmat (cell , mat , strains , density , lambda_1 , mu_1 , lambda_0 ,
@@ -67,17 +72,17 @@ def calculate_dstress_dmat(cell, mat, strains, density, lambda_1, mu_1, lambda_0
67
72
raise ValueError (message )
68
73
69
74
# Derivatives of Lamé constants with respect to the phase
70
- lame1_deriv = order * density ** (order - 1 ) * \
75
+ lame1_deriv = order * density . flatten ( order = 'F' ) ** (order - 1 ) * \
71
76
(lambda_1 - lambda_0 )
72
- lame2_deriv = order * density ** (order - 1 ) * \
77
+ lame2_deriv = order * density . flatten ( order = 'F' ) ** (order - 1 ) * \
73
78
(mu_1 - mu_0 )
74
79
75
80
# Set derivatives as material parameters in mat
76
81
for pixel_id , pixel in cell .pixels .enumerate ():
77
82
quad_id = nb_quad_pts * pixel_id
78
83
for i in range (nb_quad_pts ):
79
- mat .set_lame_constants (quad_id + i , lame1_deriv [( i , * tuple ( pixel )) ],
80
- lame2_deriv [( i , * tuple ( pixel )) ])
84
+ mat .set_lame_constants (quad_id + i , lame1_deriv [quad_id + i ],
85
+ lame2_deriv [quad_id + i ])
81
86
82
87
# Calculate dstress_dphase
83
88
dstress_dmat = []
@@ -86,13 +91,13 @@ def calculate_dstress_dmat(cell, mat, strains, density, lambda_1, mu_1, lambda_0
86
91
dstress_dmat .append (cell .evaluate_stress (strain ).copy ())
87
92
88
93
# Reset material parameters of cell
89
- lame1 = density ** order * (lambda_1 - lambda_0 ) + lambda_0
90
- lame2 = density ** order * (mu_1 - mu_0 ) + mu_0
94
+ lame1 = density . flatten ( order = 'F' ) ** order * (lambda_1 - lambda_0 ) + lambda_0
95
+ lame2 = density . flatten ( order = 'F' ) ** order * (mu_1 - mu_0 ) + mu_0
91
96
for pixel_id , pixel in cell .pixels .enumerate ():
92
97
quad_id = nb_quad_pts * pixel_id
93
98
for i in range (nb_quad_pts ):
94
- mat .set_lame_constants (quad_id + i , lame1 [( i , * tuple ( pixel )) ],
95
- lame2 [( i , * tuple ( pixel )) ])
99
+ mat .set_lame_constants (quad_id + i , lame1 [quad_id + i ],
100
+ lame2 [quad_id + i ])
96
101
97
102
return dstress_dmat
98
103
@@ -205,7 +210,7 @@ def sensitivity_analysis_per_grid_pts(cell, krylov_solver, strains,
205
210
for i in range (len (strains )):
206
211
helper = adjoint_list [i ] * dstress_dphase_list [i ]
207
212
helper = helper .reshape ([- 1 , cell .nb_quad_pts , * cell .nb_subdomain_grid_pts ], order = 'F' )
208
- helper = df_dphase_2_quad_pts_derivative_sequential (helper )
213
+ helper = df_dphase_2_quad_pts_derivative (helper . copy (), cell )
209
214
S += np .sum (helper , axis = (0 )).flatten (order = 'F' )
210
215
211
216
return S
@@ -273,7 +278,7 @@ def wrapper(phase, cell, mat, lambda_1, mu_1, lambda_0, mu_0, order,
273
278
tuple (cell .nb_domain_grid_pts ), MPI .COMM_WORLD )
274
279
275
280
# Change material of cell
276
- density = node_to_quad_pt_2_quad_pts_sequential (phase )
281
+ density = node_to_quad_pt_2_quad_pts (phase , cell )
277
282
density = density .flatten (order = 'F' )
278
283
lame1 = (lambda_1 - lambda_0 ) * density ** order + lambda_0
279
284
lame2 = (mu_1 - mu_0 ) * density ** order + mu_0
@@ -331,6 +336,209 @@ def wrapper(phase, cell, mat, lambda_1, mu_1, lambda_0, mu_0, order,
331
336
if name is not None :
332
337
os .remove (name )
333
338
339
+ # Save the last step
340
+ if folder is not None :
341
+ phase = phase .reshape (cell .nb_subdomain_grid_pts , order = 'F' )
342
+ name = folder + f'phase_last.npy'
343
+ save_npy (name , phase , tuple (cell .subdomain_locations ),
344
+ tuple (cell .nb_domain_grid_pts ), MPI .COMM_WORLD )
345
+ if calc_sensitivity :
346
+ norm_S = np .linalg .norm (S )** 2
347
+ norm_S = np .sqrt (Reduction (MPI .COMM_WORLD ).sum (norm_S ))
348
+ if (MPI .COMM_WORLD .rank == 0 ):
349
+ name = folder + 'evolution.txt'
350
+ with open (name , 'a' ) as f :
351
+ if calc_sensitivity :
352
+ np .savetxt (f , [aim , norm_S ], delimiter = ' ' , newline = ' ' )
353
+ else :
354
+ np .savetxt (f , [aim ], delimiter = ' ' , newline = ' ' )
355
+ for average_stress in average_stresses :
356
+ np .savetxt (f , average_stress .flatten (),
357
+ delimiter = ' ' , newline = ' ' )
358
+ print ('' , file = f )
359
+
360
+ if calc_sensitivity :
361
+ return aim , S .flatten (order = 'F' )
362
+ else :
363
+ return aim
364
+
365
+ def sensitivity_analysis_per_grid_pts_sequential (cell , krylov_solver , strains ,
366
+ aim_deriv_strains , aim_deriv_phase , dstress_dphase_list , tol ):
367
+ """ Worker for doing the sensitivity analysis with the adjoint method if the
368
+ design parameters (=phase) are defined at the grid points, e.g. the nodes.
369
+ The design parameters are interpolated with linear finite elements
370
+ (rectangular triangles) to get the material density in the elements.
371
+
372
+ Parameters
373
+ ----------
374
+ cell: object
375
+ muSpectre cell object
376
+ krylov_solver: object
377
+ muSpectre krylov_solver object belonging to cell
378
+ solver_args: list
379
+ List of additional arguments passed to the solver
380
+ strains: list of np.ndarray(dim**2 * nb_quad_pts * nb_pixels) of floats
381
+ List of microscopic equilibrium strains in column-major order
382
+ aim_deriv_strains: list of np.ndarray(dim**2 * nb_quad_pts * nb_pixels) of floats
383
+ List of the derivativ of the aim function with respect to the strains in column-major order
384
+ aim_deriv_phase: np.ndarray(nb_grid_pts) of floats
385
+ Derivative of the aim function with respect to the phase in column-major order
386
+ dstress_dphase_list: list of np.arrays of floats
387
+ List of the derivative of the stresses with respect to the phase in column-major order.
388
+ Each entry has the shape: [dim, dim, nb_quad_pts, *nb_grid_pts, *nb_grid_pts]
389
+ tol: float
390
+ Tolerance for stopping the solution of the adjoint equation
391
+
392
+ Returns
393
+ -------
394
+ S: np.ndarray(nb_pixels) of floats
395
+ Sensitivity in column-major order
396
+ """
397
+ dim = cell .dim
398
+ shape = [dim , dim , cell .nb_quad_pts , * cell .nb_subdomain_grid_pts ]
399
+ # Solve the adjoint equations G:K:adjoint = -G:aim_deriv_strain
400
+ adjoint_list = []
401
+ for i in range (len (strains )):
402
+ #strain = strains[i].reshape(shape, order='F')
403
+ #cell.evaluate_stress_tangent(strain)
404
+ rhs = aim_deriv_strains [i ]
405
+ if np .linalg .norm (rhs ) > tol :
406
+ rhs = rhs .reshape (shape , order = 'F' )
407
+ rhs = - cell .project (rhs ).flatten (order = 'F' )
408
+ adjoint = krylov_solver .solve (rhs )
409
+ adjoint = adjoint .reshape (shape , order = 'F' )
410
+ adjoint_list .append (adjoint .copy ())
411
+ else :
412
+ adjoint_list .append (np .zeros (shape ))
413
+
414
+ # Sensitivity equation S = dfdrho + dKdrho:F adjoint
415
+ S = aim_deriv_phase .flatten (order = 'F' )
416
+ for i in range (len (strains )):
417
+ helper = adjoint_list [i ] * dstress_dphase_list [i ]
418
+ helper = helper .reshape ([- 1 , cell .nb_quad_pts , * cell .nb_subdomain_grid_pts ], order = 'F' )
419
+ helper = df_dphase_2_quad_pts_derivative_sequential (helper )
420
+ S += np .sum (helper , axis = (0 )).flatten (order = 'F' )
421
+
422
+ return S
423
+
424
+ def wrapper_sequential (phase , cell , mat , lambda_1 , mu_1 , lambda_0 , mu_0 , order ,
425
+ DelFs , krylov_solver_args , solver_args , aim_args ,
426
+ calc_sensitivity = True , folder = None ):
427
+ """ Calculate the aim function and the sensitivity for a topology
428
+ optimization with a target stress. The optimization problem is
429
+ regularized with a phase field approach. The discretization
430
+ consists of linear finite elements of rectangular triangles.
431
+ Linear elastic, isotropic materials and a polynomial
432
+ interpolation of the Lamé constants are used as material laws.
433
+
434
+ Parameters
435
+ ----------
436
+ phase: np.ndarray(nb_grid_pts) of floats
437
+ Design parameters. Corresponds to the material
438
+ density at each quadrature point.
439
+ cell: object
440
+ muSpectre cell object
441
+ mat: object
442
+ muSpectre cell object belonging to cell
443
+ lambda_1: float
444
+ First Lamé constant of the material at phase=1
445
+ mu_1: float
446
+ Second Lamé constant (shear modul) of the material at phase=1
447
+ lambda_0: float
448
+ First Lamé constant of the material at phase=0
449
+ mu_0: float
450
+ Second Lamé constant (shear modul) of the material at phase=0
451
+ order: float
452
+ Polynomial order of the material interpolation
453
+ DelFs: list of np.ndarray(dim, dim) of floats
454
+ List of prescribed macroscopic strain
455
+ krylov_solver_args: list
456
+ List of additional arguments passed to the krylov_solver
457
+ solver_args: list
458
+ List of additional arguments passed to the solver
459
+ aim_args: list
460
+ list with additional arguments passed to the aim function
461
+ calc_sensitivity: boolean
462
+ If False, the sensitivity is not calculated. Default is True.
463
+ folder: string or None
464
+ Name of a folder in which to save the results. If None, the
465
+ results are not saved. If a string, the following files are saved:
466
+ * file_tmp.npy: for saving the new phase before the muSpectre
467
+ calculation. After the muSpectre calculation, this file is deleted.
468
+ * file_last.npy: for saving the phase of the last
469
+ successful optimization step
470
+ * file_evo.txt: save the aim function in each optimization step
471
+
472
+ Returns
473
+ -------
474
+ aim: float
475
+ Aim function
476
+ S: np.ndarray(nb_pixels * nb_quad_pts) of floats
477
+ Sensitivity in Column-major order
478
+ """
479
+ phase = phase .reshape (cell .nb_subdomain_grid_pts , order = 'F' )
480
+ # Save temporary phase (mainly for debugging)
481
+ if folder is not None :
482
+ name = folder + f'phase_tmp.npy'
483
+ save_npy (name , phase , tuple (cell .subdomain_locations ),
484
+ tuple (cell .nb_domain_grid_pts ), MPI .COMM_WORLD )
485
+
486
+ # Change material of cell
487
+ density = node_to_quad_pt_2_quad_pts_sequential (phase )
488
+ density = density .flatten (order = 'F' )
489
+ lame1 = (lambda_1 - lambda_0 ) * density ** order + lambda_0
490
+ lame2 = (mu_1 - mu_0 ) * density ** order + mu_0
491
+ for pixel_id , pixel in cell .pixels .enumerate ():
492
+ for i in range (cell .nb_quad_pts ):
493
+ quad_id = cell .nb_quad_pts * pixel_id + i
494
+ mat .set_lame_constants (quad_id , lame1 [quad_id ], lame2 [quad_id ])
495
+
496
+ # Solve the equilibrium equations
497
+ dim = cell .dim
498
+ shape = [dim , dim , cell .nb_quad_pts , * cell .nb_subdomain_grid_pts ]
499
+ krylov_solver = µ .solvers .KrylovSolverCG (cell , * krylov_solver_args )
500
+ strains = []
501
+ stresses = []
502
+ for DelF in DelFs :
503
+ result = µ .solvers .newton_cg (cell , DelF , krylov_solver ,
504
+ * solver_args )
505
+ strain = result .grad .reshape (shape , order = 'F' ).copy ()
506
+ strains .append (strain )
507
+ stresses .append (cell .evaluate_stress (strain ).copy ())
508
+
509
+ # TODO: Give average stresses to functions to avoid recalculation
510
+ average_stresses = []
511
+ for stress in stresses :
512
+ stress_average = np .average (stress , axis = (2 , 3 , 4 ))
513
+ average_stresses .append (stress_average )
514
+
515
+ # Calculate the aim function
516
+ aim = aim_function_sequential (cell , phase , strains , stresses , * aim_args )
517
+
518
+ if calc_sensitivity :
519
+ # Partial derivatives
520
+ density = density .reshape ([cell .nb_quad_pts ,
521
+ * cell .nb_subdomain_grid_pts ], order = 'F' )
522
+ dstress_dmat_list = \
523
+ calculate_dstress_dmat (cell , mat , strains , density ,
524
+ lambda_1 , mu_1 , lambda_0 ,
525
+ mu_0 , order = order )
526
+ aim_deriv_strains = \
527
+ aim_function_deriv_strain_sequential (cell , strains , stresses , * aim_args )
528
+ aim_deriv_phase = \
529
+ aim_function_deriv_phase_sequential (cell , phase , strains , stresses ,
530
+ dstress_dmat_list , * aim_args )
531
+
532
+ S = sensitivity_analysis_per_grid_pts_sequential (
533
+ cell , krylov_solver , strains , aim_deriv_strains ,
534
+ aim_deriv_phase , dstress_dmat_list , solver_args [1 ])
535
+
536
+ # Remove file with temporary phase if muSpectre calculations worked
537
+ if (MPI .COMM_WORLD .rank == 0 ) and (folder is not None ):
538
+ name = folder + f'phase_tmp.npy'
539
+ if name is not None :
540
+ os .remove (name )
541
+
334
542
# Save the last step
335
543
if folder is not None :
336
544
phase = phase .reshape (cell .nb_subdomain_grid_pts , order = 'F' )
0 commit comments