Neko-TOP
A portable framework for high-order spectral element flow toplogy optimization.
Loading...
Searching...
No Matches
design_brinkman.f90
1! Copyright (c) 2024, The Neko Authors
2! All rights reserved.
3!
4! Redistribution and use in source and binary forms, with or without
5! modification, are permitted provided that the following conditions
6! are met:
7!
8! * Redistributions of source code must retain the above copyright
9! notice, this list of conditions and the following disclaimer.
10!
11! * Redistributions in binary form must reproduce the above
12! copyright notice, this list of conditions and the following
13! disclaimer in the documentation and/or other materials provided
14! with the distribution.
15!
16! * Neither the name of the authors nor the names of its
17! contributors may be used to endorse or promote products derived
18! from this software without specific prior written permission.
19!
20! THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21! "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22! LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23! FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
24! COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
25! INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
26! BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27! LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28! CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29! LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30! ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31! POSSIBILITY OF SUCH DAMAGE.
32
33! Implements the `brinkman_design_t` type.
34module brinkman_design
35 use num_types, only: rp, sp
36 use field, only: field_t
37 use json_module, only: json_file
39 use coefs, only: coef_t
41 use scratch_registry, only: neko_scratch_registry
42 use fld_file_output, only: fld_file_output_t
43 use point_zone_registry, only: neko_point_zone_registry
44 use point_zone, only: point_zone_t
46 use neko_config, only: neko_bcknd_device
47 use device, only: device_memcpy, host_to_device
48 use design, only: design_t
49 use math, only: rzero
50 use simulation_m, only: simulation_t
51 use json_module, only: json_file
53 use vector, only: vector_t
54 use math, only: copy
55 use device_math, only: device_copy
56 use field_registry, only: neko_field_registry
59 use field_math, only: field_rzero
60 use json_utils, only: json_get, json_get_or_default, json_get
61 use utils, only: neko_error
62 implicit none
63 private
64
66 type, extends(design_t), public :: brinkman_design_t
67 private
68
69 ! TODO
70 ! in the future make this a derived type of a `design_variable`
71 ! type, public, extends(design_variable_t) :: brinkman_design_t
73 type(field_t), pointer :: design_indicator
74
76 ! TODO
77 ! NOTE: Tim, right now we only have the brinkman term
78 ! in the future we may map to other coeeficients for other equations...
79 ! in which case this should be a field list
80 !
81 ! or as I describe below, we also have multiple constraints,
82 ! so a list-of-lists may be the correct way forward
83 type(field_t), pointer :: brinkman_amplitude
84
85 ! NOTE:
86 ! again, we have to be so clear with nomenclature.
87 ! If we have an objective function F.
88 ! I also like to use \rho to describe the design_indicator
89 !
90 ! and let's say, for completness, we're doing conjugate heat transfer,
91 ! so we have to map
92 ! to 3 coefficients, \chi, C and \kappa.
93 !
94 ! then we will perform 3 mapping,
95 ! \rho -> \chi
96 ! \rho -> C
97 ! \rho -> \kappa
98 !
99 ! Then during the forward/adjoint looping there will be an additional
100 ! object, the "objective_function" object that will be responsible for
101 ! computing the sensitivity of the objective function with respect to the
102 ! coefficients
103 ! ie,
104 ! dF/d\chi, dF/dC and dF/d\kappa
105 !
106 ! What I'm calling "sensitivity" here, is the sensitivity with respect to
107 ! the design indicator
108 ! so dF/d\rho
109 !
110 ! so the proceedure "map_backwards" will take in the field list
111 ! dF/d\chi, dF/dC and dF/d\kappa
112 !and chain rule it's way back to
113 ! dF/d\rho
114 ! and store it here v
115 type(field_t), pointer :: sensitivity
116 ! have a field list here
117 ! type(filed_list_t), public :: constraint_sensitivity
118 ! HOWEVER !
119 ! What is a bit confusing to me... is how we'll deal with constraints.
120 !
121 ! Because in principle we could have constraints C1, C2, C3 etc
122 ! Implying we also want dC1/d\rho, dC2/d\rho etc
123 ! So this "sensitivity" should also be a list...
124 !
125 ! So I bet you'll have a nice abstract way of constructing this in the
126 ! future but I think a sort of list-of-lists would be nice.
127 !
128 ! For F:
129 ! \rho -> \tild{\rho} -> \chi
130 ! \rho -> \tild{\rho} -> C
131 ! \rho -> \tild{\rho} -> \kappa
132 !
133 ! For C1:
134 ! \rho -> \tild{\rho} (eg for a volume constraint or so)
135 !
136 ! For C2:
137 ! \rho -> \tild{\rho} (for something else)
138 !
139 ! etc..
140 !
141 ! So now we have multiple instances of the "objective" type,
142 ! each for F, C1, C2 etc
143 ! each of them can pass their dF\d_coefficents to the design
144 ! and we continue from there.
145 !
146 ! perhaps even the "objective" type is defined as a list of objectives.
147
148 ! Let's say way have a chain of two mappings
149
150 ! This is still up for discussion where the mapping will eventually live.
151 ! Maybe it makes more sense to keep the design as clean as possible,
152 ! and then the resposibility of converting the design into coefficients
153 ! used in the PDE being solved would be the responsibility of the module
154 ! used for solving the PDE, ie, the simulation.
155 ! Then the simulation would responsiblity for asking "do we have a scalar?"
156 ! ok, let me figure out how to map this design into a coeffient in the
157 ! scalar equation.
158 !
159 ! I guess then the mapping backwards gets confusing to me.
160 ! anyway, for now, let's keep things how they are and I suppose the
161 ! intention is to have different types of designs for different types of
162 ! problems.
163
169
171 class(point_zone_t), pointer :: optimization_domain
173 logical :: has_mask
174
175 ! TODO
176 ! you also had logicals for convergence etc,
177 ! I feel they should live in "problem"
178
179 ! afield writer would be nice too
180 type(fld_file_output_t), private :: output
181
182 contains
183
184 ! ----------------------------------------------------------------------- !
185 ! Initializations
186
188 generic, public :: init => init_from_json_sim, init_from_components
190 procedure, pass(this), public :: init_from_json_sim => &
191 brinkman_design_init_from_json_sim
193 procedure, pass(this), public :: init_from_components => &
194 brinkman_design_init_from_components
195
197 procedure, pass(this) :: get_values => brinkman_design_get_design
199 procedure, pass(this) :: get_sensitivity => brinkman_design_get_sensitivity
200
202 procedure, pass(this) :: design_get_x => brinkman_design_get_x
204 procedure, pass(this) :: design_get_x_i => brinkman_design_get_x_i
206 procedure, pass(this) :: design_get_y => brinkman_design_get_y
208 procedure, pass(this) :: design_get_y_i => brinkman_design_get_y_i
210 procedure, pass(this) :: design_get_z => brinkman_design_get_z
212 procedure, pass(this) :: design_get_z_i => brinkman_design_get_z_i
213
215 procedure, pass(this) :: update_design => brinkman_design_update_design
216
218 ! design_indicator -> filtering -> chi
219 ! and ultimately handle mapping different coeficients!
220 procedure, pass(this) :: map_forward => brinkman_design_map_forward
222 ! d_design_indicator <- d_filtering <- d_chi
223 ! and ultimately handle mapping different coeficients!
224 procedure, pass(this) :: map_backward => brinkman_design_map_backward
225 ! TODO
226 ! maybe it would have been smarter to have a "coeficient" type,
227 ! which is just a scalar field and set of mappings going from
228 ! design_indicator -> coeficient and their corresponding chain rules
229 ! maybe also some information about what equation they live in...
230
231 ! a writer being called from outside would be nice
232 procedure, pass(this) :: write => brinkman_design_write
233
235 procedure, pass(this) :: free => brinkman_design_free
236 ! TODO
237 ! I'm not sure who owns the optimizer...
238 ! but it would make sense to have it in here so you provide it
239 ! with dF/d_design_indicator and it updates itself.
240 ! procedure, pass(this) :: update => brinkman_design_update_design
241 end type brinkman_design_t
242
243contains
244
246 subroutine brinkman_design_init_from_json_sim(this, parameters, simulation)
247 class(brinkman_design_t), intent(inout) :: this
248 type(json_file), intent(inout) :: parameters
249 type(simulation_t), intent(inout) :: simulation
250 type(json_file) :: json_subdict
251 character(len=:), allocatable :: domain_name, domain_type, name
252 logical :: dealias
253
254 call json_get_or_default(parameters, 'name', name, 'Brinkman Design')
255 call json_get_or_default(parameters, 'domain.type', domain_type, 'full')
256 call json_get_or_default(parameters, 'dealias', dealias, .true.)
257
258 select case (trim(domain_type))
259 case ('full')
260 this%has_mask = .false.
261 case ('point_zone')
262 this%has_mask = .true.
263 call json_get(parameters, 'domain.zone_name', domain_name)
264 this%optimization_domain => &
265 neko_point_zone_registry%get_point_zone(domain_name)
266
267 case default
268 call neko_error('brinkman design only supports point_zones for ' // &
269 'optimization domain types')
270
271 end select
272
273 ! Initialize and inject into the simulation
274 call this%init_from_components(name, simulation, dealias)
275
276 ! Initialize the mapper
277 associate(coef => simulation%neko_case%fluid%c_Xh, &
278 gs => simulation%neko_case%fluid%gs_Xh)
279
280 if ('mapping' .in. parameters) then
281 call this%mapping%init_base(coef)
282 call this%mapping%add(parameters, 'mapping')
283 end if
284
285 if ('initial_distribution' .in. parameters) then
286 call json_get(parameters, 'initial_distribution', json_subdict)
287 call set_optimization_ic(this%design_indicator, coef, gs, &
288 json_subdict)
289 else
290 call field_rzero(this%design_indicator)
291 end if
292 end associate
293
294 ! Map to the Brinkman amplitude
295 call this%map_forward()
296
297 end subroutine brinkman_design_init_from_json_sim
298
300 subroutine brinkman_design_free(this)
301 class(brinkman_design_t), intent(inout) :: this
302
303 call this%free_base()
304 call this%brinkman_amplitude%free()
305 call this%design_indicator%free()
306 call this%sensitivity%free()
307
308 end subroutine brinkman_design_free
309
310 subroutine brinkman_design_init_from_components(this, name, simulation, &
311 dealias)
312 class(brinkman_design_t), intent(inout) :: this
313 character(len=*), intent(in) :: name
314 type(simulation_t), intent(inout) :: simulation
315 logical, intent(in) :: dealias
316 integer :: n, i
317 type(simple_brinkman_source_term_t) :: forward_brinkman, adjoint_brinkman
318
319 associate(dof => simulation%neko_case%fluid%dm_Xh)
320
321 call neko_field_registry%add_field(dof, "design_indicator", .true.)
322 call neko_field_registry%add_field(dof, "brinkman_amplitude", .true.)
323 call neko_field_registry%add_field(dof, "sensitivity", .true.)
324
325 end associate
326
327 this%design_indicator => &
328 neko_field_registry%get_field("design_indicator")
329 this%brinkman_amplitude => &
330 neko_field_registry%get_field("brinkman_amplitude")
331 this%sensitivity => &
332 neko_field_registry%get_field("sensitivity")
333
334 ! TODO
335 ! this is where we steal basically everything in
336 ! brinkman_source_term regarding loading initial fields
337 ! for now, make it a cylinder by hand
338 this%design_indicator = 0.0_rp
339 this%brinkman_amplitude = 0.0_rp
340 this%design_indicator%x = 0.0_rp
341
342 n = this%design_indicator%dof%size()
343 ! This is probably getting fixed in tim's PR anyway, otherwise I'll fix it.
344 do i = 1, n
345 this%design_indicator%x(i,1,1,1) = 0.0_rp
346 end do
347
348 ! again this will be handled better in the future...
349 if (neko_bcknd_device .eq. 1) then
350 call device_memcpy(this%design_indicator%x, &
351 this%design_indicator%x_d, n, &
352 host_to_device, sync = .false.)
353 end if
354
355 ! TODO
356 ! Regarding masks and filters,
357 ! I suppose there are two ways of thinking about it:
358 ! 1) Mask first, then filter
359 ! 2) filter first, then mask
360 !
361 ! Each one can be used to achieve different results, and when we do complex
362 ! non-linear filter cascades the choice here has implications for exactly
363 ! how we define "minimum size control".
364 !
365 ! On top of this, I've only ever used spatial convolution filters before,
366 ! not PDE based filters.
367 ! With these filters you need to think of how your domain is "padded" when
368 ! you move the kernel over the boundary. Again, you can achieve different
369 ! effects depending on the choice of padding.
370 ! I don't really know if there's a similar implication for PDE based
371 ! based filters, I bet there is. (We should ask Niels and Casper) I bet it
372 ! means we need to consider the boundary of the mask as the boundary we
373 ! enforce to be Nuemann, not the boundary of the computational domain.
374 ! That sounds REALLY hard to implement...I hope it doesn't come down to
375 ! that.
376 !
377 ! We can always change this decision later, but I'm going with (1)
378 ! mask first, then filter.
379 ! The reason being, one of the purposes of the filtering is to avoid sharp
380 ! interfaces, if we filter first then mask there's a chance we have a sharp
381 ! interface on the boundary of the optimization domain.
382 ! if we mask first then filter, at least all the boundaries will be smooth.
383 if (this%has_mask) then
384 call mask_exterior_const(this%design_indicator, &
385 this%optimization_domain, 0.0_rp)
386 end if
387
388 ! a field writer would be nice to output
389 ! - design indicator (\rho)
390 ! - mapped design (\chi)
391 ! - sensitivity (dF/d\chi)
392 ! TODO
393 ! do this properly with JSON
394 ! TODO
395 ! obviously when we do the mappings properly, to many coeficients,
396 ! we'll also have to modify this
397 call this%output%init(sp, 'design', 3)
398 call this%output%fields%assign_to_field(1, this%design_indicator)
399 call this%output%fields%assign_to_field(2, this%brinkman_amplitude)
400 call this%output%fields%assign_to_field(3, this%sensitivity)
401
402 call this%init_base(name, n)
403
404 ! init the simple brinkman term for the forward problem
405 call forward_brinkman%init_from_components( &
406 simulation%fluid%f_x, &
407 simulation%fluid%f_y, &
408 simulation%fluid%f_z, &
409 this%brinkman_amplitude, &
410 simulation%fluid%u, &
411 simulation%fluid%v, &
412 simulation%fluid%w, &
413 simulation%fluid%c_Xh, &
414 simulation%adjoint_fluid%c_Xh_GL, &
415 simulation%adjoint_fluid%GLL_to_GL, &
416 dealias, simulation%adjoint_fluid%scratch_GL)
417 ! append brinkman source term to the forward problem
418 call simulation%fluid%source_term%add(forward_brinkman)
419
420 ! init the simple brinkman term for the adjoint
421 call adjoint_brinkman%init_from_components( &
422 simulation%adjoint_fluid%f_adj_x, &
423 simulation%adjoint_fluid%f_adj_y, &
424 simulation%adjoint_fluid%f_adj_z, &
425 this%brinkman_amplitude, &
426 simulation%adjoint_fluid%u_adj, &
427 simulation%adjoint_fluid%v_adj, &
428 simulation%adjoint_fluid%w_adj, &
429 simulation%adjoint_fluid%c_Xh, &
430 simulation%adjoint_fluid%c_Xh_GL, &
431 simulation%adjoint_fluid%GLL_to_GL, &
432 dealias, simulation%adjoint_fluid%scratch_GL)
433 ! append brinkman source term based on design
434
435 select type (f => simulation%adjoint_fluid)
436 type is (adjoint_fluid_pnpn_t)
437 call f%source_term%add(adjoint_brinkman)
438 class default
439 end select
440
441 end subroutine brinkman_design_init_from_components
442
443
444 subroutine brinkman_design_map_forward(this)
445 class(brinkman_design_t), intent(inout) :: this
446
447 ! TODO, see previous todo about mask first, then mapping
448 if (this%has_mask) then
449 call mask_exterior_const(this%design_indicator, &
450 this%optimization_domain, 0.0_rp)
451 end if
452
453 call this%mapping%apply_forward(this%brinkman_amplitude, &
454 this%design_indicator)
455
456 end subroutine brinkman_design_map_forward
457
458 subroutine brinkman_design_get_design(this, values)
459 class(brinkman_design_t), intent(in) :: this
460 type(vector_t), intent(inout) :: values
461 integer :: n
462
463 n = this%size()
464 call copy(values%x, this%design_indicator%x, n)
465 if (neko_bcknd_device .eq. 1) then
466 call device_copy(values%x_d, this%design_indicator%x_d, n)
467 end if
468
469 end subroutine brinkman_design_get_design
470
471 subroutine brinkman_design_get_sensitivity(this, values)
472 class(brinkman_design_t), intent(in) :: this
473 type(vector_t), intent(inout) :: values
474 integer :: n
475
476 n = this%size()
477 call copy(values%x, this%sensitivity%x, n)
478 if (neko_bcknd_device .eq. 1) then
479 call device_copy(values%x_d, this%sensitivity%x_d, n)
480 end if
481
482 end subroutine brinkman_design_get_sensitivity
483
484 subroutine brinkman_design_get_x(this, x)
485 class(brinkman_design_t), intent(in) :: this
486 type(vector_t), intent(inout) :: x
487 integer :: n
488
489 n = this%size()
490 call copy(x%x, this%design_indicator%dof%x, n)
491 if (neko_bcknd_device .eq. 1) then
492 call device_copy(x%x_d, this%design_indicator%dof%x_d, n)
493 end if
494
495 end subroutine brinkman_design_get_x
496
497 function brinkman_design_get_x_i(this, i) result(x_i)
498 class(brinkman_design_t), intent(in) :: this
499 integer, intent(in) :: i
500 real(kind=rp) :: x_i
501 integer :: n
502
503 n = this%size()
504 if (i .lt. 1 .or. i .gt. n) then
505 call neko_error('brinkman_design_get_x_i: index out of bounds')
506 end if
507
508 x_i = this%design_indicator%dof%x(i,1,1,1)
509
510 end function brinkman_design_get_x_i
511
512 subroutine brinkman_design_get_y(this, y)
513 class(brinkman_design_t), intent(in) :: this
514 type(vector_t), intent(inout) :: y
515 integer :: n
516
517 n = this%size()
518 call copy(y%x, this%design_indicator%dof%y, n)
519 if (neko_bcknd_device .eq. 1) then
520 call device_copy(y%x_d, this%design_indicator%dof%y_d, n)
521 end if
522
523 end subroutine brinkman_design_get_y
524
525 function brinkman_design_get_y_i(this, i) result(y_i)
526 class(brinkman_design_t), intent(in) :: this
527 integer, intent(in) :: i
528 real(kind=rp) :: y_i
529 integer :: n
530
531 n = this%size()
532 if (i .lt. 1 .or. i .gt. n) then
533 call neko_error('brinkman_design_get_y_i: index out of bounds')
534 end if
535
536 y_i = this%design_indicator%dof%y(i,1,1,1)
537
538 end function brinkman_design_get_y_i
539
540 subroutine brinkman_design_get_z(this, z)
541 class(brinkman_design_t), intent(in) :: this
542 type(vector_t), intent(inout) :: z
543 integer :: n
544
545 n = this%size()
546 call copy(z%x, this%design_indicator%dof%z, n)
547 if (neko_bcknd_device .eq. 1) then
548 call device_copy(z%x_d, this%design_indicator%dof%z_d, n)
549 end if
550
551 end subroutine brinkman_design_get_z
552
553 function brinkman_design_get_z_i(this, i) result(z_i)
554 class(brinkman_design_t), intent(in) :: this
555 integer, intent(in) :: i
556 real(kind=rp) :: z_i
557 integer :: n
558
559 n = this%size()
560 if (i .lt. 1 .or. i .gt. n) then
561 call neko_error('brinkman_design_get_z_i: index out of bounds')
562 end if
563
564 z_i = this%design_indicator%dof%z(i,1,1,1)
565
566 end function brinkman_design_get_z_i
567
568 subroutine brinkman_design_update_design(this, values)
569 class(brinkman_design_t), intent(inout) :: this
570 type(vector_t), intent(inout) :: values
571 integer :: n
572
573 n = this%size()
574 call copy(this%design_indicator%x, values%x, n)
575 if (neko_bcknd_device .eq. 1) then
576 call device_copy(this%design_indicator%x_d, values%x_d, n)
577 end if
578
579 call this%map_forward()
580
581 call copy(values%x, this%design_indicator%x, n)
582 if (neko_bcknd_device .eq. 1) then
583 call device_copy(values%x_d, this%design_indicator%x_d, n)
584 end if
585
586 end subroutine brinkman_design_update_design
587
588 subroutine brinkman_design_map_backward(this, sensitivity)
589 class(brinkman_design_t), intent(inout) :: this
590 type(vector_t), intent(in) :: sensitivity
591 type(field_t), pointer :: tmp_fld
592 integer :: temp_indices(1)
593
594 call neko_scratch_registry%request_field(tmp_fld, temp_indices(1))
595
596 call vector_to_field(tmp_fld, sensitivity)
597
598 call this%mapping%apply_backward(this%sensitivity, tmp_fld)
599
600 if (this%has_mask) then
601 call mask_exterior_const(this%sensitivity, this%optimization_domain, &
602 0.0_rp)
603 end if
604
605 call neko_scratch_registry%relinquish_field(temp_indices)
606
607 end subroutine brinkman_design_map_backward
608
609 subroutine brinkman_design_write(this, idx)
610 class(brinkman_design_t), intent(inout) :: this
611 integer, intent(in) :: idx
612
613 call this%output%sample(real(idx, kind=rp))
614
615 end subroutine brinkman_design_write
616
617end module brinkman_design
Adjoint Pn/Pn formulation.
Implements the design_t.
Definition design.f90:34
Implements the mapping_handler_t type.
Mappings to be applied to a scalar field.
Definition mapping.f90:35
Some common Masking operations we may need.
Definition mask_ops.f90:34
Contains extensions to the neko library required to run the topology optimization code.
Definition neko_ext.f90:9
subroutine, public field_to_vector(vector, field)
Field to vector.
Definition neko_ext.f90:373
subroutine, public vector_to_field(field, vector)
Vector to field.
Definition neko_ext.f90:349
Optimization initial condition.
Implements the simple_brinkman_source_term_t type.
Implements the steady_problem_t type.
A topology optimization design variable.
An abstract design type.
Definition design.f90:52
Abstract class for handling mapping_cascade.