MPI-AMRVAC 3.2
The MPI - Adaptive Mesh Refinement - Versatile Advection Code (development version)
Loading...
Searching...
No Matches
mod_rt_sc_slab.t
Go to the documentation of this file.
1!=============================================================================
2!> Public face of the short-characteristics (SC) radiative transfer and its coupling to the
3!> (M)HD
4!>
5!> The rest of AMRVAC (amrvac.t, mod_errest and mod_radiative_cooling) and the user files reach
6!> the RT through this module alone. It re-exports mod_rt_sc_state and mod_rt_sc_solvers and adds
7!> the routines that couple the transfer to the gas. rt_sc_add_source applies the heating every
8!> stage, limited so that one stage changes the gas energy by at most rt_sc_cfl of itself.
9!> rt_sc_thin_weight divides the radiative losses between SC and the thin cooling.
10!> rt_sc_pin_refine holds the bands on rt_sc_amr_level, and rt_sc_emergent_tau1 measures the flux
11!> on the tau = 1 surface for a flux controller, and rt_sc_selftest checks the sweep on an analytic
12!> slab
13!>
14!> The modules form a chain without cycles. mod_rt_sc_opacity and mod_rt_sc_kernel are leaves,
15!> mod_rt_sc_state uses both, mod_rt_sc_solvers uses the state, and this module uses the solvers
16!>
17!> Jack Jenkins (10.2026); More or less finished in v1 state for amrvac4.0
18!=============================================================================
22 implicit none
23
24 public :: rt_sc_activate
25 public :: rt_sc_thin_weight
26 public :: rt_sc_add_source
27 public :: rt_sc_selftest
28 public :: rt_sc_pin_refine
29 public :: rt_sc_emergent_tau1
30
31contains
32
33 !> Switch the RT on. The physics module calls this from its initialisation when
34 !> hd_radiative_transfer or mhd_radiative_transfer is set. It reads &rt_list, reserves the
35 !> intensity slots of the wavefront and registers the output variables Qrad and Frad, with Iout
36 !> as well for the replicated solver and the validation mode, which fill it. rt_sc_setup follows
37 !> once the units and the domain are known
38 subroutine rt_sc_activate()
42 rt_sc_active = .true.
44 i_qrad = var_set_extravar('Qrad', 'Qrad')
45 i_frad = var_set_extravar('Frad', 'Frad')
46 if(.not.rt_sc_distributed .or. rt_sc_valpipe) i_iout = var_set_extravar('Iout', 'Iout')
47 end subroutine rt_sc_activate
48
49 !> Hold every block overlapping an RT band on rt_sc_amr_level. Called from forcedrefine_grid
50 !> after usr_refine_grid, so it overrides both the estimator and the user
51 subroutine rt_sc_pin_refine(igrid, level, my_refine, my_coarsen)
53 integer, intent(in) :: igrid, level
54 integer, intent(inout) :: my_refine, my_coarsen
55 integer :: ib
56 double precision :: blo, bhi
57 if(.not. rt_sc_active) return
58 ! Only the extent along updim matters, since a band spans the full width of the domain
59 blo = rnode(rpxmin0_+updim,igrid)
60 bhi = rnode(rpxmax0_+updim,igrid)
61 do ib = 1, rt_sc_nband
62 if(bhi > band_lo_c(ib) .and. blo < band_hi_c(ib)) then
63 if(level < rt_sc_amr_level) then
64 my_refine = 1; my_coarsen = 0
65 else if(level == rt_sc_amr_level) then
66 my_refine = -1; my_coarsen = -1
67 else
68 my_refine = 0; my_coarsen = 1
69 end if
70 return
71 end if
72 end do
73 end subroutine rt_sc_pin_refine
74
75 !> Self-test of the real sweep on an analytic slab. J must approach B
76 !> where the slab is optically thick, stay finite in the tenuous top, where the gray FLD
77 !> coefficient D = c lambda/(kappa rho) would diverge, and stay bounded by the source everywhere.
78 !> The slab is horizontally infinite, so the test sets thermal side inflows, which are exact for
79 !> it, a thermal bottom and an open top, and restores the run's boundaries afterwards
80 subroutine rt_sc_selftest()
82 {^iftwod
83 integer :: j, k
84 double precision :: tau, taumax, b0, ap, jb_err_deep, jmax, jmin, chimax, chimin
85 logical :: run_bc(5)
86 run_bc = [bc_h_periodic, bc_hlo_thermal, bc_hhi_thermal, bc_lo_thermal, bc_hi_thermal]
87 bc_h_periodic = .false.; bc_hlo_thermal = .true.; bc_hhi_thermal = .true.
88 bc_lo_thermal = .true.; bc_hi_thermal = .false.
89 ! The source is uniform, S = B = B0, and chi grows geometrically downwards over six decades
90 nxg = 4; nyg = 200
91 dxg = 1.0d0; dyg = 1.0d0
92 call rt_grid_alloc()
93 ! The quadrature is built here when no solve has built it yet
94 if(.not.allocated(mux)) call rt_sc_init()
95 b0 = 1.0d0
96 taumax = 1.0d3
97 do j = 1, nyg
98 ! ap runs from one in the bottom row to zero in the top row
99 ap = dble(nyg-j)/dble(nyg-1)
100 ! chi_j = (taumax/nyg) 10^(6 ap - 3), which falls from 1e3 to 1e-3 times taumax/nyg
101 chi_g(:,j) = (taumax/dble(nyg)) * 10.0d0**(6.0d0*ap - 3.0d0)
102 b_g(:,j) = b0
103 end do
104 call sc_pad_grid()
105 ! J is as always the weighted sum of the intensities of all rays
106 jm_g = 0.0d0
107 do k = 1, rt_sc_nray
108 call sc_sweep_2d(mux(k), muy(k))
109 ! J = sum_k w_k I_k
110 jm_g(:,:) = jm_g(:,:) + wmu(k)*ii_g(:,:)
111 end do
112 jmax = maxval(jm_g); jmin = minval(jm_g)
113 chimax = maxval(chi_g); chimin = minval(chi_g)
114 ! The optical depth is accumulated down the first column. Below tau ~ 10 the relative
115 ! departure of J from B measures how well the deep slab thermalises
116 tau = 0.0d0; jb_err_deep = 0.0d0
117 do j = nyg, 1, -1
118 tau = tau + chi_g(1,j)*dyg
119 ! err = max abs(J - B)/B over the cells with tau > 10
120 if(tau > 10.0d0) jb_err_deep = max(jb_err_deep, dabs(jm_g(1,j)-b_g(1,j))/b_g(1,j))
121 end do
122 ! The test passes when J is finite and non-negative everywhere and within 5% of B deep down
123 if(mype==0) then
124 print *, "=== rt_sc_selftest (uniform-source slab, B0=1) ==="
125 print *, " grid nxg,nyg =", nxg, nyg, " rays =", rt_sc_nray
126 print *, " chi per-cell range :", chimin, chimax
127 print *, " J range :", jmin, jmax, " (should be finite, in [0, ~B0])"
128 print *, " max |J-B|/B for tau>10 (deep thermalisation, want <~1e-2):", jb_err_deep
129 print *, " J at top cell (thin), col1 :", jm_g(1,nyg)
130 print *, " J at bottom cell (deep), col1 :", jm_g(1,1)
131 if(jmax < bigdouble .and. jmin >= -1.0d-10 .and. jb_err_deep < 5.0d-2) then
132 print *, " result: pass (finite, non-negative, thermalises deep)"
133 else
134 print *, " result: fail"
135 end if
136 end if
137 bc_h_periodic = run_bc(1); bc_hlo_thermal = run_bc(2); bc_hhi_thermal = run_bc(3)
138 bc_lo_thermal = run_bc(4); bc_hi_thermal = run_bc(5)
139 }
140 end subroutine rt_sc_selftest
141
142 !> Horizontal mean of Fy on the tau = rt_sc_tau_emergent surface, the photosphere, into
143 !> rt_sc_Ftau1. The flux controller needs this value, since Frad is not conserved above tau = 1
144 !> and the flux at the band top reads larger. Only the first band is searched, since it is the
145 !> one that holds the photosphere, and tau comes from the band top as in the KBA heat blend
148 use mod_comm_lib, only: mpistop
149 {^iftwod
150 double precision :: taub(ixg^t), tau_up, fy_up, ww, gbuf(2), gout(2)
151 double precision, allocatable :: tautop(:,:)
152 integer :: igrid, iblk, ih, iv, i1, i2, ig, ierr
153 if(.not.rt_sc_active) return
154 if(.not.rt_sc_ready) call mpistop("rt_sc_emergent_tau1: setup not called")
155 if(i_frad < 0) call mpistop("rt_sc_emergent_tau1: i_frad slot not registered")
156 if(.not.associated(rt_sc_chib)) call mpistop("rt_sc_emergent_tau1: opacity callback not set")
157 call rt_band_blocks(1)
158 allocate(tautop(rg_nh,wv_nbxv))
159 call rt_tau_tops(tautop)
160 ! Each column walks down from the top face of its block, and Fy is interpolated linearly in
161 ! tau where the column crosses the surface. At the top face the flux takes its value in the
162 ! top cell. gbuf sums Fy at the crossings and counts the columns that found one
163 gbuf = 0.0d0
164 do iblk = 1, wv_nblk
165 igrid = wv_ig(iblk)
166 call rt_block_tau(iblk, tautop, taub)
167 do ih = imlo_h, imhi_h
168 ig = (wv_bi(iblk)-1)*rg_bh + (ih - imlo_h + 1)
169 tau_up = tautop(ig,wv_bj(iblk))
170 ! When the top face already lies below the surface, the crossing is in a block above
171 if(tau_up >= rt_sc_tau_emergent) cycle
172 call hv(ih, imhi_v, i1, i2)
173 fy_up = ps(igrid)%w(i1,i2,i_frad)
174 do iv = imhi_v, imlo_v, -1
175 call hv(ih, iv, i1, i2)
176 if(taub(i1,i2) >= rt_sc_tau_emergent) then
177 ! Fy(tau_e) = Fy_up + ww (Fy - Fy_up), ww = (tau_e - tau_up)/(tau_c - tau_up)
178 ww = (rt_sc_tau_emergent-tau_up)/max(taub(i1,i2)-tau_up,1.0d-30)
179 gbuf(1) = gbuf(1) + fy_up + ww*(ps(igrid)%w(i1,i2,i_frad)-fy_up)
180 gbuf(2) = gbuf(2) + 1.0d0
181 exit
182 end if
183 tau_up = taub(i1,i2)
184 fy_up = ps(igrid)%w(i1,i2,i_frad)
185 end do
186 end do
187 end do
188 ! Ftau1 = sum of Fy(tau_e) over the crossings / number of crossings
189 call mpi_allreduce(gbuf, gout, 2, mpi_double_precision, mpi_sum, icomm, ierr)
190 rt_sc_ftau1 = gout(1)/max(gout(2),1.0d0)
191 deallocate(tautop)
192 }
193 end subroutine rt_sc_emergent_tau1
194
195 !> Unsplit heating source of one block and stage, e += qdt Q, with Q read from i_qrad, which is
196 !> zero outside the bands. The heating is limited so that one stage changes the gas energy by
197 !> at most rt_sc_cfl of itself
198 subroutine rt_sc_add_source(qdt, ixI^L, ixO^L, wCT, w, x, thin_share, e_internal)
200 use mod_eos, only: eos
201 integer, intent(in) :: ixi^l, ixo^l
202 double precision, intent(in) :: qdt, wct(ixi^s,1:nw), x(ixi^s,1:ndim)
203 double precision, intent(inout) :: w(ixi^s,1:nw)
204 !> True when the thin cooling is also on. The SC heating then takes only the share
205 !> 1 - w_thin, so that no loss is counted twice
206 logical, intent(in) :: thin_share
207 !> True when the energy variable is the internal energy, as with mhd_internal_e
208 logical, intent(in) :: e_internal
209 {^iftwod
210 integer :: ix1, ix2
211 double precision :: egas, ke, em, qlim, dn, qc, te(ixi^s), pth(ixi^s)
212 if(.not.rt_sc_active .or. i_qrad < 0) return
213 ! The thin-cooling share needs the temperature and the gas pressure, both from the EoS
214 if(thin_share) then
215 call eos%get_Te(wct, x, ixi^l, ixo^l, te)
216 call eos%get_thermal_pressure(wct, x, ixi^l, ixo^l, pth)
217 end if
218 do ix2=ixomin2,ixomax2
219 do ix1=ixomin1,ixomax1
220 ! Cells without heating, which include every cell outside the bands, are skipped
221 qc = wct(ix1,ix2,i_qrad)
222 if(qc == 0.0d0) cycle
223 ! The gas energy is the total less the kinetic and magnetic parts, taken from wCT like Q,
224 ! or the energy variable itself when that is already the internal energy
225 ! e_gas = e - rho v^2/2 - B^2/2
226 if(e_internal) then
227 egas = wct(ix1,ix2,iw_e)
228 else
229 ke = 0.5d0*sum(wct(ix1,ix2,iw_mom(:))**2)/wct(ix1,ix2,iw_rho)
230 em = 0.0d0
231 if(allocated(iw_mag)) em = 0.5d0*sum(wct(ix1,ix2,iw_mag(:))**2)
232 egas = wct(ix1,ix2,iw_e) - ke - em
233 end if
234 ! dn is the largest rate that changes egas by rt_sc_cfl of itself over qdt. The limiter
235 ! leaves smaller rates untouched and caps larger ones at dn, keeping their sign
236 ! dn = cfl e_gas/qdt, q_lim = q dn/max(dn, abs(q)), e = e + qdt w q_lim
237 dn = rt_sc_cfl*max(egas,smalldouble)/max(qdt,smalldouble)
238 qlim = qc*dn/max(dn,dabs(qc))
239 if(thin_share) qlim = qlim*(1.0d0 - rt_sc_thin_weight(te(ix1,ix2), x(ix1,ix2,updim), pth(ix1,ix2)))
240 w(ix1,ix2,iw_e) = w(ix1,ix2,iw_e) + qdt*qlim
241 end do
242 end do
243 }
244 end subroutine rt_sc_add_source
245
246 !> Share of the radiative losses taken by the optically thin treatment, with SC taking the rest,
247 !> 1 - w_thin. The thin cooling and the user SC source both call this, so they share one
248 !> definition. Inside a band w_thin is the product of a temperature gate and a pressure gate, so
249 !> a cell goes to SC when it is cool or dense, as in the opacity switch. The pressure gate
250 !> max(0, 1 - (p/p_TR)^2) of MURaM's Qthin is exactly zero in the photosphere. Outside every
251 !> band, or without RT, w_thin = 1, because there is no SC there to take over. The step to one
252 !> at the band edge marks where SC stops, and the gas there is coronal, with the temperature
253 !> gate already close to one
254 pure elemental function rt_sc_thin_weight(Te_code, y_code, p_code) result(w_thin)
256 !> Temperature in code units
257 double precision, intent(in) :: te_code
258 !> Height along updim in code units
259 double precision, intent(in) :: y_code
260 !> Thermal pressure in code units
261 double precision, intent(in) :: p_code
262 double precision :: w_thin, pr
263 integer :: ib
264
265 w_thin = 1.0d0
266 if(.not. rt_sc_active) return
267 do ib = 1, rt_sc_nband
268 ! The band edges are those after the block snap, which is where SC acts
269 if(y_code >= band_lo_c(ib) .and. y_code <= band_hi_c(ib)) then
270 ! The gate parameters are in K and cgs, so the comparison is made in physical units
271 ! w_thin = (1 + tanh((T - T0)/dT))/2 max(0, 1 - (p/p_TR)^2)
272 pr = p_code*unit_pressure/rt_sc_p_tr
273 w_thin = 0.5d0*(1.0d0 + tanh((te_code*unit_temperature - rt_sc_blend_t0)/rt_sc_blend_dt)) &
274 * max(0.0d0, 1.0d0 - pr*pr)
275 return
276 end if
277 end do
278 end function rt_sc_thin_weight
279
280end module mod_rt_sc_slab
subroutine, public mpistop(message)
Exit MPI-AMRVAC with an error message.
Equation of state for AMRVAC, handled through a single eos_container object.
Definition mod_eos.t:30
This module contains definitions of global parameters and variables and some generic functions/subrou...
double precision unit_pressure
Physical scaling factor for pressure.
integer, parameter ndim
Number of spatial dimensions for grid variables.
character(len=std_len), dimension(:), allocatable par_files
Which par files are used as input.
integer icomm
The MPI communicator.
integer mype
The rank of the current MPI task.
double precision, dimension(:), allocatable, parameter d
double precision, dimension(:,:), allocatable rnode
Corner coordinates.
double precision unit_temperature
Physical scaling factor for temperature.
Public face of the short-characteristics (SC) radiative transfer and its coupling to the (M)HD.
pure elemental double precision function, public rt_sc_thin_weight(te_code, y_code, p_code)
Share of the radiative losses taken by the optically thin treatment, with SC taking the rest,...
subroutine, public rt_sc_add_source(qdt, ixil, ixol, wct, w, x, thin_share, e_internal)
Unsplit heating source of one block and stage, e += qdt Q, with Q read from i_qrad,...
subroutine, public rt_sc_selftest()
Self-test of the real sweep on an analytic slab. J must approach B where the slab is optically thick,...
subroutine, public rt_sc_emergent_tau1()
Horizontal mean of Fy on the tau = rt_sc_tau_emergent surface, the photosphere, into rt_sc_Ftau1....
subroutine, public rt_sc_pin_refine(igrid, level, my_refine, my_coarsen)
Hold every block overlapping an RT band on rt_sc_amr_level. Called from forcedrefine_grid after usr_r...
subroutine, public rt_sc_activate()
Switch the RT on. The physics module calls this from its initialisation when hd_radiative_transfer or...
Solvers of the short-characteristics (SC) radiative transfer and their dispatch.
subroutine rt_tau_tops(tautop)
Tau at the top face of every block row of the current band, per global column along hdim....
subroutine rt_band_blocks(ib)
List the blocks of this rank that lie in band ib, with their block position (bi, bj),...
subroutine rt_block_tau(iblk, tautop, taub)
Tau from the band top to every cell centre of block iblk, integrated down from the top face of the bl...
Parameters, resolved state and lifecycle of the short-characteristics (SC) radiative transfer,...
integer, public i_frad
integer, public rt_sc_nband
logical, public rt_sc_distributed
The distributed KBA wavefront. Each rank sweeps only its own band blocks, one diagonal at a time,...
integer, public rt_sc_amr_level
The AMR level the RT runs on. The bands are held at this level so that the RT sees a uniform grid,...
double precision, public rt_sc_ftau1
Horizontal mean of the flux on the tau = rt_sc_tau_emergent surface, from rt_sc_emergent_tau1....
logical, public rt_sc_active
True when the physics module has switched the RT on, with hd_radiative_transfer or mhd_radiative_tran...
integer rg_nh
The RT grid on rt_sc_amr_level along (hdim, updim): cells across the domain, cells per block,...
subroutine, public rt_sc_reserve_slots()
Reserve the wavefront intensity slots in ps_rt, four in all, one per sweep corner,...
integer, dimension(:), allocatable wv_ig
integer, dimension(:), allocatable wv_bi
double precision, public rt_sc_cfl
integer, public i_iout
pure subroutine hv(ih, iv, i1, i2)
Block indices (i1, i2) of the cell at position ih along hdim and iv along updim.
integer, public i_qrad
Extra-variable indices of the heating Q, the vertical flux F and the emergent intensity Iout,...
double precision, public rt_sc_blend_dt
integer wv_nblk
The band blocks of this rank with their block position (bi, bj), the block rows of the current band a...
double precision, dimension(rt_sc_maxband) band_lo_c
integer, dimension(:), allocatable wv_bj
double precision, public rt_sc_tau_emergent
subroutine, public rt_sc_params_read(files)
Read &rt_list from each parameter file. The values stay physical until rt_sc_setup,...
double precision, dimension(rt_sc_maxband) band_hi_c
logical, public rt_sc_valpipe
For validation, runs the ray-by-ray and pipelined waves on the same state and prints max|dQ|.
double precision, public rt_sc_blend_t0
Centre and half-width in K of the temperature gate in rt_sc_thin_weight, which divides the radiative ...
integer function var_set_extravar(name_cons, name_prim, ix)
Set extra variable in w, which is not advected and has no boundary conditions. This has to be done af...