MPI-AMRVAC 3.2
The MPI - Adaptive Mesh Refinement - Versatile Advection Code (development version)
Loading...
Searching...
No Matches
mod_rt_sc_kernel.t
Go to the documentation of this file.
1!=============================================================================
2!> Transport kernel of the short-characteristics (SC) radiative transfer
3!>
4!> The transfer equation is solved along one ray direction at a time with the linear short
5!> characteristics of Olson and Kunasz. Each cell takes the intensity at the upwind point on the
6!> face the ray crosses first, interpolated linearly along that face, and integrates across one
7!> step with the source taken as linear in optical depth. sc_sweep_block sweeps one block whose
8!> ghost ring already holds the upwind values and the inflows, and it is the only sweep in the
9!> code. The wavefront solvers call it block by block, and the gather calls it once on the whole
10!> replicated band, which sc_pad_grid has padded with the inflows
11!>
12!> The module also builds the angular quadrature, Gauss-Legendre or the Carlson A4 set, and in 2D
13!> merges the rays that differ only out of the plane. It holds the replicated single-level grid
14!> of the gather and forms the heating in blend_heating, which blends c chi (J - B) with -div F.
15!> Nothing here communicates, since the drivers fill chi and B beforehand
16!>
17!> The gather is the original implementation, kept for comparison and not for production. Every
18!> rank holds the whole band, and its periodic seam is not exact (see sc_sweep_2d). Production runs
19!> should use KBA, which is far better optimised
20!>
21!> Structure:
22!> A. quadrature rt_sc_init, rt_sc_dedup_rays, build_gl, build_carlson and
23!> gauss_legendre_nodes;
24!> B. heating blend_heating, and rt_sc_heat_blend on the replicated grid;
25!> C. transport sc_pad_grid and sc_sweep_2d for the replicated grid, ka_coeffs for the
26!> weights of one step, and sc_sweep_block
27!>
28!> Jack Jenkins (10.2026); More or less finished in v1 state for amrvac4.0
29!=============================================================================
31 use mod_global_parameters, only: bigdouble, smalldouble
32 implicit none
33
34 !> The angular quadrature, either 'GL' (Gauss-Legendre polar nodes times equally spaced azimuths)
35 !> or 'Carlson', the level-symmetric A4 set that MURaM uses
36 character(len=16), public :: rt_sc_quad = 'GL'
37 !> Azimuthal directions and polar nodes of the GL set, with nmu one of 2, 4, 6 or 8
38 integer, public :: rt_sc_ndir = 6
39 integer, public :: rt_sc_nmu = 4
40 integer, public :: rt_sc_nray = 0
41 !> Stratification axis (1 = x, 2 = y), set by the driver before rt_sc_init. The GL set places
42 !> its polar nodes about this axis, while Carlson is symmetric and does not need it
43 integer, public :: rt_sc_polar_axis = 2
44 !> Weight of the heating blend, w = exp(-blend_k tau), and the scale of the taper
45 !> tau^2/(tau^2 + tau_min^2) that switches the SC heating off where the column above is thin
46 double precision, public :: rt_sc_blend_k = 10.0d0, rt_sc_tau_min = 1.0d-6
47 !> Incoming intensity at the lower and upper ends of the current band, thermal (I = B) when set
48 !> and open (I = 0) otherwise
49 logical, public :: bc_lo_thermal = .true., bc_hi_thermal = .false.
50 !> The horizontal edges wrap when the domain is periodic and otherwise act as inflow boundaries,
51 !> exactly like the band ends. rt_sc_setup copies these from periodB(hdim)
52 logical, public :: bc_h_periodic = .true.
53 logical, public :: bc_hlo_thermal = .false., bc_hhi_thermal = .false.
54 !> Direction cosines and weights, normalised so that sum(wmu) = 1. In 2D the out-of-plane muz
55 !> only lengthens the path of each step through ds
56 double precision, allocatable, public :: mux(:), muy(:), muz(:), wmu(:)
57 !> The replicated single-level grid with chi = kappa rho, B = a T^4, the mean intensity J, the
58 !> intensity of the ray being swept, the heating, both flux components and tau from the top
59 double precision, allocatable, public :: chi_g(:,:), b_g(:,:), jm_g(:,:), ii_g(:,:), q_g(:,:)
60 double precision, allocatable, public :: fx_g(:,:), fy_g(:,:), tau_g(:,:)
61 ! Size of that grid along hdim and updim, left at -1 until the grid is first built
62 integer, public :: nxg = -1, nyg = -1
63 ! Cell size of the grid along hdim and updim
64 double precision, public :: dxg, dyg
65 ! chi, B and the intensity of the grid padded with one ghost layer, which holds the band-end and
66 ! edge inflows, so that the grid can be swept by sc_sweep_block as a single block
67 double precision, allocatable :: chi_p(:,:), b_p(:,:), i_p(:,:)
68
69contains
70
71 !> Sign of a direction cosine as -1, 0 or 1. The zero case occurs, for example for the GL rays
72 !> at phi = pi/2
73 pure integer function ray_sign(a)
74 double precision, intent(in) :: a
75 ray_sign = merge(1, 0, a > 0.0d0) - merge(1, 0, a < 0.0d0)
76 end function ray_sign
77
78 !> Build the quadrature into mux, muy, muz and wmu as full 3D cosines, according to rt_sc_quad
79 subroutine rt_sc_init()
81 ! Even in 2D we build the full 3D set, because the out-of-plane cosine still sets how long
82 ! each in-plane step is
83 select case(trim(adjustl(rt_sc_quad)))
84 case('Carlson','carlson')
85 call build_carlson()
86 case default
87 call build_gl()
88 end select
89 ! Rays that differ only in muz are indistinguishable to a 2D sweep, so we merge them and
90 ! save the repeated work
91 call rt_sc_dedup_rays()
92 if(mype==0) print *, "rt_sc_init: quadrature '"//trim(adjustl(rt_sc_quad))//"', rays =", rt_sc_nray
93 end subroutine rt_sc_init
94
95 !> Merge rays that share (mux, muy) and add up their weights. Since the 2D sweep and the moments
96 !> see only those two cosines, Carlson's +-muz pairs and GL's phi, 2pi-phi pairs sweep the same.
97 !> The total weight is preserved, so J, Fx and Fy are unchanged to round-off
98 subroutine rt_sc_dedup_rays()
100 double precision, allocatable :: nx(:), ny(:), nz(:), wsum(:)
101 logical, allocatable :: gone(:)
102 integer :: k, j, nnew
103 ! Cosines that agree to this tolerance are treated as the same direction
104 double precision, parameter :: tol = 1.0d-12
105 if(rt_sc_nray <= 1) return
106 allocate(nx(rt_sc_nray), ny(rt_sc_nray), nz(rt_sc_nray), wsum(rt_sc_nray), gone(rt_sc_nray))
107 gone = .false.; nnew = 0
108 ! Each ray not yet absorbed opens a new entry in the merged set, and any later ray with the
109 ! same in-plane cosines hands its weight over and is marked gone
110 do k = 1, rt_sc_nray
111 if(gone(k)) cycle
112 nnew = nnew + 1
113 nx(nnew) = mux(k); ny(nnew) = muy(k); nz(nnew) = muz(k); wsum(nnew) = wmu(k)
114 ! w_merged = w_k + the sum of w_j over the later rays j with the same (mux, muy)
115 do j = k+1, rt_sc_nray
116 if(gone(j)) cycle
117 if(abs(mux(j)-mux(k)) < tol .and. abs(muy(j)-muy(k)) < tol) then
118 wsum(nnew) = wsum(nnew) + wmu(j); gone(j) = .true.
119 end if
120 end do
121 end do
122 ! The quadrature is only replaced when something was actually merged
123 if(nnew < rt_sc_nray) then
124 deallocate(mux, muy, muz, wmu)
125 allocate(mux(nnew), muy(nnew), muz(nnew), wmu(nnew))
126 mux = nx(1:nnew); muy = ny(1:nnew); muz = nz(1:nnew); wmu = wsum(1:nnew)
127 if(mype==0) print *, "rt_sc_dedup_rays: (mux,muy) rays", rt_sc_nray, "->", nnew, " (2D-identical merged)"
128 rt_sc_nray = nnew
129 end if
130 deallocate(nx, ny, nz, wsum, gone)
131 end subroutine rt_sc_dedup_rays
132
133 !> Product Gauss-Legendre set, with nmu polar nodes about rt_sc_polar_axis and ndir azimuths at
134 !> the midpoints of equal intervals
135 subroutine build_gl()
137 integer :: im, ip, k, nmu, nphi
138 double precision :: phi, s, mu, xmu(8), wgl(8)
139 ! With fewer than three azimuths the plane is not resolved, so we never go below three
140 if(rt_sc_ndir < 3) rt_sc_ndir = 3
141 nphi = rt_sc_ndir; nmu = rt_sc_nmu
142 ! The node routine may round nmu up to the next order it supports
143 call gauss_legendre_nodes(nmu, xmu, wgl)
144 rt_sc_nray = nmu*nphi
145 if(allocated(mux)) deallocate(mux, muy, muz, wmu)
147 k = 0
148 do im = 1, nmu
149 ! The polar cosine and its sine, mu = x_GL(im), s = sqrt(1 - mu^2)
150 mu = xmu(im)
151 s = dsqrt(max(1.0d0 - mu*mu, 0.0d0))
152 do ip = 1, nphi
153 ! phi = 2 pi (ip - 1/2)/nphi, mu_h = s cos(phi), mu_z = s sin(phi)
154 phi = 2.0d0*dpi*(dble(ip)-0.5d0)/dble(nphi)
155 k = k + 1
156 ! The polar cosine lies along the stratification and the azimuth turns about it
157 if(rt_sc_polar_axis == 2) then
158 muy(k) = mu; mux(k) = s*dcos(phi)
159 else
160 mux(k) = mu; muy(k) = s*dcos(phi)
161 end if
162 muz(k) = s*dsin(phi)
163 ! The GL weights add up to 2 on [-1, 1] and the azimuths share each one equally
164 ! w = w_GL/(2 nphi), so that sum w = 1
165 wmu(k) = 0.5d0*wgl(im)/dble(nphi)
166 end do
167 end do
168 end subroutine build_gl
169
170 !> Carlson A4 level-symmetric set, built from the three cyclic permutations of (mu1, mu2, mu2)
171 !> with mu1^2 + 2 mu2^2 = 1 in each of the 8 octants, which gives 24 directions of weight 1/24
172 !> as in MURaM's carlson3
173 subroutine build_carlson()
174 ! m1^2 + 2 m2^2 = 1, so every direction built from these is a unit vector
175 double precision, parameter :: m1 = 0.88191710d0, m2 = 0.33333333d0
176 double precision :: base(3,3)
177 integer :: ib, sgx, sgy, sgz, k
178 ! Each column places the long cosine on a different axis
179 base(:,1) = (/ m1, m2, m2 /)
180 base(:,2) = (/ m2, m1, m2 /)
181 base(:,3) = (/ m2, m2, m1 /)
182 rt_sc_nray = 24
183 if(allocated(mux)) deallocate(mux, muy, muz, wmu)
184 allocate(mux(24), muy(24), muz(24), wmu(24))
185 k = 0
186 ! The sign loops run over the eight octants, and every octant receives all three
187 ! permutations with the same weight, as a level-symmetric set requires
188 do sgz = -1, 1, 2; do sgy = -1, 1, 2; do sgx = -1, 1, 2
189 do ib = 1, 3
190 k = k + 1
191 ! mu = (sgx b_1, sgy b_2, sgz b_3) for the permutation b, w = 1/24
192 mux(k) = sgx*base(1,ib); muy(k) = sgy*base(2,ib); muz(k) = sgz*base(3,ib)
193 wmu(k) = 1.0d0/24.0d0
194 end do
195 end do; end do; end do
196 end subroutine build_carlson
197
198 !> Gauss-Legendre nodes and weights on [-1,1] for n in {2,4,6,8}. Any other n is moved up to
199 !> the next supported order
200 subroutine gauss_legendre_nodes(n, x, w)
201 integer, intent(inout) :: n
202 double precision, intent(out) :: x(8), w(8)
203 x = 0.0d0; w = 0.0d0
204 ! Only four orders are ever needed, so they are tabulated to double precision rather than
205 ! computed by root finding
206 select case(n)
207 case(1,2)
208 n = 2
209 x(1:2) = (/-0.5773502691896257d0, 0.5773502691896257d0/)
210 w(1:2) = (/ 1.0000000000000000d0, 1.0000000000000000d0/)
211 case(3,4)
212 n = 4
213 x(1:4) = (/-0.8611363115940526d0,-0.3399810435848563d0, 0.3399810435848563d0, 0.8611363115940526d0/)
214 w(1:4) = (/ 0.3478548451374538d0, 0.6521451548625461d0, 0.6521451548625461d0, 0.3478548451374538d0/)
215 case(5,6)
216 n = 6
217 x(1:6) = (/-0.9324695142031521d0,-0.6612093864662645d0,-0.2386191860831969d0, &
218 0.2386191860831969d0, 0.6612093864662645d0, 0.9324695142031521d0/)
219 w(1:6) = (/ 0.1713244923791704d0, 0.3607615730481386d0, 0.4679139345726910d0, &
220 0.4679139345726910d0, 0.3607615730481386d0, 0.1713244923791704d0/)
221 case default
222 n = 8
223 x(1:8) = (/-0.9602898564975363d0,-0.7966664774136267d0,-0.5255324099163290d0,-0.1834346424956498d0, &
224 0.1834346424956498d0, 0.5255324099163290d0, 0.7966664774136267d0, 0.9602898564975363d0/)
225 w(1:8) = (/ 0.1012285362903763d0, 0.2223810344533745d0, 0.3137066458778873d0, 0.3626837833783620d0, &
226 0.3626837833783620d0, 0.3137066458778873d0, 0.2223810344533745d0, 0.1012285362903763d0/)
227 end select
228 end subroutine gauss_legendre_nodes
229
230 {^iftwod
231 !> Heating of one cell. The thin imbalance c chi (J - B) is blended with the thick, conservative
232 !> -div F through w = exp(-rt_sc_blend_k tau), and the taper tau^2/(tau^2 + tau_min^2) removes
233 !> the heating where the column above is optically thin
234 pure double precision function blend_heating(chi, jm, bpl, divf, tau) result(q)
236 double precision, intent(in) :: chi, jm, bpl, divf, tau
237 double precision :: w
238 ! w = exp(-k tau)
239 ! Q = [w c chi (J - B) + (1 - w)(-div F)] tau^2/(tau^2 + tau_min^2)
240 w = dexp(-rt_sc_blend_k*tau)
241 q = (w*(c_norm*chi*(jm-bpl)) + (1.0d0-w)*(-divf)) * tau*tau/(tau*tau + rt_sc_tau_min**2)
242 end function blend_heating
243
244 !> Heating q_g, blending the thin imbalance c chi (J - B) with the thick, conservative -div F
245 !> through w = exp(-rt_sc_blend_k tau), tau measured from the top. Taking -div F at depth avoids
246 !> the stiff, cancelling J - B there. Needs jm_g, b_g, fx_g, fy_g and chi_g
247 subroutine rt_sc_heat_blend()
249 integer :: i, j, ip, im
250 double precision :: divf
251 ! The blend and the taper both depend on how far below the top each cell sits in optical
252 ! depth, so we integrate tau down every column first. The top cell centre lies half a cell
253 ! below the band top, and each step down adds the trapezoid of chi between two centres
254 do i = 1, nxg
255 ! tau_top = chi_top dy/2, tau_j = tau_(j+1) + (chi_(j+1) + chi_j) dy/2
256 tau_g(i,nyg) = 0.5d0*chi_g(i,nyg)*dyg
257 do j = nyg-1, 1, -1
258 tau_g(i,j) = tau_g(i,j+1) + 0.5d0*(chi_g(i,j+1)+chi_g(i,j))*dyg
259 end do
260 end do
261 do j = 1, nyg
262 do i = 1, nxg
263 ! The flux divergence uses centred differences, wrapping round in x when the domain is
264 ! periodic and falling back to one-sided differences at a closed edge or a band end.
265 ! This latter point introduces non negligible errors since we do not illuminate from the
266 ! sides...
267 ! div F = (Fx_(i+1) - Fx_(i-1))/(2 dx) + (Fy_(j+1) - Fy_(j-1))/(2 dy)
268 if(bc_h_periodic) then
269 ip = i+1; if(ip>nxg) ip=1
270 im = i-1; if(im<1) im=nxg
271 divf = (fx_g(ip,j)-fx_g(im,j))/(2.0d0*dxg)
272 else
273 ip = min(i+1,nxg); im = max(i-1,1)
274 divf = (fx_g(ip,j)-fx_g(im,j))/(dble(ip-im)*dxg)
275 end if
276 if(j==1) then
277 divf = divf + (fy_g(i,2)-fy_g(i,1))/dyg
278 else if(j==nyg) then
279 divf = divf + (fy_g(i,nyg)-fy_g(i,nyg-1))/dyg
280 else
281 divf = divf + (fy_g(i,j+1)-fy_g(i,j-1))/(2.0d0*dyg)
282 end if
283 q_g(i,j) = blend_heating(chi_g(i,j), jm_g(i,j), b_g(i,j), divf, tau_g(i,j))
284 end do
285 end do
286 end subroutine rt_sc_heat_blend
287
288 !> Allocate the replicated grid at nxg by nyg, and keep it when its size is unchanged
289 subroutine rt_grid_alloc()
290 if(allocated(chi_g)) then
291 if(size(chi_g,1)==nxg .and. size(chi_g,2)==nyg) return
292 deallocate(chi_g, b_g, jm_g, ii_g, q_g, fx_g, fy_g, tau_g)
293 end if
294 allocate(chi_g(nxg,nyg), b_g(nxg,nyg), jm_g(nxg,nyg), ii_g(nxg,nyg), q_g(nxg,nyg), &
296 end subroutine rt_grid_alloc
297
298 !> Pad the replicated grid with one ghost layer for sc_sweep_block, once per band after chi_g
299 !> and b_g are filled. Beyond the band ends chi and B take the edge rows and the intensity is
300 !> the band-end inflow, B for a thermal end and zero for an open one. A closed horizontal edge
301 !> is padded the same way, while a periodic one needs no ghosts of course
302 subroutine sc_pad_grid()
303 integer :: j
304 if(allocated(chi_p)) then
305 if(size(chi_p,1) /= nxg+2 .or. size(chi_p,2) /= nyg+2) deallocate(chi_p, b_p, i_p)
306 end if
307 if(.not.allocated(chi_p)) allocate(chi_p(0:nxg+1,0:nyg+1), b_p(0:nxg+1,0:nyg+1), i_p(0:nxg+1,0:nyg+1))
308 chi_p(1:nxg,1:nyg) = chi_g; b_p(1:nxg,1:nyg) = b_g
309 chi_p(1:nxg,0) = chi_g(:,1); b_p(1:nxg,0) = b_g(:,1)
310 chi_p(1:nxg,nyg+1) = chi_g(:,nyg); b_p(1:nxg,nyg+1) = b_g(:,nyg)
311 ! The edge columns are copied after the rows, so the corners take the corner cells
312 chi_p(0,:) = chi_p(1,:); chi_p(nxg+1,:) = chi_p(nxg,:)
313 b_p(0,:) = b_p(1,:); b_p(nxg+1,:) = b_p(nxg,:)
314 ! I_ghost = B_edge at a thermal end or edge, and 0 at an open one
315 i_p = 0.0d0
316 if(bc_lo_thermal) i_p(1:nxg,0) = b_g(:,1)
317 if(bc_hi_thermal) i_p(1:nxg,nyg+1) = b_g(:,nyg)
318 if(.not.bc_h_periodic) then
319 do j = 1, nyg
320 if(bc_hlo_thermal) i_p(0,j) = b_g(1,j)
321 if(bc_hhi_thermal) i_p(nxg+1,j) = b_g(nxg,j)
322 end do
323 end if
324 end subroutine sc_pad_grid
325
326 !> One sweep of the whole replicated grid for direction (nx, ny) into ii_g. The padded grid of
327 !> sc_pad_grid is swept as a single block, which wraps in x when the domain is periodic. The wrap
328 !> is not exact: a ray swept column by column reaches the seam before its upwind column holds
329 !> this ray, so the seam takes a vacuum inflow for it. This is accepted, since the gather is only
330 !> a comparison path; the KBA solvers wrap with the intensity of the previous solve
331 subroutine sc_sweep_2d(nx, ny)
332 double precision, intent(in) :: nx, ny
333 ! The interior starts from zero for every ray, while the ghosts keep the inflows
334 i_p(1:nxg,1:nyg) = 0.0d0
335 call sc_sweep_block(nx, ny, dxg, dyg, .true., 0,0,nxg+1,nyg+1, 1,1,nxg,nyg, chi_p, b_p, i_p)
336 ii_g = i_p(1:nxg,1:nyg)
337 end subroutine sc_sweep_2d
338
339 !> Textbook Olson-Kunasz weights for linear short characteristics over an optical-depth step dtau
340 subroutine ka_coeffs(dtau, ex, psi0, psiu)
341 double precision, intent(in) :: dtau
342 double precision, intent(out) :: ex, psi0, psiu
343 double precision :: w0, w1
344 ! A step this thin is transparent, so the intensity passes through unchanged
345 if(dtau <= 1.0d-10) then
346 ex = 1.0d0; psi0 = 0.0d0; psiu = 0.0d0; return
347 end if
348 ! The upwind intensity is attenuated by exp(-dtau). The source contributes the integrals of
349 ! e^-t and t e^-t over the step, which we evaluate in closed form unless dtau is so small that
350 ! the closed forms cancel, in which case their Taylor series are used
351 ! I = I_u exp(-dtau) + psi_u S_u + psi_0 S_0, with
352 ! w0 = int_0^dtau exp(-t) dt = 1 - exp(-dtau)
353 ! w1 = int_0^dtau t exp(-t) dt = w0 - dtau exp(-dtau)
354 ex = dexp(-dtau)
355 if(dtau > 1.0d-4) then
356 w0 = 1.0d0 - ex
357 w1 = w0 - dtau*ex
358 else
359 ! w0 = dtau - dtau^2/2 + dtau^3/6, w1 = dtau^2/2 - dtau^3/3
360 w0 = dtau - dtau*dtau*0.5d0 + dtau*dtau*dtau/6.0d0
361 w1 = dtau*dtau*0.5d0 - dtau*dtau*dtau/3.0d0
362 end if
363 ! With the source varying linearly in tau, w0 divides into a weight on the upwind source and a
364 ! weight on the local one
365 ! psi_u = w1/dtau, psi_0 = w0 - psi_u
366 psiu = w1/dtau
367 psi0 = w0 - psiu
368 end subroutine ka_coeffs
369
370 !> Sweep of one block for ray (nx, ny), the kernel of the wavefront solvers. The upwind face and
371 !> corner values sit in the ghost ring that getbc fills. With xwrap the block spans the whole
372 !> periodic x-domain and wraps iu in place. Otherwise the x-upwind values come from the ghosts,
373 !> and the block at the periodic seam reads the wrap ghost left by the previous solve
374 subroutine sc_sweep_block(nx, ny, dxb, dyb, xwrap, ixI^L, ixO^L, chi, bpl, ii)
376 double precision, intent(in) :: nx, ny, dxb, dyb
377 logical, intent(in) :: xwrap
378 integer, intent(in) :: ixI^L, ixO^L
379 double precision, intent(in) :: chi(ixI^S), bpl(ixI^S)
380 double precision, intent(inout) :: ii(ixI^S)
381 integer :: i, j, sx, sy, nxper
382 double precision :: absnx, absny, ds, frac
383 absnx = dabs(nx); absny = dabs(ny)
384 sx = ray_sign(nx); sy = ray_sign(ny)
385 ! The block width in cells, needed when the block wraps onto itself in x
386 nxper = ixomax1 - ixomin1 + 1
387 ! The upwind point lies on the face the ray crosses first, the x-face when
388 ! abs(mu_x) dy >= abs(mu_y) dx. Its first neighbour A is then the x-neighbour (iu, j) and the
389 ! columns are swept in turn, and otherwise A is the y-neighbour (i, ju) and the rows are
390 ! swept in turn. The second neighbour B is always the diagonal (iu, ju)
391 if(absnx*dyb >= absny*dxb) then
392 ! ds = dx/abs(mu_x), frac = (dx/dy) abs(mu_y)/abs(mu_x)
393 ds = dxb/max(absnx,smalldouble); frac = (dxb/dyb)*absny/max(absnx,smalldouble)
394 if(sx > 0) then
395 do i = ixomin1, ixomax1; call sweep_column(i); end do
396 else
397 do i = ixomax1, ixomin1, -1; call sweep_column(i); end do
398 end if
399 else
400 ! ds = dy/abs(mu_y), frac = (dy/dx) abs(mu_x)/abs(mu_y)
401 ds = dyb/max(absny,smalldouble); frac = (dyb/dxb)*absnx/max(absny,smalldouble)
402 if(sy > 0) then
403 do j = ixomin2, ixomax2; call sweep_row(j); end do
404 else
405 do j = ixomax2, ixomin2, -1; call sweep_row(j); end do
406 end if
407 end if
408 contains
409 !> x-upwind index of column i, wrapped in place when the block spans the periodic domain
410 integer function upwind_x(i) result(iu)
411 integer, intent(in) :: i
412 iu = i - sx
413 if(xwrap .and. bc_h_periodic) then
414 if(iu<ixomin1) iu=iu+nxper
415 if(iu>ixomax1) iu=iu-nxper
416 end if
417 end function upwind_x
418 subroutine sweep_column(i)
419 integer, intent(in) :: i
420 integer :: j, iu
421 iu = upwind_x(i)
422 do j = ixomin2, ixomax2
423 call step(i, j, iu, j, iu, j-sy)
424 end do
425 end subroutine sweep_column
426 subroutine sweep_row(j)
427 integer, intent(in) :: j
428 integer :: i
429 do i = ixomin1, ixomax1
430 call step(i, j, i, j-sy, upwind_x(i), j-sy)
431 end do
432 end subroutine sweep_row
433 !> One cell (i, j) from its upwind neighbours A = (ia, ja) and B = (iu, ju). The upwind
434 !> values may sit in ghost cells, where the driver has already written the band-end and edge
435 !> inflows. chi and B do not depend on the ray, so their ghosts are always current
436 subroutine step(i, j, ia, ja, iu, ju)
437 integer, intent(in) :: i, j, ia, ja, iu, ju
438 double precision :: in_a, in_b, iu_val, chiu, bu, dtau, ex, psi0, psiu
439 in_a = ii(ia,ja)
440 ! The intensity in the diagonal ghost is less reliable. In the pipelined solver it may
441 ! already belong to the next ray, while both face ghosts still hold the current. Naturally
442 ! this is problematic so we avoid it by reconstructing it from the adjacent faces instead.
443 ! Seems to be a reasonable approximation at least
444 if( (iu<ixomin1 .or. iu>ixomax1) .and. (ju<ixomin2 .or. ju>ixomax2) ) then
445 ! I_B = (I(iu,j) + I(i,ju))/2
446 in_b = 0.5d0*(ii(iu,j) + ii(i,ju))
447 else
448 in_b = ii(iu,ju)
449 end if
450 ! I_u = (1 - frac) I_A + frac I_B, and chi_u and B_u alike
451 iu_val = (1.0d0-frac)*in_a + frac*in_b
452 chiu = (1.0d0-frac)*chi(ia,ja) + frac*chi(iu,ju)
453 bu = (1.0d0-frac)*bpl(ia,ja) + frac*bpl(iu,ju)
454 ! dtau = (chi_u + chi) ds/2, I = I_u exp(-dtau) + B psi_0 + B_u psi_u
455 dtau = 0.5d0*(chiu + chi(i,j))*ds
456 call ka_coeffs(dtau, ex, psi0, psiu)
457 ii(i,j) = iu_val*ex + bpl(i,j)*psi0 + bu*psiu
458 end subroutine step
459 end subroutine sc_sweep_block
460 }
461
462end module mod_rt_sc_kernel
integer function upwind_x(i)
x-upwind index of column i, wrapped in place when the block spans the periodic domain
This module contains definitions of global parameters and variables and some generic functions/subrou...
integer mype
The rank of the current MPI task.
double precision, dimension(:), allocatable, parameter d
double precision c_norm
Normalised speed of light.
Transport kernel of the short-characteristics (SC) radiative transfer.
integer, public rt_sc_polar_axis
Stratification axis (1 = x, 2 = y), set by the driver before rt_sc_init. The GL set places its polar ...
subroutine build_gl()
Product Gauss-Legendre set, with nmu polar nodes about rt_sc_polar_axis and ndir azimuths at the midp...
double precision, dimension(:,:), allocatable, public fx_g
subroutine rt_grid_alloc()
Allocate the replicated grid at nxg by nyg, and keep it when its size is unchanged.
subroutine ka_coeffs(dtau, ex, psi0, psiu)
Textbook Olson-Kunasz weights for linear short characteristics over an optical-depth step dtau.
double precision, dimension(:), allocatable, public muy
double precision, public dyg
subroutine rt_sc_dedup_rays()
Merge rays that share (mux, muy) and add up their weights. Since the 2D sweep and the moments see onl...
integer, public rt_sc_nray
double precision, dimension(:,:), allocatable, public ii_g
subroutine rt_sc_heat_blend()
Heating q_g, blending the thin imbalance c chi (J - B) with the thick, conservative -div F through w ...
double precision, dimension(:,:), allocatable, public q_g
subroutine rt_sc_init()
Build the quadrature into mux, muy, muz and wmu as full 3D cosines, according to rt_sc_quad.
integer, public rt_sc_ndir
Azimuthal directions and polar nodes of the GL set, with nmu one of 2, 4, 6 or 8.
pure double precision function blend_heating(chi, jm, bpl, divf, tau)
Heating of one cell. The thin imbalance c chi (J - B) is blended with the thick, conservative -div F ...
double precision, dimension(:,:), allocatable i_p
double precision, dimension(:,:), allocatable, public chi_g
The replicated single-level grid with chi = kappa rho, B = a T^4, the mean intensity J,...
pure integer function ray_sign(a)
Sign of a direction cosine as -1, 0 or 1. The zero case occurs, for example for the GL rays at phi = ...
double precision, dimension(:), allocatable, public wmu
double precision, dimension(:,:), allocatable, public tau_g
double precision, dimension(:), allocatable, public muz
subroutine gauss_legendre_nodes(n, x, w)
Gauss-Legendre nodes and weights on [-1,1] for n in {2,4,6,8}. Any other n is moved up to the next su...
subroutine sc_sweep_2d(nx, ny)
One sweep of the whole replicated grid for direction (nx, ny) into ii_g. The padded grid of sc_pad_gr...
subroutine build_carlson()
Carlson A4 level-symmetric set, built from the three cyclic permutations of (mu1, mu2,...
double precision, public rt_sc_blend_k
Weight of the heating blend, w = exp(-blend_k tau), and the scale of the taper tau^2/(tau^2 + tau_min...
double precision, dimension(:,:), allocatable, public b_g
double precision, dimension(:,:), allocatable b_p
character(len=16), public rt_sc_quad
The angular quadrature, either 'GL' (Gauss-Legendre polar nodes times equally spaced azimuths) or 'Ca...
subroutine sc_pad_grid()
Pad the replicated grid with one ghost layer for sc_sweep_block, once per band after chi_g and b_g ar...
double precision, public dxg
double precision, dimension(:,:), allocatable, public fy_g
logical, public bc_hhi_thermal
logical, public bc_hlo_thermal
double precision, public rt_sc_tau_min
integer, public rt_sc_nmu
integer, public nyg
logical, public bc_hi_thermal
double precision, dimension(:), allocatable, public mux
Direction cosines and weights, normalised so that sum(wmu) = 1. In 2D the out-of-plane muz only lengt...
integer, public nxg
logical, public bc_h_periodic
The horizontal edges wrap when the domain is periodic and otherwise act as inflow boundaries,...
double precision, dimension(:,:), allocatable chi_p
subroutine sc_sweep_block(nx, ny, dxb, dyb, xwrap, ixil, ixol, chi, bpl, ii)
Sweep of one block for ray (nx, ny), the kernel of the wavefront solvers. The upwind face and corner ...
logical, public bc_lo_thermal
Incoming intensity at the lower and upper ends of the current band, thermal (I = B) when set and open...
double precision, dimension(:,:), allocatable, public jm_g