MPI-AMRVAC 3.2
The MPI - Adaptive Mesh Refinement - Versatile Advection Code (development version)
Loading...
Searching...
No Matches
mod_nlfff_grad_rubin.t
Go to the documentation of this file.
1!> MPI-parallel fixed-grid Grad--Rubin NLFFF extrapolation.
2!>
3!> This is an independent implementation of the current-field iteration in
4!> Wheatland (2006, 2007). The dense-grid representation is deliberate: it
5!> gives every rank deterministic read-only access during field-line tracing.
7 implicit none
8 private
9
10 integer, parameter, public :: gr_trace_bottom=1
11 integer, parameter, public :: gr_trace_open=2
12 integer, parameter, public :: gr_trace_weak=3
13 integer, parameter, public :: gr_trace_max_steps=4
14
16 integer :: fft_padding_factor=2
17 character(len=16) :: flux_treatment='strict'
18 double precision :: max_flux_imbalance=0.1d0
19 integer :: polarity=1
20 character(len=24) :: alpha_source='vector_magnetogram'
21 double precision :: bz_taper_zero=0.01d0
22 double precision :: bz_taper_full=0.02d0
23 double precision :: relaxation_factor=0.5d0
24 double precision :: fieldline_step_fraction=0.5d0
25 integer :: fieldline_max_steps=10000
26 integer :: max_iterations=50
27 integer :: convergence_streak=3
28 double precision :: field_change_tolerance=1.d-5
29 double precision :: energy_change_tolerance=1.d-6
30 integer :: self_consistency_cycles=0
31 integer :: self_consistency_streak=1
32 double precision :: self_consistency_field_tolerance=1.d-3
33 double precision :: self_consistency_alpha_tolerance=1.d-3
34 integer :: log_interval=1
35 double precision :: memory_limit_mb=2048.d0
36 logical :: write_detailed_history=.false.
38
40 integer :: polarity=0
41 integer :: iterations=0
42 logical :: converged=.false.
43 character(len=32) :: stop_reason='not_started'
44 double precision :: initial_energy=0.d0
45 double precision :: final_energy=0.d0
46 double precision :: rms_field_change=0.d0
47 double precision :: relative_energy_change=0.d0
48 double precision :: current_weighted_theta=0.d0
49 double precision :: epsilon_div=0.d0
50 integer :: closed_fieldlines=0
51 integer :: open_fieldlines=0
52 integer :: weak_fieldlines=0
53 integer :: max_step_fieldlines=0
54 integer :: valid_alpha_pixels=0
55 double precision :: alpha_min=0.d0
56 double precision :: alpha_max=0.d0
57 double precision :: memory_required_mb=0.d0
58 integer :: self_consistency_cycles_completed=0
59 double precision :: positive_negative_rms_difference=0.d0
60 double precision :: boundary_alpha_rms_change=0.d0
62
63{^ifthreed
64 double precision, allocatable, save :: boundary_b(:,:,:)
65 double precision, allocatable, save :: external_alpha_raw_full(:,:)
66 double precision, allocatable, save :: external_alpha_full(:,:)
67 double precision, allocatable, save :: external_alpha_weight_full(:,:)
68 integer, allocatable, save :: external_alpha_pil_full(:,:)
69 integer, allocatable, save :: external_alpha_valid_full(:,:)
70 integer, allocatable, save :: external_alpha_polarity_full(:,:)
71 double precision, allocatable, save :: external_alpha_x(:),external_alpha_y(:)
72 character(len=1024), save :: external_alpha_filename=''
73 double precision, save :: external_alpha_unit_length_cm=0.d0
74 double precision, save :: external_alpha_unit_magneticfield_g=0.d0
75
79 public :: gr_taper_weight
81 public :: gr_trilinear_sample
82 public :: gr_bilinear_sample
85}
86
87contains
88
89{^ifthreed
90 subroutine init_nlfff_grad_rubin_boundary(filename,unit_length,&
91 unit_magneticfield,qxc1,qxc2,alpha_filename)
93
94 character(len=*), intent(in) :: filename
95 double precision, intent(in) :: unit_length,unit_magneticfield
96 double precision, intent(in), optional :: qxc1,qxc2
97 character(len=*), intent(in), optional :: alpha_filename
98
99 if(allocated(boundary_b)) deallocate(boundary_b)
100 call init_b_fff_data_driven_boundary(filename,unit_length,&
101 unit_magneticfield,qxc1,qxc2,boundary_b)
102 if(present(alpha_filename)) then
103 if(len_trim(alpha_filename)>0) call init_nlfff_grad_rubin_external_alpha(&
104 trim(alpha_filename),unit_length,unit_magneticfield)
105 end if
106 end subroutine init_nlfff_grad_rubin_boundary
107
108 subroutine init_nlfff_grad_rubin_external_alpha(filename,unit_length,&
109 unit_magneticfield)
110 use mod_comm_lib, only: mpistop
111 character(len=*), intent(in) :: filename
112 double precision, intent(in) :: unit_length,unit_magneticfield
113
114 if(.not.allocated(boundary_b)) call mpistop('external alpha requires an initialized vector boundary')
115 call read_external_alpha_product(trim(filename),unit_length,unit_magneticfield)
117
118 subroutine read_external_alpha_product(filename,unit_length,unit_magneticfield)
119 use mpi
121 use mod_lfff, only: xa1,xa2,nx1,nx2
122 use mod_comm_lib, only: mpistop
123 use, intrinsic :: ieee_arithmetic, only: ieee_is_finite
124
125 character(len=*), intent(in) :: filename
126 double precision, intent(in) :: unit_length,unit_magneticfield
127 character(len=32) :: magic
128 character(len=4096) :: metadata_blob
129 integer :: iu,ios,i,j,version,nx,ny,reserved,alpha_code,mask_code
130 integer :: metadata_length,ierrmpi
131 double precision :: product_length,product_bfield,dx_product,dy_product
132 double precision :: xc_product,yc_product,tol
133 logical :: exists
134 character(len=32), parameter :: expected_magic='AMRVAC_EXTERNAL_ALPHA_V1'
135
136 if(len_trim(filename)>len(external_alpha_filename)) &
137 call mpistop('external alpha filename is too long')
138 if(mype==0) then
139 inquire(file=trim(filename),exist=exists)
140 if(.not.exists) call mpistop('missing external alpha product')
141 open(newunit=iu,file=trim(filename),status='old',access='stream',&
142 form='unformatted',action='read',iostat=ios)
143 if(ios/=0) call mpistop('cannot open external alpha product')
144 read(iu,iostat=ios) magic,version,nx,ny,reserved,product_length,&
145 product_bfield,dx_product,dy_product,xc_product,yc_product,&
146 alpha_code,mask_code
147 if(ios/=0) call mpistop('cannot read external alpha header')
148 if(magic/=expected_magic .or. version/=1 .or. nx<1 .or. ny<1) &
149 call mpistop('invalid external alpha magic/version/grid')
150 if(alpha_code/=1 .or. mask_code/=1) &
151 call mpistop('unsupported external alpha unit/mask code')
152 allocate(external_alpha_x(nx),external_alpha_y(ny))
153 allocate(external_alpha_raw_full(nx,ny),external_alpha_full(nx,ny))
154 allocate(external_alpha_weight_full(nx,ny))
155 allocate(external_alpha_pil_full(nx,ny),external_alpha_valid_full(nx,ny))
156 allocate(external_alpha_polarity_full(nx,ny))
157 read(iu,iostat=ios) external_alpha_x,external_alpha_y
158 read(iu,iostat=ios) external_alpha_raw_full,external_alpha_full,&
159 external_alpha_weight_full,external_alpha_pil_full,&
160 external_alpha_valid_full,external_alpha_polarity_full
161 read(iu,iostat=ios) metadata_length,metadata_blob
162 close(iu)
163 if(ios/=0) call mpistop('cannot read external alpha payload')
164 if(metadata_length<0 .or. metadata_length>len(metadata_blob)) &
165 call mpistop('invalid external alpha metadata length')
166 else
167 nx=0; ny=0; product_length=0.d0; product_bfield=0.d0
168 dx_product=0.d0; dy_product=0.d0; xc_product=0.d0; yc_product=0.d0
169 alpha_code=0; mask_code=0
170 end if
171 call mpi_bcast(nx,1,mpi_integer,0,icomm,ierrmpi)
172 call mpi_bcast(ny,1,mpi_integer,0,icomm,ierrmpi)
173 call mpi_bcast(product_length,1,mpi_double_precision,0,icomm,ierrmpi)
174 call mpi_bcast(product_bfield,1,mpi_double_precision,0,icomm,ierrmpi)
175 call mpi_bcast(dx_product,1,mpi_double_precision,0,icomm,ierrmpi)
176 call mpi_bcast(dy_product,1,mpi_double_precision,0,icomm,ierrmpi)
177 call mpi_bcast(xc_product,1,mpi_double_precision,0,icomm,ierrmpi)
178 call mpi_bcast(yc_product,1,mpi_double_precision,0,icomm,ierrmpi)
179 if(mype/=0) then
180 allocate(external_alpha_x(nx),external_alpha_y(ny))
181 allocate(external_alpha_raw_full(nx,ny),external_alpha_full(nx,ny))
182 allocate(external_alpha_weight_full(nx,ny))
183 allocate(external_alpha_pil_full(nx,ny),external_alpha_valid_full(nx,ny))
184 allocate(external_alpha_polarity_full(nx,ny))
185 end if
186 call mpi_bcast(external_alpha_x,nx,mpi_double_precision,0,icomm,ierrmpi)
187 call mpi_bcast(external_alpha_y,ny,mpi_double_precision,0,icomm,ierrmpi)
188 call mpi_bcast(external_alpha_raw_full,nx*ny,mpi_double_precision,0,icomm,ierrmpi)
189 call mpi_bcast(external_alpha_full,nx*ny,mpi_double_precision,0,icomm,ierrmpi)
190 call mpi_bcast(external_alpha_weight_full,nx*ny,mpi_double_precision,0,icomm,ierrmpi)
191 call mpi_bcast(external_alpha_pil_full,nx*ny,mpi_integer,0,icomm,ierrmpi)
192 call mpi_bcast(external_alpha_valid_full,nx*ny,mpi_integer,0,icomm,ierrmpi)
193 call mpi_bcast(external_alpha_polarity_full,nx*ny,mpi_integer,0,icomm,ierrmpi)
194
195 if(.not.ieee_is_finite(product_length) .or. .not.ieee_is_finite(product_bfield) .or. &
196 product_length<=0.d0 .or. product_bfield<=0.d0) &
197 call mpistop('external alpha contains invalid unit metadata')
198 tol=1.d-12*max(1.d0,dabs(unit_length),dabs(product_length))
199 if(dabs(product_length-unit_length)>tol) &
200 call mpistop('external alpha unit_length does not match AMRVAC')
201 tol=1.d-12*max(1.d0,dabs(unit_magneticfield),dabs(product_bfield))
202 if(dabs(product_bfield-unit_magneticfield)>tol) &
203 call mpistop('external alpha magnetic unit does not match AMRVAC')
204 if(nx/=nx1 .or. ny/=nx2) then
205 if(mype==0) write(*,*) 'external alpha grid/product:',nx,ny,' magnetogram:',nx1,nx2
206 call mpistop('external alpha grid shape does not match magnetogram')
207 end if
208 tol=1.d-12*max(1.d0,maxval(dabs(external_alpha_x)),maxval(dabs(xa1)))
209 if(maxval(dabs(external_alpha_x-xa1))>tol) &
210 call mpistop('external alpha x coordinates do not match magnetogram')
211 tol=1.d-12*max(1.d0,maxval(dabs(external_alpha_y)),maxval(dabs(xa2)))
212 if(maxval(dabs(external_alpha_y-xa2))>tol) &
213 call mpistop('external alpha y coordinates do not match magnetogram')
214 if(dx_product<=0.d0 .or. dy_product<=0.d0) &
215 call mpistop('external alpha spacing must be positive')
216 if(.not.all(ieee_is_finite(external_alpha_raw_full)) .or. &
217 .not.all(ieee_is_finite(external_alpha_full)) .or. &
218 .not.all(ieee_is_finite(external_alpha_weight_full))) &
219 call mpistop('external alpha contains non-finite arrays')
220 if(any(external_alpha_weight_full<0)) &
221 call mpistop('external alpha weight contains negative values')
222 do j=1,ny
223 do i=1,nx
224 if(external_alpha_pil_full(i,j)/=-1 .and. external_alpha_pil_full(i,j)/=0 .and. &
225 external_alpha_pil_full(i,j)/=1) call mpistop('invalid external alpha PIL mask')
226 if(external_alpha_valid_full(i,j)/=0 .and. external_alpha_valid_full(i,j)/=1) &
227 call mpistop('invalid external alpha valid mask')
228 if(external_alpha_polarity_full(i,j)/=-1 .and. &
229 external_alpha_polarity_full(i,j)/=0 .and. external_alpha_polarity_full(i,j)/=1) &
230 call mpistop('invalid external alpha polarity mask')
231 if(external_alpha_valid_full(i,j)/=0 .and. &
232 external_alpha_polarity_full(i,j)/=0) then
233 if(external_alpha_polarity_full(i,j)*boundary_b(i,j,3)<=0.d0) &
234 call mpistop('external alpha polarity disagrees with Bz')
235 end if
236 if(external_alpha_valid_full(i,j)/=0 .and. &
237 external_alpha_polarity_full(i,j)==0) &
238 call mpistop('external alpha valid support requires nonzero polarity')
239 end do
240 end do
241 external_alpha_filename=trim(filename)
242 external_alpha_unit_length_cm=product_length
243 external_alpha_unit_magneticfield_g=product_bfield
244 if(mype==0) then
245 write(*,*) 'external alpha product:',trim(filename)
246 write(*,*) 'external alpha support:',count(external_alpha_valid_full/=0)
247 end if
248 end subroutine read_external_alpha_product
249
250 subroutine extrapolate_nlfff_grad_rubin(iw_b,config,result)
251 use mpi
252 use mod_comm_lib, only: mpistop
258 use, intrinsic :: ieee_arithmetic, only: ieee_is_finite
259
260 integer, intent(in) :: iw_b(3)
261 type(nlfff_grad_rubin_config), intent(in) :: config
262 type(nlfff_grad_rubin_result), intent(out) :: result
263
264 double precision, allocatable :: bcore(:,:,:),alpha0(:,:),alpha_raw(:,:),alpha_weight(:,:)
265 double precision, allocatable :: potential(:,:,:,:)
266 double precision, allocatable :: b(:,:,:,:),bnew(:,:,:,:),alpha(:,:,:)
267 double precision, allocatable :: current(:,:,:,:),bc(:,:,:,:)
268 logical, allocatable :: alpha_mask(:,:)
269 integer, allocatable :: alpha_polarity(:,:)
270 double precision :: dx1,dx2,dx3,energy_old,energy_new,rms_change,energy_change
271 double precision :: memory_mb,flux_before,flux_after,mean_correction
272 double precision :: theta,epsdiv
273 type(nlfff_physical_metrics) :: physical_metrics
274 integer :: iter,stable,log_unit,metrics_unit,flux_status,ic
275 integer :: counts(4),valid_pixels
276 character(len=16) :: polarity_name
277 logical :: log_open
278
280 result%polarity=config%polarity
281 call validate_configuration(iw_b,config)
282 if(config%self_consistency_cycles>0) then
283 call extrapolate_self_consistent(iw_b,config,result)
284 return
285 end if
286 memory_mb=estimate_memory_mb(config%fft_padding_factor)
287 result%memory_required_mb=memory_mb
288 if(memory_mb>config%memory_limit_mb) then
289 if(mype==0) write(*,*) 'Grad-Rubin required/allowed MiB per rank:',&
290 memory_mb,config%memory_limit_mb
291 call mpistop('Grad-Rubin dense solver exceeds memory_limit_mb')
292 end if
293 call extract_boundary_core(bcore)
294 dx1=dx(1,1)
295 dx2=dx(2,1)
296 dx3=dx(3,1)
297 call balance_normal_field(bcore(:,:,3),config%flux_treatment,&
298 config%max_flux_imbalance,flux_before,flux_after,mean_correction,&
299 flux_status)
300 if(flux_status/=0 .and. flux_status/=1) then
301 if(mype==0) write(*,*) 'Grad-Rubin flux imbalance:',flux_before
302 call mpistop('Grad-Rubin bottom normal field failed flux-balance policy')
303 end if
304
305 allocate(alpha0(domain_nx1,domain_nx2),alpha_raw(domain_nx1,domain_nx2))
306 allocate(alpha_weight(domain_nx1,domain_nx2),alpha_polarity(domain_nx1,domain_nx2))
307 allocate(alpha_mask(domain_nx1,domain_nx2))
308 if(trim(adjustl(config%alpha_source))=='external') then
309 call extract_external_alpha_core(alpha_raw,alpha0,alpha_weight,&
310 alpha_mask,alpha_polarity)
311 valid_pixels=count(alpha_mask)
312 else
313 call gr_compute_alpha_boundary(bcore,dx1,dx2,config%bz_taper_zero,&
314 config%bz_taper_full,alpha0,valid_pixels,alpha_raw)
315 alpha_weight=0.d0
316 where(alpha0/=0.d0) alpha_weight=1.d0
317 alpha_polarity=0
318 where(bcore(:,:,3)>0.d0) alpha_polarity=1
319 where(bcore(:,:,3)<0.d0) alpha_polarity=-1
320 alpha_mask=config%polarity*bcore(:,:,3)>0.d0 .and. &
321 dabs(bcore(:,:,3))/max(maxval(dabs(bcore(:,:,3))),tiny(1.d0))>&
322 config%bz_taper_zero
323 end if
324 if(trim(adjustl(config%alpha_source))=='external') then
325 alpha_mask=alpha_mask .and. config%polarity*bcore(:,:,3)>0.d0
326 end if
327 where(.not.alpha_mask) alpha0=0.d0
328 result%valid_alpha_pixels=count(alpha_mask)
329 if(result%valid_alpha_pixels>0) then
330 result%alpha_min=minval(alpha0,mask=alpha_mask)
331 result%alpha_max=maxval(alpha0,mask=alpha_mask)
332 end if
333 if(config%write_detailed_history .and. mype==0) then
334 call write_alpha_diagnostics(trim(base_filename),config%polarity,&
335 trim(adjustl(config%alpha_source)),alpha_raw,alpha0,alpha_weight,alpha_mask,&
336 alpha_polarity)
337 end if
338
339 call extrapolate_potential_fft_dense(bcore(:,:,3),dx1,dx2,dx3,&
340 config%fft_padding_factor,potential)
341 allocate(b,source=potential)
342 allocate(bnew,source=potential)
343 allocate(alpha(domain_nx1,domain_nx2,domain_nx3+1))
344 allocate(current(domain_nx1,domain_nx2,domain_nx3+1,3))
345 allocate(bc(domain_nx1,domain_nx2,domain_nx3+1,3))
346
347 energy_old=gr_energy(b,dx1,dx2,dx3)
348 result%initial_energy=energy_old
349 stable=0
350 log_open=.false.
351 log_unit=-1
352 metrics_unit=-1
353 if(mype==0) then
354 if(config%polarity>0) then
355 polarity_name='positive'
356 else
357 polarity_name='negative'
358 end if
359 if(config%write_detailed_history) then
360 open(newunit=log_unit,file=trim(base_filename)//'_grad_rubin_'//&
361 trim(polarity_name)//'.csv',status='replace',action='write')
362 log_open=.true.
363 write(log_unit,'(a)') 'iteration,energy,rms_field_change,'//&
364 'relative_energy_change,theta_j_deg,epsilon_div,closed,open,weak,max_steps'
365 end if
366 open(newunit=metrics_unit,file=trim(base_filename)//'_nlfff_metrics.csv',&
367 status='replace',action='write')
368 call write_nlfff_metrics_header(metrics_unit)
369 end if
370 call evaluate_nlfff_metrics_dense(b,dx1,dx2,dx3,physical_metrics)
371 if(mype==0) call write_nlfff_metrics_row(metrics_unit,0,physical_metrics)
372
373 do iter=1,config%max_iterations
374 call transport_alpha_mpi(b,alpha0,bcore(:,:,3),config,alpha,counts)
375 do concurrent(ic=1:3)
376 current(:,:,:,ic)=alpha*b(:,:,:,ic)
377 end do
378 call gr_solve_current_field_mpi(current,config%fft_padding_factor,dx1,dx2,&
379 dx3,bc)
380 bnew=(1.d0-config%relaxation_factor)*b+&
381 config%relaxation_factor*(potential+bc)
382 bnew(:,:,1,3)=bcore(:,:,3)
383 if(.not.all(ieee_is_finite(bnew))) &
384 call mpistop('non-finite field in Grad-Rubin iteration')
385
386 energy_new=gr_energy(bnew,dx1,dx2,dx3)
387 rms_change=dsqrt(sum((bnew-b)**2)/max(sum(b**2),tiny(1.d0)))
388 energy_change=dabs(energy_new-energy_old)/max(dabs(energy_old),tiny(1.d0))
389 call field_diagnostics(bnew,dx1,dx2,dx3,theta,epsdiv)
390 b=bnew
391 energy_old=energy_new
392 result%iterations=iter
393 result%final_energy=energy_new
394 result%rms_field_change=rms_change
395 result%relative_energy_change=energy_change
396 result%current_weighted_theta=theta
397 result%epsilon_div=epsdiv
398 result%closed_fieldlines=counts(1)
399 result%open_fieldlines=counts(2)
400 result%weak_fieldlines=counts(3)
401 result%max_step_fieldlines=counts(4)
402
403 if(mod(iter,config%log_interval)==0 .or. iter==1) then
404 call evaluate_nlfff_metrics_dense(b,dx1,dx2,dx3,physical_metrics)
405 if(mype==0) then
406 if(config%write_detailed_history) then
407 write(log_unit,'(i0,5(a,es24.16),4(a,i0))') iter,',',energy_new,',',&
408 rms_change,',',energy_change,',',theta,',',epsdiv,',',counts(1),&
409 ',',counts(2),',',counts(3),',',counts(4)
410 flush(log_unit)
411 end if
412 call write_nlfff_metrics_row(metrics_unit,iter,physical_metrics)
413 end if
414 end if
415
416 if(rms_change<=config%field_change_tolerance .and. &
417 energy_change<=config%energy_change_tolerance) then
418 stable=stable+1
419 else
420 stable=0
421 end if
422 if(stable>=config%convergence_streak) then
423 result%converged=.true.
424 result%stop_reason='converged'
425 exit
426 end if
427 end do
428 if(.not.result%converged) result%stop_reason='max_iterations'
429 if(mype==0) then
430 if(log_open) close(log_unit)
431 close(metrics_unit)
432 end if
433 call scatter_dense_field(b,iw_b)
434
435 deallocate(bcore,alpha0,alpha_raw,alpha_weight,alpha_polarity,alpha_mask,&
436 potential,b,bnew,alpha,current,bc)
437 end subroutine extrapolate_nlfff_grad_rubin
438
439 subroutine extrapolate_self_consistent(iw_b,config,result)
440 use mod_comm_lib, only: mpistop
446 use, intrinsic :: ieee_arithmetic, only: ieee_is_finite
447
448 integer, intent(in) :: iw_b(3)
449 type(nlfff_grad_rubin_config), intent(in) :: config
450 type(nlfff_grad_rubin_result), intent(out) :: result
451
452 type(nlfff_grad_rubin_config) :: polarity_config
453 type(nlfff_grad_rubin_result) :: positive_result,negative_result
454 double precision, allocatable :: bcore(:,:,:),alpha0(:,:),alpha_old(:,:)
455 double precision, allocatable :: alpha_raw(:,:),alpha_weight(:,:)
456 double precision, allocatable :: alpha_positive(:,:,:),alpha_negative(:,:,:)
457 double precision, allocatable :: potential(:,:,:,:),bpositive(:,:,:,:)
458 double precision, allocatable :: bnegative(:,:,:,:),bfinal(:,:,:,:)
459 logical, allocatable :: valid_mask(:,:),alpha_mask(:,:)
460 integer, allocatable :: alpha_polarity(:,:)
461 double precision :: dx1,dx2,dx3,memory_mb,flux_before,flux_after
462 double precision :: mean_correction,pn_difference,alpha_change,denom
463 type(nlfff_physical_metrics) :: physical_metrics
464 integer :: cycle,stable,flux_status,valid_pixels,log_unit,metrics_unit
465 integer :: total_iterations
466 integer :: counts_positive(4),counts_negative(4)
467 logical :: log_open
468
470 result%polarity=0
471 memory_mb=estimate_self_consistent_memory_mb(config%fft_padding_factor)
472 result%memory_required_mb=memory_mb
473 if(memory_mb>config%memory_limit_mb) then
474 if(mype==0) write(*,*) 'Grad-Rubin self-consistent required/allowed MiB per rank:',&
475 memory_mb,config%memory_limit_mb
476 call mpistop('Grad-Rubin self-consistency exceeds memory_limit_mb')
477 end if
478
479 call extract_boundary_core(bcore)
480 dx1=dx(1,1); dx2=dx(2,1); dx3=dx(3,1)
481 call balance_normal_field(bcore(:,:,3),config%flux_treatment,&
482 config%max_flux_imbalance,flux_before,flux_after,mean_correction,&
483 flux_status)
484 if(flux_status/=0 .and. flux_status/=1) &
485 call mpistop('Grad-Rubin bottom normal field failed flux-balance policy')
486
487 allocate(alpha0(domain_nx1,domain_nx2),alpha_old(domain_nx1,domain_nx2))
488 allocate(alpha_raw(domain_nx1,domain_nx2),alpha_weight(domain_nx1,domain_nx2))
489 allocate(alpha_polarity(domain_nx1,domain_nx2))
490 allocate(valid_mask(domain_nx1,domain_nx2),alpha_mask(domain_nx1,domain_nx2))
491 if(trim(adjustl(config%alpha_source))=='external') then
492 call extract_external_alpha_core(alpha_raw,alpha0,alpha_weight,&
493 alpha_mask,alpha_polarity)
494 valid_mask=alpha_mask
495 else
496 call gr_compute_alpha_boundary(bcore,dx1,dx2,config%bz_taper_zero,&
497 config%bz_taper_full,alpha0,valid_pixels,alpha_raw)
498 valid_mask=dabs(bcore(:,:,3))/max(maxval(dabs(bcore(:,:,3))),tiny(1.d0))>&
499 config%bz_taper_zero
500 alpha_weight=0.d0
501 where(alpha0/=0.d0) alpha_weight=1.d0
502 alpha_polarity=0
503 where(bcore(:,:,3)>0.d0) alpha_polarity=1
504 where(bcore(:,:,3)<0.d0) alpha_polarity=-1
505 end if
506 where(.not.valid_mask) alpha0=0.d0
507 result%valid_alpha_pixels=count(valid_mask)
508 if(result%valid_alpha_pixels>0) then
509 result%alpha_min=minval(alpha0,mask=valid_mask)
510 result%alpha_max=maxval(alpha0,mask=valid_mask)
511 end if
512
513 call extrapolate_potential_fft_dense(bcore(:,:,3),dx1,dx2,dx3,&
514 config%fft_padding_factor,potential)
515 allocate(bpositive,source=potential)
516 allocate(bnegative,source=potential)
517 allocate(bfinal,source=potential)
518 allocate(alpha_positive(domain_nx1,domain_nx2,domain_nx3+1))
519 allocate(alpha_negative(domain_nx1,domain_nx2,domain_nx3+1))
520 result%initial_energy=gr_energy(potential,dx1,dx2,dx3)
521 stable=0
522 total_iterations=0
523 log_open=.false.; log_unit=-1; metrics_unit=-1
524 if(mype==0) then
525 if(config%write_detailed_history) then
526 open(newunit=log_unit,file=trim(base_filename)//&
527 '_grad_rubin_self_consistent.csv',status='replace',action='write')
528 log_open=.true.
529 write(log_unit,'(a)') 'cycle,positive_iterations,negative_iterations,'//&
530 'positive_converged,negative_converged,'//&
531 'positive_negative_rms_difference,boundary_alpha_rms_change,'//&
532 'positive_energy,negative_energy,positive_closed,negative_closed,'//&
533 'positive_open,negative_open,positive_weak,negative_weak,'//&
534 'positive_max_steps,negative_max_steps'
535 end if
536 open(newunit=metrics_unit,file=trim(base_filename)//'_nlfff_metrics.csv',&
537 status='replace',action='write')
538 call write_nlfff_metrics_header(metrics_unit)
539 end if
540 call evaluate_nlfff_metrics_dense(potential,dx1,dx2,dx3,physical_metrics)
541 if(mype==0) call write_nlfff_metrics_row(metrics_unit,0,physical_metrics)
542
543 polarity_config=config
544 polarity_config%self_consistency_cycles=0
545 do cycle=1,config%self_consistency_cycles
546 alpha_old=alpha0
547 polarity_config%polarity=1
548 call solve_polarity(potential,bcore(:,:,3),alpha0,polarity_config,&
549 bpositive,alpha_positive,positive_result,counts_positive)
550 polarity_config%polarity=-1
551 call solve_polarity(potential,bcore(:,:,3),alpha0,polarity_config,&
552 bnegative,alpha_negative,negative_result,counts_negative)
553 total_iterations=total_iterations+positive_result%iterations+&
554 negative_result%iterations
555
556 call gr_combine_alpha_unweighted(alpha_positive(:,:,1),&
557 alpha_negative(:,:,1),alpha0)
558 where(.not.valid_mask) alpha0=0.d0
559 denom=max(sum(alpha_old**2),tiny(1.d0))
560 alpha_change=dsqrt(sum((alpha0-alpha_old)**2)/denom)
561 pn_difference=dsqrt(sum((bpositive-bnegative)**2)/&
562 max(0.5d0*(sum(bpositive**2)+sum(bnegative**2)),tiny(1.d0)))
563 if(.not.ieee_is_finite(alpha_change) .or. &
564 .not.ieee_is_finite(pn_difference)) &
565 call mpistop('non-finite Grad-Rubin self-consistency diagnostic')
566
567 result%self_consistency_cycles_completed=cycle
568 result%positive_negative_rms_difference=pn_difference
569 result%boundary_alpha_rms_change=alpha_change
570 result%iterations=total_iterations
571 result%final_energy=negative_result%final_energy
572 result%rms_field_change=negative_result%rms_field_change
573 result%relative_energy_change=negative_result%relative_energy_change
574 result%closed_fieldlines=counts_positive(1)+counts_negative(1)
575 result%open_fieldlines=counts_positive(2)+counts_negative(2)
576 result%weak_fieldlines=counts_positive(3)+counts_negative(3)
577 result%max_step_fieldlines=counts_positive(4)+counts_negative(4)
578 call evaluate_nlfff_metrics_dense(bnegative,dx1,dx2,dx3,physical_metrics)
579
580 if(mype==0) then
581 if(config%write_detailed_history) then
582 write(log_unit,'(i0,2(",",i0),2(",",l1),4(",",es24.16),'//&
583 '8(",",i0))') cycle,positive_result%iterations,&
584 negative_result%iterations,positive_result%converged,&
585 negative_result%converged,pn_difference,alpha_change,&
586 positive_result%final_energy,negative_result%final_energy,&
587 counts_positive(1),counts_negative(1),counts_positive(2),&
588 counts_negative(2),counts_positive(3),counts_negative(3),&
589 counts_positive(4),counts_negative(4)
590 flush(log_unit)
591 end if
592 call write_nlfff_metrics_row(metrics_unit,cycle,physical_metrics)
593 end if
594
595 if(pn_difference<=config%self_consistency_field_tolerance .and. &
596 alpha_change<=config%self_consistency_alpha_tolerance) then
597 stable=stable+1
598 else
599 stable=0
600 end if
601 if(stable>=config%self_consistency_streak) then
602 result%converged=.true.
603 result%stop_reason='self_consistent'
604 exit
605 end if
606 end do
607 if(.not.result%converged) result%stop_reason='self_consistency_cycles'
608 if(mype==0) then
609 if(log_open) close(log_unit)
610 close(metrics_unit)
611 end if
612 if(result%valid_alpha_pixels>0) then
613 result%alpha_min=minval(alpha0,mask=valid_mask)
614 result%alpha_max=maxval(alpha0,mask=valid_mask)
615 end if
616
617 ! CFIT alternates complete P and N solutions and leaves the last polarity
618 ! field as its output. Use the final N solution because every AMRVAC cycle
619 ! above is a complete P/N pair; the P/N discrepancy is returned explicitly.
620 bfinal=bnegative
621 call field_diagnostics(bfinal,dx1,dx2,dx3,result%current_weighted_theta,&
622 result%epsilon_div)
623 call scatter_dense_field(bfinal,iw_b)
624
625 deallocate(bcore,alpha0,alpha_old,alpha_raw,alpha_weight,alpha_polarity,&
626 valid_mask,alpha_mask,alpha_positive,alpha_negative,potential,bpositive,&
627 bnegative,bfinal)
628 end subroutine extrapolate_self_consistent
629
630 subroutine solve_polarity(potential,bz0,alpha0,config,b,alpha,result,counts)
631 use mod_comm_lib, only: mpistop
632 use mod_global_parameters, only: dx
633 use, intrinsic :: ieee_arithmetic, only: ieee_is_finite
634
635 double precision, intent(in) :: potential(:,:,:,:),bz0(:,:),alpha0(:,:)
636 type(nlfff_grad_rubin_config), intent(in) :: config
637 double precision, intent(out) :: b(:,:,:,:),alpha(:,:,:)
638 type(nlfff_grad_rubin_result), intent(out) :: result
639 integer, intent(out) :: counts(4)
640 double precision, allocatable :: bnew(:,:,:,:),current(:,:,:,:),bc(:,:,:,:)
641 double precision :: energy_old,energy_new,rms_change,energy_change,theta,epsdiv
642 integer :: iter,stable,ic
643
645 result%polarity=config%polarity
646 allocate(bnew,source=potential)
647 allocate(current(size(potential,1),size(potential,2),size(potential,3),3))
648 allocate(bc(size(potential,1),size(potential,2),size(potential,3),3))
649 b=potential
650 energy_old=gr_energy(b,dx(1,1),dx(2,1),dx(3,1))
651 result%initial_energy=energy_old
652 stable=0; counts=0
653 do iter=1,config%max_iterations
654 call transport_alpha_mpi(b,alpha0,bz0,config,alpha,counts)
655 do concurrent(ic=1:3)
656 current(:,:,:,ic)=alpha*b(:,:,:,ic)
657 end do
658 call gr_solve_current_field_mpi(current,config%fft_padding_factor,&
659 dx(1,1),dx(2,1),dx(3,1),bc)
660 bnew=(1.d0-config%relaxation_factor)*b+&
661 config%relaxation_factor*(potential+bc)
662 bnew(:,:,1,3)=bz0
663 if(.not.all(ieee_is_finite(bnew))) &
664 call mpistop('non-finite field in Grad-Rubin polarity solve')
665 energy_new=gr_energy(bnew,dx(1,1),dx(2,1),dx(3,1))
666 rms_change=dsqrt(sum((bnew-b)**2)/max(sum(b**2),tiny(1.d0)))
667 energy_change=dabs(energy_new-energy_old)/max(dabs(energy_old),tiny(1.d0))
668 b=bnew; energy_old=energy_new
669 result%iterations=iter
670 result%final_energy=energy_new
671 result%rms_field_change=rms_change
672 result%relative_energy_change=energy_change
673 if(rms_change<=config%field_change_tolerance .and. &
674 energy_change<=config%energy_change_tolerance) then
675 stable=stable+1
676 else
677 stable=0
678 end if
679 if(stable>=config%convergence_streak) then
680 result%converged=.true.
681 result%stop_reason='converged'
682 exit
683 end if
684 end do
685 if(.not.result%converged) result%stop_reason='max_iterations'
686 call field_diagnostics(b,dx(1,1),dx(2,1),dx(3,1),theta,epsdiv)
687 result%current_weighted_theta=theta; result%epsilon_div=epsdiv
688 result%closed_fieldlines=counts(1); result%open_fieldlines=counts(2)
689 result%weak_fieldlines=counts(3); result%max_step_fieldlines=counts(4)
690 deallocate(bnew,current,bc)
691 end subroutine solve_polarity
692
693 pure subroutine gr_combine_alpha_unweighted(alpha_positive,alpha_negative,&
694 alpha_combined)
695 double precision, intent(in) :: alpha_positive(:,:),alpha_negative(:,:)
696 double precision, intent(out) :: alpha_combined(size(alpha_positive,1),&
697 size(alpha_positive,2))
698 alpha_combined=0.5d0*(alpha_positive+alpha_negative)
699 end subroutine gr_combine_alpha_unweighted
700
701 subroutine validate_configuration(iw_b,config)
702 use mod_comm_lib, only: mpistop
705 use, intrinsic :: ieee_arithmetic, only: ieee_is_finite
706
707 integer, intent(in) :: iw_b(3)
708 type(nlfff_grad_rubin_config), intent(in) :: config
709
710 if(.not.allocated(boundary_b)) call mpistop('Grad-Rubin boundary is not initialized')
711 if(.not.all(ieee_is_finite(boundary_b))) &
712 call mpistop('Grad-Rubin boundary contains non-finite values')
713 if(ndim/=3 .or. coordinate/=cartesian) &
714 call mpistop('Grad-Rubin v1 requires Cartesian 3D')
715 if(any(stretched_dim) .or. refine_max_level/=1 .or. levmax/=1) &
716 call mpistop('Grad-Rubin v1 requires a single uniform level')
717 if(stagger_grid) call mpistop('Grad-Rubin v1 does not support stagger_grid')
718 if(any(iw_b<1) .or. any(iw_b>nw)) call mpistop('invalid Grad-Rubin B indices')
719 if(config%polarity/=1 .and. config%polarity/=-1) &
720 call mpistop('Grad-Rubin polarity must be +1 or -1')
721 select case(trim(adjustl(config%alpha_source)))
722 case('vector_magnetogram')
723 continue
724 case('external')
725 if(.not.allocated(external_alpha_full)) &
726 call mpistop('alpha_source=external requires an external alpha product')
727 case default
728 call mpistop('Grad-Rubin alpha_source must be vector_magnetogram or external')
729 end select
730 if(config%fft_padding_factor<1) call mpistop('Grad-Rubin padding must be positive')
731 if(domain_nx1<3 .or. domain_nx2<3 .or. domain_nx3<2) &
732 call mpistop('Grad-Rubin grid must be at least 3 x 3 x 2')
733 if(mod(domain_nx1,block_nx1)/=0 .or. mod(domain_nx2,block_nx2)/=0 .or. &
734 mod(domain_nx3,block_nx3)/=0) &
735 call mpistop('Grad-Rubin domain sizes must be divisible by block sizes')
736 if(config%max_flux_imbalance<0.d0 .or. config%max_flux_imbalance>1.d0) &
737 call mpistop('Grad-Rubin max_flux_imbalance must be in [0,1]')
738 if(config%bz_taper_zero<0.d0 .or. &
739 config%bz_taper_full<=config%bz_taper_zero .or. &
740 config%bz_taper_full>1.d0) &
741 call mpistop('invalid Grad-Rubin Bz taper thresholds')
742 if(config%relaxation_factor<=0.d0 .or. config%relaxation_factor>1.d0) &
743 call mpistop('Grad-Rubin relaxation factor must be in (0,1]')
744 if(config%fieldline_step_fraction<=0.d0 .or. &
745 config%fieldline_max_steps<1 .or. config%max_iterations<1 .or. &
746 config%convergence_streak<1 .or. config%log_interval<1) &
747 call mpistop('invalid Grad-Rubin iteration controls')
748 if(config%field_change_tolerance<0.d0 .or. &
749 config%energy_change_tolerance<0.d0 .or. config%memory_limit_mb<=0.d0) &
750 call mpistop('invalid Grad-Rubin tolerance or memory limit')
751 if(config%self_consistency_cycles<0 .or. &
752 config%self_consistency_streak<1 .or. &
753 config%self_consistency_field_tolerance<0.d0 .or. &
754 config%self_consistency_alpha_tolerance<0.d0) &
755 call mpistop('invalid Grad-Rubin self-consistency controls')
756 if(.not.ieee_is_finite(config%max_flux_imbalance) .or. &
757 .not.ieee_is_finite(config%bz_taper_zero) .or. &
758 .not.ieee_is_finite(config%bz_taper_full) .or. &
759 .not.ieee_is_finite(config%relaxation_factor) .or. &
760 .not.ieee_is_finite(config%fieldline_step_fraction) .or. &
761 .not.ieee_is_finite(config%field_change_tolerance) .or. &
762 .not.ieee_is_finite(config%energy_change_tolerance) .or. &
763 .not.ieee_is_finite(config%self_consistency_field_tolerance) .or. &
764 .not.ieee_is_finite(config%self_consistency_alpha_tolerance) .or. &
765 .not.ieee_is_finite(config%memory_limit_mb)) &
766 call mpistop('non-finite Grad-Rubin configuration')
767 select case(trim(adjustl(config%flux_treatment)))
768 case('strict','subtract_mean')
769 continue
770 case default
771 call mpistop('Grad-Rubin flux_treatment must be strict or subtract_mean')
772 end select
773 end subroutine validate_configuration
774
775 subroutine extract_external_alpha_core(alpha_raw,alpha_clean,alpha_weight,&
776 alpha_mask,alpha_polarity)
777 use mod_comm_lib, only: mpistop
779 use mod_lfff, only: xa1,xa2
780
781 double precision, intent(out) :: alpha_raw(domain_nx1,domain_nx2)
782 double precision, intent(out) :: alpha_clean(domain_nx1,domain_nx2)
783 double precision, intent(out) :: alpha_weight(domain_nx1,domain_nx2)
784 logical, intent(out) :: alpha_mask(domain_nx1,domain_nx2)
785 integer, intent(out) :: alpha_polarity(domain_nx1,domain_nx2)
786 double precision :: err,tol
787 integer :: i,j,i0,j0,s
788
789 i0=0
790 tol=1.d-8*max(1.d0,dabs(xprobmin1),dabs(xprobmax1),dx(1,1))
791 do s=1,size(external_alpha_x)-domain_nx1+1
792 err=0.d0
793 do i=1,domain_nx1
794 err=max(err,dabs(external_alpha_x(s+i-1)-&
795 (xprobmin1+(dble(i)-0.5d0)*dx(1,1))))
796 end do
797 if(err<=tol) then
798 i0=s
799 exit
800 end if
801 end do
802 j0=0
803 tol=1.d-8*max(1.d0,dabs(xprobmin2),dabs(xprobmax2),dx(2,1))
804 do s=1,size(external_alpha_y)-domain_nx2+1
805 err=0.d0
806 do j=1,domain_nx2
807 err=max(err,dabs(external_alpha_y(s+j-1)-&
808 (xprobmin2+(dble(j)-0.5d0)*dx(2,1))))
809 end do
810 if(err<=tol) then
811 j0=s
812 exit
813 end if
814 end do
815 if(i0==0 .or. j0==0) call mpistop('external alpha coordinates do not match active grid')
816 alpha_raw=external_alpha_raw_full(i0:i0+domain_nx1-1,j0:j0+domain_nx2-1)
817 alpha_clean=external_alpha_full(i0:i0+domain_nx1-1,j0:j0+domain_nx2-1)
818 alpha_weight=external_alpha_weight_full(i0:i0+domain_nx1-1,j0:j0+domain_nx2-1)
819 alpha_mask=external_alpha_valid_full(i0:i0+domain_nx1-1,j0:j0+domain_nx2-1)/=0
820 alpha_polarity=external_alpha_polarity_full(i0:i0+domain_nx1-1,j0:j0+domain_nx2-1)
821 end subroutine extract_external_alpha_core
822
823 subroutine write_alpha_diagnostics(base,polarity,source,alpha_raw,alpha_selected,&
824 alpha_weight,alpha_mask,alpha_polarity)
825 use mod_comm_lib, only: mpistop
826 use mod_global_parameters, only: mype
827
828 character(len=*), intent(in) :: base,source
829 integer, intent(in) :: polarity
830 double precision, intent(in) :: alpha_raw(:,:),alpha_selected(:,:),alpha_weight(:,:)
831 logical, intent(in) :: alpha_mask(:,:)
832 integer, intent(in) :: alpha_polarity(:,:)
833 character(len=1024) :: filename,polarity_name
834 integer :: iu,i,j,ios
835
836 if(mype/=0) return
837 if(polarity>0) then
838 polarity_name='positive'
839 else
840 polarity_name='negative'
841 end if
842 filename=trim(base)//'_grad_rubin_'//trim(polarity_name)//'_alpha.csv'
843 open(newunit=iu,file=trim(filename),status='replace',action='write',iostat=ios)
844 if(ios/=0) call mpistop('cannot write Grad-Rubin alpha diagnostics')
845 write(iu,'(a)') 'i,j,alpha_raw,alpha_selected,weight,valid,polarity,source'
846 do j=1,size(alpha_raw,2)
847 do i=1,size(alpha_raw,1)
848 write(iu,'(2(i0,","),3(es24.16,","),i0,",",i0,",",a)') i,j,&
849 alpha_raw(i,j),alpha_selected(i,j),alpha_weight(i,j),merge(1,0,alpha_mask(i,j)),&
850 alpha_polarity(i,j),trim(source)
851 end do
852 end do
853 close(iu)
854 end subroutine write_alpha_diagnostics
855
856 subroutine extract_boundary_core(core)
857 use mod_comm_lib, only: mpistop
859 use mod_lfff, only: xa1,xa2
860
861 double precision, allocatable, intent(out) :: core(:,:,:)
862 double precision :: err,tol
863 integer :: i,j,i0,j0,s
864
865 i0=0
866 tol=1.d-8*max(1.d0,dabs(xprobmin1),dabs(xprobmax1),dx(1,1))
867 do s=1,size(boundary_b,1)-domain_nx1+1
868 err=0.d0
869 do i=1,domain_nx1
870 err=max(err,dabs(xa1(s+i-1)-(xprobmin1+(dble(i)-0.5d0)*dx(1,1))))
871 end do
872 if(err<=tol) then
873 i0=s
874 exit
875 end if
876 end do
877 j0=0
878 tol=1.d-8*max(1.d0,dabs(xprobmin2),dabs(xprobmax2),dx(2,1))
879 do s=1,size(boundary_b,2)-domain_nx2+1
880 err=0.d0
881 do j=1,domain_nx2
882 err=max(err,dabs(xa2(s+j-1)-(xprobmin2+(dble(j)-0.5d0)*dx(2,1))))
883 end do
884 if(err<=tol) then
885 j0=s
886 exit
887 end if
888 end do
889 if(i0==0 .or. j0==0) call mpistop('Grad-Rubin boundary does not match grid centres')
890 allocate(core(domain_nx1,domain_nx2,3))
891 core=boundary_b(i0:i0+domain_nx1-1,j0:j0+domain_nx2-1,:)
892 end subroutine extract_boundary_core
893
894 subroutine balance_normal_field(bz,treatment,max_imbalance,before,after,&
895 correction,status)
896 double precision, intent(inout) :: bz(:,:)
897 character(len=*), intent(in) :: treatment
898 double precision, intent(in) :: max_imbalance
899 double precision, intent(out) :: before,after,correction
900 integer, intent(out) :: status
901 double precision :: unsigned
902
903 unsigned=sum(dabs(bz))
904 before=dabs(sum(bz))/max(unsigned,tiny(1.d0))
905 after=before
906 correction=0.d0
907 status=0
908 select case(trim(adjustl(treatment)))
909 case('strict')
910 if(before>1.d-8) status=2
911 case('subtract_mean')
912 if(before>max_imbalance) then
913 status=3
914 else if(before>1.d-8) then
915 correction=sum(bz)/dble(size(bz))
916 bz=bz-correction
917 after=dabs(sum(bz))/max(sum(dabs(bz)),tiny(1.d0))
918 status=1
919 end if
920 case default
921 status=4
922 end select
923 end subroutine balance_normal_field
924
925 pure double precision function gr_taper_weight(r,zero_threshold,full_threshold)
926 double precision, intent(in) :: r,zero_threshold,full_threshold
927 double precision :: pi,s
928
929 pi=4.d0*datan(1.d0)
930 if(r<=zero_threshold) then
931 gr_taper_weight=0.d0
932 else if(r>=full_threshold) then
933 gr_taper_weight=1.d0
934 else
935 s=(r-zero_threshold)/(full_threshold-zero_threshold)
936 gr_taper_weight=0.5d0*(1.d0-dcos(pi*s))
937 end if
938 end function gr_taper_weight
939
940 subroutine gr_compute_alpha_boundary(b,dx1,dx2,zero_threshold,full_threshold,&
941 alpha,valid_pixels,alpha_raw)
942 double precision, intent(in) :: b(:,:,:),dx1,dx2
943 double precision, intent(in) :: zero_threshold,full_threshold
944 double precision, intent(out) :: alpha(size(b,1),size(b,2))
945 integer, intent(out) :: valid_pixels
946 double precision, intent(out), optional :: alpha_raw(size(b,1),size(b,2))
947 double precision :: dbxdy,dbydx,bzmax,r,w
948 integer :: i,j,nx,ny
949
950 nx=size(b,1)
951 ny=size(b,2)
952 bzmax=maxval(dabs(b(:,:,3)))
953 alpha=0.d0
954 if(present(alpha_raw)) alpha_raw=0.d0
955 valid_pixels=0
956 if(bzmax<=0.d0) return
957 do j=1,ny
958 do i=1,nx
959 dbydx=derivative_1d(b(:,j,2),i,dx1)
960 dbxdy=derivative_1d(b(i,:,1),j,dx2)
961 r=dabs(b(i,j,3))/bzmax
962 w=gr_taper_weight(r,zero_threshold,full_threshold)
963 if(w>0.d0 .and. b(i,j,3)/=0.d0) then
964 if(present(alpha_raw)) alpha_raw(i,j)=(dbydx-dbxdy)/b(i,j,3)
965 alpha(i,j)=w*(dbydx-dbxdy)/b(i,j,3)
966 valid_pixels=valid_pixels+1
967 end if
968 end do
969 end do
970 end subroutine gr_compute_alpha_boundary
971
972 pure double precision function derivative_1d(f,i,h)
973 double precision, intent(in) :: f(:),h
974 integer, intent(in) :: i
975 integer :: n
976
977 n=size(f)
978 if(n<3) then
979 derivative_1d=0.d0
980 else if(i==1) then
981 derivative_1d=(-3.d0*f(1)+4.d0*f(2)-f(3))/(2.d0*h)
982 else if(i==n) then
983 derivative_1d=(3.d0*f(n)-4.d0*f(n-1)+f(n-2))/(2.d0*h)
984 else
985 derivative_1d=(f(i+1)-f(i-1))/(2.d0*h)
986 end if
987 end function derivative_1d
988
989 subroutine transport_alpha_mpi(b,alpha0,bz0,config,alpha,global_counts)
990 use mpi
992
993 double precision, intent(in) :: b(:,:,:,:),alpha0(:,:),bz0(:,:)
994 type(nlfff_grad_rubin_config), intent(in) :: config
995 double precision, intent(out) :: alpha(:,:,:)
996 integer, intent(out) :: global_counts(4)
997 double precision, allocatable :: local_alpha(:),gathered_alpha(:)
998 double precision :: x(3),end_forward(3),end_backward(3),value,bzf,bzb
999 double precision :: z0,step,bfloor
1000 integer, allocatable :: recvcounts(:),displs(:)
1001 integer :: i,j,k,index,first_index,last_index,local_index,nseed
1002 integer :: statusf,statusb,local_counts(4),chosen,rank
1003
1004 nseed=size(alpha)
1005 allocate(recvcounts(npe),displs(npe))
1006 do rank=0,npe-1
1007 displs(rank+1)=nseed*rank/npe
1008 recvcounts(rank+1)=nseed*(rank+1)/npe-displs(rank+1)
1009 end do
1010 first_index=displs(mype+1)+1
1011 last_index=displs(mype+1)+recvcounts(mype+1)
1012 allocate(local_alpha(recvcounts(mype+1)),gathered_alpha(nseed))
1013 local_alpha=0.d0
1014 local_counts=0
1015 z0=xprobmin3-0.5d0*dx(3,1)
1016 step=config%fieldline_step_fraction*minval(dx(:,1))
1017 bfloor=1.d-12*maxval(dsqrt(sum(b**2,dim=4)))
1018 do index=first_index,last_index
1019 k=(index-1)/(size(alpha,1)*size(alpha,2))+1
1020 j=mod(index-1,size(alpha,1)*size(alpha,2))/size(alpha,1)+1
1021 i=mod(index-1,size(alpha,1))+1
1022 local_index=index-first_index+1
1023 x=(/xprobmin1+(dble(i)-0.5d0)*dx(1,1),&
1024 xprobmin2+(dble(j)-0.5d0)*dx(2,1),z0+dble(k-1)*dx(3,1)/)
1025 call trace_to_boundary(b,x,1.d0,step,config%fieldline_max_steps,&
1026 bfloor,end_forward,statusf)
1027 call trace_to_boundary(b,x,-1.d0,step,config%fieldline_max_steps,&
1028 bfloor,end_backward,statusb)
1029 if(statusf==gr_trace_bottom .and. statusb==gr_trace_bottom) then
1030 local_counts(1)=local_counts(1)+1
1031 bzf=gr_bilinear_sample(bz0,end_forward(1),end_forward(2),&
1032 xprobmin1,xprobmin2,dx(1,1),dx(2,1))
1033 bzb=gr_bilinear_sample(bz0,end_backward(1),end_backward(2),&
1034 xprobmin1,xprobmin2,dx(1,1),dx(2,1))
1035 chosen=0
1036 if(config%polarity*bzf>0.d0) chosen=1
1037 if(config%polarity*bzb>0.d0) chosen=2
1038 if(chosen==1) then
1039 value=gr_bilinear_sample(alpha0,end_forward(1),end_forward(2),&
1040 xprobmin1,xprobmin2,dx(1,1),dx(2,1))
1041 local_alpha(local_index)=value
1042 else if(chosen==2) then
1043 value=gr_bilinear_sample(alpha0,end_backward(1),end_backward(2),&
1044 xprobmin1,xprobmin2,dx(1,1),dx(2,1))
1045 local_alpha(local_index)=value
1046 end if
1047 else if(statusf==gr_trace_max_steps .or. statusb==gr_trace_max_steps) then
1048 local_counts(4)=local_counts(4)+1
1049 else if(statusf==gr_trace_weak .or. statusb==gr_trace_weak) then
1050 local_counts(3)=local_counts(3)+1
1051 else
1052 local_counts(2)=local_counts(2)+1
1053 end if
1054 end do
1055 call mpi_allgatherv(local_alpha,size(local_alpha),mpi_double_precision,&
1056 gathered_alpha,recvcounts,displs,mpi_double_precision,icomm,ierrmpi)
1057 alpha=reshape(gathered_alpha,shape(alpha))
1058 call mpi_allreduce(local_counts,global_counts,4,mpi_integer,mpi_sum,icomm,ierrmpi)
1059 deallocate(local_alpha,gathered_alpha,recvcounts,displs)
1060 end subroutine transport_alpha_mpi
1061
1062 subroutine trace_to_boundary(b,start,direction,step,max_steps,bfloor,endpoint,status)
1063 use mod_global_parameters, only: xprobmin1,xprobmax1,xprobmin2,xprobmax2,&
1064 xprobmax3,dx,xprobmin3
1065
1066 double precision, intent(in) :: b(:,:,:,:),start(3),direction,step,bfloor
1067 integer, intent(in) :: max_steps
1068 double precision, intent(out) :: endpoint(3)
1069 integer, intent(out) :: status
1070 double precision :: x(3),xnew(3),k1(3),k2(3),k3(3),k4(3),z0,t
1071 logical :: ok
1072 integer :: n
1073
1074 z0=xprobmin3-0.5d0*dx(3,1)
1075 x=start
1076 do n=1,max_steps
1077 call field_direction(b,x,direction,bfloor,k1,ok)
1078 if(.not.ok) then
1079 endpoint=x
1080 status=gr_trace_weak
1081 return
1082 end if
1083 if(point_is_open(x+0.5d0*step*k1)) then
1084 endpoint=x+0.5d0*step*k1
1085 status=gr_trace_open
1086 return
1087 end if
1088 if(x(3)+0.5d0*step*k1(3)<=z0) then
1089 t=(z0-x(3))/(0.5d0*step*k1(3))
1090 endpoint=x+t*(0.5d0*step*k1)
1091 endpoint(3)=z0
1092 status=gr_trace_bottom
1093 return
1094 end if
1095 call field_direction(b,x+0.5d0*step*k1,direction,bfloor,k2,ok)
1096 if(.not.ok) then
1097 endpoint=x
1098 status=gr_trace_weak
1099 return
1100 end if
1101 if(point_is_open(x+0.5d0*step*k2)) then
1102 endpoint=x+0.5d0*step*k2
1103 status=gr_trace_open
1104 return
1105 end if
1106 if(x(3)+0.5d0*step*k2(3)<=z0) then
1107 t=(z0-x(3))/(0.5d0*step*k2(3))
1108 endpoint=x+t*(0.5d0*step*k2)
1109 endpoint(3)=z0
1110 status=gr_trace_bottom
1111 return
1112 end if
1113 call field_direction(b,x+0.5d0*step*k2,direction,bfloor,k3,ok)
1114 if(.not.ok) then
1115 endpoint=x
1116 status=gr_trace_weak
1117 return
1118 end if
1119 if(point_is_open(x+step*k3)) then
1120 endpoint=x+step*k3
1121 status=gr_trace_open
1122 return
1123 end if
1124 if(x(3)+step*k3(3)<=z0) then
1125 t=(z0-x(3))/(step*k3(3))
1126 endpoint=x+t*(step*k3)
1127 endpoint(3)=z0
1128 status=gr_trace_bottom
1129 return
1130 end if
1131 call field_direction(b,x+step*k3,direction,bfloor,k4,ok)
1132 if(.not.ok) then
1133 endpoint=x
1134 status=gr_trace_weak
1135 return
1136 end if
1137 xnew=x+step*(k1+2.d0*k2+2.d0*k3+k4)/6.d0
1138 if(xnew(3)<=z0) then
1139 t=(z0-x(3))/min(xnew(3)-x(3),-tiny(1.d0))
1140 endpoint=x+t*(xnew-x)
1141 endpoint(3)=z0
1142 status=gr_trace_bottom
1143 return
1144 end if
1145 if(xnew(1)<xprobmin1 .or. xnew(1)>xprobmax1 .or. &
1146 xnew(2)<xprobmin2 .or. xnew(2)>xprobmax2 .or. xnew(3)>xprobmax3) then
1147 endpoint=xnew
1148 status=gr_trace_open
1149 return
1150 end if
1151 x=xnew
1152 end do
1153 endpoint=x
1154 status=gr_trace_max_steps
1155 end subroutine trace_to_boundary
1156
1157 logical function point_is_open(x)
1158 use mod_global_parameters, only: xprobmin1,xprobmax1,xprobmin2,xprobmax2,&
1159 xprobmax3
1160 double precision, intent(in) :: x(3)
1161 point_is_open=x(1)<xprobmin1 .or. x(1)>xprobmax1 .or. &
1162 x(2)<xprobmin2 .or. x(2)>xprobmax2 .or. x(3)>xprobmax3
1163 end function point_is_open
1164
1165 subroutine field_direction(b,x,direction,bfloor,bhat,ok)
1166 use mod_global_parameters, only: xprobmin1,xprobmax1,xprobmin2,xprobmax2,&
1167 xprobmin3,xprobmax3,dx
1168 double precision, intent(in) :: b(:,:,:,:),x(3),direction,bfloor
1169 double precision, intent(out) :: bhat(3)
1170 logical, intent(out) :: ok
1171 double precision :: value(3),norm,z0
1172
1173 z0=xprobmin3-0.5d0*dx(3,1)
1174 ! RK stages may lie just across a boundary within the current step. The
1175 ! sampler clamps those stages; trace_to_boundary classifies the completed
1176 ! step and computes the exact lower-plane intersection.
1177 call gr_trilinear_sample(b,x,xprobmin1,xprobmin2,z0,dx(1,1),dx(2,1),&
1178 dx(3,1),value)
1179 norm=dsqrt(dot_product(value,value))
1180 ok=(norm>bfloor)
1181 if(ok) then
1182 bhat=direction*value/norm
1183 else
1184 bhat=0.d0
1185 end if
1186 end subroutine field_direction
1187
1188 subroutine gr_trilinear_sample(field,x,xmin,ymin,zmin,dx1,dx2,dx3,value)
1189 double precision, intent(in) :: field(:,:,:,:),x(3),xmin,ymin,zmin,dx1,dx2,dx3
1190 double precision, intent(out) :: value(size(field,4))
1191 double precision :: tx,ty,tz,fx,fy,fz
1192 integer :: i0,j0,k0,i1,j1,k1
1193
1194 fx=(x(1)-(xmin+0.5d0*dx1))/dx1+1.d0
1195 fy=(x(2)-(ymin+0.5d0*dx2))/dx2+1.d0
1196 fz=(x(3)-zmin)/dx3+1.d0
1197 fx=max(1.d0,min(dble(size(field,1)),fx))
1198 fy=max(1.d0,min(dble(size(field,2)),fy))
1199 fz=max(1.d0,min(dble(size(field,3)),fz))
1200 i0=min(int(floor(fx)),size(field,1)-1); i1=i0+1; tx=fx-dble(i0)
1201 j0=min(int(floor(fy)),size(field,2)-1); j1=j0+1; ty=fy-dble(j0)
1202 k0=min(int(floor(fz)),size(field,3)-1); k1=k0+1; tz=fz-dble(k0)
1203 if(size(field,1)==1) then; i0=1; i1=1; tx=0.d0; end if
1204 if(size(field,2)==1) then; j0=1; j1=1; ty=0.d0; end if
1205 if(size(field,3)==1) then; k0=1; k1=1; tz=0.d0; end if
1206 value=(1.d0-tz)*((1.d0-ty)*((1.d0-tx)*field(i0,j0,k0,:)+&
1207 tx*field(i1,j0,k0,:))+ty*((1.d0-tx)*field(i0,j1,k0,:)+&
1208 tx*field(i1,j1,k0,:)))+tz*((1.d0-ty)*((1.d0-tx)*&
1209 field(i0,j0,k1,:)+tx*field(i1,j0,k1,:))+ty*((1.d0-tx)*&
1210 field(i0,j1,k1,:)+tx*field(i1,j1,k1,:)))
1211 end subroutine gr_trilinear_sample
1212
1213 pure double precision function gr_bilinear_sample(field,x,y,xmin,ymin,dx1,dx2)
1214 double precision, intent(in) :: field(:,:),x,y,xmin,ymin,dx1,dx2
1215 double precision :: fx,fy,tx,ty
1216 integer :: i0,i1,j0,j1
1217
1218 fx=max(1.d0,min(dble(size(field,1)),(x-(xmin+0.5d0*dx1))/dx1+1.d0))
1219 fy=max(1.d0,min(dble(size(field,2)),(y-(ymin+0.5d0*dx2))/dx2+1.d0))
1220 i0=min(int(floor(fx)),size(field,1)-1); i1=i0+1; tx=fx-dble(i0)
1221 j0=min(int(floor(fy)),size(field,2)-1); j1=j0+1; ty=fy-dble(j0)
1222 gr_bilinear_sample=(1.d0-ty)*((1.d0-tx)*field(i0,j0)+tx*field(i1,j0))+&
1223 ty*((1.d0-tx)*field(i0,j1)+tx*field(i1,j1))
1224 end function gr_bilinear_sample
1225
1226 subroutine gr_solve_current_field_mpi(current,padding,dx1,dx2,dx3,bc)
1227 use mpi
1229 use mod_fft, only: fft_2d
1230
1231 double precision, intent(in) :: current(:,:,:,:),dx1,dx2,dx3
1232 integer, intent(in) :: padding
1233 double precision, intent(out) :: bc(:,:,:,:)
1234 double complex, allocatable :: fj(:,:,:,:),fb(:,:,:,:),plane(:,:)
1235 double complex :: ia(3),ib(3),im(3),sumz,diffx,diffy
1236 double precision, allocatable :: kx(:),ky(:),z(:),bc_global(:,:,:,:)
1237 double precision :: q
1238 integer :: nx,ny,nz,npx,npy,ip0,jp0,i,j,k,s,ic,mode
1239
1240 nx=size(current,1); ny=size(current,2); nz=size(current,3)
1241 npx=padding*nx; npy=padding*ny
1242 ip0=(npx-nx)/2+1; jp0=(npy-ny)/2+1
1243 allocate(fj(npx,npy,nz,3),fb(npx,npy,nz,3),plane(npx,npy))
1244 allocate(kx(npx),ky(npy),z(nz))
1245 fj=(0.d0,0.d0); fb=(0.d0,0.d0)
1246 do i=1,npx
1247 mode=i-1; if(mode>npx/2) mode=mode-npx
1248 kx(i)=2.d0*dpi*dble(mode)/(dble(npx)*dx1)
1249 end do
1250 do j=1,npy
1251 mode=j-1; if(mode>npy/2) mode=mode-npy
1252 ky(j)=2.d0*dpi*dble(mode)/(dble(npy)*dx2)
1253 end do
1254 do k=1,nz; z(k)=dble(k-1)*dx3; end do
1255
1256 do k=1,nz
1257 if(mod(k-1,npe)/=mype) cycle
1258 do ic=1,3
1259 plane=(0.d0,0.d0)
1260 plane(ip0:ip0+nx-1,jp0:jp0+ny-1)=&
1261 dcmplx(current(:,:,k,ic),0.d0)
1262 call fft_2d(plane,.false.)
1263 fj(:,:,k,ic)=plane
1264 end do
1265 end do
1266 if(npe>1) then
1267 call mpi_allreduce(fj,fb,size(fj),mpi_double_complex,mpi_sum,icomm,ierrmpi)
1268 fj=fb
1269 fb=(0.d0,0.d0)
1270 end if
1271
1272 do k=1,nz
1273 if(mod(k-1,npe)/=mype) cycle
1274 do j=1,npy
1275 do i=1,npx
1276 q=dsqrt(kx(i)**2+ky(j)**2)
1277 if(q<=0.d0) then
1278 ia=(0.d0,0.d0)
1279 if(k<nz) then
1280 do s=k,nz-1
1281 ia=ia+0.5d0*dx3*(fj(i,j,s,:)+fj(i,j,s+1,:))
1282 end do
1283 end if
1284 fb(i,j,k,1)=-ia(2)
1285 fb(i,j,k,2)= ia(1)
1286 fb(i,j,k,3)=(0.d0,0.d0)
1287 else
1288 ia=(0.d0,0.d0); ib=(0.d0,0.d0); im=(0.d0,0.d0)
1289 if(k<nz) then
1290 do s=k,nz-1
1291 ia=ia+0.5d0*dx3*(dexp(-q*(z(s)-z(k)))*fj(i,j,s,:)+&
1292 dexp(-q*(z(s+1)-z(k)))*fj(i,j,s+1,:))
1293 end do
1294 end if
1295 if(k>1) then
1296 do s=1,k-1
1297 ib=ib+0.5d0*dx3*(dexp(-q*(z(k)-z(s)))*fj(i,j,s,:)+&
1298 dexp(-q*(z(k)-z(s+1)))*fj(i,j,s+1,:))
1299 end do
1300 end if
1301 do s=1,nz-1
1302 im=im+0.5d0*dx3*(dexp(-q*(z(k)+z(s)))*fj(i,j,s,:)+&
1303 dexp(-q*(z(k)+z(s+1)))*fj(i,j,s+1,:))
1304 end do
1305 sumz=ia(3)+ib(3)+im(3)
1306 diffx=ia(1)-ib(1)+im(1)
1307 diffy=ia(2)-ib(2)+im(2)
1308 ! With mod_fft's exp(-i k.x) forward convention, curl(A) uses
1309 ! +i ky Az-dAy/dz, dAx/dz-i kx Az, and
1310 ! +i kx Ay-i ky Ax. These signs are also the ones used by the
1311 ! Wheatland/CFIT open-half-space solution.
1312 fb(i,j,k,1)= (0.d0,1.d0)*ky(j)*sumz/(2.d0*q)-0.5d0*diffy
1313 fb(i,j,k,2)= 0.5d0*diffx-(0.d0,1.d0)*kx(i)*sumz/(2.d0*q)
1314 fb(i,j,k,3)=((0.d0,1.d0)*kx(i)*(ia(2)+ib(2)-im(2))-&
1315 (0.d0,1.d0)*ky(j)*(ia(1)+ib(1)-im(1)))/(2.d0*q)
1316 end if
1317 end do
1318 end do
1319 end do
1320 if(npe>1) then
1321 call mpi_allreduce(fb,fj,size(fb),mpi_double_complex,mpi_sum,icomm,ierrmpi)
1322 fb=fj
1323 end if
1324 bc=0.d0
1325 do k=1,nz
1326 if(mod(k-1,npe)/=mype) cycle
1327 do ic=1,3
1328 plane=fb(:,:,k,ic)
1329 call fft_2d(plane,.true.)
1330 bc(:,:,k,ic)=dble(plane(ip0:ip0+nx-1,jp0:jp0+ny-1))
1331 end do
1332 end do
1333 if(npe>1) then
1334 allocate(bc_global(nx,ny,nz,3))
1335 call mpi_allreduce(bc,bc_global,size(bc),mpi_double_precision,mpi_sum,&
1336 icomm,ierrmpi)
1337 bc=bc_global
1338 deallocate(bc_global)
1339 end if
1340 bc(:,:,1,3)=0.d0
1341 deallocate(fj,fb,plane,kx,ky,z)
1342 end subroutine gr_solve_current_field_mpi
1343
1344 subroutine scatter_dense_field(b,iw_b)
1346 use mod_forest, only: tree_root
1347 double precision, intent(in) :: b(:,:,:,:)
1348 integer, intent(in) :: iw_b(3)
1349 integer :: ig1,ig2,ig3,igrid,i,j,k,ic,gi,gj,gk
1350
1351 do ig3=1,domain_nx3/block_nx3
1352 do ig2=1,domain_nx2/block_nx2
1353 do ig1=1,domain_nx1/block_nx1
1354 if(tree_root(ig1,ig2,ig3)%node%ipe/=mype) cycle
1355 igrid=tree_root(ig1,ig2,ig3)%node%igrid
1356 do k=1,block_nx3; gk=(ig3-1)*block_nx3+k+1
1357 do j=1,block_nx2; gj=(ig2-1)*block_nx2+j
1358 do i=1,block_nx1; gi=(ig1-1)*block_nx1+i
1359 do ic=1,3
1360 ps(igrid)%w(ixmlo1+i-1,ixmlo2+j-1,ixmlo3+k-1,iw_b(ic))=&
1361 b(gi,gj,gk,ic)
1362 end do
1363 end do
1364 end do
1365 end do
1366 end do
1367 end do
1368 end do
1369 end subroutine scatter_dense_field
1370
1371 pure double precision function gr_energy(b,dx1,dx2,dx3)
1372 double precision, intent(in) :: b(:,:,:,:),dx1,dx2,dx3
1373 gr_energy=0.5d0*sum(b(:,:,2:,:)**2)*dx1*dx2*dx3
1374 end function gr_energy
1375
1376 subroutine field_diagnostics(b,dx1,dx2,dx3,theta_deg,epsilon_div)
1377 double precision, intent(in) :: b(:,:,:,:),dx1,dx2,dx3
1378 double precision, intent(out) :: theta_deg,epsilon_div
1379 double precision :: db(3,3),jvec(3),bv(3),cross(3),divb,b2
1380 double precision :: sum_cross,sum_j,sum_div2,sum_b2,pi,current_scale,h
1381 double precision :: ncells
1382 integer :: i,j,k,ic
1383
1384 sum_cross=0.d0; sum_j=0.d0; sum_div2=0.d0; sum_b2=0.d0
1385 do k=2,size(b,3)
1386 do j=1,size(b,2)
1387 do i=1,size(b,1)
1388 do ic=1,3
1389 db(ic,1)=derivative_1d(b(:,j,k,ic),i,dx1)
1390 db(ic,2)=derivative_1d(b(i,:,k,ic),j,dx2)
1391 db(ic,3)=derivative_1d(b(i,j,:,ic),k,dx3)
1392 end do
1393 jvec=(/db(3,2)-db(2,3),db(1,3)-db(3,1),db(2,1)-db(1,2)/)
1394 bv=b(i,j,k,:)
1395 cross=(/jvec(2)*bv(3)-jvec(3)*bv(2),&
1396 jvec(3)*bv(1)-jvec(1)*bv(3),jvec(1)*bv(2)-jvec(2)*bv(1)/)
1397 b2=dot_product(bv,bv)
1398 if(b2>0.d0) sum_cross=sum_cross+dsqrt(dot_product(cross,cross)/b2)
1399 sum_j=sum_j+dsqrt(dot_product(jvec,jvec))
1400 divb=db(1,1)+db(2,2)+db(3,3)
1401 sum_div2=sum_div2+divb**2
1402 sum_b2=sum_b2+b2
1403 end do
1404 end do
1405 end do
1406 pi=4.d0*datan(1.d0)
1407 ncells=dble(size(b,1)*size(b,2)*(size(b,3)-1))
1408 h=(dx1*dx2*dx3)**(1.d0/3.d0)
1409 current_scale=dsqrt(max(sum_b2*ncells,tiny(1.d0)))/h
1410 if(sum_j<=1.d-12*current_scale) then
1411 theta_deg=0.d0
1412 else
1413 theta_deg=dasin(min(1.d0,sum_cross/sum_j))*180.d0/pi
1414 end if
1415 epsilon_div=h*dsqrt(sum_div2/max(sum_b2,tiny(1.d0)))
1416 end subroutine field_diagnostics
1417
1418 double precision function estimate_memory_mb(padding)
1419 use mod_global_parameters, only: domain_nx1,domain_nx2,domain_nx3,npe
1420 integer, intent(in) :: padding
1421 double precision :: n,nxy,nfft,nxyfft,boundary_n,base_bytes
1422 double precision :: trace_bytes,fft_bytes,potential_bytes
1423
1424 n=dble(domain_nx1)*dble(domain_nx2)*dble(domain_nx3+1)
1425 nxy=dble(domain_nx1)*dble(domain_nx2)
1426 nxyfft=dble(padding*domain_nx1)*dble(padding*domain_nx2)
1427 nfft=dble(padding*domain_nx1)*dble(padding*domain_nx2)*&
1428 dble(domain_nx3+1)
1429 boundary_n=0.d0
1430 if(allocated(boundary_b)) boundary_n=dble(size(boundary_b))
1431 ! Persistent arrays: input boundary, extracted vector boundary, alpha0,
1432 ! five dense vector fields, and dense alpha. The two alternatives add
1433 ! either Allgatherv buffers or the padded complex Poisson work arrays.
1434 base_bytes=8.d0*(boundary_n+5.d0*nxy+16.d0*n)
1435 trace_bytes=8.d0*(2.d0*n+dble(ceiling(n/dble(npe))))+&
1436 4.d0*dble(2*npe)
1437 fft_bytes=16.d0*(6.d0*nfft+nxyfft)+max(8.d0*3.d0*n,16.d0*nxyfft)+&
1438 8.d0*dble(padding*domain_nx1+padding*domain_nx2+domain_nx3+1)
1439 ! During the initial potential solve only potential and four padded real
1440 ! planes coexist with the boundary arrays.
1441 potential_bytes=8.d0*(boundary_n+5.d0*nxy+3.d0*n+4.d0*nxyfft+&
1442 dble(padding*domain_nx1+padding*domain_nx2))
1443 estimate_memory_mb=max(base_bytes+trace_bytes,base_bytes+fft_bytes,&
1444 potential_bytes)/(1024.d0**2)
1445 end function estimate_memory_mb
1446
1447 double precision function estimate_self_consistent_memory_mb(padding)
1448 use mod_global_parameters, only: domain_nx1,domain_nx2,domain_nx3
1449 integer, intent(in) :: padding
1450 double precision :: n,nxy,extra_bytes
1451
1452 n=dble(domain_nx1)*dble(domain_nx2)*dble(domain_nx3+1)
1453 nxy=dble(domain_nx1)*dble(domain_nx2)
1454 ! In addition to a single-polarity solve, self-consistency retains the
1455 ! potential field, both P/N fields and both transported-alpha volumes.
1456 extra_bytes=8.d0*(9.d0*n+3.d0*nxy)
1457 estimate_self_consistent_memory_mb=estimate_memory_mb(padding)+&
1458 extra_bytes/(1024.d0**2)
1459 end function estimate_self_consistent_memory_mb
1460}
1461
1462end module mod_nlfff_grad_rubin
subroutine, public mpistop(message)
Exit MPI-AMRVAC with an error message.
Self-contained complex FFT utilities based on the mixed-radix Singleton transform historically embedd...
Definition mod_fft.t:3
subroutine, public fft_2d(data, inverse)
In-place complex two-dimensional FFT. The inverse is normalized.
Definition mod_fft.t:152
Module with basic grid data structures.
Definition mod_forest.t:2
type(tree_node_ptr), dimension(:^d &), allocatable, save tree_root
Pointers to the coarse grid.
Definition mod_forest.t:29
Module with geometry-related routines (e.g., divergence, curl)
Definition mod_geometry.t:2
integer coordinate
Definition mod_geometry.t:7
integer, parameter cartesian
Definition mod_geometry.t:8
This module contains definitions of global parameters and variables and some generic functions/subrou...
integer, parameter ndim
Number of spatial dimensions for grid variables.
double precision unit_length
Physical scaling factor for length.
logical stagger_grid
True for using stagger grid.
integer icomm
The MPI communicator.
integer mype
The rank of the current MPI task.
integer ierrmpi
A global MPI error return code.
double precision, dimension(:), allocatable, parameter d
double precision unit_magneticfield
Physical scaling factor for magnetic field.
logical, dimension(ndim) stretched_dim
True if a dimension is stretched.
integer npe
The number of MPI tasks.
double precision, dimension(:,:), allocatable dx
spatial steps for all dimensions at all levels
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.
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
subroutine extrapolate_potential_fft_dense(bz, dx1, dx2, dx3, padding_factor, b)
Construct an open-half-space potential field on one dense Cartesian grid. Plane 1 is the magnetogram ...
Definition mod_lfff.t:246
integer, save nx1
Definition mod_lfff.t:33
integer, save nx2
Definition mod_lfff.t:33
double precision, dimension(:), allocatable, save xa2
Definition mod_lfff.t:32
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 evaluate_nlfff_metrics_dense(b, dx1, dx2, dx3, metrics)
Evaluate the same metrics on a replicated uniform dense field. Plane 1 is the lower boundary and plan...
subroutine, public write_nlfff_metrics_row(unit, iteration, metrics)
MPI-parallel fixed-grid Grad–Rubin NLFFF extrapolation.
subroutine, public gr_solve_current_field_mpi(current, padding, dx1, dx2, dx3, bc)
pure subroutine, public gr_combine_alpha_unweighted(alpha_positive, alpha_negative, alpha_combined)
pure double precision function, public gr_taper_weight(r, zero_threshold, full_threshold)
integer, parameter, public gr_trace_open
subroutine, public gr_compute_alpha_boundary(b, dx1, dx2, zero_threshold, full_threshold, alpha, valid_pixels, alpha_raw)
subroutine, public init_nlfff_grad_rubin_boundary(filename, unit_length, unit_magneticfield, qxc1, qxc2, alpha_filename)
subroutine, public extrapolate_nlfff_grad_rubin(iw_b, config, result)
subroutine, public init_nlfff_grad_rubin_external_alpha(filename, unit_length, unit_magneticfield)
subroutine, public gr_trilinear_sample(field, x, xmin, ymin, zmin, dx1, dx2, dx3, value)
pure double precision function, public gr_bilinear_sample(field, x, y, xmin, ymin, dx1, dx2)
integer, parameter, public gr_trace_max_steps
integer, parameter, public gr_trace_bottom
double precision, dimension(:,:,:), allocatable, save boundary_b
integer, parameter, public gr_trace_weak