MPI-AMRVAC 3.2
The MPI - Adaptive Mesh Refinement - Versatile Advection Code (development version)
Loading...
Searching...
No Matches
mod_nlfff_optimization.t
Go to the documentation of this file.
1!> Nonlinear force-free extrapolation by the weighted optimization method.
2!>
3!> The implementation follows Equations (10), (13), (15), and (17) of
4!> Wiegelmann (2004), Solar Physics 219, 87, using fixed nodal physical
5!> boundaries and explicit adaptive-step controls.
7 use mod_physicaldata, only: state
8 implicit none
9 private
10
11 double precision, parameter :: nlfff_step_grow=1.01d0
12 double precision, parameter :: nlfff_step_shrink=0.5d0
13 ! Thomas's reference controller uses mue > 1e-7 * dx**2 as its
14 ! absolute pseudo-time-step floor. Keep this tied to the mesh spacing,
15 ! rather than to a user-selected initial step scale.
16 double precision, parameter :: nlfff_step_floor_dx2=1.d-7
17
19 integer :: fft_padding_factor=-1
20 character(len=16) :: fft_top_boundary=''
21 character(len=16) :: flux_treatment=''
22 double precision :: max_flux_imbalance=-1.d0
23 integer :: buffer_cells=-1
24 integer :: max_iterations=-1
25 double precision :: initial_step_scale=-1.d0
26 integer :: log_interval=-1
27 character(len=24) :: update_preconditioner=''
28 character(len=16) :: initialization_mode='potential'
29 logical :: write_detailed_history=.false.
30 logical :: plateau_enabled=.true.
31 integer :: plateau_interval=10
32 integer :: plateau_window=10
33 double precision :: plateau_tolerance=1.d-4
35
37 integer :: attempts=0
38 integer :: accepted_steps=0
39 integer :: rejected_steps=0
40 integer :: functional_evaluations=0
41 character(len=32) :: stop_reason='not_started'
42 double precision :: initial_l=0.d0
43 double precision :: initial_l_force=0.d0
44 double precision :: initial_l_div=0.d0
45 double precision :: initial_b2_integral=0.d0
46 double precision :: initial_epsilon_force=0.d0
47 double precision :: initial_epsilon_div=0.d0
48 double precision :: step_floor=0.d0
49 double precision :: final_l=0.d0
50 double precision :: final_l_force=0.d0
51 double precision :: final_l_div=0.d0
52 double precision :: final_b2_integral=0.d0
53 double precision :: final_epsilon_force=0.d0
54 double precision :: final_epsilon_div=0.d0
55 double precision :: final_step=0.d0
56 integer :: plateau_count=0
57 double precision :: final_relative_functional_change=1.d0
59
60{^ifthreed
61 ! Internal performance counters. They are reported as rank maxima after a
62 ! run and are deliberately kept out of the public result and CSV schemas.
63 type :: nlfff_optimization_timing
64 double precision :: update_kernel=0.d0
65 double precision :: binomial_halo_x=0.d0
66 double precision :: binomial_halo_y=0.d0
67 double precision :: trial_b_exchange=0.d0
68 double precision :: qs_exchange=0.d0
69 double precision :: functional_local=0.d0
70 double precision :: functional_reductions=0.d0
71 double precision :: unified_diagnostics=0.d0
72 end type nlfff_optimization_timing
73
74 type(nlfff_optimization_timing), save :: optimization_timing
75 logical, save :: optimization_timing_active=.false.
76
77 double precision, allocatable, save :: bottom_b(:,:,:)
78 ! Static Cartesian-grid weights are reused by every trial and functional
79 ! evaluation. Cache one-dimensional factors rather than a block-sized
80 ! three-dimensional array, so the cache remains negligible on HMI cases.
81 double precision, allocatable, save :: nlfff_weight_x(:)
82 double precision, allocatable, save :: nlfff_weight_y(:)
83 double precision, allocatable, save :: nlfff_weight_z(:)
84 ! Q=(Omega_a x B) and s=(Omega_b dot B) require four communication slots,
85 ! independent of the persistent physics width. Keeping accepted and trial
86 ! values separate lets rejected trials be discarded without rollback.
87 type(state), allocatable, target, save :: accepted_qs_state(:)
88 type(state), allocatable, target, save :: trial_qs_state(:)
89
94 public :: nlfff_weight_product
100}
101
102contains
103
104{^ifthreed
105 !> Read one Python V1 data-driven vector magnetogram. The same read also
106 !> initializes mod_lfff's normalized Bz data for the potential FFT.
107 subroutine init_nlfff_optimization_boundary(filename,qLunit,qBunit,qxc1,qxc2)
109
110 character(len=*), intent(in) :: filename
111 double precision, intent(in) :: qlunit,qbunit
112 double precision, intent(in), optional :: qxc1,qxc2
113
114 if(allocated(bottom_b)) deallocate(bottom_b)
115 call init_b_fff_data_driven_boundary(filename,qlunit,qbunit,qxc1,qxc2,bottom_b)
117
118 !> Perform a one-shot, fixed-grid weighted optimization extrapolation.
119 subroutine extrapolate_nlfff_optimization(iw_b,config,result)
120 use mpi
121 use mod_comm_lib, only: mpistop
128
129 integer, intent(in) :: iw_b(3)
130 type(nlfff_optimization_config), intent(in) :: config
131 type(nlfff_optimization_result), intent(out) :: result
132
133 double precision :: step,step_floor,lold,lforce_old,ldiv_old,b2old
134 double precision :: ltrial,lforce_trial,ldiv_trial,b2trial
135 double precision :: epsilon_force,epsilon_div,characteristic_spacing
136 double precision :: plateau_reference_l,plateau_relative_change
137 double precision :: timing_start
138 type(nlfff_physical_metrics) :: physical_metrics
139 integer :: log_unit,metrics_unit
140 integer :: plateau_count
141 logical :: accepted_trial,plateau_stop
142
144 optimization_timing=nlfff_optimization_timing()
145 optimization_timing_active=.true.
146 call validate_configuration(iw_b,config)
147 call initialize_nlfff_weight_cache(config%buffer_cells)
148 call validate_exchange_state()
149 call allocate_qs_states()
150 call prepare_partial_exchange_types(iw_b)
151
152 if(trim(config%initialization_mode)=='potential') then
153 ! The observed magnetogram is centered in the adjacent lower ghost layer.
154 call extrapolate_potential_fft(iw_b,config%fft_padding_factor,&
155 0.5d0*dx(3,1),0.d0,config%fft_top_boundary,&
156 config%flux_treatment,config%max_flux_imbalance)
157 end if
158
159 call apply_physical_boundaries(ps,iw_b)
160 call exchange_b_internal(iw_b)
161 call evaluate_functional(ps,iw_b,accepted_qs_state,config%buffer_cells,&
162 lold,lforce_old,ldiv_old,b2old)
163 result%functional_evaluations=1
164
165 step=config%initial_step_scale*minval(dx(:,1))**2
166 step_floor=nlfff_step_floor_dx2*minval(dx(:,1))**2
167 result%step_floor=step_floor
168 plateau_count=0
169 plateau_reference_l=lold
170 plateau_relative_change=1.d0
171 plateau_stop=.false.
172 result%final_relative_functional_change=plateau_relative_change
173 characteristic_spacing=product(dx(:,1))**(1.d0/3.d0)
174 call nlfff_dimensionless_metrics(lforce_old,ldiv_old,b2old,&
175 characteristic_spacing,epsilon_force,epsilon_div)
176 result%initial_L=lold
177 result%initial_L_force=lforce_old
178 result%initial_L_div=ldiv_old
179 result%initial_B2_integral=b2old
180 result%initial_epsilon_force=epsilon_force
181 result%initial_epsilon_div=epsilon_div
182 result%final_L=lold
183 result%final_L_force=lforce_old
184 result%final_L_div=ldiv_old
185 result%final_B2_integral=b2old
186 result%final_epsilon_force=epsilon_force
187 result%final_epsilon_div=epsilon_div
188 result%final_step=step
189
190 log_unit=-1
191 metrics_unit=-1
192 accepted_trial=.false.
193 if(mype==0) then
194 if(config%write_detailed_history) then
195 open(newunit=log_unit,file=trim(base_filename)//'_nlfff_opt.csv',&
196 status='replace',action='write')
197 write(log_unit,'(a)') 'attempt,accepted,rejected,step,L,L_force,L_div,'//&
198 'B2_integral,epsilon_force,epsilon_div,functional_evaluations,'//&
199 'step_floor,plateau_count,plateau_relative_change,accepted_trial'
200 call write_log_row(log_unit,result,step,lold,lforce_old,ldiv_old,b2old,&
201 epsilon_force,epsilon_div,accepted_trial)
202 end if
203 open(newunit=metrics_unit,file=trim(base_filename)//'_nlfff_metrics.csv',&
204 status='replace',action='write')
205 call write_nlfff_metrics_header(metrics_unit)
206 write(*,*) 'Weighted NLFFF optimization initial L:',lold
207 end if
208 timing_start=mpi_wtime()
209 call evaluate_nlfff_metrics_amrvac(iw_b,physical_metrics)
210 call accumulate_timing(optimization_timing%unified_diagnostics,timing_start)
211 if(mype==0) call write_nlfff_metrics_row(metrics_unit,0,physical_metrics)
212
213 do while(result%accepted_steps<config%max_iterations .and. step>=step_floor .and. &
214 .not.plateau_stop)
215 result%attempts=result%attempts+1
216 call form_trial_field(iw_b,config%buffer_cells,&
217 config%update_preconditioner,step)
218 call apply_physical_boundaries(ps1,iw_b)
219 timing_start=mpi_wtime()
220 call exchange_trial_b_internal(iw_b)
221 call accumulate_timing(optimization_timing%trial_b_exchange,timing_start)
222 call evaluate_functional(ps1,iw_b,trial_qs_state,config%buffer_cells,&
223 ltrial,lforce_trial,ldiv_trial,b2trial)
224 result%functional_evaluations=result%functional_evaluations+1
225
226 accepted_trial=(ltrial<lold)
227 if(accepted_trial) then
228 result%accepted_steps=result%accepted_steps+1
229 call accept_trial_field(iw_b)
230 call swap_qs_states()
231 lold=ltrial
232 lforce_old=lforce_trial
233 ldiv_old=ldiv_trial
234 b2old=b2trial
235 step=step*nlfff_step_grow
236 else
237 result%rejected_steps=result%rejected_steps+1
238 step=step*nlfff_step_shrink
239 end if
240
241 result%final_L=lold
242 result%final_L_force=lforce_old
243 result%final_L_div=ldiv_old
244 result%final_B2_integral=b2old
245 call nlfff_dimensionless_metrics(lforce_old,ldiv_old,b2old,&
246 characteristic_spacing,epsilon_force,epsilon_div)
247 result%final_epsilon_force=epsilon_force
248 result%final_epsilon_div=epsilon_div
249 result%final_step=step
250
251 ! Thomas checks the relative functional change every ten accepted
252 ! iterations and stops after ten consecutive plateau checks. Rejected
253 ! trials do not advance this diagnostic, matching Thomas's it=it-1
254 ! rollback of rejected iterations.
255 if(config%plateau_enabled .and. accepted_trial .and. &
256 result%accepted_steps>=config%plateau_interval .and. &
257 mod(result%accepted_steps,config%plateau_interval)==0) then
258 plateau_relative_change=abs((lold-plateau_reference_l)/&
259 max(abs(lold),tiny(1.d0)))
260 if(plateau_relative_change<config%plateau_tolerance) then
261 plateau_count=plateau_count+1
262 else
263 plateau_count=0
264 end if
265 plateau_reference_l=lold
266 result%plateau_count=plateau_count
267 result%final_relative_functional_change=plateau_relative_change
268 if(plateau_count>=config%plateau_window) plateau_stop=.true.
269 end if
270 if(mod(result%attempts,config%log_interval)==0) then
271 timing_start=mpi_wtime()
272 call evaluate_nlfff_metrics_amrvac(iw_b,physical_metrics)
273 call accumulate_timing(optimization_timing%unified_diagnostics,timing_start)
274 if(mype==0) then
275 if(config%write_detailed_history) call write_log_row(log_unit,result,&
276 step,lold,lforce_old,ldiv_old,b2old,epsilon_force,epsilon_div,&
277 accepted_trial)
278 call write_nlfff_metrics_row(metrics_unit,result%attempts,&
279 physical_metrics)
280 write(*,*) 'NLFFF optimization:',result%accepted_steps,&
281 result%rejected_steps,step,lold
282 end if
283 end if
284 end do
285
286 if(plateau_stop) then
287 result%stop_reason='plateau'
288 else if(result%accepted_steps>=config%max_iterations) then
289 result%stop_reason='max_iterations'
290 else
291 result%stop_reason='step_floor'
292 end if
293 result%final_step=step
294 if(mod(result%attempts,config%log_interval)/=0) then
295 timing_start=mpi_wtime()
296 call evaluate_nlfff_metrics_amrvac(iw_b,physical_metrics)
297 call accumulate_timing(optimization_timing%unified_diagnostics,timing_start)
298 if(mype==0) then
299 if(config%write_detailed_history) call write_log_row(log_unit,result,&
300 step,lold,lforce_old,ldiv_old,b2old,epsilon_force,epsilon_div,&
301 accepted_trial)
302 call write_nlfff_metrics_row(metrics_unit,result%attempts,&
303 physical_metrics)
304 end if
305 end if
306 if(mype==0) then
307 if(config%write_detailed_history) close(log_unit)
308 close(metrics_unit)
309 write(*,*) 'Weighted NLFFF optimization stopped:',trim(result%stop_reason)
310 write(*,*) 'accepted, rejected, final L:',result%accepted_steps,&
311 result%rejected_steps,result%final_L
312 write(*,*) 'step floor, plateau count, relative dL/L:',&
313 result%step_floor,result%plateau_count,&
314 result%final_relative_functional_change
315 write(*,*) 'functional evaluations:',result%functional_evaluations
316 end if
317 call report_nlfff_timing()
318 optimization_timing_active=.false.
319 call deallocate_nlfff_weight_cache()
320 call restore_full_exchange_types()
321 call deallocate_qs_states()
322 end subroutine extrapolate_nlfff_optimization
323
324 subroutine allocate_qs_states()
327
328 integer :: iigrid,igrid
329
330 if(allocated(accepted_qs_state)) deallocate(accepted_qs_state)
331 if(allocated(trial_qs_state)) deallocate(trial_qs_state)
332 allocate(accepted_qs_state(max_blocks),trial_qs_state(max_blocks))
333 do iigrid=1,igridstail
334 igrid=igrids(iigrid)
335 call alloc_state(igrid,accepted_qs_state(igrid),ixg^ll,ixg^ll,.false.)
336 call alloc_state(igrid,trial_qs_state(igrid),ixg^ll,ixg^ll,.false.)
337 deallocate(accepted_qs_state(igrid)%w,trial_qs_state(igrid)%w)
338 allocate(accepted_qs_state(igrid)%w(ixglo1:ixghi1,ixglo2:ixghi2,&
339 ixglo3:ixghi3,1:4))
340 allocate(trial_qs_state(igrid)%w(ixglo1:ixghi1,ixglo2:ixghi2,&
341 ixglo3:ixghi3,1:4))
342 accepted_qs_state(igrid)%w=0.d0
343 trial_qs_state(igrid)%w=0.d0
344 end do
345 end subroutine allocate_qs_states
346
347 subroutine deallocate_qs_states()
348 if(allocated(accepted_qs_state)) deallocate(accepted_qs_state)
349 if(allocated(trial_qs_state)) deallocate(trial_qs_state)
350 end subroutine deallocate_qs_states
351
352 subroutine initialize_nlfff_weight_cache(buffer_cells)
354
355 integer, intent(in) :: buffer_cells
356 integer :: ig1,ig2,ig3
357
358 if(allocated(nlfff_weight_x)) deallocate(nlfff_weight_x)
359 if(allocated(nlfff_weight_y)) deallocate(nlfff_weight_y)
360 if(allocated(nlfff_weight_z)) deallocate(nlfff_weight_z)
361 allocate(nlfff_weight_x(1-nghostcells:domain_nx1+nghostcells))
362 allocate(nlfff_weight_y(1-nghostcells:domain_nx2+nghostcells))
363 allocate(nlfff_weight_z(1-nghostcells:domain_nx3+nghostcells))
364 do ig1=lbound(nlfff_weight_x,1),ubound(nlfff_weight_x,1)
365 nlfff_weight_x(ig1)=nlfff_cosine_side_weight(ig1,domain_nx1,buffer_cells)
366 end do
367 do ig2=lbound(nlfff_weight_y,1),ubound(nlfff_weight_y,1)
368 nlfff_weight_y(ig2)=nlfff_cosine_side_weight(ig2,domain_nx2,buffer_cells)
369 end do
370 do ig3=lbound(nlfff_weight_z,1),ubound(nlfff_weight_z,1)
371 nlfff_weight_z(ig3)=nlfff_cosine_top_weight(ig3,domain_nx3,buffer_cells)
372 end do
373 end subroutine initialize_nlfff_weight_cache
374
375 subroutine deallocate_nlfff_weight_cache()
376 if(allocated(nlfff_weight_x)) deallocate(nlfff_weight_x)
377 if(allocated(nlfff_weight_y)) deallocate(nlfff_weight_y)
378 if(allocated(nlfff_weight_z)) deallocate(nlfff_weight_z)
379 end subroutine deallocate_nlfff_weight_cache
380
381 logical function cached_fixed_active_cell(igrid,ix1,ix2,ix3)
382 use mod_global_parameters, only: block_nx1,block_nx2,block_nx3,nghostcells,node,&
383 pig1_,pig2_,pig3_,domain_nx1,domain_nx2,domain_nx3
384
385 integer, intent(in) :: igrid,ix1,ix2,ix3
386 integer :: ig1,ig2,ig3
387
388 ig1=(node(pig1_,igrid)-1)*block_nx1+ix1-nghostcells
389 ig2=(node(pig2_,igrid)-1)*block_nx2+ix2-nghostcells
390 ig3=(node(pig3_,igrid)-1)*block_nx3+ix3-nghostcells
391 cached_fixed_active_cell=nlfff_fixed_active_index(ig1,ig2,ig3,&
392 domain_nx1,domain_nx2,domain_nx3)
393 end function cached_fixed_active_cell
394
395 double precision function cached_cell_weight(igrid,ix1,ix2,ix3)
396 use mod_global_parameters, only: block_nx1,block_nx2,block_nx3,nghostcells,node,&
397 pig1_,pig2_,pig3_
398
399 integer, intent(in) :: igrid,ix1,ix2,ix3
400 integer :: ig1,ig2,ig3
401
402 ig1=(node(pig1_,igrid)-1)*block_nx1+ix1-nghostcells
403 ig2=(node(pig2_,igrid)-1)*block_nx2+ix2-nghostcells
404 ig3=(node(pig3_,igrid)-1)*block_nx3+ix3-nghostcells
405 cached_cell_weight=nlfff_weight_x(ig1)*nlfff_weight_y(ig2)*nlfff_weight_z(ig3)
406 end function cached_cell_weight
407
408 subroutine report_nlfff_timing()
409 use mpi
411
412 double precision :: local_values(8),global_values(8)
413
414 local_values=(/optimization_timing%update_kernel,&
415 optimization_timing%binomial_halo_x,&
416 optimization_timing%binomial_halo_y,&
417 optimization_timing%trial_b_exchange,&
418 optimization_timing%qs_exchange,&
419 optimization_timing%functional_local,&
420 optimization_timing%functional_reductions,&
421 optimization_timing%unified_diagnostics/)
422 call mpi_allreduce(local_values,global_values,8,mpi_double_precision,&
423 mpi_max,icomm,ierrmpi)
424 if(mype==0) then
425 write(*,'(a,8(1x,es16.8))') 'NLFFF timing rank-max [s]:',global_values
426 write(*,'(a)') ' update_kernel binomial_halo_x binomial_halo_y '//&
427 'trial_b_exchange qs_exchange functional_local '//&
428 'functional_reductions unified_diagnostics'
429 end if
430 end subroutine report_nlfff_timing
431
432 subroutine accumulate_timing(counter,timing_start)
433 use mpi
434
435 double precision, intent(inout) :: counter
436 double precision, intent(in) :: timing_start
437
438 if(optimization_timing_active) counter=counter+mpi_wtime()-timing_start
439 end subroutine accumulate_timing
440
441 subroutine validate_configuration(iw_b,config)
442 use mod_comm_lib, only: mpistop
445 use mod_lfff, only: xa1,xa2
446 use, intrinsic :: ieee_arithmetic, only: ieee_is_finite
447
448 integer, intent(in) :: iw_b(3)
449 type(nlfff_optimization_config), intent(in) :: config
450 double precision :: dx1m,dx2m,tol
451 integer :: pad1,pad2
452
453 if(ndim/=3 .or. ndir/=3) call mpistop('NLFFF optimization requires 3D vectors')
454 if(coordinate/=cartesian) call mpistop('NLFFF optimization requires Cartesian coordinates')
455 if(any(stretched_dim)) call mpistop('NLFFF optimization requires a uniform mesh')
456 if(refine_max_level/=1 .or. levmax/=1) &
457 call mpistop('NLFFF optimization requires refine_max_level=1')
458 if(stagger_grid) call mpistop('NLFFF optimization requires cell-centered B')
459 if(b0field) call mpistop('NLFFF optimization does not support B0 splitting')
460 if(any(periodb)) call mpistop('NLFFF optimization requires physical side and top boundaries')
461 if(nghostcells<2) call mpistop('NLFFF optimization requires at least two ghost cells')
462 if(block_nx1<3 .or. block_nx2<3 .or. block_nx3<3) &
463 call mpistop('NLFFF one-sided boundary stencil requires at least three cells per block')
464 if(nw<3) call mpistop('NLFFF optimization requires three magnetic variables')
465 if(iw_b(1)<1 .or. iw_b(3)>nw) &
466 call mpistop('NLFFF magnetic variable indices are outside the state')
467 if(any(iw_b/=(/iw_b(1),iw_b(1)+1,iw_b(1)+2/))) &
468 call mpistop('NLFFF optimization requires contiguous B components')
469 if(.not.allocated(bottom_b)) call mpistop('NLFFF vector boundary has not been initialized')
470 if(.not.all(ieee_is_finite(bottom_b))) call mpistop('NLFFF vector boundary is non-finite')
471
472 if(config%fft_padding_factor<1) call mpistop('NLFFF fft_padding_factor must be set')
473 if(trim(config%fft_top_boundary)/='open' .and. &
474 trim(config%fft_top_boundary)/='closed') &
475 call mpistop("NLFFF fft_top_boundary must be 'open' or 'closed'")
476 if(trim(config%flux_treatment)/='strict' .and. &
477 trim(config%flux_treatment)/='subtract_mean') &
478 call mpistop("NLFFF flux_treatment must be 'strict' or 'subtract_mean'")
479 if(.not.ieee_is_finite(config%max_flux_imbalance) .or. &
480 config%max_flux_imbalance<0.d0 .or. config%max_flux_imbalance>1.d0) &
481 call mpistop('NLFFF max_flux_imbalance must be explicitly set in [0,1]')
482 if(config%buffer_cells<2) call mpistop('NLFFF buffer_cells must be at least two')
483 if(2*config%buffer_cells>=domain_nx1 .or. &
484 2*config%buffer_cells>=domain_nx2 .or. &
485 config%buffer_cells>=domain_nx3) &
486 call mpistop('NLFFF cosine buffer leaves no interior physical region')
487 if(config%max_iterations<1) call mpistop('NLFFF max_iterations must be positive')
488 if(.not.ieee_is_finite(config%initial_step_scale) .or. &
489 config%initial_step_scale<=0.d0) &
490 call mpistop('NLFFF initial_step_scale must be positive')
491 if(config%log_interval<1) call mpistop('NLFFF log_interval must be positive')
492 if(config%plateau_interval<1) &
493 call mpistop('NLFFF plateau_interval must be positive')
494 if(config%plateau_window<1) &
495 call mpistop('NLFFF plateau_window must be positive')
496 if(.not.ieee_is_finite(config%plateau_tolerance) .or. &
497 config%plateau_tolerance<0.d0) &
498 call mpistop('NLFFF plateau_tolerance must be non-negative')
499 if(trim(config%update_preconditioner)/='none' .and. &
500 trim(config%update_preconditioner)/='binomial_xy') &
501 call mpistop("NLFFF update_preconditioner must be 'none' or 'binomial_xy'")
502 if(trim(config%initialization_mode)/='potential' .and. &
503 trim(config%initialization_mode)/='current_state') &
504 call mpistop("NLFFF initialization_mode must be 'potential' or 'current_state'")
505
506 if(size(bottom_b,1)<domain_nx1 .or. size(bottom_b,2)<domain_nx2) &
507 call mpistop('NLFFF vector boundary is smaller than the physical domain')
508 if(mod(size(bottom_b,1)-domain_nx1,2)/=0 .or. &
509 mod(size(bottom_b,2)-domain_nx2,2)/=0) &
510 call mpistop('NLFFF vector boundary padding must be symmetric')
511 pad1=(size(bottom_b,1)-domain_nx1)/2
512 pad2=(size(bottom_b,2)-domain_nx2)/2
513 if(pad1<nghostcells .or. pad2<nghostcells) &
514 call mpistop('NLFFF vector boundary lacks horizontal ghost padding')
515 if(size(xa1)<2 .or. size(xa2)<2) call mpistop('NLFFF boundary coordinates are incomplete')
516 dx1m=xa1(2)-xa1(1)
517 dx2m=xa2(2)-xa2(1)
518 tol=1.d-10*max(1.d0,abs(dx(1,1)),abs(dx(2,1)))
519 if(abs(dx1m-dx(1,1))>tol .or. abs(dx2m-dx(2,1))>tol) &
520 call mpistop('NLFFF vector boundary spacing does not match the grid')
521 if(abs(xa1(pad1+1)-(xprobmin1+0.5d0*dx(1,1)))>tol .or. &
522 abs(xa1(pad1+domain_nx1)-(xprobmax1-0.5d0*dx(1,1)))>tol .or. &
523 abs(xa2(pad2+1)-(xprobmin2+0.5d0*dx(2,1)))>tol .or. &
524 abs(xa2(pad2+domain_nx2)-(xprobmax2-0.5d0*dx(2,1)))>tol) &
525 call mpistop('NLFFF vector boundary center does not match the grid')
526 end subroutine validate_configuration
527
528 !> The initialization hook normally enters with the full-state datatype.
529 !> Refuse a nested partial exchange because its MPI datatype targets would
530 !> otherwise be overwritten and could not be reconstructed here.
531 subroutine validate_exchange_state()
532 use mod_comm_lib, only: mpistop
534
535 if(.not.associated(type_send_srl,type_send_srl_f) .or. &
536 .not.associated(type_recv_srl,type_recv_srl_f) .or. &
537 .not.associated(type_send_r,type_send_r_f) .or. &
538 .not.associated(type_recv_r,type_recv_r_f) .or. &
539 .not.associated(type_send_p,type_send_p_f) .or. &
540 .not.associated(type_recv_p,type_recv_p_f) .or. .not.bcphys) &
541 call mpistop('NLFFF optimization requires full-state ghost exchange on entry')
542 end subroutine validate_exchange_state
543
544 subroutine prepare_partial_exchange_types(iw_b)
546
547 integer, intent(in) :: iw_b(3)
548
555 call create_bc_mpi_datatype(iw_b(1),3)
556
563 call create_bc_mpi_datatype(1,4,4)
564 call restore_full_exchange_types()
565 end subroutine prepare_partial_exchange_types
566
567 subroutine restore_full_exchange_types()
569
576 bcphys=.true.
577 end subroutine restore_full_exchange_types
578
579 subroutine exchange_b_internal(iw_b)
582
583 integer, intent(in) :: iw_b(3)
584
591 bcphys=.false.
592 call getbc(global_time,0.d0,ps,iw_b(1),3)
593 call restore_full_exchange_types()
594 end subroutine exchange_b_internal
595
596 subroutine exchange_qs_internal(qs_state)
599
600 type(state), target, intent(inout) :: qs_state(max_blocks)
601
608 bcphys=.false.
609 call getbc(global_time,0.d0,qs_state,1,4)
610 call restore_full_exchange_types()
611 end subroutine exchange_qs_internal
612
613 subroutine exchange_trial_b_internal(iw_b)
616
617 integer, intent(in) :: iw_b(3)
618
625 bcphys=.false.
626 call getbc(global_time,0.d0,ps1,iw_b(1),3)
627 call restore_full_exchange_types()
628 end subroutine exchange_trial_b_internal
629
630 !> Fill physical ghosts without invoking user MHD boundary callbacks.
631 subroutine apply_physical_boundaries(field_state,iw_b)
632 use mod_comm_lib, only: mpistop
634 use mod_lfff, only: xa1,xa2
635
636 integer, intent(in) :: iw_b(3)
637 type(state), target, intent(inout) :: field_state(max_blocks)
638 integer :: iigrid,igrid,idir,ix1,ix2,ix3,ib1,ib2
639 double precision :: dx1m,dx2m
640
641 dx1m=xa1(2)-xa1(1)
642 dx2m=xa2(2)-xa2(1)
643 do iigrid=1,igridstail
644 igrid=igrids(iigrid)
645 if(ps(igrid)%is_physical_boundary(1)) then
646 do ix1=ixglo1,ixmlo1-1
647 do idir=1,3
648 field_state(igrid)%w(ix1,ixglo2:ixghi2,ixglo3:ixghi3,iw_b(idir))=&
649 field_state(igrid)%w(ixmlo1,ixglo2:ixghi2,ixglo3:ixghi3,iw_b(idir))
650 end do
651 end do
652 end if
653 if(ps(igrid)%is_physical_boundary(2)) then
654 do ix1=ixmhi1+1,ixghi1
655 do idir=1,3
656 field_state(igrid)%w(ix1,ixglo2:ixghi2,ixglo3:ixghi3,iw_b(idir))=&
657 field_state(igrid)%w(ixmhi1,ixglo2:ixghi2,ixglo3:ixghi3,iw_b(idir))
658 end do
659 end do
660 end if
661 if(ps(igrid)%is_physical_boundary(3)) then
662 do ix2=ixglo2,ixmlo2-1
663 do idir=1,3
664 field_state(igrid)%w(ixglo1:ixghi1,ix2,ixglo3:ixghi3,iw_b(idir))=&
665 field_state(igrid)%w(ixglo1:ixghi1,ixmlo2,ixglo3:ixghi3,iw_b(idir))
666 end do
667 end do
668 end if
669 if(ps(igrid)%is_physical_boundary(4)) then
670 do ix2=ixmhi2+1,ixghi2
671 do idir=1,3
672 field_state(igrid)%w(ixglo1:ixghi1,ix2,ixglo3:ixghi3,iw_b(idir))=&
673 field_state(igrid)%w(ixglo1:ixghi1,ixmhi2,ixglo3:ixghi3,iw_b(idir))
674 end do
675 end do
676 end if
677 if(ps(igrid)%is_physical_boundary(6)) then
678 do ix3=ixmhi3+1,ixghi3
679 do idir=1,3
680 field_state(igrid)%w(ixglo1:ixghi1,ixglo2:ixghi2,ix3,iw_b(idir))=&
681 field_state(igrid)%w(ixglo1:ixghi1,ixglo2:ixghi2,ixmhi3,iw_b(idir))
682 end do
683 end do
684 end if
685
686 ! Apply the measured lower boundary last, so it owns lower-side corners.
687 if(ps(igrid)%is_physical_boundary(5)) then
688 do ix3=ixglo3,ixmlo3-1
689 do ix2=ixglo2,ixghi2
690 do ix1=ixglo1,ixghi1
691 ib1=nint((ps(igrid)%x(ix1,ix2,ix3,1)-xa1(1))/dx1m)+1
692 ib2=nint((ps(igrid)%x(ix1,ix2,ix3,2)-xa2(1))/dx2m)+1
693 if(ib1<1 .or. ib1>size(bottom_b,1) .or. &
694 ib2<1 .or. ib2>size(bottom_b,2)) then
695 write(*,*) 'NLFFF boundary pixel outside frame:',mype,igrid,ib1,ib2
696 call mpistop('NLFFF lower-boundary mapping failed')
697 end if
698 do idir=1,3
699 field_state(igrid)%w(ix1,ix2,ix3,iw_b(idir))=&
700 bottom_b(ib1,ib2,idir)
701 end do
702 end do
703 end do
704 end do
705 end if
706 end do
707 end subroutine apply_physical_boundaries
708
709 !> Evaluate the unmodified weighted functional and prepare Q,s in one pass.
710 subroutine evaluate_functional(field_state,iw_b,qs_state,buffer_cells,L,&
711 Lforce,Ldiv,B2integral)
712 use mpi
715 use, intrinsic :: ieee_arithmetic, only: ieee_is_finite
716
717 integer, intent(in) :: iw_b(3),buffer_cells
718 type(state), target, intent(in) :: field_state(max_blocks)
719 type(state), target, intent(inout) :: qs_state(max_blocks)
720 double precision, intent(out) :: l,lforce,ldiv,b2integral
721
722 double precision :: local_force,local_div,local_b2
723 double precision :: part_force,part_div,part_b2
724 double precision :: cell_force,cell_div,diagnostic(3)
725 double precision :: local_values(3),global_values(3)
726 double precision :: timing_start
727 double precision :: bvec(ixglo1:ixghi1,ixglo2:ixghi2,ixglo3:ixghi3,1:3)
728 double precision :: current(ixglo1:ixghi1,ixglo2:ixghi2,ixglo3:ixghi3,1:3)
729 double precision :: divb_array(ixglo1:ixghi1,ixglo2:ixghi2,&
730 ixglo3:ixghi3)
731 double precision :: oa(3),ob(3),b(3),j(3),divb,b2,wcell,cell_volume
732 integer :: iigrid,igrid,ix1,ix2,ix3,ixb3,idirmin
733
734 local_force=0.d0
735 local_div=0.d0
736 local_b2=0.d0
737 timing_start=mpi_wtime()
738 do iigrid=1,igridstail
739 igrid=igrids(iigrid)
740 qs_state(igrid)%w(ixg^t,1:4)=0.d0
741 end do
742
743 do iigrid=1,igridstail
744 igrid=igrids(iigrid)
745 block=>ps(igrid)
746 dxlevel(:)=dx(:,1)
747 do ix3=ixglo3,ixghi3
748 do ix2=ixglo2,ixghi2
749 do ix1=ixglo1,ixghi1
750 bvec(ix1,ix2,ix3,:)=&
751 field_state(igrid)%w(ix1,ix2,ix3,iw_b(:))
752 end do
753 end do
754 end do
755 current=0.d0
756 divb_array=0.d0
757 idirmin=1
758 call curlvector(bvec,ixg^ll,ixm^ll,current,idirmin,1,3)
759 call divvector(bvec,ixg^ll,ixm^ll,divb_array,1)
760 call override_fixed_boundary_curl_div(bvec,igrid,current,divb_array)
761 part_force=0.d0
762 part_div=0.d0
763 part_b2=0.d0
764 do ix3=ixmlo3,ixmhi3
765 do ix2=ixmlo2,ixmhi2
766 do ix1=ixmlo1,ixmhi1
767 b=bvec(ix1,ix2,ix3,:)
768 j=current(ix1,ix2,ix3,:)
769 divb=divb_array(ix1,ix2,ix3)
770 b2=dot_product(b,b)
771 call nlfff_compute_omega_cell(b,j,divb,oa,ob)
772 qs_state(igrid)%w(ix1,ix2,ix3,1:3)=cross3(oa,b)
773 qs_state(igrid)%w(ix1,ix2,ix3,4)=dot_product(ob,b)
774 wcell=cached_cell_weight(igrid,ix1,ix2,ix3)
775 cell_volume=ps(igrid)%dvolume(ix1,ix2,ix3)
776 cell_force=0.d0
777 cell_div=0.d0
778 if(b2>0.d0) then
779 cell_force=wcell*b2*dot_product(oa,oa)*cell_volume
780 cell_div=wcell*b2*dot_product(ob,ob)*cell_volume
781 end if
782 if(.not.all(ieee_is_finite(b)) .or. &
783 .not.all(ieee_is_finite(j)) .or. &
784 .not.ieee_is_finite(divb) .or. &
785 .not.all(ieee_is_finite(oa)) .or. &
786 .not.all(ieee_is_finite(ob)) .or. &
787 .not.ieee_is_finite(cell_force) .or. &
788 .not.ieee_is_finite(cell_div)) then
789 diagnostic=(/wcell,cell_force,cell_div/)
790 call report_nonfinite('functional',igrid,ix1,ix2,ix3,&
791 b,j,divb,oa,ob,diagnostic)
792 end if
793 part_force=part_force+cell_force
794 part_div=part_div+cell_div
795 part_b2=part_b2+wcell*b2*cell_volume
796 end do
797 end do
798 end do
799
800 ! Match the nodal optimization discretization at the photosphere: the
801 ! fixed magnetogram node contributes one full uniform-cell volume to L.
802 if(ps(igrid)%is_physical_boundary(5)) then
803 ixb3=ixmlo3-1
804 do ix2=ixmlo2,ixmhi2
805 do ix1=ixmlo1,ixmhi1
806 b=bvec(ix1,ix2,ixb3,:)
807 call compute_curl_div_node(bvec,igrid,ix1,ix2,ixb3,.true.,j,divb)
808 b2=dot_product(b,b)
809 call nlfff_compute_omega_cell(b,j,divb,oa,ob)
810 qs_state(igrid)%w(ix1,ix2,ixb3,1:3)=cross3(oa,b)
811 qs_state(igrid)%w(ix1,ix2,ixb3,4)=dot_product(ob,b)
812 wcell=cached_cell_weight(igrid,ix1,ix2,ixb3)
813 cell_volume=ps(igrid)%dvolume(ix1,ix2,ixmlo3)
814 cell_force=0.d0
815 cell_div=0.d0
816 if(b2>0.d0) then
817 cell_force=wcell*b2*dot_product(oa,oa)*cell_volume
818 cell_div=wcell*b2*dot_product(ob,ob)*cell_volume
819 end if
820 if(.not.all(ieee_is_finite(b)) .or. &
821 .not.all(ieee_is_finite(j)) .or. &
822 .not.ieee_is_finite(divb) .or. &
823 .not.all(ieee_is_finite(oa)) .or. &
824 .not.all(ieee_is_finite(ob)) .or. &
825 .not.ieee_is_finite(cell_force) .or. &
826 .not.ieee_is_finite(cell_div)) then
827 diagnostic=(/wcell,cell_force,cell_div/)
828 call report_nonfinite('lower-boundary functional',igrid,ix1,&
829 ix2,ixb3,b,j,divb,oa,ob,diagnostic)
830 end if
831 part_force=part_force+cell_force
832 part_div=part_div+cell_div
833 part_b2=part_b2+wcell*b2*cell_volume
834 end do
835 end do
836 end if
837 local_force=local_force+part_force
838 local_div=local_div+part_div
839 local_b2=local_b2+part_b2
840 end do
841 call accumulate_timing(optimization_timing%functional_local,timing_start)
842 timing_start=mpi_wtime()
843 local_values=(/local_force,local_div,local_b2/)
844 call mpi_allreduce(local_values,global_values,3,mpi_double_precision,&
845 mpi_sum,icomm,ierrmpi)
846 lforce=global_values(1)
847 ldiv=global_values(2)
848 b2integral=global_values(3)
849 call accumulate_timing(optimization_timing%functional_reductions,timing_start)
850 l=lforce+ldiv
851 timing_start=mpi_wtime()
852 call exchange_qs_internal(qs_state)
853 call accumulate_timing(optimization_timing%qs_exchange,timing_start)
854 end subroutine evaluate_functional
855
856 subroutine form_trial_field(iw_b,buffer_cells,update_preconditioner,step)
857 use mpi
860 use, intrinsic :: ieee_arithmetic, only: ieee_is_finite
861
862 integer, intent(in) :: iw_b(3),buffer_cells
863 character(len=*), intent(in) :: update_preconditioner
864 double precision, intent(in) :: step
865 double precision :: bvec(ixglo1:ixghi1,ixglo2:ixghi2,ixglo3:ixghi3,1:3)
866 double precision :: current(ixglo1:ixghi1,ixglo2:ixghi2,ixglo3:ixghi3,1:3)
867 double precision :: divb_array(ixglo1:ixghi1,ixglo2:ixghi2,&
868 ixglo3:ixghi3)
869 double precision :: qvec(ixglo1:ixghi1,ixglo2:ixghi2,ixglo3:ixghi3,1:3)
870 double precision :: scalar_s(ixglo1:ixghi1,ixglo2:ixghi2,ixglo3:ixghi3)
871 double precision :: curlq(ixglo1:ixghi1,ixglo2:ixghi2,ixglo3:ixghi3,1:3)
872 double precision :: weight(ixglo1:ixghi1,ixglo2:ixghi2,ixglo3:ixghi3)
873 double precision :: grad_s_array(ixglo1:ixghi1,ixglo2:ixghi2,&
874 ixglo3:ixghi3,1:3)
875 double precision :: grad_w(ixglo1:ixghi1,ixglo2:ixghi2,&
876 ixglo3:ixghi3,1:3)
877 double precision :: b(3),j(3),oa(3),ob(3),ftilde(3),gw(3)
878 double precision :: q(3),curl_q(3),grad_scalar_s(3),divb,wcell
879 double precision :: timing_start
880 integer :: iigrid,igrid,ix1,ix2,ix3,idir,idirmin
881
882 timing_start=mpi_wtime()
883 do iigrid=1,igridstail
884 igrid=igrids(iigrid)
885 ps1(igrid)%w(ixg^t,iw_b(:))=0.d0
886 end do
887 do iigrid=1,igridstail
888 igrid=igrids(iigrid)
889 block=>ps(igrid)
890 dxlevel(:)=dx(:,1)
891 do ix3=ixglo3,ixghi3
892 do ix2=ixglo2,ixghi2
893 do ix1=ixglo1,ixghi1
894 bvec(ix1,ix2,ix3,:)=ps(igrid)%w(ix1,ix2,ix3,iw_b(:))
895 qvec(ix1,ix2,ix3,:)=accepted_qs_state(igrid)%w(ix1,ix2,ix3,1:3)
896 scalar_s(ix1,ix2,ix3)=accepted_qs_state(igrid)%w(ix1,ix2,ix3,4)
897 weight(ix1,ix2,ix3)=cached_cell_weight(igrid,ix1,ix2,ix3)
898 end do
899 end do
900 end do
901 current=0.d0
902 divb_array=0.d0
903 curlq=0.d0
904 grad_s_array=0.d0
905 grad_w=0.d0
906 idirmin=1
907 call curlvector(bvec,ixg^ll,ixm^ll,current,idirmin,1,3)
908 call divvector(bvec,ixg^ll,ixm^ll,divb_array,1)
909 idirmin=1
910 call curlvector(qvec,ixg^ll,ixm^ll,curlq,idirmin,1,3)
911 do idir=1,3
912 call gradient(scalar_s,ixg^ll,ixm^ll,idir,&
913 grad_s_array(ixg^t,idir),1)
914 call gradient(weight,ixg^ll,ixm^ll,idir,grad_w(ixg^t,idir),1)
915 end do
916 do ix3=ixmlo3,ixmhi3
917 do ix2=ixmlo2,ixmhi2
918 do ix1=ixmlo1,ixmhi1
919 if(cached_fixed_active_cell(igrid,ix1,ix2,ix3)) cycle
920 b=bvec(ix1,ix2,ix3,:)
921 j=current(ix1,ix2,ix3,:)
922 divb=divb_array(ix1,ix2,ix3)
923 q=qvec(ix1,ix2,ix3,:)
924 curl_q=curlq(ix1,ix2,ix3,:)
925 grad_scalar_s=grad_s_array(ix1,ix2,ix3,:)
926 gw=grad_w(ix1,ix2,ix3,:)
927 call nlfff_compute_omega_cell(b,j,divb,oa,ob)
928 call nlfff_compose_update_cell(b,j,divb,oa,ob,q,&
929 scalar_s(ix1,ix2,ix3),curl_q,grad_scalar_s,gw,&
930 weight(ix1,ix2,ix3),ftilde)
931 if(.not.all(ieee_is_finite(b)) .or. &
932 .not.all(ieee_is_finite(j)) .or. &
933 .not.ieee_is_finite(divb) .or. &
934 .not.all(ieee_is_finite(oa)) .or. &
935 .not.all(ieee_is_finite(ob)) .or. &
936 .not.all(ieee_is_finite(ftilde))) &
937 call report_nonfinite('update force',igrid,ix1,ix2,ix3,&
938 b,j,divb,oa,ob,ftilde)
939 ps1(igrid)%w(ix1,ix2,ix3,iw_b(:))=ftilde
940 end do
941 end do
942 end do
943 end do
944 call accumulate_timing(optimization_timing%update_kernel,timing_start)
945
946 if(trim(update_preconditioner)=='binomial_xy') then
947 timing_start=mpi_wtime()
948 call exchange_trial_b_internal(iw_b)
949 call accumulate_timing(optimization_timing%binomial_halo_x,timing_start)
950 do iigrid=1,igridstail
951 igrid=igrids(iigrid)
952 trial_qs_state(igrid)%w(ixg^t,1:3)=0.d0
953 do ix3=ixmlo3,ixmhi3
954 do ix2=ixmlo2,ixmhi2
955 do ix1=ixmlo1,ixmhi1
956 trial_qs_state(igrid)%w(ix1,ix2,ix3,1:3)=&
957 0.25d0*ps1(igrid)%w(ix1-1,ix2,ix3,iw_b(:))+&
958 0.5d0*ps1(igrid)%w(ix1,ix2,ix3,iw_b(:))+&
959 0.25d0*ps1(igrid)%w(ix1+1,ix2,ix3,iw_b(:))
960 end do
961 end do
962 end do
963 do ix3=ixmlo3,ixmhi3
964 do ix2=ixmlo2,ixmhi2
965 do ix1=ixmlo1,ixmhi1
966 ps1(igrid)%w(ix1,ix2,ix3,iw_b(:))=&
967 trial_qs_state(igrid)%w(ix1,ix2,ix3,1:3)
968 end do
969 end do
970 end do
971 end do
972 timing_start=mpi_wtime()
973 call exchange_trial_b_internal(iw_b)
974 call accumulate_timing(optimization_timing%binomial_halo_y,timing_start)
975 do iigrid=1,igridstail
976 igrid=igrids(iigrid)
977 do ix3=ixmlo3,ixmhi3
978 do ix2=ixmlo2,ixmhi2
979 do ix1=ixmlo1,ixmhi1
980 trial_qs_state(igrid)%w(ix1,ix2,ix3,1:3)=&
981 0.25d0*ps1(igrid)%w(ix1,ix2-1,ix3,iw_b(:))+&
982 0.5d0*ps1(igrid)%w(ix1,ix2,ix3,iw_b(:))+&
983 0.25d0*ps1(igrid)%w(ix1,ix2+1,ix3,iw_b(:))
984 end do
985 end do
986 end do
987 do ix3=ixmlo3,ixmhi3
988 do ix2=ixmlo2,ixmhi2
989 do ix1=ixmlo1,ixmhi1
990 ps1(igrid)%w(ix1,ix2,ix3,iw_b(:))=&
991 trial_qs_state(igrid)%w(ix1,ix2,ix3,1:3)
992 end do
993 end do
994 end do
995 end do
996 end if
997
998 do iigrid=1,igridstail
999 igrid=igrids(iigrid)
1000 do ix3=ixmlo3,ixmhi3
1001 do ix2=ixmlo2,ixmhi2
1002 do ix1=ixmlo1,ixmhi1
1003 if(cached_fixed_active_cell(igrid,ix1,ix2,ix3)) then
1004 ps1(igrid)%w(ix1,ix2,ix3,iw_b(:))=&
1005 ps(igrid)%w(ix1,ix2,ix3,iw_b(:))
1006 else
1007 ps1(igrid)%w(ix1,ix2,ix3,iw_b(:))=&
1008 ps(igrid)%w(ix1,ix2,ix3,iw_b(:))+&
1009 step*ps1(igrid)%w(ix1,ix2,ix3,iw_b(:))
1010 end if
1011 end do
1012 end do
1013 end do
1014 end do
1015 end subroutine form_trial_field
1016
1017 subroutine accept_trial_field(iw_b)
1019
1020 integer, intent(in) :: iw_b(3)
1021 integer :: iigrid,igrid
1022
1023 do iigrid=1,igridstail
1024 igrid=igrids(iigrid)
1025 ps(igrid)%w(ixg^t,iw_b(:))=ps1(igrid)%w(ixg^t,iw_b(:))
1026 end do
1027 end subroutine accept_trial_field
1028
1029 subroutine swap_qs_states()
1030 type(state), allocatable :: temporary(:)
1031
1032 call move_alloc(accepted_qs_state,temporary)
1033 call move_alloc(trial_qs_state,accepted_qs_state)
1034 call move_alloc(temporary,trial_qs_state)
1035 end subroutine swap_qs_states
1036
1037 !> Replace centered physical-boundary derivatives by second-order inward
1038 !> one-sided derivatives. Block and MPI interfaces remain centered.
1039 subroutine override_fixed_boundary_curl_div(bvec,igrid,current,divb)
1041
1042 integer, intent(in) :: igrid
1043 double precision, intent(in) :: bvec(ixglo1:ixghi1,ixglo2:ixghi2,&
1044 ixglo3:ixghi3,1:3)
1045 double precision, intent(inout) :: current(ixglo1:ixghi1,&
1046 ixglo2:ixghi2,ixglo3:ixghi3,1:3)
1047 double precision, intent(inout) :: divb(ixglo1:ixghi1,&
1048 ixglo2:ixghi2,ixglo3:ixghi3)
1049 double precision :: j(3),div
1050 integer :: ix1,ix2,ix3
1051
1052 do ix3=ixmlo3,ixmhi3
1053 do ix2=ixmlo2,ixmhi2
1054 do ix1=ixmlo1,ixmhi1
1055 if((ps(igrid)%is_physical_boundary(1) .and. ix1==ixmlo1) .or. &
1056 (ps(igrid)%is_physical_boundary(2) .and. ix1==ixmhi1) .or. &
1057 (ps(igrid)%is_physical_boundary(3) .and. ix2==ixmlo2) .or. &
1058 (ps(igrid)%is_physical_boundary(4) .and. ix2==ixmhi2) .or. &
1059 (ps(igrid)%is_physical_boundary(6) .and. ix3==ixmhi3)) then
1060 call compute_curl_div_node(bvec,igrid,ix1,ix2,ix3,.false.,j,div)
1061 current(ix1,ix2,ix3,:)=j
1062 divb(ix1,ix2,ix3)=div
1063 end if
1064 end do
1065 end do
1066 end do
1067 end subroutine override_fixed_boundary_curl_div
1068
1069 subroutine compute_curl_div_node(bvec,igrid,ix1,ix2,ix3,bottom_node,&
1070 current,divb)
1072
1073 integer, intent(in) :: igrid,ix1,ix2,ix3
1074 logical, intent(in) :: bottom_node
1075 double precision, intent(in) :: bvec(ixglo1:ixghi1,ixglo2:ixghi2,&
1076 ixglo3:ixghi3,1:3)
1077 double precision, intent(out) :: current(3),divb
1078 double precision :: db(3,3)
1079 integer :: ic,idir
1080
1081 do idir=1,3
1082 do ic=1,3
1083 call derivative_at_node(bvec,igrid,ix1,ix2,ix3,ic,idir,&
1084 bottom_node,db(ic,idir))
1085 end do
1086 end do
1087 current(1)=db(3,2)-db(2,3)
1088 current(2)=db(1,3)-db(3,1)
1089 current(3)=db(2,1)-db(1,2)
1090 divb=db(1,1)+db(2,2)+db(3,3)
1091 end subroutine compute_curl_div_node
1092
1093 subroutine derivative_at_node(bvec,igrid,ix1,ix2,ix3,ic,idir,&
1094 bottom_node,derivative)
1096
1097 integer, intent(in) :: igrid,ix1,ix2,ix3,ic,idir
1098 logical, intent(in) :: bottom_node
1099 double precision, intent(in) :: bvec(ixglo1:ixghi1,ixglo2:ixghi2,&
1100 ixglo3:ixghi3,1:3)
1101 double precision, intent(out) :: derivative
1102
1103 select case(idir)
1104 case(1)
1105 if(ps(igrid)%is_physical_boundary(1) .and. ix1==ixmlo1) then
1106 derivative=nlfff_boundary_derivative(bvec(ix1,ix2,ix3,ic),&
1107 bvec(ix1+1,ix2,ix3,ic),bvec(ix1+2,ix2,ix3,ic),dx(1,1),.true.)
1108 else if(ps(igrid)%is_physical_boundary(2) .and. ix1==ixmhi1) then
1109 derivative=nlfff_boundary_derivative(bvec(ix1,ix2,ix3,ic),&
1110 bvec(ix1-1,ix2,ix3,ic),bvec(ix1-2,ix2,ix3,ic),dx(1,1),.false.)
1111 else
1112 derivative=(bvec(ix1+1,ix2,ix3,ic)-&
1113 bvec(ix1-1,ix2,ix3,ic))/(2.d0*dx(1,1))
1114 end if
1115 case(2)
1116 if(ps(igrid)%is_physical_boundary(3) .and. ix2==ixmlo2) then
1117 derivative=nlfff_boundary_derivative(bvec(ix1,ix2,ix3,ic),&
1118 bvec(ix1,ix2+1,ix3,ic),bvec(ix1,ix2+2,ix3,ic),dx(2,1),.true.)
1119 else if(ps(igrid)%is_physical_boundary(4) .and. ix2==ixmhi2) then
1120 derivative=nlfff_boundary_derivative(bvec(ix1,ix2,ix3,ic),&
1121 bvec(ix1,ix2-1,ix3,ic),bvec(ix1,ix2-2,ix3,ic),dx(2,1),.false.)
1122 else
1123 derivative=(bvec(ix1,ix2+1,ix3,ic)-&
1124 bvec(ix1,ix2-1,ix3,ic))/(2.d0*dx(2,1))
1125 end if
1126 case(3)
1127 if(bottom_node) then
1128 derivative=nlfff_boundary_derivative(bvec(ix1,ix2,ix3,ic),&
1129 bvec(ix1,ix2,ix3+1,ic),bvec(ix1,ix2,ix3+2,ic),dx(3,1),.true.)
1130 else if(ps(igrid)%is_physical_boundary(6) .and. ix3==ixmhi3) then
1131 derivative=nlfff_boundary_derivative(bvec(ix1,ix2,ix3,ic),&
1132 bvec(ix1,ix2,ix3-1,ic),bvec(ix1,ix2,ix3-2,ic),dx(3,1),.false.)
1133 else
1134 derivative=(bvec(ix1,ix2,ix3+1,ic)-&
1135 bvec(ix1,ix2,ix3-1,ic))/(2.d0*dx(3,1))
1136 end if
1137 end select
1138 end subroutine derivative_at_node
1139
1140 pure double precision function nlfff_boundary_derivative(f0,f1,f2,h,at_low)
1141 double precision, intent(in) :: f0,f1,f2,h
1142 logical, intent(in) :: at_low
1143
1144 if(at_low) then
1145 nlfff_boundary_derivative=(-3.d0*f0+4.d0*f1-f2)/(2.d0*h)
1146 else
1147 nlfff_boundary_derivative=(3.d0*f0-4.d0*f1+f2)/(2.d0*h)
1148 end if
1149 end function nlfff_boundary_derivative
1150
1151 pure subroutine nlfff_compute_omega_cell(b,j,divb,omega_a,omega_b)
1152 double precision, intent(in) :: b(3),j(3),divb
1153 double precision, intent(out) :: omega_a(3),omega_b(3)
1154 double precision :: b2
1155
1156 b2=dot_product(b,b)
1157 if(b2>0.d0) then
1158 omega_a=cross3(j,b)/b2
1159 omega_b=divb*b/b2
1160 else
1161 omega_a=0.d0
1162 omega_b=0.d0
1163 end if
1164 end subroutine nlfff_compute_omega_cell
1165
1166 !> Pointwise terms in Equations (13) and (15) of Wiegelmann (2004).
1167 !> Derivatives and Q=Omega_a x B, s=Omega_b dot B are supplied by the
1168 !> caller so this kernel stays independent of a particular stencil.
1169 pure subroutine nlfff_compose_update_cell(b,j,divb,omega_a,omega_b,q,s,&
1170 curl_q,grad_s,grad_w,weight,ftilde)
1171 double precision, intent(in) :: b(3),j(3),divb
1172 double precision, intent(in) :: omega_a(3),omega_b(3),q(3),s
1173 double precision, intent(in) :: curl_q(3),grad_s(3),grad_w(3),weight
1174 double precision, intent(out) :: ftilde(3)
1175 double precision :: f(3)
1176
1177 f=curl_q-cross3(omega_a,j)+grad_s-omega_b*divb+&
1178 (dot_product(omega_a,omega_a)+dot_product(omega_b,omega_b))*b
1179 ftilde=weight*f+cross3(q,grad_w)+s*grad_w
1180 end subroutine nlfff_compose_update_cell
1181
1182 pure subroutine nlfff_dimensionless_metrics(Lforce,Ldiv,B2integral,&
1183 characteristic_spacing,epsilon_force,epsilon_div)
1184 double precision, intent(in) :: lforce,ldiv,b2integral
1185 double precision, intent(in) :: characteristic_spacing
1186 double precision, intent(out) :: epsilon_force,epsilon_div
1187
1188 if(b2integral>0.d0) then
1189 epsilon_force=characteristic_spacing*sqrt(max(0.d0,lforce)/b2integral)
1190 epsilon_div=characteristic_spacing*sqrt(max(0.d0,ldiv)/b2integral)
1191 else
1192 epsilon_force=0.d0
1193 epsilon_div=0.d0
1194 end if
1195 end subroutine nlfff_dimensionless_metrics
1196
1197 !> One-dimensional binomial factor used by the transverse update
1198 !> preconditioner. Exposing the three-point kernel keeps its numerical
1199 !> properties testable without an initialized AMRVAC mesh.
1200 pure function nlfff_binomial_triplet(left,center,right) result(filtered)
1201 double precision, intent(in) :: left(3),center(3),right(3)
1202 double precision :: filtered(3)
1203
1204 filtered=0.25d0*left+0.5d0*center+0.25d0*right
1205 end function nlfff_binomial_triplet
1206
1207 pure function cross3(a,b) result(c)
1208 double precision, intent(in) :: a(3),b(3)
1209 double precision :: c(3)
1210
1211 c(1)=a(2)*b(3)-a(3)*b(2)
1212 c(2)=a(3)*b(1)-a(1)*b(3)
1213 c(3)=a(1)*b(2)-a(2)*b(1)
1214 end function cross3
1215
1216 pure double precision function nlfff_cosine_side_weight(index,ncell,buffer_cells)
1217 integer, intent(in) :: index,ncell,buffer_cells
1218 double precision :: s,pi
1219
1220 pi=4.d0*datan(1.d0)
1221 if(index<1 .or. index>ncell) then
1223 else if(index<=buffer_cells) then
1224 s=dble(index-1)/dble(buffer_cells-1)
1225 nlfff_cosine_side_weight=0.5d0*(1.d0-dcos(pi*s))
1226 else if(index>=ncell-buffer_cells+1) then
1227 s=dble(ncell-index)/dble(buffer_cells-1)
1228 nlfff_cosine_side_weight=0.5d0*(1.d0-dcos(pi*s))
1229 else
1231 end if
1232 end function nlfff_cosine_side_weight
1233
1234 pure double precision function nlfff_cosine_top_weight(index,ncell,buffer_cells)
1235 integer, intent(in) :: index,ncell,buffer_cells
1236 double precision :: s,pi
1237
1238 pi=4.d0*datan(1.d0)
1239 if(index>ncell) then
1241 else if(index<1) then
1243 else if(index>=ncell-buffer_cells+1) then
1244 s=dble(ncell-index)/dble(buffer_cells-1)
1245 nlfff_cosine_top_weight=0.5d0*(1.d0-dcos(pi*s))
1246 else
1248 end if
1249 end function nlfff_cosine_top_weight
1250
1251 pure double precision function nlfff_weight_product(index1,index2,index3,&
1252 ncell1,ncell2,ncell3,buffer_cells)
1253 integer, intent(in) :: index1,index2,index3
1254 integer, intent(in) :: ncell1,ncell2,ncell3,buffer_cells
1255
1257 nlfff_cosine_side_weight(index1,ncell1,buffer_cells)*&
1258 nlfff_cosine_side_weight(index2,ncell2,buffer_cells)*&
1259 nlfff_cosine_top_weight(index3,ncell3,buffer_cells)
1260 end function nlfff_weight_product
1261
1262 pure logical function nlfff_fixed_active_index(index1,index2,index3,&
1263 ncell1,ncell2,ncell3)
1264 integer, intent(in) :: index1,index2,index3,ncell1,ncell2,ncell3
1265
1266 nlfff_fixed_active_index=(index3==ncell3 .or. index1==1 .or. &
1267 index1==ncell1 .or. index2==1 .or. index2==ncell2)
1268 end function nlfff_fixed_active_index
1269
1270 pure double precision function cell_weight(x1,x2,x3,buffer_cells)
1271 use mod_global_parameters, only: xprobmin1,xprobmin2,xprobmin3,dx,&
1272 domain_nx1,domain_nx2,domain_nx3
1273
1274 double precision, intent(in) :: x1,x2,x3
1275 integer, intent(in) :: buffer_cells
1276 integer :: ig1,ig2,ig3
1277
1278 ig1=nint((x1-xprobmin1)/dx(1,1)+0.5d0)
1279 ig2=nint((x2-xprobmin2)/dx(2,1)+0.5d0)
1280 ig3=nint((x3-xprobmin3)/dx(3,1)+0.5d0)
1281 cell_weight=nlfff_weight_product(ig1,ig2,ig3,domain_nx1,domain_nx2,&
1282 domain_nx3,buffer_cells)
1283 end function cell_weight
1284
1285 pure logical function fixed_active_cell(x1,x2,x3)
1286 use mod_global_parameters, only: xprobmin1,xprobmin2,xprobmin3,dx,&
1287 domain_nx1,domain_nx2,domain_nx3
1288
1289 double precision, intent(in) :: x1,x2,x3
1290 integer :: ig1,ig2,ig3
1291
1292 ig1=nint((x1-xprobmin1)/dx(1,1)+0.5d0)
1293 ig2=nint((x2-xprobmin2)/dx(2,1)+0.5d0)
1294 ig3=nint((x3-xprobmin3)/dx(3,1)+0.5d0)
1295 fixed_active_cell=nlfff_fixed_active_index(ig1,ig2,ig3,domain_nx1,&
1296 domain_nx2,domain_nx3)
1297 end function fixed_active_cell
1298
1299 subroutine report_nonfinite(quantity,igrid,ix1,ix2,ix3,b,j,divb,oa,ob,extra)
1300 use mod_comm_lib, only: mpistop
1301 use mod_global_parameters, only: mype
1302
1303 character(len=*), intent(in) :: quantity
1304 integer, intent(in) :: igrid,ix1,ix2,ix3
1305 double precision, intent(in) :: b(3),j(3),divb,oa(3),ob(3),extra(3)
1306
1307 write(*,*) 'Non-finite NLFFF ',trim(quantity),' at rank/grid/cell:',&
1308 mype,igrid,ix1,ix2,ix3
1309 write(*,*) 'B:',b
1310 write(*,*) 'curl(B):',j
1311 write(*,*) 'div(B):',divb
1312 write(*,*) 'Omega_a:',oa
1313 write(*,*) 'Omega_b:',ob
1314 write(*,*) 'stage values:',extra
1315 call mpistop('non-finite value in NLFFF optimization')
1316 end subroutine report_nonfinite
1317
1318 subroutine write_log_row(unit,result,step,L,Lforce,Ldiv,B2integral,&
1319 epsilon_force,epsilon_div,accepted_trial)
1320 integer, intent(in) :: unit
1321 type(nlfff_optimization_result), intent(in) :: result
1322 double precision, intent(in) :: step,l,lforce,ldiv,b2integral
1323 double precision, intent(in) :: epsilon_force,epsilon_div
1324 logical, intent(in) :: accepted_trial
1325
1326 write(unit,'(i0,a,i0,a,i0,7(a,es24.16),a,i0,a,es24.16,a,i0,a,es24.16,a,l1)') result%attempts,',',&
1327 result%accepted_steps,',',result%rejected_steps,',',step,',',l,',',&
1328 lforce,',',ldiv,',',b2integral,',',epsilon_force,',',epsilon_div,',',&
1329 result%functional_evaluations,',',result%step_floor,',',&
1330 result%plateau_count,',',result%final_relative_functional_change,',',&
1331 accepted_trial
1332 flush(unit)
1333 end subroutine write_log_row
1334}
1335
1336end module mod_nlfff_optimization
subroutine, public alloc_state(igrid, s, ixgl, ixgextl, alloc_once_for_ps)
allocate memory to physical state of igrid node
subroutine, public mpistop(message)
Exit MPI-AMRVAC with an error message.
Module with geometry-related routines (e.g., divergence, curl)
Definition mod_geometry.t:2
subroutine divvector(qvec, ixil, ixol, divq, nth_in)
integer coordinate
Definition mod_geometry.t:7
integer, parameter cartesian
Definition mod_geometry.t:8
subroutine curlvector(qvec, ixil, ixol, curlvec, idirmin, idirmin0, ndir0, fourthorder)
Calculate curl of a vector qvec within ixL Options to employ standard second order CD evaluations use...
subroutine gradient(q, ixil, ixol, idir, gradq, nth_in)
update ghost cells of all blocks including physical boundaries
integer, dimension(0:3^d &), target type_recv_p_p1
integer, dimension( :^d &), pointer type_recv_r
integer, dimension(-1:1^d &), target type_send_srl_p1
integer, dimension(-1:1^d &), target type_send_r_p2
subroutine getbc(time, qdt, psb, nwstart, nwbc)
do update ghost cells of all blocks including physical boundaries
integer, dimension(0:3^d &), target type_send_p_f
subroutine create_bc_mpi_datatype(nwstart, nwbc, nwfull)
integer, dimension( :^d &), pointer type_send_p
integer, dimension(0:3^d &), target type_recv_p_p2
integer, dimension( :^d &), pointer type_send_srl
integer, dimension(-1:1^d &), target type_send_r_p1
integer, dimension(-1:1^d &), target type_send_srl_p2
integer, dimension(0:3^d &), target type_send_p_p2
integer, dimension(0:3^d &), target type_recv_r_f
integer, dimension(-1:1^d &), target type_recv_srl_f
integer, dimension(-1:1^d &), target type_send_r_f
integer, dimension(-1:1^d &), target type_send_srl_f
integer, dimension(0:3^d &), target type_send_p_p1
integer, dimension( :^d &), pointer type_send_r
integer, dimension(0:3^d &), target type_recv_r_p1
integer, dimension(0:3^d &), target type_recv_r_p2
integer, dimension(-1:1^d &), target type_recv_srl_p2
integer, dimension( :^d &), pointer type_recv_p
integer, dimension(-1:1^d &), target type_recv_srl_p1
integer, dimension(0:3^d &), target type_recv_p_f
integer, dimension( :^d &), pointer type_recv_srl
This module contains definitions of global parameters and variables and some generic functions/subrou...
type(state), pointer block
Block pointer for using one block and its previous state.
double precision global_time
The global simulation time.
integer, parameter ndim
Number of spatial dimensions for grid variables.
logical stagger_grid
True for using stagger grid.
integer icomm
The MPI communicator.
integer mype
The rank of the current MPI task.
integer ndir
Number of spatial dimensions (components) for vector variables.
integer ixm
the mesh range of a physical block without ghost cells
integer ierrmpi
A global MPI error return code.
double precision, dimension(:), allocatable, parameter d
logical, dimension(ndim) stretched_dim
True if a dimension is stretched.
logical, dimension(ndim) periodb
True for dimensions with periodic boundaries.
logical b0field
split magnetic field as background B0 field
double precision, dimension(:,:), allocatable dx
spatial steps for all dimensions at all levels
integer nghostcells
Number of ghost cells surrounding a grid.
double precision, dimension(^nd) dxlevel
store unstretched cell size of current level
character(len=std_len) base_filename
Base file name for simulation output, which will be followed by a number.
integer refine_max_level
Maximal number of AMR levels.
integer max_blocks
The maximum number of grid blocks in a processor.
integer, dimension(:,:), allocatable node
Program to extrapolate linear force-free fields in 3D Cartesian coordinates, based on exact Green fun...
Definition mod_lfff.t:27
subroutine init_b_fff_data_driven_boundary(boundaryname, qlunit, qbunit, qxc1, qxc2, bvector)
Definition mod_lfff.t:111
double precision, dimension(:), allocatable, save xa2
Definition mod_lfff.t:32
subroutine extrapolate_potential_fft(iw_b, padding_factor, source_plane_depth, alpha, top_boundary, flux_treatment, max_flux_imbalance)
Extrapolate a Cartesian potential field with horizontal Fourier modes. The bottom magnetogram must ma...
Definition mod_lfff.t:344
double precision, dimension(:), allocatable, save xa1
Definition mod_lfff.t:32
Common user-facing diagnostics for NLFFF relaxation and extrapolation.
subroutine, public write_nlfff_metrics_header(unit)
subroutine, public write_nlfff_metrics_row(unit, iteration, metrics)
subroutine, public evaluate_nlfff_metrics_amrvac(iw_b, metrics)
Evaluate the common metrics on all active AMRVAC cells. The local cell size makes epsilon_force and e...
Nonlinear force-free extrapolation by the weighted optimization method.
subroutine, public init_nlfff_optimization_boundary(filename, qlunit, qbunit, qxc1, qxc2)
Read one Python V1 data-driven vector magnetogram. The same read also initializes mod_lfff's normaliz...
pure double precision function, public nlfff_weight_product(index1, index2, index3, ncell1, ncell2, ncell3, buffer_cells)
pure subroutine, public nlfff_dimensionless_metrics(lforce, ldiv, b2integral, characteristic_spacing, epsilon_force, epsilon_div)
pure double precision function, public nlfff_cosine_top_weight(index, ncell, buffer_cells)
pure double precision function, public nlfff_boundary_derivative(f0, f1, f2, h, at_low)
pure subroutine, public nlfff_compute_omega_cell(b, j, divb, omega_a, omega_b)
subroutine, public extrapolate_nlfff_optimization(iw_b, config, result)
Perform a one-shot, fixed-grid weighted optimization extrapolation.
type(nlfff_optimization_timing), save optimization_timing
pure logical function, public nlfff_fixed_active_index(index1, index2, index3, ncell1, ncell2, ncell3)
pure subroutine, public nlfff_compose_update_cell(b, j, divb, omega_a, omega_b, q, s, curl_q, grad_s, grad_w, weight, ftilde)
Pointwise terms in Equations (13) and (15) of Wiegelmann (2004). Derivatives and Q=Omega_a x B,...
pure double precision function, public nlfff_cosine_side_weight(index, ncell, buffer_cells)
type(state), dimension(:), allocatable, target ps
array of physical states for all blocks on my processor
type(state), dimension(:), allocatable, target ps1
array of physical states, temp 1 for multi-step time integrator