Skip to content

Commit 8c62659

Browse files
committed
ENH: Parallelization of code
1 parent ce5b524 commit 8c62659

File tree

9 files changed

+1164
-31
lines changed

9 files changed

+1164
-31
lines changed

examples/example_with_parallel_l_bfgs.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
from NuMPI.Tools import Reduction
3030

3131
from muTopOpt.Controller import wrapper
32-
from muTopOpt.MaterialDensity import node_to_quad_pt_2_quad_pts_sequential
32+
from muTopOpt.MaterialDensity import node_to_quad_pt_2_quad_pts
3333

3434

3535
################################################################################
@@ -107,7 +107,7 @@
107107
maxcor = 10
108108

109109
### ----- Folder for saving data ----- ###
110-
folder = f'examples/results_sequential/'
110+
folder = f'examples/results_parallel/'
111111

112112
################################################################################
113113
### ----------------------------- Target stress ---------------------------- ###
@@ -212,7 +212,7 @@
212212
################################################################################
213213
### ----- Initialisation ----- ###
214214
# Calculate material density
215-
density = node_to_quad_pt_2_quad_pts_sequential(phase)
215+
density = node_to_quad_pt_2_quad_pts(phase, cell)
216216

217217
# Material
218218
density = density.reshape([cell.nb_quad_pts, -1], order='F')

muTopOpt/AimFunction.py

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,26 +20,46 @@
2020
import muTopOpt.StressTarget as st
2121
import muTopOpt.PhaseField as pf
2222
from muTopOpt.MaterialDensity import df_dphase_2_quad_pts_derivative_sequential
23+
from muTopOpt.MaterialDensity import df_dphase_2_quad_pts_derivative
24+
25+
def aim_function_sequential(cell, phase, strains, stresses, target_stresses, eta,
26+
weight_phase_field):
27+
aim = st.square_error_target_stresses_sequential(cell, strains,
28+
stresses,
29+
target_stresses)
30+
if cell.nb_quad_pts == 2:
31+
aim += weight_phase_field * pf.phase_field_energy_sequential(phase, cell, eta)
32+
else:
33+
aim += weight_phase_field * pf.phase_field_rectangular_grid(phase, eta, cell)
34+
return aim
2335

2436
def aim_function(cell, phase, strains, stresses, target_stresses, eta,
2537
weight_phase_field):
2638
aim = st.square_error_target_stresses(cell, strains,
2739
stresses,
2840
target_stresses)
2941
if cell.nb_quad_pts == 2:
30-
aim += weight_phase_field * pf.phase_field_energy_sequential(phase, cell, eta)
42+
aim += weight_phase_field * pf.phase_field_energy(phase, cell, eta)
3143
else:
3244
aim += weight_phase_field * pf.phase_field_rectangular_grid(phase, eta, cell)
3345
return aim
3446

47+
def aim_function_deriv_strain_sequential(cell, strains, stresses, target_stresses,
48+
eta, weight_phase_field):
49+
aim_deriv_strain =\
50+
st.square_error_target_stresses_deriv_strains_sequential(cell, strains,
51+
stresses,
52+
target_stresses)
53+
return aim_deriv_strain
54+
3555
def aim_function_deriv_strain(cell, strains, stresses, target_stresses,
3656
eta, weight_phase_field):
3757
aim_deriv_strain =\
3858
st.square_error_target_stresses_deriv_strains(cell, strains, stresses,
3959
target_stresses)
4060
return aim_deriv_strain
4161

42-
def aim_function_deriv_phase(cell, phase, strains, stresses,
62+
def aim_function_deriv_phase_sequential(cell, phase, strains, stresses,
4363
dstress_dmat_list, target_stresses,
4464
eta, weight_phase_field):
4565

@@ -60,3 +80,25 @@ def aim_function_deriv_phase(cell, phase, strains, stresses,
6080
weight_phase_field * pf.phase_field_rectangular_grid_deriv_phase(phase, eta, cell)
6181

6282
return aim_deriv_phase
83+
84+
def aim_function_deriv_phase(cell, phase, strains, stresses,
85+
dstress_dmat_list, target_stresses,
86+
eta, weight_phase_field):
87+
88+
if cell.nb_quad_pts == 2:
89+
helper = st.square_error_target_stresses_deriv_phase(cell, stresses,
90+
target_stresses,
91+
dstress_dmat_list)
92+
helper = helper.reshape([-1, cell.nb_quad_pts, *cell.nb_subdomain_grid_pts], order='F')
93+
aim_deriv_phase = df_dphase_2_quad_pts_derivative(helper, cell)
94+
aim_deriv_phase = aim_deriv_phase.reshape(cell.nb_subdomain_grid_pts)
95+
aim_deriv_phase +=\
96+
weight_phase_field * pf.phase_field_energy_deriv(phase, cell, eta)
97+
else:
98+
aim_deriv_phase = st.square_error_target_stresses_deriv_phase(cell, stresses,
99+
target_stresses,
100+
dstress_dmat_list)
101+
aim_deriv_phase +=\
102+
weight_phase_field * pf.phase_field_rectangular_grid_deriv_phase(phase, eta, cell)
103+
104+
return aim_deriv_phase

muTopOpt/Controller.py

Lines changed: 220 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,16 @@
1616
from NuMPI.Tools import Reduction
1717
from NuMPI.IO import save_npy
1818

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
1924
from muTopOpt.AimFunction import aim_function
2025
from muTopOpt.AimFunction import aim_function_deriv_strain
2126
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
2429

2530

2631
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
6772
raise ValueError(message)
6873

6974
# 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) *\
7176
(lambda_1 - lambda_0)
72-
lame2_deriv = order * density ** (order - 1) *\
77+
lame2_deriv = order * density.flatten(order='F') ** (order - 1) *\
7378
(mu_1 - mu_0)
7479

7580
# Set derivatives as material parameters in mat
7681
for pixel_id, pixel in cell.pixels.enumerate():
7782
quad_id = nb_quad_pts * pixel_id
7883
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])
8186

8287
# Calculate dstress_dphase
8388
dstress_dmat = []
@@ -86,13 +91,13 @@ def calculate_dstress_dmat(cell, mat, strains, density, lambda_1, mu_1, lambda_0
8691
dstress_dmat.append(cell.evaluate_stress(strain).copy())
8792

8893
# 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
9196
for pixel_id, pixel in cell.pixels.enumerate():
9297
quad_id = nb_quad_pts * pixel_id
9398
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])
96101

97102
return dstress_dmat
98103

@@ -205,7 +210,7 @@ def sensitivity_analysis_per_grid_pts(cell, krylov_solver, strains,
205210
for i in range(len(strains)):
206211
helper = adjoint_list[i] * dstress_dphase_list[i]
207212
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)
209214
S += np.sum(helper, axis=(0)).flatten(order='F')
210215

211216
return S
@@ -273,7 +278,7 @@ def wrapper(phase, cell, mat, lambda_1, mu_1, lambda_0, mu_0, order,
273278
tuple(cell.nb_domain_grid_pts), MPI.COMM_WORLD)
274279

275280
# 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)
277282
density = density.flatten(order='F')
278283
lame1 = (lambda_1 - lambda_0) * density ** order + lambda_0
279284
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,
331336
if name is not None:
332337
os.remove(name)
333338

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+
334542
# Save the last step
335543
if folder is not None:
336544
phase = phase.reshape(cell.nb_subdomain_grid_pts, order='F')

0 commit comments

Comments
 (0)