MPI-AMRVAC 3.2
The MPI - Adaptive Mesh Refinement - Versatile Advection Code (development version)
Loading...
Searching...
No Matches
mod_lfff.t
Go to the documentation of this file.
1!> Program to extrapolate linear force-free fields in 3D Cartesian coordinates,
2!> based on exact Green function method (Chiu & Hilton 1977 ApJ 212,873).
3!>
4!> Usage:
5!> 1 In the subroutine usr_set_parameters of mod_usr.t:
6!> To extrapolate a linear force free field from a observed magnetogram
7!> prepared in a data file, e.g., 'hmiM720sxxxx.dat' replace
8!> call init_bc_fff_data('hmiM720sxxxx.dat',unit_length,unit_magneticfield)
9!> 'hmiM720sxxxx.dat' must be a binary file containing nx1,nx2,xc1,xc2,dxm1,
10!> dxm2, Bz0(nx1,nx2). Integers nx1 and nx2 give the resolution of the
11!> uniform-grid magentogram. Others are double-precision floats. xc1 and xc2
12!> are coordinates of the central point of the magnetogram. dxm1 and dxm2
13!> are the cell sizes for each direction, Bz0 is the vertical conponent
14!> of magetic field on the solar surface from observations.
15!>2 In the subroutine usr_init_one_grid of mod_usr.t,
16!> add lines like:
17!>
18!> double precision :: Bf(ixG^S,1:ndir), alpha, zshift
19!>
20!> alpha=0.d0 ! potential field
21!> !alpha=0.08d0 ! non-potential linear force-free field
22!> zshift=0.05d0 ! lift your box zshift heigher to the bottom magnetogram
23!> call calc_lin_fff(ixG^L,ix^L,Bf,x,alpha,zshift)
24!>
25!>3 Notice that the resolution of input magnetogram must be better than the best
26!> resolution of your AMR grid to have a good behavior close to the bottom layer
28 implicit none
29
30 double precision, save :: bzmax,darea
31 double precision, allocatable, save :: bz0(:,:)
32 double precision, allocatable, save :: xa1(:),xa2(:)
33 integer, save :: nx1,nx2
34 double precision, parameter :: lfff_mode_tolerance=1.d-12
35 double precision, parameter :: lfff_resonance_tolerance=1.d-10
36 double precision, parameter :: lfff_flux_balance_tolerance=1.d-8
37{^iftwod
38 double precision, allocatable, save :: l2d_br(:),l2d_bi(:),l2d_k(:)
39 double precision, save :: l2d_b0,l2d_alpha,l2d_y0,l2d_ytop
40 integer, save :: l2d_nm
41 logical, save :: l2d_closed,l2d_ready=.false.
42}
43
44contains
45
46 subroutine init_b_fff_data(magnetogramname,qLunit,qBunit)
48 use mod_comm_lib, only: mpistop
49
50 double precision, intent(in) :: qLunit,qBunit
51 double precision :: xc1,xc2,dxm1,dxm2
52 integer, dimension(MPI_STATUS_SIZE) :: statuss
53 integer :: file_handle,i
54 logical :: aexist
55 character(len=*), intent(in) :: magnetogramname
56 ! nx1,nx2 are numbers of cells for each direction
57 ! xc1,xc2 are coordinates of the central point of the magnetogram
58 ! dxm1,dxm2 are cell sizes for each direction
59 ! Bz0 is the 2D Bz magnetogram
60 inquire(file=magnetogramname,exist=aexist)
61 if(.not. aexist) then
62 if(mype==0) write(*,'(2a)') "can not find file:",magnetogramname
63 call mpistop("no input magnetogram----init_b_fff_data")
64 end if
65 call mpi_file_open(icomm,magnetogramname,mpi_mode_rdonly,mpi_info_null,&
66 file_handle,ierrmpi)
67 call mpi_file_read_all(file_handle,nx1,1,mpi_integer,statuss,ierrmpi)
68 call mpi_file_read_all(file_handle,nx2,1,mpi_integer,statuss,ierrmpi)
69 allocate(bz0(nx1,nx2))
70 call mpi_file_read_all(file_handle,xc1,1,mpi_double_precision,statuss,ierrmpi)
71 call mpi_file_read_all(file_handle,xc2,1,mpi_double_precision,statuss,ierrmpi)
72 call mpi_file_read_all(file_handle,dxm1,1,mpi_double_precision,statuss,ierrmpi)
73 call mpi_file_read_all(file_handle,dxm2,1,mpi_double_precision,statuss,ierrmpi)
74 call mpi_file_read_all(file_handle,bz0,nx1*nx2,mpi_double_precision,&
75 statuss,ierrmpi)
76 call mpi_file_close(file_handle,ierrmpi)
77 allocate(xa1(nx1))
78 allocate(xa2(nx2))
79 do i=1,nx1
80 xa1(i) = xc1 + (dble(i) - dble(nx1)/2.d0 - 0.5d0)*dxm1
81 enddo
82 do i=1,nx2
83 xa2(i) = xc2 + (dble(i) - dble(nx2)/2.d0 - 0.5d0)*dxm2
84 enddo
85 ! declare and define global variables Lunit and Bunit to be your length unit in
86 ! cm and magnetic strength unit in Gauss first
87 dxm1=dxm1/qlunit
88 dxm2=dxm2/qlunit
89 xa1=xa1/qlunit
90 xa2=xa2/qlunit
91 darea=dxm1*dxm2
92 bz0=bz0/qbunit
93 bzmax=maxval(dabs(bz0(:,:)))
94
95 ! normalize b
97 if(mype==0) then
98 print*,'magnetogram xrange:',minval(xa1),maxval(xa1)
99 print*,'magnetogram yrange:',minval(xa2),maxval(xa2)
100 end if
101
102 if(mype==0) then
103 print*,'extrapolating 3D force-free field from an observed Bz '
104 print*,'magnetogram of',nx1,'by',nx2,'pixels. Bzmax=',bzmax
105 endif
106
107 end subroutine init_b_fff_data
108
109{^ifthreed
110 subroutine init_b_fff_data_driven_boundary(boundaryname,qLunit,qBunit,qxc1,qxc2,bvector)
112 use mod_comm_lib, only: mpistop
114 use, intrinsic :: ieee_arithmetic, only: ieee_is_finite
115
116 character(len=*), intent(in) :: boundaryname
117 double precision, intent(in) :: qLunit,qBunit
118 double precision, intent(in), optional :: qxc1,qxc2
119 !> Optional unmodified vector magnetogram in AMRVAC magnetic-field units.
120 double precision, allocatable, intent(out), optional :: bvector(:,:,:)
121
122 double precision :: snapshot_time,dxm1,dxm2,xc1,xc2
123 double precision, allocatable :: bframe(:,:,:)
124 integer :: i,bnx,bny
125
126 if(.not.ieee_is_finite(qlunit) .or. .not.ieee_is_finite(qbunit) .or. &
127 qlunit<=0.d0 .or. qbunit<=0.d0) &
128 call mpistop('data-driven boundary units must be positive')
129 call read_data_driven_boundary_frame(boundaryname,snapshot_time,bnx,bny,dxm1,dxm2,bframe)
130 if(bnx<1 .or. bny<1 .or. .not.ieee_is_finite(snapshot_time) .or. &
131 .not.ieee_is_finite(dxm1) .or. &
132 .not.ieee_is_finite(dxm2) .or. dxm1<=0.d0 .or. dxm2<=0.d0) &
133 call mpistop('invalid data-driven boundary geometry')
134 if(.not.all(ieee_is_finite(bframe))) &
135 call mpistop('non-finite magnetic value in data-driven boundary frame')
136
137 nx1 = bnx
138 nx2 = bny
139 if(allocated(bz0)) deallocate(bz0)
140 if(allocated(xa1)) deallocate(xa1)
141 if(allocated(xa2)) deallocate(xa2)
142 allocate(bz0(nx1,nx2))
143 allocate(xa1(nx1))
144 allocate(xa2(nx2))
145
146 ! Python V1 stores dx/dy in km and B in Gauss.
147 dxm1 = dxm1*1.d5
148 dxm2 = dxm2*1.d5
149 if(present(qxc1)) then
150 xc1 = qxc1
151 else
152 xc1 = 0.5d0*(xprobmin1+xprobmax1)*qlunit
153 end if
154 if(present(qxc2)) then
155 xc2 = qxc2
156 else
157 xc2 = 0.5d0*(xprobmin2+xprobmax2)*qlunit
158 end if
159 if(.not.ieee_is_finite(xc1) .or. .not.ieee_is_finite(xc2)) &
160 call mpistop('non-finite data-driven boundary center')
161
162 bz0(:,:) = bframe(:,:,3)
163 if(present(bvector)) then
164 allocate(bvector(nx1,nx2,3))
165 bvector = bframe/qbunit
166 end if
167 deallocate(bframe)
168
169 do i=1,nx1
170 xa1(i) = xc1 + (dble(i) - dble(nx1)/2.d0 - 0.5d0)*dxm1
171 end do
172 do i=1,nx2
173 xa2(i) = xc2 + (dble(i) - dble(nx2)/2.d0 - 0.5d0)*dxm2
174 end do
175
176 dxm1 = dxm1/qlunit
177 dxm2 = dxm2/qlunit
178 xa1 = xa1/qlunit
179 xa2 = xa2/qlunit
180 darea = dxm1*dxm2
181 bz0 = bz0/qbunit
182 bzmax = maxval(dabs(bz0(:,:)))
183 if(bzmax<=0.d0) call mpistop('zero Bz in data-driven boundary frame')
184 bz0 = bz0/bzmax
185
186 if(mype==0) then
187 print*,'data-driven boundary frame:',trim(boundaryname)
188 print*,'snapshot_time [s]:',snapshot_time
189 print*,'magnetogram xrange:',minval(xa1),maxval(xa1)
190 print*,'magnetogram yrange:',minval(xa2),maxval(xa2)
191 print*,'extrapolating potential field from Bz of',nx1,'by',nx2,'pixels. Bzmax=',bzmax
192 end if
194}
195
196 !> Check a constant-alpha magnetogram and optionally remove its core mean.
197 !> Status: 0 accepted unchanged, 1 mean removed, 2 invalid treatment,
198 !> 3 invalid threshold, 4 strict-mode imbalance, 5 above auto-balance limit.
199 subroutine lfff_balance_bottom_flux(bz,treatment,max_imbalance,&
200 imbalance_before,imbalance_after,mean_correction,status)
201 double precision, intent(inout) :: bz(:,:)
202 character(len=*), intent(in) :: treatment
203 double precision, intent(in) :: max_imbalance
204 double precision, intent(out) :: imbalance_before,imbalance_after
205 double precision, intent(out) :: mean_correction
206 integer, intent(out) :: status
207
208 double precision :: unsigned_flux
209
210 mean_correction=0.d0
211 unsigned_flux=sum(dabs(bz))
212 imbalance_before=dabs(sum(bz))/max(unsigned_flux,tiny(1.d0))
213 imbalance_after=imbalance_before
214 status=0
215
216 if(max_imbalance<0.d0 .or. max_imbalance>1.d0) then
217 status=3
218 return
219 end if
220
221 select case(trim(adjustl(treatment)))
222 case('strict')
223 if(imbalance_before>lfff_flux_balance_tolerance) status=4
224 case('subtract_mean')
225 if(imbalance_before>max_imbalance) then
226 status=5
227 return
228 end if
229 if(imbalance_before>lfff_flux_balance_tolerance) then
230 mean_correction=sum(bz)/dble(size(bz))
231 bz=bz-mean_correction
232 unsigned_flux=sum(dabs(bz))
233 imbalance_after=dabs(sum(bz))/max(unsigned_flux,tiny(1.d0))
234 status=1
235 end if
236 case default
237 status=2
238 end select
239 end subroutine lfff_balance_bottom_flux
240
241{^ifthreed
242 !> Construct an open-half-space potential field on one dense Cartesian grid.
243 !> Plane 1 is the magnetogram plane; planes 2:nz+1 are cell centres one,
244 !> two, ... grid spacings above it. The input Bz is already in AMRVAC units.
245 subroutine extrapolate_potential_fft_dense(bz,dx1,dx2,dx3,padding_factor,b)
246 use mod_comm_lib, only: mpistop
248 use mod_global_parameters, only: domain_nx1,domain_nx2,domain_nx3,dpi
249
250 double precision, intent(in) :: bz(:,:),dx1,dx2,dx3
251 integer, intent(in) :: padding_factor
252 double precision, allocatable, intent(out) :: b(:,:,:,:)
253
254 double precision, allocatable :: sr(:,:),si(:,:),wr(:,:),wi(:,:)
255 double precision, allocatable :: kx(:),ky(:)
256 double precision :: k2,q,z,fac,kxd,kyd
257 integer :: nx,ny,nz,npx,npy,ip0,jp0,i,j,k,ic,mode
258
259 nx=size(bz,1)
260 ny=size(bz,2)
261 nz=domain_nx3
262 if(nx/=domain_nx1 .or. ny/=domain_nx2) &
263 call mpistop('dense potential Bz does not match the physical grid')
264 if(padding_factor<1) call mpistop('dense potential padding must be positive')
265 npx=padding_factor*nx
266 npy=padding_factor*ny
267 if(.not.fft_size_supported(npx) .or. .not.fft_size_supported(npy)) &
268 call mpistop('dense potential padded FFT size is unsupported')
269
270 allocate(b(nx,ny,nz+1,3))
271 allocate(sr(npx,npy),si(npx,npy),wr(npx,npy),wi(npx,npy))
272 allocate(kx(npx),ky(npy))
273 ip0=(npx-nx)/2+1
274 jp0=(npy-ny)/2+1
275 sr=0.d0
276 si=0.d0
277 sr(ip0:ip0+nx-1,jp0:jp0+ny-1)=bz
278 call fft_2d_real_imag(sr,si,.false.)
279
280 do i=1,npx
281 mode=i-1
282 if(mode>npx/2) mode=mode-npx
283 kx(i)=2.d0*dpi*dble(mode)/(dble(npx)*dx1)
284 end do
285 do j=1,npy
286 mode=j-1
287 if(mode>npy/2) mode=mode-npy
288 ky(j)=2.d0*dpi*dble(mode)/(dble(npy)*dx2)
289 end do
290
291 do k=1,nz+1
292 z=dble(k-1)*dx3
293 do ic=1,3
294 do j=1,npy
295 do i=1,npx
296 k2=kx(i)**2+ky(j)**2
297 if(k2<=0.d0) then
298 if(ic==3) then
299 wr(i,j)=sr(i,j)
300 wi(i,j)=si(i,j)
301 else
302 wr(i,j)=0.d0
303 wi(i,j)=0.d0
304 end if
305 else
306 q=dsqrt(k2)
307 kxd=kx(i)
308 kyd=ky(j)
309 if(mod(npx,2)==0 .and. i==npx/2+1) kxd=0.d0
310 if(mod(npy,2)==0 .and. j==npy/2+1) kyd=0.d0
311 select case(ic)
312 case(1)
313 fac=kxd/q*dexp(-q*z)
314 wr(i,j)=fac*si(i,j)
315 wi(i,j)=-fac*sr(i,j)
316 case(2)
317 fac=kyd/q*dexp(-q*z)
318 wr(i,j)=fac*si(i,j)
319 wi(i,j)=-fac*sr(i,j)
320 case(3)
321 fac=dexp(-q*z)
322 wr(i,j)=fac*sr(i,j)
323 wi(i,j)=fac*si(i,j)
324 end select
325 end if
326 end do
327 end do
328 call fft_2d_real_imag(wr,wi,.true.)
329 b(:,:,k,ic)=wr(ip0:ip0+nx-1,jp0:jp0+ny-1)
330 end do
331 end do
332
333 ! Preserve the prescribed normal field exactly at the magnetogram plane.
334 b(:,:,1,3)=bz
335 deallocate(sr,si,wr,wi,kx,ky)
337
338 !> Extrapolate a Cartesian potential field with horizontal Fourier modes.
339 !> The bottom magnetogram must match the level-one physical cell centers.
340 !> Results are streamed one AMRVAC block layer at a time and written directly
341 !> to the distributed cell-centered magnetic variables.
342 subroutine extrapolate_potential_fft(iw_b,padding_factor,source_plane_depth,&
343 alpha,top_boundary,flux_treatment,max_flux_imbalance)
344 use mpi
346 use mod_comm_lib, only: mpistop
349 use mod_forest, only: tree_root
350 use mod_geometry
351
352 integer, intent(in) :: iw_b(3)
353 integer, intent(in), optional :: padding_factor
354 double precision, intent(in), optional :: source_plane_depth
355 double precision, intent(in), optional :: alpha
356 character(len=*), intent(in), optional :: top_boundary
357 character(len=*), intent(in), optional :: flux_treatment
358 double precision, intent(in), optional :: max_flux_imbalance
359
360 double precision, allocatable :: bcore(:,:),spec_r(:,:),spec_i(:,:)
361 double precision, allocatable :: work_r(:,:),work_i(:,:),bplane(:,:,:)
362 double precision, allocatable :: sendbuf(:),recvbuf(:),kx(:),ky(:)
363 integer, allocatable :: sendcounts(:),recvcounts(:),sdispls(:),rdispls(:)
364 integer, allocatable :: cursor(:),blockpos(:,:)
365 double precision :: dx1,dx2,dx3,tol1,tol2,maxerr,z,fac,source_depth
366 double precision :: bmean,bflux,tstart,memory_mb,alpha_fft,alpha2,k2
367 double precision :: beta,transfer_b,transfer_d,top_height
368 double precision :: kx_der,ky_der,kmin,flux_imbalance,unsigned_flux
369 double precision :: flux_imbalance_before,bmean_before,bflux_before
370 double precision :: mean_correction,max_imbalance
371 double precision, parameter :: fft_memory_limit_mb=2048.d0
372 integer :: pad,npx,npy,ip0,jp0,ix0,iy0,starti
373 integer :: nb1,nb2,nb3,layer,layer_owner,kg,klocal
374 integer :: ig1,ig2,ipe,igrid,ic,ix1,ix2,pos,base,payload,slice_size
375 integer :: mode,next1,next2,nrecv,nsend,mode_status,flux_status
376 character(len=16) :: top_mode,flux_mode
377 character(len=256) :: message
378 logical :: top_closed,alpha_nonzero
379
380 if(ndim/=3) call mpistop('FFT potential field requires three dimensions')
381 if(coordinate/=cartesian) call mpistop('FFT potential field requires Cartesian coordinates')
382 if(any(stretched_dim)) call mpistop('FFT potential field requires a uniform mesh')
383 if(refine_max_level/=1 .or. levmax/=1) &
384 call mpistop('FFT potential field v1 requires refine_max_level=1')
385 if(stagger_grid) call mpistop('FFT potential field v1 does not support stagger_grid')
386 if(.not.allocated(bz0) .or. .not.allocated(xa1) .or. .not.allocated(xa2)) &
387 call mpistop('FFT potential field requires initialized magnetogram data')
388
389 pad=2
390 if(present(padding_factor)) pad=padding_factor
391 if(pad<1) call mpistop('FFT padding factor must be at least one')
392
393 source_depth=0.d0
394 if(present(source_plane_depth)) source_depth=source_plane_depth
395 if(source_depth<0.d0) call mpistop('FFT source-plane depth must not be negative')
396
397 alpha_fft=0.d0
398 if(present(alpha)) alpha_fft=alpha
399 alpha2=alpha_fft**2
400 alpha_nonzero=(alpha_fft/=0.d0)
401
402 top_mode='open'
403 if(present(top_boundary)) top_mode=trim(adjustl(top_boundary))
404 select case(trim(top_mode))
405 case('open')
406 top_closed=.false.
407 case('closed')
408 top_closed=.true.
409 case default
410 call mpistop("FFT top boundary must be 'open' or 'closed'")
411 end select
412
413 flux_mode='strict'
414 if(present(flux_treatment)) flux_mode=trim(adjustl(flux_treatment))
415 select case(trim(flux_mode))
416 case('strict','subtract_mean')
417 continue
418 case default
419 call mpistop("LFFF flux treatment must be 'strict' or 'subtract_mean'")
420 end select
421 max_imbalance=0.1d0
422 if(present(max_flux_imbalance)) max_imbalance=max_flux_imbalance
423 if(max_imbalance<0.d0 .or. max_imbalance>1.d0) &
424 call mpistop('LFFF maximum flux imbalance must be between zero and one')
425
426 dx1=dx(1,1)
427 dx2=dx(2,1)
428 dx3=dx(3,1)
429 top_height=source_depth+dble(domain_nx3)*dx3
430 tol1=1.d-8*max(1.d0,dabs(xprobmin1),dabs(xprobmax1),dabs(dx1))
431 tol2=1.d-8*max(1.d0,dabs(xprobmin2),dabs(xprobmax2),dabs(dx2))
432
433 ! Locate, by coordinates, the physical magnetogram core. This naturally
434 ! removes any symmetric ghost layers without assuming their width.
435 ix0=0
436 if(nx1>=domain_nx1) then
437 do starti=1,nx1-domain_nx1+1
438 maxerr=0.d0
439 do ix1=1,domain_nx1
440 maxerr=max(maxerr,dabs(xa1(starti+ix1-1)-&
441 (xprobmin1+(dble(ix1)-0.5d0)*dx1)))
442 end do
443 if(maxerr<=tol1) then
444 ix0=starti
445 exit
446 end if
447 end do
448 end if
449 iy0=0
450 if(nx2>=domain_nx2) then
451 do starti=1,nx2-domain_nx2+1
452 maxerr=0.d0
453 do ix2=1,domain_nx2
454 maxerr=max(maxerr,dabs(xa2(starti+ix2-1)-&
455 (xprobmin2+(dble(ix2)-0.5d0)*dx2)))
456 end do
457 if(maxerr<=tol2) then
458 iy0=starti
459 exit
460 end if
461 end do
462 end if
463 if(ix0==0 .or. iy0==0) then
464 if(mype==0) then
465 write(*,*) 'magnetogram size:',nx1,nx2
466 write(*,*) 'required physical FFT size:',domain_nx1,domain_nx2
467 write(*,*) 'AMRVAC dx/dy:',dx1,dx2
468 end if
469 call mpistop('FFT magnetogram centers do not match the level-one physical grid')
470 end if
471
472 npx=pad*domain_nx1
473 npy=pad*domain_nx2
474 if(.not.fft_size_supported(npx) .or. .not.fft_size_supported(npy)) then
475 next1=fft_next_supported(npx)
476 next2=fft_next_supported(npy)
477 write(message,'(a,i0,a,a,a,i0,a,a,a,i0,a,i0)') &
478 'unsupported padded FFT size ',npx,' (',trim(fft_factorization(npx)),&
479 ') x ',npy,' (',trim(fft_factorization(npy)),&
480 '); next supported sizes are ',next1,' x ',next2
481 call mpistop(trim(message))
482 end if
483
484 if(mod(domain_nx1,block_nx1)/=0 .or. mod(domain_nx2,block_nx2)/=0 .or. &
485 mod(domain_nx3,block_nx3)/=0) &
486 call mpistop('FFT potential field requires domain_nx divisible by block_nx')
487 nb1=domain_nx1/block_nx1
488 nb2=domain_nx2/block_nx2
489 nb3=domain_nx3/block_nx3
490 slice_size=block_nx1*block_nx2*3
491 payload=slice_size*block_nx3
492
493 ! Conservative per-rank upper bound: four padded spectral/work planes,
494 ! core plus three-component physical plane, and one full block layer in
495 ! each MPI send/receive buffer. Refuse an unexpectedly large allocation
496 ! instead of relying on the operating system to terminate a rank.
497 memory_mb=8.d0*(4.d0*dble(npx)*dble(npy)+4.d0*dble(domain_nx1)*&
498 dble(domain_nx2)+6.d0*dble(domain_nx1)*dble(domain_nx2)*&
499 dble(block_nx3))/(1024.d0**2)
500 if(memory_mb>fft_memory_limit_mb) then
501 write(message,'(a,f10.1,a,f10.1,a)') 'FFT potential field needs up to ',&
502 memory_mb,' MiB/rank, above the internal limit of ',&
503 fft_memory_limit_mb,' MiB; reduce block_nx3 or horizontal size'
504 call mpistop(trim(message))
505 end if
506
507 allocate(bcore(domain_nx1,domain_nx2))
508 bcore=bz0(ix0:ix0+domain_nx1-1,iy0:iy0+domain_nx2-1)
509 bmean_before=sum(bcore)/dble(size(bcore))*bzmax
510 bflux_before=sum(bcore)*bzmax*dx1*dx2
511 unsigned_flux=sum(dabs(bcore))
512 flux_imbalance_before=dabs(sum(bcore))/max(unsigned_flux,tiny(1.d0))
513 mean_correction=0.d0
514 flux_status=0
515 if(alpha_nonzero) then
516 call lfff_balance_bottom_flux(bcore,flux_mode,max_imbalance,&
517 flux_imbalance_before,flux_imbalance,mean_correction,flux_status)
518 select case(flux_status)
519 case(0,1)
520 continue
521 case(4)
522 if(mype==0) then
523 write(*,*) 'FFT LFFF alpha:',alpha_fft
524 write(*,*) 'relative bottom flux imbalance:',flux_imbalance_before
525 write(*,*) 'strict balance tolerance:',lfff_flux_balance_tolerance
526 end if
527 call mpistop('constant-alpha FFT LFFF requires a flux-balanced bottom magnetogram')
528 case(5)
529 if(mype==0) then
530 write(*,*) 'FFT LFFF alpha:',alpha_fft
531 write(*,*) 'relative bottom flux imbalance:',flux_imbalance_before
532 write(*,*) 'maximum automatic-balance imbalance:',max_imbalance
533 end if
534 call mpistop('LFFF magnetogram is too unbalanced for automatic mean subtraction')
535 case default
536 call mpistop('invalid LFFF flux-balance configuration')
537 end select
538 else
539 flux_imbalance=flux_imbalance_before
540 end if
541 bmean=sum(bcore)/dble(npx*npy)*bzmax
542 bflux=sum(bcore)*bzmax*dx1*dx2
543 ip0=(npx-domain_nx1)/2+1
544 jp0=(npy-domain_nx2)/2+1
545
546 allocate(spec_r(npx,npy),spec_i(npx,npy),work_r(npx,npy),work_i(npx,npy))
547 allocate(kx(npx),ky(npy))
548 spec_r=0.d0
549 spec_i=0.d0
550 if(mype==0) then
551 spec_r(ip0:ip0+domain_nx1-1,jp0:jp0+domain_nx2-1)=bcore
552 call fft_2d_real_imag(spec_r,spec_i,.false.)
553 end if
554 call mpi_bcast(spec_r,npx*npy,mpi_double_precision,0,icomm,ierrmpi)
555 call mpi_bcast(spec_i,npx*npy,mpi_double_precision,0,icomm,ierrmpi)
556
557 do ix1=1,npx
558 mode=ix1-1
559 if(mode>npx/2) mode=mode-npx
560 kx(ix1)=2.d0*dpi*dble(mode)/(dble(npx)*dx1)
561 end do
562 do ix2=1,npy
563 mode=ix2-1
564 if(mode>npy/2) mode=mode-npy
565 ky(ix2)=2.d0*dpi*dble(mode)/(dble(npy)*dx2)
566 end do
567
568 kmin=min(2.d0*dpi/(dble(npx)*dx1),2.d0*dpi/(dble(npy)*dx2))
569 if(alpha_nonzero .and. .not.top_closed .and. dabs(alpha_fft)>=kmin) then
570 if(mype==0) then
571 write(*,*) 'FFT LFFF alpha:',alpha_fft
572 write(*,*) 'smallest nonzero padded horizontal wavenumber:',kmin
573 end if
574 call mpistop('open-top FFT LFFF requires abs(alpha) < kmin')
575 end if
576
577 ! A finite-height closed LFFF permits oscillatory low-wavenumber modes,
578 ! except at the eigenvalues sin(beta*L)=0 where the two Bz boundary
579 ! conditions are singular. Validate these modes before distributed work.
580 if(alpha_nonzero .and. top_closed) then
581 do ix2=1,npy
582 do ix1=1,npx
583 k2=kx(ix1)**2+ky(ix2)**2
584 if(k2<=0.d0 .or. alpha2<=k2) cycle
585 beta=dsqrt(alpha2-k2)
586 call lfff_fft_transfer(k2,alpha_fft,0.d0,top_height,.true.,&
587 transfer_b,transfer_d,mode_status)
588 if(mode_status==2) then
589 if(mype==0) then
590 write(*,*) 'FFT closed LFFF resonant mode kx,ky:',kx(ix1),ky(ix2)
591 write(*,*) 'alpha, beta, top height:',alpha_fft,beta,top_height
592 end if
593 call mpistop('closed-top FFT LFFF is singular for a horizontal mode')
594 end if
595 end do
596 end do
597 end if
598
599 allocate(sendcounts(0:npe-1),recvcounts(0:npe-1))
600 allocate(sdispls(0:npe-1),rdispls(0:npe-1),cursor(0:npe-1))
601 allocate(blockpos(nb1,nb2))
602 tstart=mpi_wtime()
603
604 if(mype==0) then
605 write(*,*) 'FFT potential field physical size:',domain_nx1,domain_nx2,domain_nx3
606 write(*,*) 'FFT padded size:',npx,npy,' padding factor:',pad
607 write(*,*) 'FFT conservative memory bound [MiB/rank]:',memory_mb
608 write(*,*) 'FFT source plane below lower face:',source_depth
609 write(*,*) 'FFT alpha and top boundary:',alpha_fft,trim(top_mode)
610 if(top_closed) write(*,*) 'FFT closed-top height above source plane:',top_height
611 write(*,*) 'FFT magnetogram core starts at:',ix0,iy0
612 write(*,*) 'FFT input core mean Bz:',bmean_before,' input net bottom flux:',bflux_before
613 if(alpha_nonzero) then
614 write(*,*) 'FFT LFFF flux treatment:',trim(flux_mode)
615 write(*,*) 'FFT input relative bottom flux imbalance:',flux_imbalance_before
616 if(flux_status==1) &
617 write(*,*) 'FFT subtracted core mean Bz:',mean_correction*bzmax
618 write(*,*) 'FFT corrected relative bottom flux imbalance:',flux_imbalance
619 end if
620 write(*,*) 'FFT padded zero-mode mean Bz:',bmean,' net bottom flux:',bflux
621 end if
622
623 do layer=1,nb3
624 layer_owner=mod(layer-1,npe)
625 sendcounts=0
626 recvcounts=0
627 sdispls=0
628 rdispls=0
629 blockpos=0
630
631 if(mype==layer_owner) then
632 do ig2=1,nb2
633 do ig1=1,nb1
634 ipe=tree_root(ig1,ig2,layer)%node%ipe
635 sendcounts(ipe)=sendcounts(ipe)+payload
636 end do
637 end do
638 do ipe=1,npe-1
639 sdispls(ipe)=sdispls(ipe-1)+sendcounts(ipe-1)
640 end do
641 cursor=sdispls
642 do ig2=1,nb2
643 do ig1=1,nb1
644 ipe=tree_root(ig1,ig2,layer)%node%ipe
645 blockpos(ig1,ig2)=cursor(ipe)+1
646 cursor(ipe)=cursor(ipe)+payload
647 end do
648 end do
649 end if
650
651 nrecv=0
652 do ig2=1,nb2
653 do ig1=1,nb1
654 if(tree_root(ig1,ig2,layer)%node%ipe==mype) nrecv=nrecv+payload
655 end do
656 end do
657 recvcounts(layer_owner)=nrecv
658 nsend=sum(sendcounts)
659 allocate(sendbuf(max(1,nsend)),recvbuf(max(1,nrecv)))
660 sendbuf=0.d0
661 recvbuf=0.d0
662
663 if(mype==layer_owner) then
664 allocate(bplane(domain_nx1,domain_nx2,3))
665 do klocal=1,block_nx3
666 kg=(layer-1)*block_nx3+klocal
667 z=(dble(kg)-0.5d0)*dx3+source_depth
668
669 do ic=1,3
670 do ix2=1,npy
671 do ix1=1,npx
672 k2=kx(ix1)**2+ky(ix2)**2
673 kx_der=kx(ix1)
674 ky_der=ky(ix2)
675 ! A first derivative of the self-conjugate Nyquist mode cannot
676 ! be represented by a real grid function. Use the standard
677 ! zero derivative symbol for that one mode in each direction.
678 if(mod(npx,2)==0 .and. ix1==npx/2+1) kx_der=0.d0
679 if(mod(npy,2)==0 .and. ix2==npy/2+1) ky_der=0.d0
680
681 if(k2<=0.d0) then
682 if(alpha_nonzero) then
683 transfer_b=0.d0
684 else
685 ! A potential field retains the padded-domain mean flux.
686 ! For a closed top only the fluctuating modes are closed.
687 transfer_b=1.d0
688 end if
689 transfer_d=0.d0
690 else
691 call lfff_fft_transfer(k2,alpha_fft,z,top_height,&
692 top_closed,transfer_b,transfer_d,mode_status)
693 if(mode_status/=0) &
694 call mpistop('invalid FFT LFFF mode reached distributed solve')
695 end if
696
697 select case(ic)
698 case(1)
699 if(k2>0.d0) then
700 fac=(kx_der*transfer_d-&
701 ky_der*alpha_fft*transfer_b)/k2
702 work_r(ix1,ix2)= fac*spec_i(ix1,ix2)
703 work_i(ix1,ix2)=-fac*spec_r(ix1,ix2)
704 else
705 work_r(ix1,ix2)=0.d0
706 work_i(ix1,ix2)=0.d0
707 end if
708 case(2)
709 if(k2>0.d0) then
710 fac=(ky_der*transfer_d+&
711 kx_der*alpha_fft*transfer_b)/k2
712 work_r(ix1,ix2)= fac*spec_i(ix1,ix2)
713 work_i(ix1,ix2)=-fac*spec_r(ix1,ix2)
714 else
715 work_r(ix1,ix2)=0.d0
716 work_i(ix1,ix2)=0.d0
717 end if
718 case(3)
719 work_r(ix1,ix2)=transfer_b*spec_r(ix1,ix2)
720 work_i(ix1,ix2)=transfer_b*spec_i(ix1,ix2)
721 end select
722 end do
723 end do
724 call fft_2d_real_imag(work_r,work_i,.true.)
725 bplane(:,:,ic)=bzmax*work_r(ip0:ip0+domain_nx1-1,&
726 jp0:jp0+domain_nx2-1)
727 end do
728
729 do ig2=1,nb2
730 do ig1=1,nb1
731 base=blockpos(ig1,ig2)+(klocal-1)*slice_size
732 pos=base
733 do ic=1,3
734 do ix2=1,block_nx2
735 do ix1=1,block_nx1
736 sendbuf(pos)=bplane((ig1-1)*block_nx1+ix1,&
737 (ig2-1)*block_nx2+ix2,ic)
738 pos=pos+1
739 end do
740 end do
741 end do
742 end do
743 end do
744 end do
745 deallocate(bplane)
746 end if
747
748 call mpi_alltoallv(sendbuf,sendcounts,sdispls,mpi_double_precision,&
749 recvbuf,recvcounts,rdispls,mpi_double_precision,icomm,ierrmpi)
750
751 pos=1
752 do ig2=1,nb2
753 do ig1=1,nb1
754 if(tree_root(ig1,ig2,layer)%node%ipe/=mype) cycle
755 igrid=tree_root(ig1,ig2,layer)%node%igrid
756 do klocal=1,block_nx3
757 do ic=1,3
758 do ix2=1,block_nx2
759 do ix1=1,block_nx1
760 ps(igrid)%w(ixmlo1+ix1-1,ixmlo2+ix2-1,&
761 ixmlo3+klocal-1,iw_b(ic))=recvbuf(pos)
762 pos=pos+1
763 end do
764 end do
765 end do
766 end do
767 end do
768 end do
769 deallocate(sendbuf,recvbuf)
770 end do
771
772 if(mype==0) write(*,*) 'FFT potential/LFFF extrapolation took:',mpi_wtime()-tstart,'s'
773 deallocate(bcore,spec_r,spec_i,work_r,work_i,kx,ky)
774 deallocate(sendcounts,recvcounts,sdispls,rdispls,cursor,blockpos)
775 end subroutine extrapolate_potential_fft
776}
777
778 !> Vertical transfer functions for one nonzero horizontal constant-alpha
779 !> Fourier mode. transfer_b multiplies Bz0, while transfer_d=-d(transfer_b)/dz
780 !> enters the horizontal field. status is zero on success, one for an
781 !> oscillatory mode in an open half-space, two for a closed-box resonance,
782 !> and three for invalid geometry.
783 subroutine lfff_fft_transfer(k2,alpha,z,top_height,top_closed,&
784 transfer_b,transfer_d,status)
785 double precision, intent(in) :: k2,alpha,z,top_height
786 logical, intent(in) :: top_closed
787 double precision, intent(out) :: transfer_b,transfer_d
788 integer, intent(out) :: status
789
790 double precision :: q2,q,beta,denominator,scale
791
792 transfer_b=0.d0
793 transfer_d=0.d0
794 status=0
795 if(k2<=0.d0 .or. top_height<=0.d0 .or. z<0.d0 .or. z>top_height) then
796 status=3
797 return
798 end if
799
800 q2=k2-alpha**2
801 scale=max(1.d0,k2,alpha**2)
802 if(.not.top_closed) then
803 if(q2< -lfff_mode_tolerance*scale) then
804 status=1
805 return
806 end if
807 q=dsqrt(max(0.d0,q2))
808 transfer_b=dexp(-q*z)
809 transfer_d=q*transfer_b
810 else if(q2>lfff_mode_tolerance*scale) then
811 q=dsqrt(q2)
812 if(q*top_height<50.d0) then
813 denominator=dsinh(q*top_height)
814 transfer_b=dsinh(q*(top_height-z))/denominator
815 transfer_d=q*dcosh(q*(top_height-z))/denominator
816 else
817 ! Algebraically identical exponential form that cannot overflow for
818 ! large q*L when 0 <= z <= L.
819 denominator=1.d0-dexp(-2.d0*q*top_height)
820 transfer_b=(dexp(-q*z)-dexp(-q*(2.d0*top_height-z)))/denominator
821 transfer_d=q*(dexp(-q*z)+dexp(-q*(2.d0*top_height-z)))/denominator
822 end if
823 else if(q2< -lfff_mode_tolerance*scale) then
824 beta=dsqrt(-q2)
825 denominator=dsin(beta*top_height)
826 if(dabs(denominator)<lfff_resonance_tolerance) then
827 status=2
828 return
829 end if
830 transfer_b=dsin(beta*(top_height-z))/denominator
831 transfer_d=beta*dcos(beta*(top_height-z))/denominator
832 else
833 ! Critical q=0 limit of sinh(q*(L-z))/sinh(q*L).
834 transfer_b=1.d0-z/top_height
835 transfer_d=1.d0/top_height
836 end if
837 end subroutine lfff_fft_transfer
838
839{^ifthreed
840 subroutine calc_lin_fff(ixI^L,ixO^L,Bf,x,alpha,zshift,idir)
841 ! PURPOSE:
842 ! Calculation to determine linear FFF from the field on
843 ! the lower boundary (Chiu and Hilton 1977 ApJ 212,873).
844 ! NOTE: Only works for Cartesian coordinates
845 ! INPUT: Bf,x
846 ! OUTPUT: updated b in w
848
849 integer, intent(in) :: ixI^L, ixO^L
850 integer, optional, intent(in) :: idir
851 double precision, intent(in) :: x(ixI^S,1:ndim),alpha,zshift
852 double precision, intent(inout) :: Bf(ixI^S,1:ndir)
853
854 double precision, dimension(ixO^S) :: cos_az,sin_az,zk,bigr,r,r2,r3,cos_ar,sin_ar,g,dgdz
855 double precision, dimension(ixO^S) :: dx1,dx2,invr3
856 double precision :: twopiinv
857 logical :: compute_dir(1:ndir)
858 integer :: idim,ixp1,ixp2
859
860 bf=0.d0
861 twopiinv = 0.5d0/dpi*bzmax*darea
862 zk(ixo^s)=x(ixo^s,3)-xprobmin3+zshift
863
864 compute_dir=.true.
865 if(present(idir)) then
866 compute_dir=.false.
867 if(idir>=1 .and. idir<=ndir) compute_dir(idir)=.true.
868 end if
869
870 ! For a potential field, the Green-function kernel simplifies exactly to
871 ! (dx,dy,z)/r**3. Avoid the trigonometric functions, singular 1/bigr
872 ! factors, and intermediate arrays needed by the general linear FFF form.
873 if(alpha==0.d0) then
874 do ixp2=1,nx2
875 do ixp1=1,nx1
876 dx1(ixo^s)=x(ixo^s,1)-xa1(ixp1)
877 dx2(ixo^s)=x(ixo^s,2)-xa2(ixp2)
878 r2(ixo^s)=dx1(ixo^s)**2+dx2(ixo^s)**2+zk(ixo^s)**2
879 where(r2(ixo^s)>0.d0)
880 invr3(ixo^s)=1.d0/(r2(ixo^s)*dsqrt(r2(ixo^s)))
881 elsewhere
882 invr3(ixo^s)=0.d0
883 end where
884 if(compute_dir(1)) bf(ixo^s,1)=bf(ixo^s,1)+&
885 bz0(ixp1,ixp2)*dx1(ixo^s)*invr3(ixo^s)
886 if(compute_dir(2)) bf(ixo^s,2)=bf(ixo^s,2)+&
887 bz0(ixp1,ixp2)*dx2(ixo^s)*invr3(ixo^s)
888 if(compute_dir(3)) bf(ixo^s,3)=bf(ixo^s,3)+&
889 bz0(ixp1,ixp2)*zk(ixo^s)*invr3(ixo^s)
890 end do
891 end do
892 bf(ixo^s,:)=bf(ixo^s,:)*twopiinv
893 return
894 end if
895
896 ! get cos and sin arrays for a non-zero linear force-free alpha
897 cos_az(ixo^s)=dcos(alpha*zk(ixo^s))
898 sin_az(ixo^s)=dsin(alpha*zk(ixo^s))
899 ! looping Bz0 pixels
900 do ixp2=1,nx2
901 do ixp1=1,nx1
902 bigr(ixo^s)=dsqrt((x(ixo^s,1)-xa1(ixp1))**2+&
903 (x(ixo^s,2)-xa2(ixp2))**2)
904 r2=bigr**2+zk**2
905 r=dsqrt(r2)
906 r3=r**3
907 cos_ar=dcos(alpha*r)
908 sin_ar=dsin(alpha*r)
909 where(bigr/=0.d0)
910 bigr=1.d0/bigr
911 end where
912 where(r/=0.d0)
913 r=1.d0/r
914 end where
915 where(r2/=0.d0)
916 r2=1.d0/r2
917 end where
918 where(r3/=0.d0)
919 r3=1.d0/r3
920 end where
921 g=(zk*cos_ar*r-cos_az)*bigr
922 dgdz=(cos_ar*(r-zk**2*r3)-alpha*zk**2*sin_ar*r2+alpha*sin_az)*bigr
923 do idim=1,ndim
924 if(present(idir)) then
925 if(idim/=idir) cycle
926 end if
927 select case(idim)
928 case(1)
929 bf(ixo^s,1)=bf(ixo^s,1)+bz0(ixp1,ixp2)*((x(ixo^s,1)-xa1(ixp1))*dgdz(ixo^s)&
930 +alpha*g(ixo^s)*(x(ixo^s,2)-xa2(ixp2)))*bigr(ixo^s)
931 case(2)
932 bf(ixo^s,2)=bf(ixo^s,2)+bz0(ixp1,ixp2)*((x(ixo^s,2)-xa2(ixp2))*dgdz(ixo^s)&
933 -alpha*g(ixo^s)*(x(ixo^s,1)-xa1(ixp1)))*bigr(ixo^s)
934 case(3)
935 bf(ixo^s,3)=bf(ixo^s,3)+bz0(ixp1,ixp2)*(zk(ixo^s)*cos_ar(ixo^s)*r3(ixo^s)+alpha*&
936 zk(ixo^s)*sin_ar(ixo^s)*r2(ixo^s))
937 end select
938 end do
939 end do
940 end do
941 bf(ixo^s,:)=bf(ixo^s,:)*twopiinv
942
943 end subroutine calc_lin_fff
944
945 subroutine get_potential_field_potential(ixI^L,ixO^L,potential,x,zshift)
946 ! PURPOSE:
947 ! Calculation scalar potential of potential field given
948 ! Bz at photosphere (Schmidt 1964 NASSP).
949 ! NOTE: Only works for Cartesian coordinates
950 ! INPUT: x,zshift
951 ! OUTPUT: potential
953
954 integer, intent(in) :: ixI^L, ixO^L
955 double precision, intent(in) :: x(ixI^S,1:ndim),zshift
956 double precision, intent(inout) :: potential(ixI^S)
957
958 double precision :: zk
959 integer :: ixp1,ixp2,ix^D
960
961 potential=0.d0
962 ! looping Bz0 pixels see equation (2)
963 !$OMP PARALLEL DO
964 do ix3=ixomin3,ixomax3
965 zk=x(ixomin1,ixomin2,ix3,3)-xprobmin3+zshift
966 do ix2=ixomin2,ixomax2
967 do ix1=ixomin1,ixomax1
968 do ixp2=1,nx2
969 do ixp1=1,nx1
970 potential(ix^d)=potential(ix^d)+0.5d0*bz0(ixp1,ixp2)*darea/&
971 (dpi*dsqrt((x(ix^d,1)-xa1(ixp1))**2+(x(ix^d,2)-xa2(ixp2))**2+zk**2))
972 end do
973 end do
974 end do
975 end do
976 end do
977 !$OMP END PARALLEL DO
978 end subroutine get_potential_field_potential
979
980 subroutine get_potential_field_potential_sphere(ixI^L,x,potential,nth,nph,magnetogram,theta,phi,r_sphere)
981 ! PURPOSE:
982 ! Calculation scalar potential of potential field given
983 ! Bz at photosphere (Schmidt 1964 NASSP).
984 ! NOTE: Only works for spherical coordinates
985 ! OUTPUT: potential
987 integer, intent(in) :: ixI^L,nth,nph
988 real*8, intent(in) :: x(ixi^s,1:ndim)
989 ! magnetogram Br on photosphere
990 real*8, intent(in) :: magnetogram(nth,nph)
991 ! theta and phi grid of the photospheric magnetogram
992 real*8, intent(in) :: theta(nth),phi(nph)
993 ! radius of photosphere
994 real*8, intent(in) :: r_sphere
995 real*8, intent(out) :: potential(ixi^s)
996
997 real*8 :: area(nth),dtheta_half,dphi,inv2pi
998 integer :: ixp1,ixp2,ix^D
999
1000 potential=0.d0
1001 ! assume uniformly discretized theta and phi
1002 dtheta_half=0.5d0*(theta(2)-theta(1))
1003 dphi=phi(2)-phi(1)
1004 area(1:nth)=2.d0*r_sphere**2*sin(theta(1:nth))*sin(dtheta_half)*sin(dphi)
1005 inv2pi=1.d0/(2.d0*dpi)
1006
1007 !$OMP PARALLEL DO
1008 do ix3=iximin3,iximax3
1009 do ix2=iximin2,iximax2
1010 do ix1=iximin1,iximax1
1011 do ixp2=1,nph
1012 do ixp1=1,nth
1013 potential(ix^d)=potential(ix^d)+inv2pi*magnetogram(ixp1,ixp2)*area(ixp1)/&
1014 dsqrt(x(ix^d,1)**2+r_sphere**2-2.d0*x(ix^d,1)*r_sphere*&
1015 (dsin(x(ix^d,2))*dsin(theta(ixp1))*dcos(phi(ixp2)-x(ix^d,3))+dcos(x(ix^d,2))*&
1016 dcos(theta(ixp1))))
1017 end do
1018 end do
1019 end do
1020 end do
1021 end do
1022 !$OMP END PARALLEL DO
1023
1025
1026 !> get potential magnetic field energy given normal B on all boundaries
1027 subroutine potential_field_energy_mg(benergy)
1031
1032 real*8, intent(out) :: benergy
1033 type(magnetic_reference_config) :: config
1034 type(magnetic_reference_result) :: result
1035 type(magnetic_reference_field) :: bp
1036
1037 call solve_magnetic_reference_fv(config,bp,result)
1038 benergy=result%magnetic_energy
1040
1041 end subroutine potential_field_energy_mg
1042
1043 !> Solve Poisson equation of scalar potential using multigrid solver
1057
1058 !> To set boundary condition on physical boundaries for mg Poisson solver
1059 subroutine multigrid_bc(box, nc, iv, nb, bc_type, bc)
1062 type(mg_box_t), intent(in) :: box
1063 integer, intent(in) :: nc
1064 integer, intent(in) :: iv !< Index of variable
1065 integer, intent(in) :: nb !< number of boundary from 1 to 6 for 3D
1066 integer, intent(out) :: bc_type !< Type of b.c.
1067 ! mg boundary values
1068 double precision, intent(out) :: bc(nc, nc)
1069 call magnetic_reference_bc(box,nc,iv,nb,bc_type,bc)
1070
1071 end subroutine multigrid_bc
1072}
1073
1074{^iftwod
1075 subroutine init_lfff_2d(bn_bottom,nx,qalpha,qy0,qtop_closed,qy_top,flux_treatment,nmodes_keep)
1077 use mod_comm_lib, only: mpistop
1078 integer, intent(in) :: nx
1079 double precision, intent(in) :: bn_bottom(nx),qalpha,qy0,qy_top
1080 logical, intent(in) :: qtop_closed
1081 character(len=*), intent(in), optional :: flux_treatment
1082 integer, intent(in), optional :: nmodes_keep
1083
1084 double precision :: Lx,xj,arg
1085 integer :: m,j,nm
1086
1087 lx=xprobmax1-xprobmin1
1088 nm=nx/2-1
1089 if(present(nmodes_keep)) nm=min(nm,nmodes_keep)
1090 if(allocated(l2d_br)) deallocate(l2d_br,l2d_bi,l2d_k)
1091 allocate(l2d_br(nm),l2d_bi(nm),l2d_k(nm))
1092 l2d_b0=sum(bn_bottom)/dble(nx)
1093 if(present(flux_treatment)) then
1094 if(flux_treatment=='subtract') then
1095 if(mype==0.and.dabs(l2d_b0)>lfff_flux_balance_tolerance) &
1096 print*,'init_lfff_2d: subtracting net flux ',l2d_b0
1097 l2d_b0=0.d0
1098 end if
1099 end if
1100 do m=1,nm
1101 l2d_k(m)=2.d0*dpi*dble(m)/lx
1102 l2d_br(m)=0.d0; l2d_bi(m)=0.d0
1103 do j=1,nx
1104 xj=xprobmin1+(dble(j)-0.5d0)*lx/dble(nx)
1105 arg=l2d_k(m)*xj
1106 l2d_br(m)=l2d_br(m)+bn_bottom(j)*dcos(arg)
1107 l2d_bi(m)=l2d_bi(m)-bn_bottom(j)*dsin(arg)
1108 end do
1109 l2d_br(m)=2.d0*l2d_br(m)/dble(nx)
1110 l2d_bi(m)=2.d0*l2d_bi(m)/dble(nx)
1111 end do
1112 l2d_nm=nm; l2d_alpha=qalpha; l2d_y0=qy0; l2d_ytop=qy_top; l2d_closed=qtop_closed
1113 if(.not.qtop_closed .and. qalpha**2>=l2d_k(1)**2) &
1114 call mpistop('init_lfff_2d: |alpha| >= k_1, oscillatory open modes')
1115 l2d_ready=.true.
1116 end subroutine init_lfff_2d
1117
1118 !> B from the stored modes at arbitrary points; Bf(:,:,1:3)=(Bx,By,Bz=alpha*A)
1119 subroutine calc_lfff_2d(ixI^L,ixO^L,x,Bf)
1121 use mod_comm_lib, only: mpistop
1122 integer, intent(in) :: ixI^L,ixO^L
1123 double precision, intent(in) :: x(ixI^S,1:ndim)
1124 double precision, intent(out) :: Bf(ixI^S,1:3)
1125
1126 double precision :: tb,td,ca,sa,cme,sme,zz
1127 integer :: ix^D,m,istat
1128
1129 if(.not.l2d_ready) call mpistop('calc_lfff_2d: call init_lfff_2d first')
1130 bf(ixo^s,1)=0.d0
1131 bf(ixo^s,2)=l2d_b0
1132 bf(ixo^s,3)=0.d0
1133 {do ix^db=ixomin^db,ixomax^db\}
1134 zz=min(max(x(ix^d,2)-l2d_y0,0.d0),l2d_ytop-l2d_y0)
1135 do m=1,l2d_nm
1137 l2d_closed,tb,td,istat)
1138 if(istat==2) call mpistop('calc_lfff_2d: closed-box resonance')
1139 cme=dcos(l2d_k(m)*x(ix^d,1)); sme=dsin(l2d_k(m)*x(ix^d,1))
1140 ca=l2d_br(m)*cme-l2d_bi(m)*sme
1141 sa=l2d_bi(m)*cme+l2d_br(m)*sme
1142 bf(ix^d,2)=bf(ix^d,2)+ca*tb
1143 bf(ix^d,1)=bf(ix^d,1)+sa*td/l2d_k(m)
1144 bf(ix^d,3)=bf(ix^d,3)+l2d_alpha*sa*tb/l2d_k(m)
1145 end do
1146 {end do\}
1147 end subroutine calc_lfff_2d
1148}
1149
1150end module mod_lfff
subroutine, public mpistop(message)
Exit MPI-AMRVAC with an error message.
Utilities for reading AMRVAC data-driven magnetic boundary frames.
subroutine read_data_driven_boundary_frame(filename, snapshot_time, nx, ny, dx_km, dy_km, bframe)
Self-contained complex FFT utilities based on the mixed-radix Singleton transform historically embedd...
Definition mod_fft.t:3
subroutine, public fft_2d_real_imag(ar, ai, inverse)
In-place two-dimensional FFT on split real/imaginary storage. This avoids allocation and copying in r...
Definition mod_fft.t:181
character(len=128) function, public fft_factorization(n)
Human-readable prime factorization used in unsupported-size diagnostics.
Definition mod_fft.t:83
integer function, public fft_next_supported(n)
Definition mod_fft.t:73
logical function, public fft_size_supported(n)
Return whether the legacy mixed-radix workspace can handle a transform.
Definition mod_fft.t:19
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.
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
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
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
double precision, save darea
Definition mod_lfff.t:30
double precision, dimension(:,:), allocatable, save bz0
Definition mod_lfff.t:31
subroutine multigrid_bc(box, nc, iv, nb, bc_type, bc)
To set boundary condition on physical boundaries for mg Poisson solver.
Definition mod_lfff.t:1060
subroutine init_b_fff_data_driven_boundary(boundaryname, qlunit, qbunit, qxc1, qxc2, bvector)
Definition mod_lfff.t:111
logical, save l2d_ready
Definition mod_lfff.t:41
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
double precision, save l2d_b0
Definition mod_lfff.t:39
integer, save nx1
Definition mod_lfff.t:33
subroutine get_potential_field_potential_sphere(ixil, x, potential, nth, nph, magnetogram, theta, phi, r_sphere)
Definition mod_lfff.t:981
double precision, parameter lfff_mode_tolerance
Definition mod_lfff.t:34
subroutine lfff_fft_transfer(k2, alpha, z, top_height, top_closed, transfer_b, transfer_d, status)
Vertical transfer functions for one nonzero horizontal constant-alpha Fourier mode....
Definition mod_lfff.t:785
double precision, parameter lfff_resonance_tolerance
Definition mod_lfff.t:35
subroutine lfff_balance_bottom_flux(bz, treatment, max_imbalance, imbalance_before, imbalance_after, mean_correction, status)
Check a constant-alpha magnetogram and optionally remove its core mean. Status: 0 accepted unchanged,...
Definition mod_lfff.t:201
subroutine init_lfff_2d(bn_bottom, nx, qalpha, qy0, qtop_closed, qy_top, flux_treatment, nmodes_keep)
Definition mod_lfff.t:1076
subroutine init_b_fff_data(magnetogramname, qlunit, qbunit)
Definition mod_lfff.t:47
subroutine get_potential_field_potential_mg()
Solve Poisson equation of scalar potential using multigrid solver.
Definition mod_lfff.t:1045
subroutine calc_lin_fff(ixil, ixol, bf, x, alpha, zshift, idir)
Definition mod_lfff.t:841
double precision, save l2d_y0
Definition mod_lfff.t:39
subroutine potential_field_energy_mg(benergy)
get potential magnetic field energy given normal B on all boundaries
Definition mod_lfff.t:1028
double precision, parameter lfff_flux_balance_tolerance
Definition mod_lfff.t:36
integer, save nx2
Definition mod_lfff.t:33
integer, save l2d_nm
Definition mod_lfff.t:40
double precision, dimension(:), allocatable, save l2d_k
Definition mod_lfff.t:38
double precision, dimension(:), allocatable, save xa2
Definition mod_lfff.t:32
double precision, dimension(:), allocatable, save l2d_br
Definition mod_lfff.t:38
double precision, save bzmax
Definition mod_lfff.t:30
double precision, save l2d_alpha
Definition mod_lfff.t:39
logical, save l2d_closed
Definition mod_lfff.t:41
subroutine calc_lfff_2d(ixil, ixol, x, bf)
B from the stored modes at arbitrary points; Bf(:,:,1:3)=(Bx,By,Bz=alpha*A)
Definition mod_lfff.t:1120
subroutine extrapolate_potential_fft(iw_b, padding_factor, source_plane_depth, alpha, top_boundary, flux_treatment, max_flux_imbalance)
Extrapolate a Cartesian potential field with horizontal Fourier modes. The bottom magnetogram must ma...
Definition mod_lfff.t:344
subroutine get_potential_field_potential(ixil, ixol, potential, x, zshift)
Definition mod_lfff.t:946
double precision, save l2d_ytop
Definition mod_lfff.t:39
double precision, dimension(:), allocatable, save xa1
Definition mod_lfff.t:32
double precision, dimension(:), allocatable, save l2d_bi
Definition mod_lfff.t:38
Finite-volume potential reference field for magnetic diagnostics.
subroutine, public magnetic_reference_bc(box, nc, iv, nb, bc_type, bc)
Supply d(phi)/dx_i = B_i on a physical multigrid boundary.
subroutine, public solve_magnetic_reference_fv(config, bp, result)
subroutine, public free_magnetic_reference_field(bp)
Module to couple the octree-mg library to AMRVAC. This file uses the VACPP preprocessor,...