|
| 1 | +""" |
| 2 | +@file example_with_parallel_l_bfgs.py |
| 3 | +
|
| 4 | +@author Indre Jödicke <[email protected]> |
| 5 | +
|
| 6 | +@date 19 Sep 2024 |
| 7 | +
|
| 8 | +@brief Example for a topology optimization using |
| 9 | + the scipy L-BFGS-B optimizer. |
| 10 | +""" |
| 11 | +import sys |
| 12 | +import os |
| 13 | +import shutil |
| 14 | + |
| 15 | +import numpy as np |
| 16 | +import time |
| 17 | + |
| 18 | +# Default path of the library |
| 19 | +sys.path.insert(0, os.path.join(os.getcwd(), "../muspectre/builddir/language_bindings/python")) |
| 20 | +sys.path.insert(0, os.path.join(os.getcwd(), "../muspectre/builddir/language_bindings/libmufft/python")) |
| 21 | +sys.path.insert(0, os.path.join(os.getcwd(), "../muspectre/builddir/language_bindings/libmugrid/python")) |
| 22 | +import muSpectre as µ |
| 23 | +import muFFT |
| 24 | + |
| 25 | +from NuMPI.Optimization import LBFGS |
| 26 | +from NuMPI import MPI |
| 27 | +from NuMPI.IO import save_npy |
| 28 | +from NuMPI.IO import load_npy |
| 29 | +from NuMPI.Tools import Reduction |
| 30 | + |
| 31 | +from muTopOpt.Controller import wrapper |
| 32 | +from muTopOpt.MaterialDensity import node_to_quad_pt_2_quad_pts_sequential |
| 33 | + |
| 34 | + |
| 35 | +################################################################################ |
| 36 | +### ------------------------ Parameter declarations ------------------------ ### |
| 37 | +################################################################################ |
| 38 | +t = time.time() |
| 39 | +if MPI.COMM_WORLD.rank == 0: |
| 40 | + print(f'MPI: Rank {MPI.COMM_WORLD.rank}, size {MPI.COMM_WORLD.size}') |
| 41 | + |
| 42 | +### ----- Geometry ----- ### |
| 43 | +nb_grid_pts = [16, 16] |
| 44 | +lengths = [1, 1] |
| 45 | + |
| 46 | +dim = len(nb_grid_pts) |
| 47 | +hx = lengths[0] / nb_grid_pts[0] |
| 48 | +hy = lengths[1] / nb_grid_pts[1] |
| 49 | + |
| 50 | +gradient, weights = µ.linear_finite_elements.gradient_2d |
| 51 | + |
| 52 | +### ----- Target elastic constants ----- ### |
| 53 | +poisson_target = -0.5 |
| 54 | +young_target = 0.25 |
| 55 | + |
| 56 | +mu_target = young_target / 2 / (1 + poisson_target) |
| 57 | +lambda_target = young_target * poisson_target |
| 58 | +lambda_target = lambda_target / (1 + poisson_target) / (1 - 2 * poisson_target) |
| 59 | + |
| 60 | +### ----- Weighting parameters ----- ### |
| 61 | +eta = 0.01 |
| 62 | +weight_phase_field = 0.01 |
| 63 | + |
| 64 | +### ----- Formulation ----- ### |
| 65 | +formulation = µ.Formulation.small_strain |
| 66 | + |
| 67 | +### ----- FFT-engine ----- ### |
| 68 | +fft = 'mpi' #'fftwmpi' # Parallel |
| 69 | +# fft = 'fftw' # Seriel |
| 70 | + |
| 71 | +### ----- Random seed ----- ### |
| 72 | +# For random phase distribution |
| 73 | +rng = np.random.default_rng(132131888) |
| 74 | + |
| 75 | +### ----- Macroscopic strain ----- ### |
| 76 | +DelFs = [np.zeros([dim, dim])] |
| 77 | +DelFs[0][1, 1] = 1. |
| 78 | +DelFs[0][0, 0] = 1. |
| 79 | + |
| 80 | +### ----- Material parameter ----- ### |
| 81 | +order = 2 # Interpolation order |
| 82 | + |
| 83 | +# First material: Solid |
| 84 | +young = 1 |
| 85 | +poisson = 0.2 |
| 86 | +mu_1 = young / 2 / (1 + poisson) |
| 87 | +lambda_1 = young * poisson / (1 + poisson) / (1 - 2 * poisson) |
| 88 | + |
| 89 | +# Second material: Void |
| 90 | +mu_0 = 0 |
| 91 | +lambda_0 = 0 |
| 92 | + |
| 93 | + |
| 94 | +### ----- muSpectre solver parameters ----- ### |
| 95 | +newton_tol = 1e-4 |
| 96 | +cg_tol = 1e-5 # tolerance for cg algo |
| 97 | +equil_tol = 1e-4 # tolerance for equilibrium |
| 98 | +maxiter = 15000 |
| 99 | +verbose = µ.Verbosity.Silent |
| 100 | +krylov_solver_args = (cg_tol, maxiter, verbose) |
| 101 | +solver_args = (newton_tol, equil_tol, verbose) |
| 102 | + |
| 103 | + |
| 104 | +### ----- Optimization parameters ----- ### |
| 105 | +gtol = 1e-5 |
| 106 | +ftol = 1e-15 |
| 107 | +maxcor = 10 |
| 108 | + |
| 109 | +### ----- Folder for saving data ----- ### |
| 110 | +folder = f'examples/results_sequential/' |
| 111 | + |
| 112 | +################################################################################ |
| 113 | +### ----------------------------- Target stress ---------------------------- ### |
| 114 | +################################################################################ |
| 115 | +### ----- Construct cell with aimed for material properties ----- ### |
| 116 | +cell = µ.Cell([1, 1], lengths, formulation, fft=fft) |
| 117 | +mat = µ.material.MaterialElasticLocalLame_2d.make(cell, "material") |
| 118 | +mat.add_pixel_lame(0, lambda_target, mu_target) |
| 119 | + |
| 120 | +### ----- Calculate the aimed for average stresses ----- ### |
| 121 | +target_stresses = [] |
| 122 | +for DelF in DelFs: |
| 123 | + strain = DelF |
| 124 | + strain = strain.reshape([*DelF.shape, 1, 1], order='F') |
| 125 | + target_stress = cell.evaluate_stress(strain) |
| 126 | + target_stress = np.average(target_stress, axis=(2, 3, 4)) |
| 127 | + target_stresses.append(target_stress) |
| 128 | + |
| 129 | +# Arguments passed to the aim function |
| 130 | +aim_args = (target_stresses, eta, weight_phase_field) |
| 131 | + |
| 132 | + |
| 133 | +################################################################################ |
| 134 | +### ---------------------- Save important information ---------------------- ### |
| 135 | +################################################################################ |
| 136 | +### ----- Create folder for saving data ----- ### |
| 137 | +if MPI.COMM_WORLD.rank == 0: |
| 138 | + if not os.path.exists(folder): |
| 139 | + os.makedirs(folder) |
| 140 | + else: |
| 141 | + shutil.rmtree(folder) |
| 142 | + os.makedirs(folder) |
| 143 | + |
| 144 | +### ----- Print some data ----- ### |
| 145 | +if MPI.COMM_WORLD.rank == 0: |
| 146 | + print(µ.version.info()) |
| 147 | + |
| 148 | + print('Optimize') |
| 149 | + print(' mu_target =', mu_target) |
| 150 | + print(' lambda_target =', lambda_target) |
| 151 | + print(' nb_grid_pts =', nb_grid_pts) |
| 152 | + |
| 153 | +### ----- Save metadata of simulation ----- ### |
| 154 | +file_name = folder + '/metadata.txt' |
| 155 | +if MPI.COMM_WORLD.rank == 0: |
| 156 | + with open(file_name, 'w') as f: |
| 157 | + ### ----- Save metadata ----- ### |
| 158 | + print('nb_grid_pts, lenghts, weight_eta, weight_phase_field, first_lame_1, ' |
| 159 | + 'second_lame_1, first_lame_0, second_lame_0, ' |
| 160 | + 'newton_tolerance, cg_tolerance, equil_tolerance, maxiter, ' |
| 161 | + 'gtol, ftol, maxcor, first_lame_target, second_lame_target, ', |
| 162 | + 'loading', file=f) |
| 163 | + np.savetxt(f, nb_grid_pts, delimiter=' ', newline=' ') |
| 164 | + np.savetxt(f, lengths, delimiter=' ', newline=' ') |
| 165 | + np.savetxt(f, [eta], delimiter=' ', newline=' ') |
| 166 | + np.savetxt(f, [weight_phase_field], delimiter=' ', newline=' ') |
| 167 | + np.savetxt(f, [lambda_1], delimiter=' ', newline=' ') |
| 168 | + np.savetxt(f, [mu_1], delimiter=' ', newline=' ') |
| 169 | + np.savetxt(f, [lambda_0], delimiter=' ', newline=' ') |
| 170 | + np.savetxt(f, [mu_0], delimiter=' ', newline=' ') |
| 171 | + np.savetxt(f, [newton_tol], delimiter=' ', newline=' ') |
| 172 | + np.savetxt(f, [cg_tol], delimiter=' ', newline=' ') |
| 173 | + np.savetxt(f, [equil_tol], delimiter=' ', newline=' ') |
| 174 | + np.savetxt(f, [maxiter], delimiter=' ', newline=' ') |
| 175 | + np.savetxt(f, [gtol], delimiter=' ', newline=' ') |
| 176 | + np.savetxt(f, [ftol], delimiter=' ', newline=' ') |
| 177 | + np.savetxt(f, [maxcor], delimiter=' ', newline=' ') |
| 178 | + np.savetxt(f, [lambda_target], delimiter=' ', newline=' ') |
| 179 | + np.savetxt(f, [mu_target], delimiter=' ') |
| 180 | + |
| 181 | + print('nb_loading_cases, loadings', file=f) |
| 182 | + np.savetxt(f, [len(DelFs)], delimiter=' ', newline=' ') |
| 183 | + for i in range(len(DelFs)): |
| 184 | + np.savetxt(f, DelFs[i].flatten(), delimiter=' ', newline=' ') |
| 185 | + print('', file=f) |
| 186 | + |
| 187 | + print('MPI_size', file=f) |
| 188 | + print(MPI.COMM_WORLD.size, file=f) |
| 189 | + |
| 190 | +### ----- Save initial phase distribution ----- ### |
| 191 | +# Cell construction |
| 192 | +cell = µ.Cell(nb_grid_pts, lengths, formulation, gradient, |
| 193 | + weights=weights, fft=fft, communicator=MPI.COMM_WORLD) |
| 194 | + |
| 195 | +# Initial phase distribution |
| 196 | +phase_ini = rng.random(cell.nb_domain_grid_pts) |
| 197 | +phase_ini = phase_ini[cell.fft_engine.subdomain_slices] |
| 198 | + |
| 199 | +# Saving |
| 200 | +file_name = folder + f'phase_initial.npy' |
| 201 | +save_npy(file_name, phase_ini, tuple(cell.subdomain_locations), |
| 202 | + tuple(cell.nb_domain_grid_pts), MPI.COMM_WORLD) |
| 203 | +phase = phase_ini |
| 204 | + |
| 205 | +# Copy this file into the folder |
| 206 | +if MPI.COMM_WORLD.rank == 0: |
| 207 | + helper = 'cp examples/example_with_parallel_l_bfgs.py ' + folder |
| 208 | + os.system(helper) |
| 209 | + |
| 210 | +################################################################################ |
| 211 | +### ----------------------------- Optimization ----------------------------- ### |
| 212 | +################################################################################ |
| 213 | +### ----- Initialisation ----- ### |
| 214 | +# Calculate material density |
| 215 | +density = node_to_quad_pt_2_quad_pts_sequential(phase) |
| 216 | + |
| 217 | +# Material |
| 218 | +density = density.reshape([cell.nb_quad_pts, -1], order='F') |
| 219 | +mat2 = µ.material.MaterialElasticLocalLame_2d.make(cell, "material") |
| 220 | +lame1 = density ** order * (lambda_1 - lambda_0) + lambda_0 |
| 221 | +lame2 = density ** order * (mu_1 - mu_0) + mu_0 |
| 222 | +for pixel_id, pixel in cell.pixels.enumerate(): |
| 223 | + mat2.add_pixel_lame(pixel_id, lame1[:, pixel_id], lame2[:, pixel_id]) |
| 224 | +cell.initialise() |
| 225 | + |
| 226 | +# Save evolution of aim function |
| 227 | +if MPI.COMM_WORLD.rank == 0: |
| 228 | + name = folder + 'evolution.txt' |
| 229 | + with open(name, 'w') as f: |
| 230 | + title = 'aim_function norm_sensitivity average_stresses' |
| 231 | + print(title, file=f) |
| 232 | + |
| 233 | + |
| 234 | +# Passing function arguments in NuMPI conform way |
| 235 | +opt_args = (cell, mat2, lambda_1, mu_1, lambda_0, mu_0, order, |
| 236 | + DelFs, krylov_solver_args, solver_args, aim_args, |
| 237 | + True, folder) |
| 238 | +def fun(x): |
| 239 | + return wrapper(x, *opt_args) |
| 240 | + |
| 241 | + |
| 242 | +### ----- Optimization ----- ### |
| 243 | +t = time.time() |
| 244 | +# With NuMPI optimizer |
| 245 | +opt_result = LBFGS(fun, phase, args=opt_args, jac=True, gtol=gtol, |
| 246 | + ftol=ftol, maxcor=maxcor, comm=MPI.COMM_WORLD, max_ls=100) |
| 247 | + |
| 248 | +################################################################################ |
| 249 | +### ----------------------------- Save results ----------------------------- ### |
| 250 | +################################################################################ |
| 251 | +### ----- Save metadata of convergence ----- ### |
| 252 | +if MPI.COMM_WORLD.rank == 0: |
| 253 | + file_name = folder + 'metadata_convergence.txt' |
| 254 | + with open(file_name, 'a') as f: |
| 255 | + print('opt_success, opt_time (min), opt_nb_of_iteration', file=f) |
| 256 | + np.savetxt(f, [opt_result.success, (time.time() - t) / 60, opt_result.nit], |
| 257 | + delimiter=' ', newline=' ') |
| 258 | + if not opt_result.success: |
| 259 | + print(file=f) |
| 260 | + print(opt_result.message, file=f) |
| 261 | + |
| 262 | +### ----- Save final phase distribution ----- ### |
| 263 | +# Final phase |
| 264 | +phase_final = opt_result.x.reshape(*cell.nb_subdomain_grid_pts, order='F') |
| 265 | +file_name = folder + 'phase_last.npy' |
| 266 | +save_npy(file_name, phase_final, tuple(cell.subdomain_locations), |
| 267 | + tuple(cell.nb_domain_grid_pts), MPI.COMM_WORLD) |
| 268 | + |
| 269 | +t = time.time() - t |
| 270 | +if MPI.COMM_WORLD.rank == 0: |
| 271 | + print() |
| 272 | + if opt_result.success: |
| 273 | + print('Optimization successful') |
| 274 | + else: |
| 275 | + print('Optimization failed') |
| 276 | + print('Time (min) =', t/60) |
0 commit comments