Neko-TOP
A portable framework for high-order spectral element flow toplogy optimization.
Loading...
Searching...
No Matches
steepest_descent_optimizer.f90
Go to the documentation of this file.
1
34
55 use optimizer, only: optimizer_t
56 use problem, only: problem_t
57 use design, only: design_t
58 use brinkman_design, only: brinkman_design_t
59 use simulation_m, only: simulation_t
60 use num_types, only: rp
61 use utils, only: neko_error
62 use json_utils, only: json_get, json_get_or_default
63 use logger, only: neko_log
64 use profiler, only: profiler_start_region, profiler_end_region
65 use scratch_registry, only: neko_scratch_registry
68
69 ! External modules
70 use json_module, only: json_file
71 use vector, only: vector_t
72 use vector_math, only: vector_copy, vector_cfill, vector_sub3, &
73 vector_absval, vector_glmax, vector_glmin, vector_glsc2, &
74 vector_glsubnorm
75 use, intrinsic :: ieee_arithmetic, only: ieee_is_nan, ieee_is_finite
76
77 implicit none
78 private
79
81
84
86 type(vector_t), private :: xold1
88 type(vector_t), private :: xold2
90 type(vector_t), private :: delta
91
93 real(kind=rp), private :: xmin = 0.0_rp
95 real(kind=rp), private :: xmax = 1.0_rp
97 real(kind=rp), private :: step_length = 1.0_rp
99 real(kind=rp), private :: move_limit_initial = 0.1_rp
101 real(kind=rp), private :: move_limit_min = 0.001_rp
103 real(kind=rp), private :: move_limit_max = 0.1_rp
105 real(kind=rp), private :: move_limit_decrease = 0.7_rp
107 real(kind=rp), private :: move_limit_increase = 1.1_rp
109 real(kind=rp), private :: last_max_gradient = 0.0_rp
110
112 logical, private :: enable_output = .true.
113
114 contains
115
116 ! Override the deferred methods
117 procedure, pass(this) :: init_from_json => sd_optimizer_init_from_json
118 procedure, pass(this) :: free => sd_optimizer_free
119 procedure, pass(this) :: initialize => sd_optimizer_initialize
120 procedure, pass(this) :: step => sd_optimizer_step
121 procedure, pass(this) :: validate => sd_optimizer_validate
122 procedure, pass(this) :: write => sd_optimizer_write
123
124 procedure, pass(this) :: save_checkpoint_components => &
125 sd_optimizer_save_checkpoint_components
126 procedure, pass(this) :: load_checkpoint_components => &
127 sd_optimizer_load_checkpoint_components
128
130
131 ! -------------------------------------------------------------------------- !
132 ! IO routines for HDF5 checkpoints
133
134 interface
135 ! Save the steepest descent state to an HDF5 checkpoint
136 module subroutine sd_optimizer_save_checkpoint_hdf5(object, filename, &
137 overwrite)
138 class(steepest_descent_optimizer_t), intent(inout) :: object
139 character(len=*), intent(in) :: filename
140 logical, intent(in), optional :: overwrite
141 end subroutine sd_optimizer_save_checkpoint_hdf5
142
143 ! Load the steepest descent state from an HDF5 checkpoint
144 module subroutine sd_optimizer_load_checkpoint_hdf5(object, filename)
145 class(steepest_descent_optimizer_t), intent(inout) :: object
146 character(len=*), intent(in) :: filename
147 end subroutine sd_optimizer_load_checkpoint_hdf5
148 end interface
149
150contains
151
152 ! -------------------------------------------------------------------------- !
153 ! Constructor and destructor
154
164 subroutine sd_optimizer_init_from_json(this, parameters, problem, design, &
165 simulation)
166 class(steepest_descent_optimizer_t), intent(inout) :: this
167 type(json_file), intent(inout) :: parameters
168 class(problem_t), intent(inout) :: problem
169 class(design_t), intent(in) :: design
170 type(simulation_t), optional, intent(in) :: simulation
171
172 type(json_file) :: solver_parameters
173 integer :: max_iterations, n
174
175 call this%free()
176
177 call neko_log%section('Optimizer Initialization')
178
179 call json_get(parameters, 'optimization.solver', solver_parameters)
180 call this%read_base_settings(solver_parameters)
181
182 if (problem%get_n_constraints() .gt. 0) then
183 call neko_error('steepest_descent: the problem has constraints, ' // &
184 'which this optimizer does not support; use the "mma" ' // &
185 'optimizer instead')
186 end if
187
188 ! Read and validate the optimizer settings
189 call json_get_or_default(solver_parameters, 'max_iterations', &
190 max_iterations, 100)
191 call json_get_or_default(solver_parameters, 'enable_output', &
192 this%enable_output, .true.)
193
194 call json_get_or_default(solver_parameters, 'steepest_descent.xmin', &
195 this%xmin, 0.0_rp)
196 call json_get_or_default(solver_parameters, 'steepest_descent.xmax', &
197 this%xmax, 1.0_rp)
198 call json_get_or_default(solver_parameters, &
199 'steepest_descent.step_length', this%step_length, 1.0_rp)
200 call json_get_or_default(solver_parameters, &
201 'steepest_descent.move_limit.initial', this%move_limit_initial, &
202 0.1_rp)
203 call json_get_or_default(solver_parameters, &
204 'steepest_descent.move_limit.min', this%move_limit_min, 0.001_rp)
205 call json_get_or_default(solver_parameters, &
206 'steepest_descent.move_limit.max', this%move_limit_max, 0.1_rp)
207 call json_get_or_default(solver_parameters, &
208 'steepest_descent.move_limit.decrease', this%move_limit_decrease, &
209 0.7_rp)
210 call json_get_or_default(solver_parameters, &
211 'steepest_descent.move_limit.increase', this%move_limit_increase, &
212 1.1_rp)
213
214 if (this%xmin .ge. this%xmax) then
215 call neko_error('steepest_descent: "steepest_descent.xmin" must ' // &
216 'be smaller than "steepest_descent.xmax"')
217 end if
218 if (this%step_length .le. 0.0_rp) then
219 call neko_error('steepest_descent: ' // &
220 '"steepest_descent.step_length" must be positive')
221 end if
222 if (this%move_limit_min .le. 0.0_rp) then
223 call neko_error('steepest_descent: ' // &
224 '"steepest_descent.move_limit.min" must be positive')
225 end if
226 if (this%move_limit_initial .lt. this%move_limit_min) then
227 call neko_error('steepest_descent: ' // &
228 '"steepest_descent.move_limit.initial" must not be smaller ' // &
229 'than "steepest_descent.move_limit.min"')
230 end if
231 if (this%move_limit_max .lt. this%move_limit_initial) then
232 call neko_error('steepest_descent: ' // &
233 '"steepest_descent.move_limit.max" must not be smaller ' // &
234 'than "steepest_descent.move_limit.initial"')
235 end if
236 if (this%move_limit_decrease .le. 0.0_rp .or. &
237 this%move_limit_decrease .gt. 1.0_rp) then
238 call neko_error('steepest_descent: ' // &
239 '"steepest_descent.move_limit.decrease" must lie in (0, 1]')
240 end if
241 if (this%move_limit_increase .lt. 1.0_rp) then
242 call neko_error('steepest_descent: ' // &
243 '"steepest_descent.move_limit.increase" must be at least 1')
244 end if
245
246 ! Start the design history from the initial design
247 n = design%size()
248 call this%xold1%init(n)
249 call this%xold2%init(n)
250 call this%delta%init(n)
251 call design%get_values(this%xold1)
252 call design%get_values(this%xold2)
253 call vector_cfill(this%delta, this%move_limit_initial)
254
255 if (this%enable_output) then
256 call this%init_log(problem, extra_headers = ['max_abs_gradient'], &
257 include_constraints = .false.)
258 end if
259
260 call this%init_base('steepest_descent', max_iterations)
261
262 call solver_parameters%destroy()
263 call neko_log%end_section()
264
265 end subroutine sd_optimizer_init_from_json
266
269 subroutine sd_optimizer_free(this)
270 class(steepest_descent_optimizer_t), intent(inout) :: this
271
272 call this%xold1%free()
273 call this%xold2%free()
274 call this%delta%free()
275 this%last_max_gradient = 0.0_rp
276
277 call this%free_base()
278
279 end subroutine sd_optimizer_free
280
281 ! -------------------------------------------------------------------------- !
282 ! Implementation of the deferred methods
283
289 subroutine sd_optimizer_initialize(this, problem, design, simulation)
290 class(steepest_descent_optimizer_t), intent(inout) :: this
291 class(problem_t), intent(inout) :: problem
292 class(design_t), intent(inout) :: design
293 type(simulation_t), optional, intent(inout) :: simulation
294
295 call problem%compute(design, simulation)
296 if (present(simulation) .and. this%enable_output) then
297 call simulation%write_forward(0)
298 end if
299 call problem%compute_sensitivity(design, simulation)
300 if (present(simulation) .and. this%enable_output) then
301 call simulation%write_adjoint(0)
302 end if
303
304 end subroutine sd_optimizer_initialize
305
315 function sd_optimizer_step(this, iter, problem, design, simulation) &
316 result(converged)
317 class(steepest_descent_optimizer_t), intent(inout) :: this
318 integer, intent(in) :: iter
319 class(problem_t), intent(inout) :: problem
320 class(design_t), intent(inout) :: design
321 type(simulation_t), optional, intent(inout) :: simulation
322 logical :: converged
323
324 type(vector_t), pointer :: x, x_new, s
325 integer :: n, indices(3)
326 real(kind=rp) :: smax
327
328 converged = .false.
329 n = design%size()
330
331 call neko_scratch_registry%request(x, indices(1), n, .false.)
332 call neko_scratch_registry%request(x_new, indices(2), n, .false.)
333 call neko_scratch_registry%request(s, indices(3), n, .false.)
334
335 ! Retrieve the current design and the gradient of the objective
336 call design%get_values(x)
337 select type (des => design)
338 type is (brinkman_design_t)
339 call des%get_sensitivity(s)
340 ! Convert gradient to directional derivative
341 call des%project_sensitivity(s)
342 class default
343 call problem%get_objective_sensitivities(s)
344 end select
345
346 ! Bound-projected steepest-descent direction
347 call sd_direction(s, x, this%xmin, this%xmax)
348
349 ! The global max and min drop NaN entries, while the sum of squares
350 ! propagates them
351 if (ieee_is_nan(vector_glsc2(s, s))) then
352 call neko_scratch_registry%relinquish(indices)
353 call neko_error('steepest_descent: the projected gradient is NaN')
354 end if
355
356 ! Maximum norm of the direction
357 smax = max(vector_glmax(s), -vector_glmin(s))
358 if (.not. ieee_is_finite(smax)) then
359 call neko_scratch_registry%relinquish(indices)
360 call neko_error('steepest_descent: the projected gradient is ' // &
361 'not finite')
362 end if
363
364 ! A vanishing projected gradient is a KKT point of the box-constrained
365 ! problem, so there is no step to take
366 if (smax .le. tiny(1.0_rp)) then
367 this%max_design_change = 0.0_rp
368 this%norm2_design_change = 0.0_rp
369 this%last_max_gradient = 0.0_rp
370 converged = .true.
371 nullify(x, x_new, s)
372 call neko_scratch_registry%relinquish(indices)
373 return
374 end if
375 this%last_max_gradient = smax
376
377 ! Adapt the move limits once two previous designs exist
378 if (iter .ge. 3) then
379 call sd_update_move_limits(this%delta, x, this%xold1, this%xold2, &
380 this%move_limit_decrease, this%move_limit_increase, &
381 this%move_limit_min, this%move_limit_max)
382 end if
383
384 ! Update the design variables, which also applies any design mask
385 call sd_update_design(x_new, x, s, this%delta, this%step_length, smax, &
386 this%xmin, this%xmax)
387 call design%update_design(x_new)
388
389 ! Evaluate the problem based on the updated design
390 call problem%compute(design, simulation)
391 if (present(simulation) .and. this%enable_output) then
392 call simulation%write_forward(iter)
393 end if
394 call problem%compute_sensitivity(design, simulation)
395 if (present(simulation) .and. this%enable_output) then
396 call simulation%write_adjoint(iter)
397 end if
398
399 ! Compute L2 norm of design change
400 this%norm2_design_change = vector_glsubnorm(x_new, x)
401
402 ! Compute maximum absolute change, reusing s as workspace
403 call vector_sub3(s, x_new, x)
404 call vector_absval(s)
405 this%max_design_change = vector_glmax(s)
406
407 ! Shift the design history, now that the move limits have used it
408 call vector_copy(this%xold2, this%xold1)
409 call vector_copy(this%xold1, x)
410
411 ! Free local resources
412 nullify(x, x_new, s)
413 call neko_scratch_registry%relinquish(indices)
414
415 end function sd_optimizer_step
416
424 subroutine sd_optimizer_validate(this, problem, design)
425 class(steepest_descent_optimizer_t), intent(inout) :: this
426 class(problem_t), intent(in) :: problem
427 class(design_t), intent(in) :: design
428
429 end subroutine sd_optimizer_validate
430
431 ! -------------------------------------------------------------------------- !
432 ! Logging and IO methods
433
438 subroutine sd_optimizer_write(this, iter, problem)
439 class(steepest_descent_optimizer_t), intent(inout) :: this
440 integer, intent(in) :: iter
441 class(problem_t), intent(inout) :: problem
442
443 if (.not. this%enable_output) return
444 call profiler_start_region('Optimizer logging')
445
446 call this%write_log(iter, problem, [this%last_max_gradient])
447
448 call profiler_end_region('Optimizer logging')
449 end subroutine sd_optimizer_write
450
455 subroutine sd_optimizer_save_checkpoint_components(this, filename, &
456 overwrite)
457 class(steepest_descent_optimizer_t), intent(inout) :: this
458 character(len=*), intent(in) :: filename
459 logical, intent(in), optional :: overwrite
460
461 call sd_optimizer_save_checkpoint_hdf5(this, filename, overwrite)
462 end subroutine sd_optimizer_save_checkpoint_components
463
467 subroutine sd_optimizer_load_checkpoint_components(this, filename)
468 class(steepest_descent_optimizer_t), intent(inout) :: this
469 character(len=*), intent(in) :: filename
470
471 call sd_optimizer_load_checkpoint_hdf5(this, filename)
472 end subroutine sd_optimizer_load_checkpoint_components
473
474 ! ========================================================================== !
475 ! Dummy implementations for module procedures
476
477#if !HAVE_HDF5
478 module subroutine sd_optimizer_save_checkpoint_hdf5(object, filename, &
479 overwrite)
480 class(steepest_descent_optimizer_t), intent(inout) :: object
481 character(len=*), intent(in) :: filename
482 logical, intent(in), optional :: overwrite
483 call neko_error('steepest_descent: HDF5 support not enabled rebuild ' // &
484 'with HAVE_HDF5')
485 end subroutine sd_optimizer_save_checkpoint_hdf5
486
487 module subroutine sd_optimizer_load_checkpoint_hdf5(object, filename)
488 class(steepest_descent_optimizer_t), intent(inout) :: object
489 character(len=*), intent(in) :: filename
490 call neko_error('steepest_descent: HDF5 support not enabled rebuild ' // &
491 'with HAVE_HDF5')
492 end subroutine sd_optimizer_load_checkpoint_hdf5
493#endif
494
Implements the design_t.
Definition design.f90:36
Defines the abstract type optimizer.
Definition optimizer.f90:40
Module for handling the optimization problem.
Definition problem.f90:41
Implements the steady_problem_t type.
Vector-level kernels of the steepest-descent optimizer.
subroutine, public sd_update_move_limits(delta, x, x1, x2, dec, inc, dmin, dmax, n)
Adapt the move limits (Eq. D23).
subroutine, public sd_update_design(x_new, x, s, delta, psi, smax, xmin, xmax, n)
Move-limited, box-constrained design update (Eq. D24).
subroutine, public sd_direction(s, x, xmin, xmax, n)
Bound-projected steepest-descent direction (Eq. D22).
Steepest descent optimizer with move limits.
subroutine sd_optimizer_init_from_json(this, parameters, problem, design, simulation)
Initialise the steepest descent optimizer from the case file.
A topology optimization design variable.
An abstract design type.
Definition design.f90:53
Abstract optimizer class.
Definition optimizer.f90:64
The abstract problem type.
Definition problem.f90:68