MPI-AMRVAC 3.2
The MPI - Adaptive Mesh Refinement - Versatile Advection Code (development version)
Loading...
Searching...
No Matches
mod_eos_lte.t
Go to the documentation of this file.
1!=============================================================================
2!> LTE (Saha-table) EoS kernels and finalise for the eos% family.
3!>
4!> Carved out of mod_eos.t (the per-type split). Owns everything LTE: the
5!> per-substep update_eos_LTE (caches Te_/Ne_), the cached-Te/Rfactor getters,
6!> the scalar table kernels (T, y, p2eint, gamma1, p/nH, eint, bisections), the
7!> per-method table finalisers, and eos_finalise_LTE (the LTE arm of the
8!> eos_finalise dispatcher). Depends only on the EoS leaf modules + shared
9!> accessors, never on mod_eos -> no circular use. mod_eos re-exports the public
10!> kernels so existing `use mod_eos` callers are unaffected.
11!=============================================================================
22 use mod_comm_lib, only: mpistop
23
24 implicit none
25 private
26
27 !> update_eos_LTE, get_Te_LTE, get_temperature_from_eint_LTE and
28 !> Rfactor_from_LTE are PRIVATE: all bound to eos% pointers inside
29 !> eos_finalise_LTE, so no external module names them.
30 !> get_temperature_from_eint_fast_LTE stays public -- the seam binds it to
31 !> tc_fl%get_temperature_ciso2_from_eint (fast TC path, optional p/rho)
33 !> Scalar table kernels (re-exported by mod_eos for the hd/mhd/ffhd seams).
34 !> T_and_y_from_nH_eint is internal-only (entropy update path).
38 !> LTE arms of the eos_init / eos_finalise dispatchers
41
42contains
43
44 !> LTE arm of eos_init (before units are known): dispatch on eos_method to
45 !> the chosen method's loader, each owned by its method module (analytic loads
46 !> nothing). The LTE temperature-from-pressure getter is a later pass, so
47 !> unlike FI/PI it is left unset here.
48 subroutine eos_init_lte()
49 select case (eos%method_id)
50 case (eos_analytic); call load_analytic_lte()
51 case (eos_entropy); call load_entropy_lte()
52 case default; call load_state_lte() ! EOS_STATE
53 end select
54 end subroutine eos_init_lte
55
56 !> LTE arm of eos_finalise: wire the LTE runtime pointer targets, then
57 !> dispatch on eos_method to the per-method finaliser, each owned by its
58 !> method module (analytic->mod_eos_lte_saha, entropy->mod_eos_lte_entropy,
59 !> tables->mod_eos_lte_tables).
60 subroutine eos_finalise_lte()
61 eos%update_eos => update_eos_lte
62 ! eos%get_thermal_pressure is set by (m)hd_link_eos
63 eos%get_temperature_from_eint => get_temperature_from_eint_lte
64 eos%get_Te => get_te_lte
65 !> R-factor: physics-independent (cached-Ne lookup), bound here rather
66 !> than in the hd/mhd/ffhd link arms so the target stays private.
67 eos%get_Rfactor => rfactor_from_lte
68 select case (eos%method_id)
71 case default; call finalise_state_lte() ! EOS_STATE
72 end select
73 end subroutine eos_finalise_lte
74
75
76 subroutine update_eos_lte(ixI^L, ixO^L, w, x)
77 use mod_physics
78 !> This routine is called before each RK substep
79 integer, intent(in) :: ixi^l,ixo^l
80 double precision, intent(in) :: x(ixi^s,1:ndim)
81 double precision, intent(inout) :: w(ixi^s,1:nw)
82
83 double precision :: wlocal(ixi^s,1:nw)
84 double precision :: new_y(ixi^s)
85 double precision :: pth(ixi^s)
86 double precision :: nh_in(ixi^s), nh(ixi^s), eint_in(ixi^s)
87 double precision :: rfactor_fi
88 double precision :: yy
89 integer :: ix^d
90
91 wlocal(ixi^s,1:nw)=w(ixi^s,1:nw)
92 ! wlocal holds the internal energy, and pth the ideal-gas pressure, valid only without ionE
93 call phys_e_to_ei(ixi^l,ixo^l,wlocal,x)
94 pth(ixo^s) = eos%gamma_minus_1 * wlocal(ixo^s,iw_e)
95
96 call eos%get_nH(w, x, ixi^l, ixo^l, nh)
97 ! Enforce internal energy floor for EoS lookup only.
98 ! Prevents NaN from dlog10(eint<0) after strong rarefactions where
99 ! kinetic energy can numerically exceed total energy.
100 ! The conserved energy w(iw_e) is NOT modified: the physical fluxes
101 ! based on the small positive pressure will naturally restore the cell.
102 ! eint_nH_min is the forward-table edge, or 100 K of neutral gas under analytic
103 wlocal(ixo^s,iw_e) = max(wlocal(ixo^s,iw_e), nh(ixo^s)*eos%eint_nH_min)
104 if (eos%method_id /= eos_analytic) then
105 nh_in(ixo^s) = dlog10(nh(ixo^s))
106 eint_in(ixo^s) = dlog10(wlocal(ixo^s,iw_e)) - nh_in(ixo^s)
107 end if
108
109 !> Constant FI Rfactor for the fully-ionised fast path.
110 !> Must NOT use eos%get_Rfactor which reads iw_ne (uninitialised at IC).
111 rfactor_fi = eos%n_per_nH_FI / eos%nH2rhoFactor
112
113 {do ix^db=ixomin^db,ixomax^db\}
114 if (wlocal(ix^d,iw_e) / w(ix^d,iw_rho) > eos%eint_rho_FI_threshold) then
115 !> Fully ionised: analytical formulae, no table lookups
116 new_y(ix^d) = eos%neOnH_FI
117 if (eos%ionE) then
118 !> T = (eint - eion*nH) * (gamma-1) / (Rfactor_FI * rho)
119 w(ix^d,iw_te) = eos%gamma_minus_1 &
120 * (wlocal(ix^d,iw_e) - eos%eion_per_nH * nh(ix^d)) &
121 / (rfactor_fi * w(ix^d,iw_rho))
122 else
123 w(ix^d,iw_te) = pth(ix^d) / (nh(ix^d) * (1.0d0 + eos%He_abundance + new_y(ix^d)))
124 end if
125 else
126 !> Ionisation zone
127 if (eos%method_id == eos_analytic) then
128 !> Analytical Saha: solve quadratic for T and y
129 call saha_t_from_nh_eint(nh(ix^d), &
130 wlocal(ix^d,iw_e) / nh(ix^d), &
131 w(ix^d,iw_te), yy)
132 new_y(ix^d) = yy
133 else
134 !> Table lookups: fused T+y for shared index computation
135 if (.not. eos%ionE) then
136 new_y(ix^d) = y_from_nh_eint(nh_in(ix^d),eint_in(ix^d))
137 w(ix^d,iw_te) = pth(ix^d) / (nh(ix^d) * (1.0d0 + eos%He_abundance + new_y(ix^d)))
138 else if (eos%method_id == eos_entropy) then
139 !> Entropy method dispatches T_and_y_from_nH_eint to the
140 !> biquintic-Hermite forward at any eint; no FI fallback
141 !> needed (and eos%T isn't loaded so var2_max is 0 here).
142 call t_and_y_from_nh_eint(nh_in(ix^d), eint_in(ix^d), &
143 w(ix^d,iw_te), yy)
144 new_y(ix^d) = yy
145 else
146 if (eint_in(ix^d) < eos%T%var2_max) then
147 call t_and_y_from_nh_eint(nh_in(ix^d), eint_in(ix^d), &
148 w(ix^d,iw_te), yy)
149 new_y(ix^d) = yy
150 else
151 !> Above-table fallback with ionisation energy correction
152 new_y(ix^d) = eos%neOnH_FI
153 w(ix^d,iw_te) = eos%gamma_minus_1 &
154 * (wlocal(ix^d,iw_e) - eos%eion_per_nH * nh(ix^d)) &
155 / (rfactor_fi * w(ix^d,iw_rho))
156 end if
157 end if
158 end if
159 end if
160 {end do\}
161
162 w(ixo^s,iw_ne) = new_y(ixo^s) * nh(ixo^s)
163
164 end subroutine update_eos_lte
165
166 !> Thermodynamic getters shared verbatim by the hd/mhd seams (eos%/phys
167 !> pointer targets). Only routines free of physics-module symbols live here:
168 !> Rfactor_from_LTE uses the cached Ne_ field; get_gamma1_FI returns
169 !> eos%gamma. The FI/LTE variants that read primitive indices (rho_,p_) or
170 !> the physics RR stay in each seam.
171 !> Rfactor from LTE EOS: R = (1 + He + ne/nH) / nH2rhoFactor
172 !> Uses stored Ne_ wextra field from update_eos_LTE.
173 subroutine rfactor_from_lte(w,x,ixI^L,ixO^L,Rfactor)
175 integer, intent(in) :: ixi^l, ixo^l
176 double precision, intent(in) :: w(ixi^s,1:nw)
177 double precision, intent(in) :: x(ixi^s,1:ndim)
178 double precision, intent(out):: rfactor(ixi^s)
179
180 double precision :: y_nh
181 integer :: ix^d
182
183 {do ix^db=ixomin^db,ixomax^db\}
184 y_nh = w(ix^d, iw_ne) * eos%nH2rhoFactor / w(ix^d, iw_rho)
185 rfactor(ix^d) = (1.0d0 + eos%He_abundance + y_nh) / eos%nH2rhoFactor
186 {end do\}
187
188 end subroutine rfactor_from_lte
189
190 !>#######################################################################
191 !> Temperature getters: cached Te accessors and temperature from the
192 !> energy/pressure variable. The from-eint variants are split by eos_type
193 !> (needed by the STS path: dt sees conserved, set_source sees eint -- see
194 !> mod_thermal_conduction).
195 !>#######################################################################
196
197 subroutine get_te_lte(w,x,ixI^L,ixO^L,T)
199 integer, intent(in) :: ixi^l, ixo^l
200 double precision, intent(in) :: w(ixi^s,1:nw)
201 double precision, intent(in) :: x(ixi^s,1:ndim)
202 double precision, intent(out) :: t(ixi^s)
203
204 t(ixo^s) = w(ixo^s,iw_te)
205
206 end subroutine get_te_lte
207
208 subroutine get_temperature_from_eint_lte(w, x, ixI^L, ixO^L, res)
209 !> Assumes input energy is internal energy.
210 !> Includes FI bypass: cells with eint/rho above threshold skip table lookups.
211 use mod_physics
212 integer, intent(in) :: ixi^l,ixo^l
213 double precision, intent(in) :: x(ixi^s,1:ndim)
214 double precision, intent(in) :: w(ixi^s,1:nw)
215 double precision, intent(out) :: res(ixi^s)
216
217 double precision :: nh(ixi^s),nh_in(ixi^s), eint_in(ixi^s)
218 double precision :: rfactor_fi, t_loc, y_loc
219 integer :: ix^d
220
221 rfactor_fi = eos%n_per_nH_FI / (1.0d0 + 4.0d0*eos%He_abundance)
222
223 call eos%get_nH(w, x, ixi^l, ixo^l, nh)
224 nh_in(ixo^s) = dlog10(nh(ixo^s))
225 eint_in(ixo^s) = dlog10(w(ixo^s,iw_e)) - nh_in(ixo^s)
226
227 {do ix^db=ixomin^db,ixomax^db\}
228 if (w(ix^d,iw_e) / w(ix^d,iw_rho) > eos%eint_rho_FI_threshold) then
229 !> Fully ionised: T = (eint - eion*nH) * (gamma-1) / (Rfactor_FI * rho)
230 res(ix^d) = eos%gamma_minus_1 &
231 * (w(ix^d,iw_e) - eos%eion_per_nH * nh(ix^d)) &
232 / (rfactor_fi * w(ix^d,iw_rho))
233 else if (eos%method_id == eos_analytic) then
234 call saha_t_from_nh_eint(nh(ix^d), &
235 w(ix^d,iw_e) / nh(ix^d), t_loc, y_loc)
236 res(ix^d) = t_loc
237 else
238 res(ix^d) = t_from_nh_eint(nh_in(ix^d),eint_in(ix^d))
239 endif
240 {end do\}
241
242 end subroutine get_temperature_from_eint_lte
243
244 subroutine get_temperature_from_eint_fast_lte(w, x, ixI^L, ixO^L, res, ciso2)
245 !> Fast TC variant: two-pass regime-aware bypass.
246 !>
247 !> Pass 1 (vectorised): compute FI formula for ALL cells as array ops.
248 !> The compiler vectorises this with SVML (no branches, pure arithmetic).
249 !>
250 !> Pass 2 (scalar): overwrite ionisation-zone cells with
251 !> bilinear table lookup + dexp. Threshold check uses multiply
252 !> (eint <= threshold * rho) to avoid a 14-cycle scalar division.
253 !> With ciso2 present it also returns p/rho = (1 + He + y) T / nH2rhoFactor at the
254 !> current eint
255 use mod_physics
256 integer, intent(in) :: ixi^l,ixo^l
257 double precision, intent(in) :: x(ixi^s,1:ndim)
258 double precision, intent(in) :: w(ixi^s,1:nw)
259 double precision, intent(out) :: res(ixi^s)
260 double precision, intent(out), optional :: ciso2(ixi^s)
261
262 double precision :: inv_rfactor_fi, rfactor_fi, eion_rho_inv, inv_nh2rho, onephe
263 double precision :: log_nh_val, log_eint_nh_val
264 double precision :: fx, fy, rx, ry, nh_loc, t_loc, y_loc
265 integer :: jx, jy, jx1, jy1, ix^d
266 logical :: want_ciso2
267 double precision, parameter :: ln10 = 2.302585092994046d0
268
269 if (eos%type_id /= eos_type_lte) call mpistop("get_temperature_from_eint_fast_LTE called outside its eos_type (LTE)")
270 call eos_timer_start()
271
272 want_ciso2 = present(ciso2)
273
274 !> Precompute scalar constants (avoid per-cell divisions)
275 inv_rfactor_fi = (1.0d0 + 4.0d0*eos%He_abundance) / eos%n_per_nH_FI
276 rfactor_fi = eos%n_per_nH_FI / (1.0d0 + 4.0d0*eos%He_abundance)
277 eion_rho_inv = eos%eion_per_nH / eos%nH2rhoFactor
278 inv_nh2rho = 1.0d0 / eos%nH2rhoFactor
279 onephe = 1.0d0 + eos%He_abundance
280
281 !> Pass 1: FI formula for ALL cells (vectorisable array operations).
282 !> T = (gamma-1) * (eint - eion_per_rho * rho) / (Rfactor_FI * rho)
283 res(ixo^s) = eos%gamma_minus_1 * inv_rfactor_fi &
284 * (w(ixo^s,iw_e) - eion_rho_inv * w(ixo^s,iw_rho)) &
285 / w(ixo^s,iw_rho)
286 if (want_ciso2) ciso2(ixo^s) = rfactor_fi * res(ixo^s)
287
288 !> Pass 2: overwrite ionisation-zone cells.
289 {do ix^db=ixomin^db,ixomax^db\}
290 if (w(ix^d,iw_e) <= eos%eint_rho_FI_threshold * w(ix^d,iw_rho)) then
291 if (eos%method_id == eos_analytic) then
292 nh_loc = w(ix^d, iw_rho) / eos%nH2rhoFactor
293 call saha_t_from_nh_eint(nh_loc, &
294 w(ix^d,iw_e) / nh_loc, t_loc, y_loc)
295 res(ix^d) = t_loc
296 if (want_ciso2) ciso2(ix^d) = (onephe + y_loc) * inv_nh2rho * t_loc
297 else if (eos%method_id == eos_entropy) then
298 if (iw_log_nh > 0) then
299 log_nh_val = block%wextra(ix^d, iw_log_nh)
300 else
301 log_nh_val = dlog10(w(ix^d, iw_rho) / eos%nH2rhoFactor)
302 end if
303 log_eint_nh_val = dlog10(w(ix^d, iw_e)) - log_nh_val
304 if (want_ciso2) then
305 call entropy_t_and_y_from_nh_eint(log_nh_val, log_eint_nh_val, &
306 t_loc, y_loc)
307 res(ix^d) = t_loc
308 ciso2(ix^d) = (onephe + y_loc) * inv_nh2rho * t_loc
309 else
310 res(ix^d) = entropy_t_from_nh_eint(log_nh_val, log_eint_nh_val)
311 end if
312 else
313 if (iw_log_nh > 0) then
314 log_nh_val = block%wextra(ix^d, iw_log_nh)
315 else
316 log_nh_val = dlog10(w(ix^d, iw_rho) / eos%nH2rhoFactor)
317 end if
318 log_eint_nh_val = dlog10(w(ix^d, iw_e)) - log_nh_val
319
320 if (eos%T%is_uniform) then
321 !> Inlined uniform bilinear (hot path: no function call).
322 ry = max(0.0d0, min((log_nh_val - eos%T%var1_min) * eos%T%step_inv_1, &
323 dble(eos%T%dim1-1)))
324 rx = max(0.0d0, min((log_eint_nh_val - eos%T%var2_min) * eos%T%step_inv_2, &
325 dble(eos%T%dim2-1)))
326 jy = int(ry); jx = int(rx)
327 jy1 = min(jy+1, eos%T%dim1-1)
328 jx1 = min(jx+1, eos%T%dim2-1)
329 fy = ry - dble(jy); fx = rx - dble(jx)
330 res(ix^d) = dexp(ln10 * ( &
331 (1.0d0-fy)*((1.0d0-fx)*eos%T%table(jy+1,jx+1) &
332 + fx *eos%T%table(jy+1,jx1+1)) &
333 + fy *((1.0d0-fx)*eos%T%table(jy1+1,jx+1) &
334 + fx *eos%T%table(jy1+1,jx1+1))))
335 else
336 !> Non-uniform: dispatch to the binary-search bilinear.
337 !> Slightly slower per cell (binary search adds ~8 cmps per axis)
338 !> but unavoidable when the grid is non-uniform.
339 res(ix^d) = dexp(ln10 * bilinear_lookup( &
340 log_nh_val, log_eint_nh_val, eos%T))
341 end if
342 if (want_ciso2) ciso2(ix^d) = (onephe &
343 + y_from_nh_eint(log_nh_val, log_eint_nh_val)) * inv_nh2rho * res(ix^d)
344 end if
345 end if
346 {end do\}
347
348 call eos_timer_stop(timeeos_tfromei)
350
351 !> Scalar EoS kernels: one cell on (log10 nH, log10 eint/nH) or (log10 nH, log10 p/nH), in
352 !> code units, each dispatching to the analytic Saha solve, the entropy tables or the tables
353
354 !> Ionisation fraction ne/nH from (log10 nH, log10 eint/nH)
355 double precision function y_from_nh_eint(nH, eint_nh) result(result_val)
356 double precision, intent(in) :: nh, eint_nh
357 double precision, parameter :: ln10 = 2.302585092994046d0
358 double precision :: t_loc, y_loc, eint_rho
359
360 if (eos%type_id /= eos_type_lte) call mpistop("y_from_nH_eint called outside its eos_type (LTE)")
361 if (eos%method_id == eos_analytic) then
362 ! A fully ionised cell skips the Saha solve
363 eint_rho = 10.0d0**eint_nh / eos%nH2rhoFactor
364 if (eint_rho > eos%eint_rho_FI_threshold) then
365 result_val = eos%neOnH_FI
366 return
367 end if
368 call saha_t_from_nh_eint(10.0d0**nh, 10.0d0**eint_nh, t_loc, y_loc)
369 result_val = y_loc
370 else if (eos%method_id == eos_entropy) then
371 result_val = entropy_y_from_nh_eint(nh, eint_nh)
372 else
373 result_val = dexp(ln10 * bicubic_lookup(nh, eint_nh, eos%neOnH))
374 end if
375 end function y_from_nh_eint
376
377 !> Temperature from (log10 nH, log10 eint/nH)
378 double precision function t_from_nh_eint(nH, eint_nh) result(result_val)
379 double precision, intent(in) :: nh, eint_nh
380 double precision, parameter :: ln10 = 2.302585092994046d0
381 double precision :: t_loc, y_loc, eint_rho
382
383 if (eos%type_id /= eos_type_lte) call mpistop("T_from_nH_eint called outside its eos_type (LTE)")
384 if (eos%method_id == eos_analytic) then
385 ! A fully ionised cell skips the Saha solve
386 eint_rho = 10.0d0**eint_nh / eos%nH2rhoFactor
387 if (eint_rho > eos%eint_rho_FI_threshold) then
388 result_val = eos%gamma_minus_1 &
389 * (10.0d0**eint_nh - eos%eion_per_nH) / eos%n_per_nH_FI
390 return
391 end if
392 call saha_t_from_nh_eint(10.0d0**nh, 10.0d0**eint_nh, t_loc, y_loc)
393 result_val = t_loc
394 else if (eos%method_id == eos_entropy) then
395 result_val = entropy_t_from_nh_eint(nh, eint_nh)
396 else
397 result_val = dexp(ln10 * bicubic_lookup(nh, eint_nh, eos%T))
398 end if
399 end function t_from_nh_eint
400
401 !> Temperature and ionisation fraction from (log10 nH, log10 eint/nH), sharing one table
402 !> locate when the interleaved table exists
403 subroutine t_and_y_from_nh_eint(log_nH, log_eint_nH, T_out, y_out)
404 double precision, intent(in) :: log_nh, log_eint_nh
405 double precision, intent(out) :: t_out, y_out
406 double precision, parameter :: ln10 = 2.302585092994046d0
407 double precision :: results(3)
408
409 if (eos%method_id == eos_analytic) then
410 call saha_t_from_nh_eint(10.0d0**log_nh, 10.0d0**log_eint_nh, t_out, y_out)
411 else if (eos%method_id == eos_entropy) then
412 call entropy_t_and_y_from_nh_eint(log_nh, log_eint_nh, t_out, y_out)
413 else if (allocated(eos%table_eint_il)) then
414 ! The interleaved table holds (T, ne/nH, p/nH) per node
415 if (eos%T%is_uniform) then
416 call interp_pchip_interleaved(log_nh, log_eint_nh, &
417 eos%table_eint_il, 3, eos%T%dim2, eos%T%dim1, &
418 eos%T%var1_min, eos%T%var1_max, &
419 eos%T%var2_min, eos%T%var2_max, results)
420 else
421 call interp_pchip_interleaved_nu(log_nh, log_eint_nh, &
422 eos%table_eint_il, 3, eos%T%dim2, eos%T%dim1, &
423 eos%T%var1_nodes, eos%T%var2_nodes, &
424 eos%T%guard_1, eos%T%guard_M_1, eos%T%guard_scale_1, &
425 eos%T%guard_2, eos%T%guard_M_2, eos%T%guard_scale_2, &
426 results)
427 end if
428 t_out = dexp(ln10 * results(1))
429 y_out = dexp(ln10 * results(2))
430 else
431 t_out = dexp(ln10 * bicubic_lookup(log_nh, log_eint_nh, eos%T))
432 y_out = dexp(ln10 * bicubic_lookup(log_nh, log_eint_nh, eos%neOnH))
433 end if
434 end subroutine t_and_y_from_nh_eint
435
436 !> Ratio eint/p from (log10 nH, log10 p/nH)
437 double precision function p2eint_from_nh_p(nH, ponH) result(result_val)
438 double precision, intent(in) :: nh, ponh
439 double precision :: nh_code, p_code, t_loc, y_loc, eint_nh_loc, p_rho
440
441 if (eos%type_id /= eos_type_lte) call mpistop("p2eint_from_nH_p called outside its eos_type (LTE)")
442 if (eos%method_id == eos_analytic) then
443 nh_code = 10.0d0**nh
444 p_code = nh_code * 10.0d0**ponh
445 ! A fully ionised cell skips the Saha solve
446 p_rho = p_code / (nh_code * eos%nH2rhoFactor)
447 if (p_rho > eos%p_rho_FI_threshold) then
448 result_val = eos%inv_gamma_minus_1 &
449 + eos%eion_per_nH * nh_code / p_code
450 return
451 end if
452 call saha_state_from_nh_p(nh_code, p_code, t_loc, y_loc, eint_nh_loc)
453 result_val = eint_nh_loc * nh_code / p_code
454 else if (eos%method_id == eos_entropy) then
455 result_val = entropy_eint_from_nh_p(nh, ponh)
456 else
457 result_val = bicubic_lookup(nh, ponh, eos%p2eint)
458 end if
459 end function p2eint_from_nh_p
460
461 !> Gamma_1 from (log10 nH, log10 p/nH), one lookup on the pressure-indexed table
462 double precision function gamma1_from_nh_p(log_nH, log_p_nH) result(g1)
463 double precision, intent(in) :: log_nh, log_p_nh
464 if (eos%type_id /= eos_type_lte) call mpistop("gamma1_from_nH_p called outside its eos_type (LTE)")
465 if (eos%method_id == eos_entropy) then
466 g1 = entropy_gamma1_from_nh_p(log_nh, log_p_nh)
467 else
468 g1 = bicubic_lookup(log_nh, log_p_nh, eos%gamma1_p)
469 end if
470 end function gamma1_from_nh_p
471
472 !> Gamma_1 of one cell from (p, rho): the ideal value where fully ionised, else the pressure-
473 !> indexed table of the method
474 double precision function lte_gamma1_from_p(pres, rho_val) result(g1)
475 double precision, intent(in) :: pres, rho_val
476 double precision :: nh_val
477 if (pres/rho_val > eos%p_rho_FI_threshold) then
478 g1 = eos%gamma
479 else
480 nh_val = rho_val/eos%nH2rhoFactor
481 g1 = gamma1_from_nh_p(dlog10(nh_val), dlog10(pres/nh_val))
482 end if
483 end function lte_gamma1_from_p
484
485 !> log10(p/nH) from (log10 nH, log10 eint/nH)
486 double precision function log_p_from_nh_eint(log_nH, log_eint_nH) result(lp)
487 double precision, intent(in) :: log_nh, log_eint_nh
488 lp = bicubic_lookup(log_nh, log_eint_nh, eos%log_p)
489 end function log_p_from_nh_eint
490
491 !> p/nH = (1 + He + y) T from (log10 nH, log10 eint/nH)
492 double precision function p_nh_from_eint(log_nH, log_eint_nH) result(p_nH)
493 double precision, intent(in) :: log_nh, log_eint_nh
494 double precision, parameter :: ln10 = 2.302585092994046d0
495 double precision :: t_loc, y_loc
496
497 if (eos%type_id /= eos_type_lte) call mpistop("p_nH_from_eint called outside its eos_type (LTE)")
498 if (eos%method_id == eos_analytic) then
499 call saha_t_from_nh_eint(10.0d0**log_nh, 10.0d0**log_eint_nh, t_loc, y_loc)
500 p_nh = (1.0d0 + eos%He_abundance + y_loc) * t_loc
501 else if (eos%method_id == eos_entropy) then
502 p_nh = entropy_p_nh_from_eint(log_nh, log_eint_nh)
503 else
504 p_nh = dexp(ln10 * bicubic_lookup(log_nh, log_eint_nh, eos%p_over_nH))
505 end if
506 end function p_nh_from_eint
507
508 !> eint/nH from (log10 nH, log10 T), from the inverse table when one is built and the Saha
509 !> solve otherwise
510 double precision function eint_nh_from_t(log_nH, log_T) result(eint_nH)
511 double precision, intent(in) :: log_nh, log_t
512 double precision, parameter :: ln10 = 2.302585092994046d0
513 double precision :: log_e_nh
514
515 if (eos%type_id /= eos_type_lte) call mpistop("eint_nH_from_T called outside its eos_type (LTE)")
516 if (eos%method_id == eos_entropy) then
517 log_e_nh = entropy_eint_from_nh_t(log_nh, log_t)
518 eint_nh = 10.0d0**log_e_nh
519 else if (allocated(eos%eint_from_T%table)) then
520 eint_nh = dexp(ln10 * bicubic_lookup(log_nh, log_t, eos%eint_from_T))
521 else
522 eint_nh = saha_eint_from_nh_t(10.0d0**log_nh, 10.0d0**log_t)
523 end if
524 end function eint_nh_from_t
525
526 !>#######################################################################
527 !> Iterative solvers: table-guessed bisection for the WB pressure->eint
528 !> inversion since the method needs high accuracy to not self-seed perturbations.
529 !>#######################################################################
530
531 !> Cached bisection on the log_p table for WB p->eint inversion.
532 !> Precomputes nH-direction indices and table values once, then
533 !> bisects using only cheap PCHIP evaluations with varying tx.
534 !> Expects a narrow initial bracket [lo, hi] (e.g. from p2eint guess).
535 !>
536 !> Adaptive-grid support: when eos%log_p%is_uniform is .false., the
537 !> y- and x-direction index calculations switch to binary search on
538 !> the explicit node arrays. The cached 4x4 stencil mechanism is
539 !> unchanged; only the index-to-cell mapping changes.
540 subroutine log_p_bisect_cached(log_nH, log_p_target, &
541 log_eint_lo, log_eint_hi, max_iter, log_eint_result)
543 double precision, intent(in) :: log_nh, log_p_target
544 double precision, intent(inout) :: log_eint_lo, log_eint_hi
545 integer, intent(in) :: max_iter
546 double precision, intent(out) :: log_eint_result
547
548 integer :: nx, ny, ix, iy, iter, ii
549 integer :: i0, i1, i2, i3, j0, j1, j2, j3
550 double precision :: tx, ty, rx, ry
551 double precision :: xstep_inv, ystep_inv
552 double precision :: vmin_x, vmax_x, vmin_y, vmax_y
553 double precision :: log_eint_mid, log_p_eval
554 logical :: is_unif
555
556 !> Table values: tv(y_row, x_col) for 4 y-rows x 4 x-cols
557 double precision :: tv(4,4)
558 !> Cached ix for detecting grid cell change
559 integer :: ix_cached
560
561 nx = eos%log_p%dim2 ! eint axis (varx)
562 ny = eos%log_p%dim1 ! nH axis (vary)
563 is_unif = eos%log_p%is_uniform
564
565 if (is_unif) then
566 vmin_x = eos%log_p%var2_min
567 vmax_x = eos%log_p%var2_max
568 vmin_y = eos%log_p%var1_min
569 vmax_y = eos%log_p%var1_max
570 xstep_inv = dble(nx-1) / (vmax_x - vmin_x)
571 ystep_inv = dble(ny-1) / (vmax_y - vmin_y)
572 end if
573
574 !> Precompute nH indices (constant across all iterations)
575 if (is_unif) then
576 ry = (log_nh - vmin_y) * ystep_inv
577 ry = max(0.0d0, min(ry, dble(ny-1)))
578 iy = int(ry)
579 ty = ry - dble(iy)
580 else
581 !> Non-uniform: binary search on var1_nodes (length ny = dim1)
582 if (log_nh <= eos%log_p%var1_nodes(1)) then
583 iy = 0; ty = 0.0d0
584 else if (log_nh >= eos%log_p%var1_nodes(ny)) then
585 iy = ny - 2; ty = 1.0d0
586 else
587 ii = find_index_guard(eos%log_p%var1_nodes, ny, log_nh, &
588 eos%log_p%guard_1, eos%log_p%guard_M_1, eos%log_p%guard_scale_1)
589 iy = max(0, min(ii - 2, ny - 2))
590 ty = (log_nh - eos%log_p%var1_nodes(iy+1)) &
591 / (eos%log_p%var1_nodes(iy+2) - eos%log_p%var1_nodes(iy+1))
592 ty = max(0.0d0, min(ty, 1.0d0))
593 end if
594 end if
595
596 !> Clamped y-row indices
597 j0 = max(0, min(ny-1, iy-1))
598 j1 = max(0, min(ny-1, iy ))
599 j2 = max(0, min(ny-1, iy+1))
600 j3 = max(0, min(ny-1, iy+2))
601
602 ix_cached = -1
603
604 do iter = 1, max_iter
605 log_eint_mid = 0.5d0 * (log_eint_lo + log_eint_hi)
606
607 !> Compute eint-direction index
608 if (is_unif) then
609 rx = (log_eint_mid - vmin_x) * xstep_inv
610 rx = max(0.0d0, min(rx, dble(nx-1)))
611 ix = int(rx)
612 tx = rx - dble(ix)
613 else
614 !> Non-uniform: binary search on var2_nodes (length nx = dim2)
615 if (log_eint_mid <= eos%log_p%var2_nodes(1)) then
616 ix = 0; tx = 0.0d0
617 else if (log_eint_mid >= eos%log_p%var2_nodes(nx)) then
618 ix = nx - 2; tx = 1.0d0
619 else
620 ii = find_index_guard(eos%log_p%var2_nodes, nx, log_eint_mid, &
621 eos%log_p%guard_2, eos%log_p%guard_M_2, eos%log_p%guard_scale_2)
622 ix = max(0, min(ii - 2, nx - 2))
623 tx = (log_eint_mid - eos%log_p%var2_nodes(ix+1)) &
624 / (eos%log_p%var2_nodes(ix+2) - eos%log_p%var2_nodes(ix+1))
625 tx = max(0.0d0, min(tx, 1.0d0))
626 end if
627 end if
628
629 !> Load 16 table values only when ix changes
630 if (ix /= ix_cached) then
631 i0 = max(0, min(nx-1, ix-1))
632 i1 = max(0, min(nx-1, ix ))
633 i2 = max(0, min(nx-1, ix+1))
634 i3 = max(0, min(nx-1, ix+2))
635
636 tv(1,1) = eos%log_p%table(j0+1, i0+1)
637 tv(1,2) = eos%log_p%table(j0+1, i1+1)
638 tv(1,3) = eos%log_p%table(j0+1, i2+1)
639 tv(1,4) = eos%log_p%table(j0+1, i3+1)
640 tv(2,1) = eos%log_p%table(j1+1, i0+1)
641 tv(2,2) = eos%log_p%table(j1+1, i1+1)
642 tv(2,3) = eos%log_p%table(j1+1, i2+1)
643 tv(2,4) = eos%log_p%table(j1+1, i3+1)
644 tv(3,1) = eos%log_p%table(j2+1, i0+1)
645 tv(3,2) = eos%log_p%table(j2+1, i1+1)
646 tv(3,3) = eos%log_p%table(j2+1, i2+1)
647 tv(3,4) = eos%log_p%table(j2+1, i3+1)
648 tv(4,1) = eos%log_p%table(j3+1, i0+1)
649 tv(4,2) = eos%log_p%table(j3+1, i1+1)
650 tv(4,3) = eos%log_p%table(j3+1, i2+1)
651 tv(4,4) = eos%log_p%table(j3+1, i3+1)
652
653 ix_cached = ix
654 end if
655
656 !> Evaluate PCHIP: 4 x-rows then 1 y-interp
657 log_p_eval = pchip_2d_from_cache(tv, tx, ty)
658
659 if (log_p_eval < log_p_target) then
660 log_eint_lo = log_eint_mid
661 else
662 log_eint_hi = log_eint_mid
663 end if
664
665 if (dabs(log_eint_hi - log_eint_lo) < 1.0d-14) exit
666 end do
667
668 log_eint_result = 0.5d0 * (log_eint_lo + log_eint_hi)
669
670 contains
671
672 pure double precision function pchip_1d(p0, p1, p2, p3, t) result(v)
673 double precision, intent(in) :: p0, p1, p2, p3, t
674 double precision :: d0, d1, d2, m1, m2, s, a1, a2, lim
675 double precision :: tt, ttt, h00, h10, h01, h11
676
677 d0 = p1 - p0; d1 = p2 - p1; d2 = p3 - p2
678
679 if (d1 == 0.0d0) then
680 m1 = 0.0d0; m2 = 0.0d0
681 else
682 if (d0*d1 <= 0.0d0) then
683 m1 = 0.0d0
684 else
685 m1 = 2.0d0*d0*d1 / (d0 + d1)
686 end if
687 if (d1*d2 <= 0.0d0) then
688 m2 = 0.0d0
689 else
690 m2 = 2.0d0*d1*d2 / (d1 + d2)
691 end if
692 s = sign(1.0d0, d1)
693 a1 = s*m1; a2 = s*m2
694 if (a1 < 0.0d0) a1 = 0.0d0
695 if (a2 < 0.0d0) a2 = 0.0d0
696 lim = 3.0d0*abs(d1)
697 if (a1 > lim) a1 = lim
698 if (a2 > lim) a2 = lim
699 m1 = s*a1; m2 = s*a2
700 end if
701
702 tt = t*t; ttt = tt*t
703 h00 = 2.0d0*ttt - 3.0d0*tt + 1.0d0
704 h10 = ttt - 2.0d0*tt + t
705 h01 = -2.0d0*ttt + 3.0d0*tt
706 h11 = ttt - tt
707 v = h00*p1 + h10*m1 + h01*p2 + h11*m2
708 end function pchip_1d
709
710 pure double precision function pchip_2d_from_cache(c, tx, ty) result(z)
711 double precision, intent(in) :: c(4,4), tx, ty
712 double precision :: g0, g1, g2, g3
713 !> 4 x-direction interpolations (one per y-row)
714 g0 = pchip_1d(c(1,1), c(1,2), c(1,3), c(1,4), tx)
715 g1 = pchip_1d(c(2,1), c(2,2), c(2,3), c(2,4), tx)
716 g2 = pchip_1d(c(3,1), c(3,2), c(3,3), c(3,4), tx)
717 g3 = pchip_1d(c(4,1), c(4,2), c(4,3), c(4,4), tx)
718 !> 1 y-direction interpolation
719 z = pchip_1d(g0, g1, g2, g3, ty)
720 end function pchip_2d_from_cache
721
722 end subroutine log_p_bisect_cached
723
724 !> Given log10(nH) and log10(p), find log10(eint/nH) by table-guessed
725 !> bisection on the forward pressure table. Wraps the bracketing logic
726 !> and log_p_bisect_cached into a single call for use by both HD and MHD
727 !> from_conserved routines.
728 subroutine eint_from_p_bisect(log_nH_val, log_p_val, log_eint_nH_out)
729 double precision, intent(in) :: log_nh_val, log_p_val
730 double precision, intent(out) :: log_eint_nh_out
731
732 double precision :: log_p_target, p2eint_ratio, log_eint_guess
733 double precision :: log_p_at_guess, log_eint_lo, log_eint_hi
734 double precision :: f_bracket, margin
735 double precision :: eint_lim_lo, eint_lim_hi
736 integer :: max_iter
737
738 if (eos%type_id /= eos_type_lte) call mpistop("eint_from_p_bisect called outside its eos_type (LTE)")
739 log_p_target = log_p_val - log_nh_val
740
741 !> eint-axis limits: var2_min/var2_max are set for uniform tables only, so an adaptive
742 !> log_p table takes its end nodes, which keeps the clamps below inside the bicubic stencil
743 if (eos%log_p%is_uniform) then
744 eint_lim_lo = eos%log_p%var2_min
745 eint_lim_hi = eos%log_p%var2_max
746 else
747 eint_lim_lo = eos%log_p%var2_nodes(1)
748 eint_lim_hi = eos%log_p%var2_nodes(eos%log_p%dim2)
749 end if
750
751 ! Initial guess from p2eint table
752 p2eint_ratio = p2eint_from_nh_p(log_nh_val, log_p_target)
753 log_eint_guess = dlog10(p2eint_ratio) + log_p_target
754
755 log_eint_guess = max(log_eint_guess, eint_lim_lo)
756 log_eint_guess = min(log_eint_guess, eint_lim_hi)
757
758 log_p_at_guess = log_p_from_nh_eint(log_nh_val, log_eint_guess)
759
760 ! Establish bracket around the guess
761 margin = 5.0d-4
762 if (log_p_at_guess < log_p_target) then
763 log_eint_lo = log_eint_guess
764 log_eint_hi = min(log_eint_guess + margin, eint_lim_hi)
765 f_bracket = log_p_from_nh_eint(log_nh_val, log_eint_hi) - log_p_target
766 else
767 log_eint_lo = max(log_eint_guess - margin, eint_lim_lo)
768 log_eint_hi = log_eint_guess
769 f_bracket = -(log_p_from_nh_eint(log_nh_val, log_eint_lo) - log_p_target)
770 end if
771
772 if (f_bracket >= 0.0d0) then
773 max_iter = 8
774 else
775 log_eint_lo = eint_lim_lo
776 log_eint_hi = eint_lim_hi
777 max_iter = 20
778 end if
779
780 call log_p_bisect_cached(log_nh_val, log_p_target, &
781 log_eint_lo, log_eint_hi, max_iter, log_eint_nh_out)
782
783 end subroutine eint_from_p_bisect
784
785 !>#######################################################################
786 !> Table-metadata helpers: expose inverse-table grid bounds without
787 !> reaching into EoS table internals.
788 !>#######################################################################
789
790 !> log_nH grid metadata of the (log_nH, log_T) inverse table (eint from T),
791 !> choosing the container by method ('tables'->eint_from_T, 'entropy'->eintT).
792 !> n_nH=0 if no such table (analytic/FI). Lets cooling's build_Y_mod_table
793 !> get its grid without reaching into EoS table internals.
794 subroutine eos_get_eintt_grid(n_nH, lg_nH_min, lg_nH_max)
795 integer, intent(out) :: n_nh
796 double precision, intent(out) :: lg_nh_min, lg_nh_max
797
798 if (eos%type_id /= eos_type_lte) call mpistop("eos_get_eintT_grid called outside its eos_type (LTE)")
799 if (allocated(eos%eint_from_T%table)) then
800 n_nh = eos%eint_from_T%dim1
801 lg_nh_min = eos%eint_from_T%var1_min
802 lg_nh_max = eos%eint_from_T%var1_max
803 else if (allocated(eos%eintT%table)) then
804 n_nh = eos%eintT%dim1
805 lg_nh_min = eos%eintT%var1_min
806 lg_nh_max = eos%eintT%var1_max
807 else
808 n_nh = 0
809 lg_nh_min = 0.0d0
810 lg_nh_max = 0.0d0
811 end if
812 end subroutine eos_get_eintt_grid
813
814 !> LTE pressure from internal energy in one cell: ideal without ionE, else the table floor, the
815 !> FI bypass or p(nH, eint). log_nH_val is read only with ionE
816 double precision function lte_p_from_eint(eint_val,nH_val,log_nH_val,inv_rho) result(pres)
817 double precision, intent(in) :: eint_val, nh_val, log_nh_val, inv_rho
818 double precision :: e_use
819 if (.not. eos%ionE) then
820 pres = eos%gamma_minus_1 * eint_val
821 return
822 end if
823 ! The floor keeps the table logarithms and the Saha solve finite
824 e_use = max(eint_val, nh_val*eos%eint_nH_min, smalldouble)
825 if (e_use*inv_rho > eos%eint_rho_FI_threshold) then
826 pres = eos%gamma_minus_1*(e_use - eos%eion_per_nH*nh_val)
827 else if (eos%method_id == eos_entropy) then
828 pres = entropy_p_from_eint(log_nh_val, dlog10(e_use) - log_nh_val)
829 else
830 pres = nh_val * p_nh_from_eint(log_nh_val, dlog10(e_use) - log_nh_val)
831 end if
832 end function lte_p_from_eint
833
834 !> LTE internal energy from pressure in one cell: FI bypass, else eint(nH, p)
835 double precision function lte_eint_from_p(pres,nH_val,log_nH_val,rho_val) result(eint)
836 double precision, intent(in) :: pres, nh_val, log_nh_val, rho_val
837 double precision :: t_solve, y_solve, eint_nh_solve, log_eint_mid
838 if (.not. eos%ionE) then
839 eint = pres*eos%inv_gamma_minus_1
840 return
841 end if
842 if (pres/rho_val > eos%p_rho_FI_threshold) then
843 eint = pres*eos%inv_gamma_minus_1 + eos%eion_per_nH*nh_val
844 else if (eos%method_id == eos_analytic) then
845 call saha_state_from_nh_p(nh_val, pres, t_solve, y_solve, eint_nh_solve)
846 eint = eint_nh_solve*nh_val
847 else if (eos%p2eint_bisect) then
848 if (eos%method_id == eos_entropy) then
849 call entropy_eint_from_p_bisect(log_nh_val, dlog10(pres) - log_nh_val, log_eint_mid)
850 eint = nh_val*10.0d0**log_eint_mid
851 else
852 call eint_from_p_bisect(log_nh_val, dlog10(pres), log_eint_mid)
853 eint = nh_val*max(10.0d0**log_eint_mid, eos%eint_nH_min)
854 end if
855 else
856 eint = pres*p2eint_from_nh_p(log_nh_val, dlog10(pres) - log_nh_val)
857 end if
858 end function lte_eint_from_p
859
860end module mod_eos_lte
pure double precision function pchip_1d(p0, p1, p2, p3, t)
subroutine, public mpistop(message)
Exit MPI-AMRVAC with an error message.
EoS state container – the single thermodynamic authority for AMRVAC.
integer, parameter, public eos_analytic
integer, parameter, public eos_entropy
type(eos_container), allocatable, public eos
The single EoS state object, allocated in eos_init and shared (read-mostly) across all EoS sub-module...
Interpolation kernels for the EoS tables (pure math; no EoS state).
Entropy-method LTE EoS: every query is ONE bicubic Hermite evaluation.
subroutine, public finalise_entropy_lte()
Entropy-method finalise: shift each loaded table's axes from CGS to code units and run the standard p...
subroutine, public load_entropy_lte()
Forward (log nH, log eint/nH) -> thermodynamic state.
Analytic hydrogen Saha EoS with neutral helium (eos_method == 'analytic').
subroutine, public finalise_analytic_lte()
Analytic-method finalise: no table unit conversion needed; build the Gamma1 table only for 'exact' mo...
subroutine, public saha_t_from_nh_eint(nh_code, eint_nh_code, t_out, y_out)
Temperature and ionisation fraction from (nH, eint/nH) in code units, by bisection.
subroutine, public load_analytic_lte()
Analytic-method load: no binary tables (on-the-fly Saha solve); just announce the method.
'state' method (eos_method='state', legacy 'tables') of the LTE EoS family.
subroutine, public finalise_state_lte()
Table-based method finalise: shift the loaded T/neOnH (and ionE-derived) tables to code units,...
subroutine, public load_state_lte()
state arms of the eos_init / eos_finalise dispatchers (mod_eos_lte)
Shared LTE lookup-table infrastructure (build/IO; not on the hot path).
LTE (Saha-table) EoS kernels and finalise for the eos% family.
Definition mod_eos_lte.t:12
double precision function, public lte_eint_from_p(pres, nh_val, log_nh_val, rho_val)
LTE internal energy from pressure in one cell: FI bypass, else eint(nH, p).
double precision function, public p_nh_from_eint(log_nh, log_eint_nh)
p/nH = (1 + He + y) T from (log10 nH, log10 eint/nH)
subroutine, public get_temperature_from_eint_fast_lte(w, x, ixil, ixol, res, ciso2)
double precision function, public p2eint_from_nh_p(nh, ponh)
Ratio eint/p from (log10 nH, log10 p/nH).
subroutine, public eos_finalise_lte()
LTE arm of eos_finalise: wire the LTE runtime pointer targets, then dispatch on eos_method to the per...
Definition mod_eos_lte.t:61
double precision function, public eint_nh_from_t(log_nh, log_t)
eint/nH from (log10 nH, log10 T), from the inverse table when one is built and the Saha solve otherwi...
subroutine, public eint_from_p_bisect(log_nh_val, log_p_val, log_eint_nh_out)
Given log10(nH) and log10(p), find log10(eint/nH) by table-guessed bisection on the forward pressure ...
double precision function, public y_from_nh_eint(nh, eint_nh)
Scalar EoS kernels: one cell on (log10 nH, log10 eint/nH) or (log10 nH, log10 p/nH),...
subroutine, public eos_get_eintt_grid(n_nh, lg_nh_min, lg_nh_max)
####################################################################### Table-metadata helpers: expos...
double precision function, public lte_gamma1_from_p(pres, rho_val)
Gamma_1 of one cell from (p, rho): the ideal value where fully ionised, else the pressure- indexed ta...
double precision function, public gamma1_from_nh_p(log_nh, log_p_nh)
Gamma_1 from (log10 nH, log10 p/nH), one lookup on the pressure-indexed table.
subroutine, public eos_init_lte()
update_eos_LTE, get_Te_LTE, get_temperature_from_eint_LTE and Rfactor_from_LTE are PRIVATE: all bound...
Definition mod_eos_lte.t:49
double precision function, public t_from_nh_eint(nh, eint_nh)
Temperature from (log10 nH, log10 eint/nH).
double precision function, public lte_p_from_eint(eint_val, nh_val, log_nh_val, inv_rho)
LTE pressure from internal energy in one cell: ideal without ionE, else the table floor,...
Shared EoS accessors (EoS-type-agnostic) for the eos% family.
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, dimension(:), allocatable, parameter d
A Fortran 90 module for creating 1D and 2D lookup tables. These tables can be used to efficiently int...
pure integer function, public find_index_bsearch(list, val)
Binary search of sorted list for the smallest ix such that list(ix) >= val. On failure,...
This module defines the procedures of a physics module. It contains function pointers for the various...
Definition mod_physics.t:4
procedure(sub_e_to_ei), pointer phys_e_to_ei
Definition mod_physics.t:66
subroutine eos_timer_start()
Open an EoS timing window; inside an OpenMP parallel region the EoS is not timed.
Definition mod_timing.t:45
subroutine eos_timer_stop(acc)
Close an EoS timing window, adding its time to acc when it is the outermost.
Definition mod_timing.t:52
double precision timeeos_tfromei
Definition mod_timing.t:16