Neko-TOP
A portable framework for high-order spectral element flow toplogy optimization.
Loading...
Searching...
No Matches
optimizer.f90
Go to the documentation of this file.
1
34
41 use json_module, only: json_file
42 use simulation_m, only: simulation_t
43 use problem, only: problem_t
44 use design, only: design_t
45 use num_types, only: rp
46 use logger, only: neko_log
47 use profiler, only: profiler_start_region, profiler_end_region
48 use mpi_f08, only: mpi_wtime
49 use utils, only: neko_error, filename_suffix
50 use csv_file, only: csv_file_t
51 use vector, only: vector_t
52 use json_utils, only: json_get_or_default
53 use comm, only: pe_rank
54 implicit none
55 private
56
58 type, abstract, public :: optimizer_t
59
61 character(len=64), private :: optimizer_type = ''
63 integer, private :: max_iterations = 0
65 integer, private :: current_iteration = 0
66
67 ! ----------------------------------------------------------------------- !
68 ! Restart related members
69
71 character(len=256), private :: checkpoint_file = ''
72
73 ! Checkpoint related information
74 character(len=256), private :: checkpoint_path = './checkpoints/'
75 character(len=256), private :: checkpoint_base = 'optimizer_checkpoint'
76 character(len=256), private :: checkpoint_format = 'h5'
77 integer, private :: checkpoint_interval = -1
78
79 ! Variables for the runtime-based stopping criteria
80 real(kind=rp), private :: max_runtime = -1.0_rp
81 real(kind=rp), private :: start_time = 0.0_rp
82 real(kind=rp), private :: average_time = 0.0_rp
83 real(kind=rp), private :: step_count = 0.0_rp
84 ! Logging state
85 logical, private :: log_initialized = .false.
86 logical, private :: log_include_constraints = .true.
87 integer, private :: log_extra_size = 0
88 type(csv_file_t), private :: log_file
89 type(vector_t), private :: log_data
90
91 contains
92
93 ! ---------------------------------------------------------------------- !
94 ! Deferred procedures for specific optimizers
95
97 procedure(optimizer_init_from_json), pass(this), public, deferred :: &
98 init_from_json
100 procedure(optimizer_free), pass(this), public, deferred :: free
101
103 procedure(optimizer_initialize), pass(this), public, deferred :: initialize
105 procedure(optimizer_step), pass(this), public, deferred :: step
107 procedure(optimizer_validate), pass(this), public, deferred :: validate
108
110 procedure(optimizer_write), pass(this), public, deferred :: write
112 procedure(optimizer_save_checkpoint_components), pass(this), deferred :: &
113 save_checkpoint_components
115 procedure(optimizer_load_checkpoint_components), pass(this), deferred :: &
116 load_checkpoint_components
117
119 procedure, pass(this) :: save_checkpoint => optimizer_save_checkpoint
121 procedure, pass(this) :: load_checkpoint => optimizer_load_checkpoint
122
123 ! ----------------------------------------------------------------------- !
124 ! Public procedures
125
127 procedure, pass(this), public :: run => optimizer_run
128
129 ! ----------------------------------------------------------------------- !
130 ! Private procedures
131
133 procedure, pass(this) :: init_base => optimizer_init_base
135 procedure, pass(this) :: free_base => optimizer_free_base
137 procedure, pass(this) :: read_base_settings => optimizer_read_base_settings
139 procedure, pass(this) :: print_status => optimizer_print_status
141 procedure, pass(this) :: out_of_time => optimizer_out_of_time
143 procedure, pass(this) :: init_log => optimizer_init_log
145 procedure, pass(this) :: write_log => optimizer_write_log
146 end type optimizer_t
147
148 ! -------------------------------------------------------------------------- !
149 ! Interface for the optimizer module.
150
151 abstract interface
152
153 subroutine optimizer_init_from_json(this, parameters, problem, design, &
154 simulation)
155 import optimizer_t, json_file, simulation_t, problem_t, design_t, rp
156 class(optimizer_t), intent(inout) :: this
157 type(json_file), intent(inout) :: parameters
158 class(problem_t), intent(inout) :: problem
159 class(design_t), intent(in) :: design
160 type(simulation_t), optional, intent(in) :: simulation
161 end subroutine optimizer_init_from_json
162
166 subroutine optimizer_initialize(this, problem, design, simulation)
168 class(optimizer_t), intent(inout) :: this
169 class(problem_t), intent(inout) :: problem
170 class(design_t), intent(inout) :: design
171 type(simulation_t), optional, intent(inout) :: simulation
172 end subroutine optimizer_initialize
173
175 subroutine optimizer_free(this)
176 import optimizer_t
177 class(optimizer_t), intent(inout) :: this
178 end subroutine optimizer_free
179
181 logical function optimizer_step(this, iter, problem, design, simulation)
183 class(optimizer_t), intent(inout) :: this
184 integer, intent(in) :: iter
185 class(problem_t), intent(inout) :: problem
186 class(design_t), intent(inout) :: design
187 type(simulation_t), optional, intent(inout) :: simulation
188 end function optimizer_step
189
191 subroutine optimizer_validate(this, problem, design)
193 class(optimizer_t), intent(inout) :: this
194 class(problem_t), intent(in) :: problem
195 class(design_t), intent(in) :: design
196 end subroutine optimizer_validate
197
199 subroutine optimizer_write(this, iter, problem)
201 class(optimizer_t), intent(inout) :: this
202 integer, intent(in) :: iter
203 class(problem_t), intent(inout) :: problem
204 end subroutine optimizer_write
205
207 subroutine optimizer_save_checkpoint_components(this, filename, overwrite)
208 import optimizer_t
209 class(optimizer_t), intent(inout) :: this
210 character(len=*), intent(in) :: filename
211 logical, intent(in), optional :: overwrite
212 end subroutine optimizer_save_checkpoint_components
213
215 subroutine optimizer_load_checkpoint_components(this, filename)
216 import optimizer_t
217 class(optimizer_t), intent(inout) :: this
218 character(len=*), intent(in) :: filename
219 end subroutine optimizer_load_checkpoint_components
220
221 end interface
222
223 ! -------------------------------------------------------------------------- !
224 ! Interfaces for the factory functions
225
233 module subroutine optimizer_factory(object, parameters, problem, design, &
234 simulation)
235 class(optimizer_t), allocatable, intent(inout) :: object
236 type(json_file), intent(inout) :: parameters
237 class(problem_t), intent(inout) :: problem
238 class(design_t), intent(in) :: design
239 type(simulation_t), optional, intent(in) :: simulation
240 end subroutine optimizer_factory
241 end interface optimizer_factory
242
243 ! -------------------------------------------------------------------------- !
244 ! IO routines for HDF5 checkpoints
245
246 interface
247
248 module subroutine optimizer_save_checkpoint_hdf5(object, filename, iter, &
249 overwrite)
250 class(optimizer_t), intent(inout) :: object
251 character(len=*), intent(in) :: filename
252 integer, intent(in) :: iter
253 logical, intent(in), optional :: overwrite
254 end subroutine optimizer_save_checkpoint_hdf5
255
257 module subroutine optimizer_load_checkpoint_hdf5(object, filename, iter)
258 class(optimizer_t), intent(inout) :: object
259 character(len=*), intent(in) :: filename
260 integer, intent(out) :: iter
261 end subroutine optimizer_load_checkpoint_hdf5
262 end interface
263
264 public :: optimizer_factory
265
266contains
267
268 ! -------------------------------------------------------------------------- !
269 ! Base initializer and free routines
270
282 subroutine optimizer_init_base(this, optimizer_type, max_iterations, &
283 max_runtime, checkpoint_file, checkpoint_path, checkpoint_base, &
284 checkpoint_format, checkpoint_interval)
285 class(optimizer_t), intent(inout) :: this
286 character(len=*), intent(in) :: optimizer_type
287 integer, intent(in) :: max_iterations
288 real(kind=rp), intent(in), optional :: max_runtime
289 character(len=*), intent(in), optional :: checkpoint_file
290 character(len=*), intent(in), optional :: checkpoint_path
291 character(len=*), intent(in), optional :: checkpoint_base
292 character(len=*), intent(in), optional :: checkpoint_format
293 integer, intent(in), optional :: checkpoint_interval
294
295 ! Mandatory settings
296 this%optimizer_type = optimizer_type
297 this%max_iterations = max_iterations
298
299 ! Optional settings
300 if (present(max_runtime)) this%max_runtime = max_runtime
301 if (present(checkpoint_file)) this%checkpoint_file = checkpoint_file
302 if (present(checkpoint_path)) this%checkpoint_path = checkpoint_path
303 if (present(checkpoint_base)) this%checkpoint_base = checkpoint_base
304 if (present(checkpoint_format)) this%checkpoint_format = checkpoint_format
305 if (present(checkpoint_interval)) then
306 this%checkpoint_interval = checkpoint_interval
307 end if
308
309 ! Initialize internals
310 this%start_time = mpi_wtime()
311
312 end subroutine optimizer_init_base
313
316 subroutine optimizer_free_base(this)
317 class(optimizer_t), intent(inout) :: this
318
319 this%optimizer_type = ''
320 this%max_iterations = 0
321 this%max_runtime = -1.0_rp
322 this%checkpoint_file = ''
323 this%checkpoint_path = './checkpoints/'
324 this%checkpoint_base = 'optimizer_checkpoint'
325 this%checkpoint_format = 'h5'
326 this%checkpoint_interval = -1
327
328 this%start_time = 0.0_rp
329 this%current_iteration = 0
330 call this%log_data%free()
331 this%log_initialized = .false.
332 this%log_extra_size = 0
333 this%log_include_constraints = .true.
334
335 end subroutine optimizer_free_base
336
340 subroutine optimizer_read_base_settings(this, solver_params)
341 class(optimizer_t), intent(inout) :: this
342 type(json_file), intent(inout) :: solver_params
343 integer :: read_int
344 real(kind=rp) :: read_real
345 character(len=:), allocatable :: read_str
346
347 call json_get_or_default(solver_params, 'max_runtime', read_real, &
348 this%max_runtime)
349 this%max_runtime = read_real
350
351 call json_get_or_default(solver_params, 'checkpoint.file', read_str, &
352 this%checkpoint_file)
353 this%checkpoint_file = read_str
354 call json_get_or_default(solver_params, 'checkpoint.path', read_str, &
355 this%checkpoint_path)
356 this%checkpoint_path = read_str
357 call json_get_or_default(solver_params, 'checkpoint.base', read_str, &
358 this%checkpoint_base)
359 this%checkpoint_base = read_str
360 call json_get_or_default(solver_params, 'checkpoint.format', read_str, &
361 this%checkpoint_format)
362 this%checkpoint_format = read_str
363 call json_get_or_default(solver_params, 'checkpoint.interval', read_int, &
364 this%checkpoint_interval)
365 this%checkpoint_interval = read_int
366
367 end subroutine optimizer_read_base_settings
368
369
370 ! -------------------------------------------------------------------------- !
371 ! Optimization loop routine
372
385 subroutine optimizer_run(this, problem, design, simulation)
386 class(optimizer_t), intent(inout) :: this
387 class(problem_t), intent(inout) :: problem
388 class(design_t), intent(inout) :: design
389 type(simulation_t), optional, intent(inout) :: simulation
390 real(kind=rp) :: iteration_time
391 logical :: converged, file_exists
392 integer :: stop_flag
393
394 ! Initialize variables
395 stop_flag = 1
396 converged = .false.
397
398 ! Restart from checkpoint if available
399 inquire(file = this%checkpoint_file, exist = file_exists)
400 if (file_exists) then
401 call this%load_checkpoint(this%checkpoint_file, this%current_iteration, &
402 design)
403 else
404 inquire(file = 'optimizer_rt_checkpoint.' // this%checkpoint_format, &
405 exist = file_exists)
406 if (file_exists) then
407 call this%load_checkpoint('optimizer_rt_checkpoint.' // &
408 this%checkpoint_format, this%current_iteration, design)
409 end if
410 end if
411
412 ! compute potential internals for for a potential restart
413 if (this%current_iteration .ne. 0) then
414 call design%set_output_counter(this%current_iteration - 1)
415 if (present(simulation)) then
416 call simulation%set_output_counter(this%current_iteration - 1)
417 end if
418 end if
419
420 ! Prepare the problem state before starting the optimization
421 call this%initialize(problem, design, simulation)
422
423 call this%write(this%current_iteration, problem)
424 call design%write(this%current_iteration)
425
426 call neko_log%section('Optimization Loop')
427
428 do while (this%current_iteration .lt. this%max_iterations)
429 this%current_iteration = this%current_iteration + 1
430 call profiler_start_region('Optimizer iteration')
431 iteration_time = mpi_wtime()
432
433 converged = this%step(this%current_iteration, problem, design, &
434 simulation)
435
436 iteration_time = mpi_wtime() - iteration_time
437 call profiler_end_region('Optimizer iteration')
438
439 ! Log the progress and outputs
440 call this%write(this%current_iteration, problem)
441 call design%write(this%current_iteration)
442
443 ! Save checkpoint if enabled
444 if (this%checkpoint_interval .gt. 0 .and. &
445 mod(this%current_iteration, this%checkpoint_interval) == 0) then
446 call this%save_checkpoint(this%current_iteration, design, .false.)
447 end if
448
449 ! --------------------------------------------------------------------- !
450 ! Check stopping criteria
451
452 if (converged) then
453 stop_flag = 0
454 exit
455 else if (this%out_of_time(iteration_time)) then
456 call this%save_checkpoint(this%current_iteration, design, .true., &
457 basename = 'optimizer_rt_checkpoint')
458 stop_flag = 2
459 exit
460 end if
461 end do
462
463 ! Check that the final design is valid
464 call this%validate(problem, design)
465 call this%print_status(stop_flag, this%current_iteration)
466
467 call neko_log%end_section()
468
469 end subroutine optimizer_run
470
471 ! ========================================================================== !
472 ! Helper routines
473
483 subroutine optimizer_print_status(this, stop_flag, iter)
484 class(optimizer_t), intent(in) :: this
485 integer, intent(in) :: stop_flag
486 integer, intent(in) :: iter
487 character(len=256) :: msg
488
489 select case (stop_flag)
490 case (0)
491 write(msg, '(A,I0,A)') 'Optimizer converged successfully after ', &
492 iter, ' iterations.'
493 call neko_log%message(msg)
494 case (1)
495 write(msg, '(A,I0,A)') 'Optimizer did not converge in ', &
496 this%max_iterations, ' iterations.'
497 call neko_log%warning(msg)
498 case (2)
499 write(msg, '(A,A,F8.2,A)') 'Optimizer stopped after reaching the ', &
500 'maximum runtime of ', this%max_runtime, ' seconds.'
501 call neko_error(trim(msg))
502
503 case default
504 write(msg, '(A)') 'Optimizer stopped for an unknown reason.'
505 call neko_error(msg)
506 end select
507 end subroutine optimizer_print_status
508
515 function optimizer_out_of_time(this, step_time) result(out_of_time)
516 class(optimizer_t), intent(inout) :: this
517 real(kind=rp), intent(in) :: step_time
518 logical :: out_of_time
519 real(kind=rp) :: elapsed_time, old_avg_weight
520
521 out_of_time = .false.
522
523 if (this%max_runtime .lt. 0.0_rp) then
524 return
525 end if
526
527 elapsed_time = mpi_wtime() - this%start_time
528 this%step_count = this%step_count + 1.0_rp
529 old_avg_weight = (this%step_count - 1) / this%step_count
530
531 ! Estimate Cumulative Average iteration time
532 this%average_time = step_time / this%step_count + &
533 this%average_time * old_avg_weight
534
535 ! Determine if next iteration would exceed max runtime
536 out_of_time = (elapsed_time + this%average_time) .gt. this%max_runtime
537
538 end function optimizer_out_of_time
539
540 ! ========================================================================== !
541 ! Logging helpers
542
549 subroutine optimizer_init_log(this, problem, extra_headers, &
550 include_constraints, filename)
551 class(optimizer_t), intent(inout) :: this
552 class(problem_t), intent(in) :: problem
553 character(len=*), intent(in), optional :: extra_headers(:)
554 logical, intent(in), optional :: include_constraints
555 character(len=*), intent(in), optional :: filename
556
557 character(len=4096) :: header
558 integer :: total_size, base_size, i
559 character(len=256) :: log_name
560
561 if (present(include_constraints)) then
562 this%log_include_constraints = include_constraints
563 end if
564
565 base_size = problem%get_log_size(this%log_include_constraints)
566
567 this%log_extra_size = 0
568 if (present(extra_headers)) this%log_extra_size = size(extra_headers)
569
570 total_size = 1 + base_size + this%log_extra_size
571 call this%log_data%init(total_size)
572
573 if (present(filename)) then
574 log_name = trim(filename)
575 else
576 log_name = 'optimization_data.csv'
577 end if
578
579 call this%log_file%init(trim(log_name))
580
581 header = 'iter, ' // &
582 trim(problem%get_log_header(this%log_include_constraints))
583 if (present(extra_headers)) then
584 do i = 1, size(extra_headers)
585 if (trim(extra_headers(i)) .eq. '') then
586 call neko_error('some headers are empty')
587 end if
588 header = trim(header) // ', ' // trim(extra_headers(i))
589 end do
590 end if
591
592 call this%log_file%set_header(trim(header))
593
594 this%log_initialized = .true.
595 end subroutine optimizer_init_log
596
602 subroutine optimizer_write_log(this, iter, problem, extra_values)
603 class(optimizer_t), intent(inout) :: this
604 integer, intent(in) :: iter
605 class(problem_t), intent(in) :: problem
606 real(kind=rp), intent(in), optional :: extra_values(:)
607 integer :: base_size, offset
608
609 if (.not. this%log_initialized) return
610
611 base_size = problem%get_log_size(this%log_include_constraints)
612 this%log_data%x = 0.0_rp
613 this%log_data%x(1) = real(iter, kind=rp)
614
615 call problem%get_log_values( &
616 this%log_data%x(2:1 + base_size), &
617 this%log_include_constraints)
618
619 offset = 2 + base_size
620 if (present(extra_values)) then
621 if (this%log_extra_size .eq. 0) then
622 call neko_error('got extra values but no headers')
623 end if
624 this%log_data%x(offset:offset + size(extra_values) - 1) = extra_values
625 end if
626
627 call this%log_file%write(this%log_data)
628
629 end subroutine optimizer_write_log
630
631 ! ========================================================================== !
632 ! IO Functions
633
642 subroutine optimizer_save_checkpoint(this, iter, design, overwrite, &
643 path, basename, extension)
644 class(optimizer_t), intent(inout) :: this
645 integer, intent(in) :: iter
646 class(design_t), intent(inout) :: design
647 logical, intent(in) :: overwrite
648 character(len=*), intent(in), optional :: path
649 character(len=*), intent(in), optional :: basename
650 character(len=*), intent(in), optional :: extension
651 character(len=256) :: file_path, file_base, file_ext, file_full
652 logical :: exist
653
654 ! Set default behaviour, read from object if not provided
655 if (.not. present(path)) file_path = trim(this%checkpoint_path)
656 if (.not. present(basename)) file_base = trim(this%checkpoint_base)
657 if (.not. present(extension)) file_ext = trim(this%checkpoint_format)
658
659 if (present(path)) file_path = trim(path)
660 if (present(basename)) file_base = trim(basename)
661 if (present(extension)) file_ext = trim(extension)
662
663 ! Make sure path is valid and exists
664 if (len_trim(file_path) .eq. 0) then
665 file_path = './'
666 else if (file_path(len_trim(file_path):len_trim(file_path)) .ne. '/') then
667 file_path = trim(file_path) // '/'
668 end if
669
670 inquire(file=file_path, exist=exist)
671 if (.not. exist) then
672 call system('mkdir -p ' // trim(file_path))
673 end if
674
675 ! Construct the full filename based on overwrite flag
676 if (overwrite) then
677 write(file_full, '(4A)') &
678 trim(file_path), trim(file_base), ".", trim(file_ext)
679 else
680 write(file_full, '(3A,I5.5,2A)') &
681 trim(file_path), trim(file_base), "_", iter, ".", trim(file_ext)
682 end if
683
684 select case (trim(file_ext))
685 case ('h5', 'hdf5', 'hf5')
686 call optimizer_save_checkpoint_hdf5(this, file_full, iter, overwrite)
687 case default
688 call neko_error('optimizer: Unsupported checkpoint format: "' // &
689 trim(file_ext) // '"')
690 end select
691
692 call this%save_checkpoint_components(file_full, overwrite)
693 call design%save_checkpoint(file_full, overwrite)
694
695 end subroutine optimizer_save_checkpoint
696
702 subroutine optimizer_load_checkpoint(this, filename, iter, design)
703 class(optimizer_t), intent(inout) :: this
704 character(len=*), intent(in) :: filename
705 integer, intent(out) :: iter
706 class(design_t), intent(inout) :: design
707 character(len=12) :: file_ext
708
709 ! Get the file extension
710 call filename_suffix(filename, file_ext)
711
712 select case (trim(file_ext))
713 case ('h5', 'hdf5', 'hf5')
714 call optimizer_load_checkpoint_hdf5(this, filename, iter)
715 case default
716 call neko_error('optimizer: Unsupported checkpoint format: "' // &
717 trim(file_ext) // '"')
718 end select
719
720 call this%load_checkpoint_components(filename)
721 call design%load_checkpoint(filename)
722
723 ! Set the current iteration to the loaded iteration
724 this%current_iteration = iter
725
726 if (pe_rank .eq. 0) then
727 write(*,*) 'Restarted simulation from checkpoint.'
728 write(*,*) ' Checkpoint file: "', trim(filename), '"'
729 write(*,*) ' Iteration : ', this%current_iteration
730 end if
731
732 end subroutine optimizer_load_checkpoint
733
734 ! ========================================================================== !
735 ! Dummy implementations for module procedures
736
737#if !HAVE_HDF5
738 module subroutine optimizer_save_checkpoint_hdf5(object, filename, iter, &
739 overwrite)
740 class(optimizer_t), intent(inout) :: object
741 character(len=*), intent(in) :: filename
742 integer, intent(in) :: iter
743 logical, intent(in), optional :: overwrite
744 call neko_error('optimizer: HDF5 support not enabled rebuild with ' // &
745 'HAVE_HDF5')
746 end subroutine optimizer_save_checkpoint_hdf5
747
748 module subroutine optimizer_load_checkpoint_hdf5(object, filename, iter)
749 class(optimizer_t), intent(inout) :: object
750 character(len=*), intent(in) :: filename
751 integer, intent(out) :: iter
752 call neko_error('optimizer: HDF5 support not enabled rebuild with ' // &
753 'HAVE_HDF5')
754 end subroutine optimizer_load_checkpoint_hdf5
755#endif
756
757end module optimizer
Factory function for the optimizer.
Interface for optimizer initialization.
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.
An abstract design type.
Definition design.f90:53
Abstract optimizer class.
Definition optimizer.f90:58
The abstract problem type.
Definition problem.f90:66