16 integer :: fft_padding_factor=2
17 character(len=16) :: flux_treatment=
'strict'
18 double precision :: max_flux_imbalance=0.1d0
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.
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
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
91 unit_magneticfield,qxc1,qxc2,alpha_filename)
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
102 if(
present(alpha_filename))
then
104 trim(alpha_filename),unit_length,unit_magneticfield)
111 character(len=*),
intent(in) :: filename
112 double precision,
intent(in) :: unit_length,unit_magneticfield
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)
118 subroutine read_external_alpha_product(filename,unit_length,unit_magneticfield)
123 use,
intrinsic :: ieee_arithmetic, only: ieee_is_finite
125 character(len=*),
intent(in) :: filename
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
134 character(len=32),
parameter :: expected_magic=
'AMRVAC_EXTERNAL_ALPHA_V1'
136 if(len_trim(filename)>len(external_alpha_filename)) &
137 call mpistop(
'external alpha filename is too long')
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,&
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
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')
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
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)
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))
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)
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))
200 call mpistop(
'external alpha unit_length does not match AMRVAC')
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')
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')
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')
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')
241 external_alpha_filename=trim(filename)
242 external_alpha_unit_length_cm=product_length
243 external_alpha_unit_magneticfield_g=product_bfield
245 write(*,*)
'external alpha product:',trim(filename)
246 write(*,*)
'external alpha support:',count(external_alpha_valid_full/=0)
248 end subroutine read_external_alpha_product
258 use,
intrinsic :: ieee_arithmetic, only: ieee_is_finite
260 integer,
intent(in) :: iw_b(3)
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
274 integer :: iter,stable,log_unit,metrics_unit,flux_status,ic
275 integer :: counts(4),valid_pixels
276 character(len=16) :: polarity_name
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)
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')
293 call extract_boundary_core(bcore)
297 call balance_normal_field(bcore(:,:,3),config%flux_treatment,&
298 config%max_flux_imbalance,flux_before,flux_after,mean_correction,&
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')
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)
314 config%bz_taper_full,alpha0,valid_pixels,alpha_raw)
316 where(alpha0/=0.d0) alpha_weight=1.d0
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))>&
324 if(trim(adjustl(config%alpha_source))==
'external')
then
325 alpha_mask=alpha_mask .and. config%polarity*bcore(:,:,3)>0.d0
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)
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,&
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))
347 energy_old=gr_energy(b,dx1,dx2,dx3)
348 result%initial_energy=energy_old
354 if(config%polarity>0)
then
355 polarity_name=
'positive'
357 polarity_name=
'negative'
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')
363 write(log_unit,
'(a)')
'iteration,energy,rms_field_change,'//&
364 'relative_energy_change,theta_j_deg,epsilon_div,closed,open,weak,max_steps'
366 open(newunit=metrics_unit,file=trim(
base_filename)//
'_nlfff_metrics.csv',&
367 status=
'replace',action=
'write')
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)
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')
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)
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)
403 if(mod(iter,config%log_interval)==0 .or. iter==1)
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)
416 if(rms_change<=config%field_change_tolerance .and. &
417 energy_change<=config%energy_change_tolerance)
then
422 if(stable>=config%convergence_streak)
then
423 result%converged=.true.
424 result%stop_reason=
'converged'
428 if(.not.result%converged) result%stop_reason=
'max_iterations'
430 if(log_open)
close(log_unit)
433 call scatter_dense_field(b,iw_b)
435 deallocate(bcore,alpha0,alpha_raw,alpha_weight,alpha_polarity,alpha_mask,&
436 potential,b,bnew,alpha,current,bc)
439 subroutine extrapolate_self_consistent(iw_b,config,result)
446 use,
intrinsic :: ieee_arithmetic, only: ieee_is_finite
448 integer,
intent(in) :: iw_b(3)
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
464 integer :: cycle,stable,flux_status,valid_pixels,log_unit,metrics_unit
465 integer :: total_iterations
466 integer :: counts_positive(4),counts_negative(4)
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')
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,&
484 if(flux_status/=0 .and. flux_status/=1) &
485 call mpistop(
'Grad-Rubin bottom normal field failed flux-balance policy')
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
497 config%bz_taper_full,alpha0,valid_pixels,alpha_raw)
498 valid_mask=dabs(bcore(:,:,3))/max(maxval(dabs(bcore(:,:,3))),tiny(1.d0))>&
501 where(alpha0/=0.d0) alpha_weight=1.d0
503 where(bcore(:,:,3)>0.d0) alpha_polarity=1
504 where(bcore(:,:,3)<0.d0) alpha_polarity=-1
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)
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)
523 log_open=.false.; log_unit=-1; metrics_unit=-1
525 if(config%write_detailed_history)
then
527 '_grad_rubin_self_consistent.csv',status=
'replace',action=
'write')
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'
536 open(newunit=metrics_unit,file=trim(
base_filename)//
'_nlfff_metrics.csv',&
537 status=
'replace',action=
'write')
543 polarity_config=config
544 polarity_config%self_consistency_cycles=0
545 do cycle=1,config%self_consistency_cycles
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
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')
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)
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)
595 if(pn_difference<=config%self_consistency_field_tolerance .and. &
596 alpha_change<=config%self_consistency_alpha_tolerance)
then
601 if(stable>=config%self_consistency_streak)
then
602 result%converged=.true.
603 result%stop_reason=
'self_consistent'
607 if(.not.result%converged) result%stop_reason=
'self_consistency_cycles'
609 if(log_open)
close(log_unit)
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)
621 call field_diagnostics(bfinal,dx1,dx2,dx3,result%current_weighted_theta,&
623 call scatter_dense_field(bfinal,iw_b)
625 deallocate(bcore,alpha0,alpha_old,alpha_raw,alpha_weight,alpha_polarity,&
626 valid_mask,alpha_mask,alpha_positive,alpha_negative,potential,bpositive,&
628 end subroutine extrapolate_self_consistent
630 subroutine solve_polarity(potential,bz0,alpha0,config,b,alpha,result,counts)
633 use,
intrinsic :: ieee_arithmetic, only: ieee_is_finite
635 double precision,
intent(in) :: potential(:,:,:,:),bz0(:,:),alpha0(:,:)
637 double precision,
intent(out) :: b(:,:,:,:),alpha(:,:,:)
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
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))
650 energy_old=gr_energy(b,
dx(1,1),
dx(2,1),
dx(3,1))
651 result%initial_energy=energy_old
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)
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)
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
679 if(stable>=config%convergence_streak)
then
680 result%converged=.true.
681 result%stop_reason=
'converged'
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
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)
701 subroutine validate_configuration(iw_b,config)
705 use,
intrinsic :: ieee_arithmetic, only: ieee_is_finite
707 integer,
intent(in) :: iw_b(3)
710 if(.not.
allocated(
boundary_b))
call mpistop(
'Grad-Rubin boundary is not initialized')
712 call mpistop(
'Grad-Rubin boundary contains non-finite values')
714 call mpistop(
'Grad-Rubin v1 requires Cartesian 3D')
716 call mpistop(
'Grad-Rubin v1 requires a single uniform level')
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')
725 if(.not.
allocated(external_alpha_full)) &
726 call mpistop(
'alpha_source=external requires an external alpha product')
728 call mpistop(
'Grad-Rubin alpha_source must be vector_magnetogram or external')
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')
771 call mpistop(
'Grad-Rubin flux_treatment must be strict or subtract_mean')
773 end subroutine validate_configuration
775 subroutine extract_external_alpha_core(alpha_raw,alpha_clean,alpha_weight,&
776 alpha_mask,alpha_polarity)
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
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
794 err=max(err,dabs(external_alpha_x(s+i-1)-&
795 (xprobmin1+(dble(i)-0.5d0)*
dx(1,1))))
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
807 err=max(err,dabs(external_alpha_y(s+j-1)-&
808 (xprobmin2+(dble(j)-0.5d0)*
dx(2,1))))
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
823 subroutine write_alpha_diagnostics(base,polarity,source,alpha_raw,alpha_selected,&
824 alpha_weight,alpha_mask,alpha_polarity)
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
838 polarity_name=
'positive'
840 polarity_name=
'negative'
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)
854 end subroutine write_alpha_diagnostics
856 subroutine extract_boundary_core(core)
861 double precision,
allocatable,
intent(out) :: core(:,:,:)
862 double precision :: err,tol
863 integer :: i,j,i0,j0,s
866 tol=1.
d-8*max(1.d0,dabs(xprobmin1),dabs(xprobmax1),
dx(1,1))
870 err=max(err,dabs(
xa1(s+i-1)-(xprobmin1+(dble(i)-0.5d0)*
dx(1,1))))
878 tol=1.
d-8*max(1.d0,dabs(xprobmin2),dabs(xprobmax2),
dx(2,1))
882 err=max(err,dabs(
xa2(s+j-1)-(xprobmin2+(dble(j)-0.5d0)*
dx(2,1))))
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
894 subroutine balance_normal_field(bz,treatment,max_imbalance,before,after,&
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
903 unsigned=sum(dabs(bz))
904 before=dabs(sum(bz))/max(unsigned,tiny(1.d0))
908 select case(trim(adjustl(treatment)))
910 if(before>1.d-8) status=2
911 case(
'subtract_mean')
912 if(before>max_imbalance)
then
914 else if(before>1.d-8)
then
915 correction=sum(bz)/dble(
size(bz))
917 after=dabs(sum(bz))/max(sum(dabs(bz)),tiny(1.d0))
923 end subroutine balance_normal_field
926 double precision,
intent(in) :: r,zero_threshold,full_threshold
927 double precision :: pi,s
930 if(r<=zero_threshold)
then
932 else if(r>=full_threshold)
then
935 s=(r-zero_threshold)/(full_threshold-zero_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
952 bzmax=maxval(dabs(b(:,:,3)))
954 if(
present(alpha_raw)) alpha_raw=0.d0
956 if(bzmax<=0.d0)
return
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
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
972 pure double precision function derivative_1d(f,i,h)
973 double precision,
intent(in) :: f(:),h
974 integer,
intent(in) :: i
981 derivative_1d=(-3.d0*f(1)+4.d0*f(2)-f(3))/(2.d0*h)
983 derivative_1d=(3.d0*f(n)-4.d0*f(n-1)+f(n-2))/(2.d0*h)
985 derivative_1d=(f(i+1)-f(i-1))/(2.d0*h)
987 end function derivative_1d
989 subroutine transport_alpha_mpi(b,alpha0,bz0,config,alpha,global_counts)
993 double precision,
intent(in) :: b(:,:,:,:),alpha0(:,:),bz0(:,:)
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
1005 allocate(recvcounts(
npe),displs(
npe))
1007 displs(rank+1)=nseed*rank/
npe
1008 recvcounts(rank+1)=nseed*(rank+1)/
npe-displs(rank+1)
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))
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)
1030 local_counts(1)=local_counts(1)+1
1032 xprobmin1,xprobmin2,
dx(1,1),
dx(2,1))
1034 xprobmin1,xprobmin2,
dx(1,1),
dx(2,1))
1036 if(config%polarity*bzf>0.d0) chosen=1
1037 if(config%polarity*bzb>0.d0) chosen=2
1040 xprobmin1,xprobmin2,
dx(1,1),
dx(2,1))
1041 local_alpha(local_index)=
value
1042 else if(chosen==2)
then
1044 xprobmin1,xprobmin2,
dx(1,1),
dx(2,1))
1045 local_alpha(local_index)=
value
1048 local_counts(4)=local_counts(4)+1
1050 local_counts(3)=local_counts(3)+1
1052 local_counts(2)=local_counts(2)+1
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
1062 subroutine trace_to_boundary(b,start,direction,step,max_steps,bfloor,endpoint,status)
1064 xprobmax3,
dx,xprobmin3
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
1074 z0=xprobmin3-0.5d0*
dx(3,1)
1077 call field_direction(b,x,direction,bfloor,k1,ok)
1083 if(point_is_open(x+0.5d0*step*k1))
then
1084 endpoint=x+0.5d0*step*k1
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)
1095 call field_direction(b,x+0.5d0*step*k1,direction,bfloor,k2,ok)
1101 if(point_is_open(x+0.5d0*step*k2))
then
1102 endpoint=x+0.5d0*step*k2
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)
1113 call field_direction(b,x+0.5d0*step*k2,direction,bfloor,k3,ok)
1119 if(point_is_open(x+step*k3))
then
1124 if(x(3)+step*k3(3)<=z0)
then
1125 t=(z0-x(3))/(step*k3(3))
1126 endpoint=x+t*(step*k3)
1131 call field_direction(b,x+step*k3,direction,bfloor,k4,ok)
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)
1145 if(xnew(1)<xprobmin1 .or. xnew(1)>xprobmax1 .or. &
1146 xnew(2)<xprobmin2 .or. xnew(2)>xprobmax2 .or. xnew(3)>xprobmax3)
then
1155 end subroutine trace_to_boundary
1157 logical function point_is_open(x)
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
1165 subroutine field_direction(b,x,direction,bfloor,bhat,ok)
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
1173 z0=xprobmin3-0.5d0*
dx(3,1)
1179 norm=dsqrt(dot_product(
value,
value))
1182 bhat=direction*
value/norm
1186 end subroutine field_direction
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
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,:)))
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
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)
1223 ty*((1.d0-tx)*field(i0,j1)+tx*field(i1,j1))
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
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)
1247 mode=i-1;
if(mode>npx/2) mode=mode-npx
1248 kx(i)=2.d0*dpi*dble(mode)/(dble(npx)*dx1)
1251 mode=j-1;
if(mode>npy/2) mode=mode-npy
1252 ky(j)=2.d0*dpi*dble(mode)/(dble(npy)*dx2)
1254 do k=1,nz; z(k)=dble(k-1)*dx3;
end do
1260 plane(ip0:ip0+nx-1,jp0:jp0+ny-1)=&
1261 dcmplx(current(:,:,k,ic),0.d0)
1262 call fft_2d(plane,.false.)
1267 call mpi_allreduce(fj,fb,
size(fj),mpi_double_complex,mpi_sum,
icomm,
ierrmpi)
1276 q=dsqrt(kx(i)**2+ky(j)**2)
1281 ia=ia+0.5d0*dx3*(fj(i,j,s,:)+fj(i,j,s+1,:))
1286 fb(i,j,k,3)=(0.d0,0.d0)
1288 ia=(0.d0,0.d0); ib=(0.d0,0.d0); im=(0.d0,0.d0)
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,:))
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,:))
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,:))
1305 sumz=ia(3)+ib(3)+im(3)
1306 diffx=ia(1)-ib(1)+im(1)
1307 diffy=ia(2)-ib(2)+im(2)
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)
1321 call mpi_allreduce(fb,fj,
size(fb),mpi_double_complex,mpi_sum,
icomm,
ierrmpi)
1329 call fft_2d(plane,.true.)
1330 bc(:,:,k,ic)=dble(plane(ip0:ip0+nx-1,jp0:jp0+ny-1))
1334 allocate(bc_global(nx,ny,nz,3))
1335 call mpi_allreduce(bc,bc_global,
size(bc),mpi_double_precision,mpi_sum,&
1338 deallocate(bc_global)
1341 deallocate(fj,fb,plane,kx,ky,z)
1344 subroutine scatter_dense_field(b,iw_b)
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
1351 do ig3=1,domain_nx3/block_nx3
1352 do ig2=1,domain_nx2/block_nx2
1353 do ig1=1,domain_nx1/block_nx1
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
1360 ps(igrid)%w(ixmlo1+i-1,ixmlo2+j-1,ixmlo3+k-1,iw_b(ic))=&
1369 end subroutine scatter_dense_field
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
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
1384 sum_cross=0.d0; sum_j=0.d0; sum_div2=0.d0; sum_b2=0.d0
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)
1393 jvec=(/db(3,2)-db(2,3),db(1,3)-db(3,1),db(2,1)-db(1,2)/)
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
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
1413 theta_deg=dasin(min(1.d0,sum_cross/sum_j))*180.d0/pi
1415 epsilon_div=h*dsqrt(sum_div2/max(sum_b2,tiny(1.d0)))
1416 end subroutine field_diagnostics
1418 double precision function estimate_memory_mb(padding)
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
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)*&
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))))+&
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)
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
1447 double precision function estimate_self_consistent_memory_mb(padding)
1449 integer,
intent(in) :: padding
1450 double precision :: n,nxy,extra_bytes
1452 n=dble(domain_nx1)*dble(domain_nx2)*dble(domain_nx3+1)
1453 nxy=dble(domain_nx1)*dble(domain_nx2)
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
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...
subroutine, public fft_2d(data, inverse)
In-place complex two-dimensional FFT. The inverse is normalized.
Module with basic grid data structures.
type(tree_node_ptr), dimension(:^d &), allocatable, save tree_root
Pointers to the coarse grid.
Module with geometry-related routines (e.g., divergence, curl)
integer, parameter cartesian
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...
subroutine init_b_fff_data_driven_boundary(boundaryname, qlunit, qbunit, qxc1, qxc2, bvector)
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 ...
double precision, dimension(:), allocatable, save xa2
double precision, dimension(:), allocatable, save xa1
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