MPI-AMRVAC 3.2
The MPI - Adaptive Mesh Refinement - Versatile Advection Code (development version)
Loading...
Searching...
No Matches
mod_hd_eos.t
Go to the documentation of this file.
1!=============================================================================
2!> HD <-> EoS seam: binds the eos% authority into hydrodynamics.
3!>
4!> hd_link_eos wires the eos%/phys_ procedure pointers (conversions, pthermal,
5!> csound2/gamma1, get_Rfactor, temperature, update_temperature) per eos_type
6!> (FI / LTE / PI, with PI energy- and prominence-mode overrides).
7!> bind_eos_to_source wires the thermal-conduction / radiative-cooling / FLD
8!> fluid-port callbacks from eos%.
9!>
10!> Holds the HD block routines that wrap the shared thermodynamics with HD's
11!> mechanical-energy bookkeeping -- total-energy ("origin") formulation,
12!> dust-aware: FI/LTE/PI conversions, p_to_e, pthermal, csound2/gamma1, and the
13!> PI energy + prominence R-factor / temperature kernels.
14!=============================================================================
17 use mod_physics
18 use mod_eos
19 !> Mode-specific kernels come from their sub-modules (the facade no longer
20 !> re-exports them); each mpistops if called under the wrong eos_type/method.
21 use mod_eos_lte
23 use mod_eos_pi
25 use mod_hd_phys
26 use mod_timing
28
29 use mod_comm_lib, only: mpistop
30
31 implicit none
32 private
33
34 procedure(sub_convert), pointer, public :: hd_to_primitive => null()
35 procedure(sub_convert), pointer, public :: hd_to_conserved => null()
36
38
39contains
40
41 !> Link the appropriate EOS conversion routines based on the selected EoS type
42 subroutine hd_link_eos()
44
45 ! PI (partial approximations) takes the FI ideal-gas conversions as
46 ! its BASE (e_int=p/(gamma-1); variable R via get_Rfactor). Energy
47 ! mode (ionE) overrides them with the PI-energy variants at the end
48 ! of this routine; FI conversions stay pristine.
49 if (eos%eos_type == 'FI' .or. eos%eos_type == 'PI') then
50 eos%to_conserved => hd_to_conserved_origin
51 eos%to_primitive => hd_to_primitive_origin
52 else if (eos%eos_type == 'LTE') then
53 eos%to_conserved => hd_to_conserved_lte
54 eos%to_primitive => hd_to_primitive_lte
55 else
56 call mpistop('Error: Unknown HD EOS type: ' // trim(eos%eos_type))
57 end if
58
59 phys_to_primitive => eos%to_primitive
60 phys_to_conserved => eos%to_conserved
61 phys_get_rho => eos%get_rho
63 eos%get_thermal_pressure => hd_get_pthermal
64 phys_bind_eos_to_source => bind_eos_to_source
65
66 eos%p_to_e => p_to_e !> suitable for both FI and LTE
67
68 ! Link sound speed and gamma1 computation
69 if (eos%eos_type == 'LTE' .and. eos%ionE) then
70 eos%get_csound2 => hd_get_csound2_lte
71 phys_get_gamma1 => hd_get_gamma1_lte
72 ! EoS-aware prolongation: interpolate in (rho, v, T) space
73 phys_to_prolong => hd_to_prolong_lte
74 phys_from_prolong => hd_from_prolong_lte
75 else
76 eos%get_csound2 => hd_get_csound2_fi
77 phys_get_gamma1 => get_gamma1_fi
78 end if
79
80 ! Rfactor: only the FI case is physics-dependent (the usr_Rfactor user
81 ! hook). LTE and PI bind pure eos% routines in eos_finalise_{LTE,PI},
82 ! which run after this and before bind_eos_to_source (so they win and
83 ! their targets stay private). Note: this makes usr_Rfactor apply only to
84 ! FI for HD -- consistent with MHD, where the EoS owns R for LTE/PI.
85 if(associated(usr_rfactor)) then
86 eos%get_Rfactor=>usr_rfactor
87 else
88 eos%get_Rfactor=>rfactor_from_constant_ionization
89 end if
90
91 !> PI energy-mode overrides (eos_type='PI', ionE=.true.)
92 ! Energy mode differs from no-energy PI only in the eint<->p relation,
93 ! so override exactly the routines that touch it (conversions, p_to_e,
94 ! csound2/gamma1, Te update), all via the portable backend. pthermal
95 ! is unchanged: hd_get_pthermal already carries a PI-energy branch.
96 if (eos%eos_type == 'PI' .and. eos%ionE) then
97 if (.not. hd_energy) &
98 call mpistop('PI energy EoS requires hd_energy=.true.')
99 eos%to_conserved => hd_to_conserved_pi
100 eos%to_primitive => hd_to_primitive_pi
101 phys_to_primitive => eos%to_primitive
102 phys_to_conserved => eos%to_conserved
103 eos%p_to_e => p_to_e_pi
104 !> eos%get_csound2 => get_csound2_PI set in eos_finalise_PI (private target)
106 ! Te_ from eint each substep (uses HD's Te_ index: PI registers
107 ! Te via var_set_auxvar, so the generic iw_te is unset).
108 end if
109 hd_to_primitive => eos%to_primitive
110 hd_to_conserved => eos%to_conserved
111
112 end subroutine hd_link_eos
113
114 subroutine bind_eos_to_source() !> this is called in eos_finalise through mod_physics procedure linking
115 if (allocated(tc_fl)) then
116 tc_fl%get_temperature_from_conserved => eos%get_temperature_from_etot
117 !> Use fast bilinear T lookup for TC STS substeps (density fixed,
118 !> TC flux dominated by corona where T(eint) is smooth)
119 if (eos%eos_type == 'LTE' .and. eos%ionE) then
120 tc_fl%get_temperature_from_eint => get_temperature_from_eint_fast_lte
121 else
122 tc_fl%get_temperature_from_eint => eos%get_temperature_from_eint
123 end if
124 tc_fl%get_rho => eos%get_rho
125 tc_fl%get_ne_nH => eos%get_ne_nH
126 tc_fl%get_var_Rfactor => eos%get_Rfactor
127 tc_fl%inv_gamma_minus_1 = eos%inv_gamma_minus_1
128 tc_fl%nH2rhoFactor = eos%nH2rhoFactor
129 tc_fl%log_T_floor = eos_get_log_t_floor()
130 tc_fl%eint_from_T => eint_nh_from_t
131 end if
132
133 if (allocated(rc_fl)) then
134 rc_fl%get_rho => eos%get_rho
135 rc_fl%get_pthermal => eos%get_thermal_pressure
136 rc_fl%get_var_Rfactor => eos%get_Rfactor
137 rc_fl%get_Te => eos%get_Te
138 rc_fl%get_ne_nH => eos%get_ne_nH
139 rc_fl%ionE = eos%ionE
140 rc_fl%method = eos%method
141 rc_fl%inv_gamma_minus_1 = eos%inv_gamma_minus_1
142 rc_fl%nH2rhoFactor = eos%nH2rhoFactor
143 rc_fl%eion_per_nH = eos%eion_per_nH
144 rc_fl%eint_from_T => eint_nh_from_t
145 rc_fl%p2eint => p2eint_from_nh_p
146 rc_fl%T_from_eint => t_from_nh_eint
147 rc_fl%y_from_eint => y_from_nh_eint
148 !> Build the variable-c_V Townsend Y_mod table now that all
149 !> EoS tables (eint_from_T, T, neOnH) are in code units.
150 !> build_Y_mod_table checks coolmethod=='exact' and .not.isPPL
151 !> internally and early-returns otherwise. LTE only:
152 !> eos_get_eintT_grid reads the LTE eintT grid; PI runs classical
153 !> Townsend (Y_mod-for-PI is a later refinement).
154 if (eos%ionE .and. eos%eos_type == 'LTE') then
155 call eos_get_eintt_grid(rc_fl%Y_mod_n_nH, &
156 rc_fl%Y_mod_lg_nH_min, rc_fl%Y_mod_lg_nH_max)
158 end if
159 end if
160
161 !> PI energy mode: repoint cooling/conduction eint<->T callbacks at
162 !> the ionisation backend (the LTE-table versions above assume LTE
163 !> tables PI does not load). Classical Townsend; Y_mod not built (above).
164 if (eos%eos_type == 'PI' .and. eos%ionE) then
165 if (allocated(tc_fl)) tc_fl%eint_from_T => eint_from_t_pi
166 if (allocated(rc_fl)) then
167 rc_fl%eint_from_T => eint_from_t_pi
168 rc_fl%p2eint => p2eint_pi
169 rc_fl%T_from_eint => t_from_eint_pi
170 rc_fl%y_from_eint => y_from_eint_pi
171 end if
172 end if
173
174 if (allocated(fld_fl)) then
175 !> Radiation (FLD) fluid: gas-EoS callbacks. get_temperature_from_pressure
176 !> is the FI T=p/(R*rho) routine (the LTE variant is a later pass).
177 fld_fl%gamma = eos%gamma
178 fld_fl%get_tgas => eos%get_temperature_from_pressure
179 fld_fl%get_Rfactor => eos%get_Rfactor
180 end if
181 end subroutine bind_eos_to_source
182
183 !> Transform primitive variables into conservative ones
184 subroutine hd_to_conserved_origin(ixI^L, ixO^L, w, x)
186 use mod_dust, only: dust_to_conserved
187 integer, intent(in) :: ixi^l, ixo^l
188 double precision, intent(inout) :: w(ixi^s, nw)
189 double precision, intent(in) :: x(ixi^s, 1:ndim)
190
191 integer :: ix^d
192
193 timeeos0 = mpi_wtime() !> For monitoring cost of eos module
194
195 {do ix^db=ixomin^db,ixomax^db\}
196 if (hd_energy) then
197 ! Calculate total energy from pressure and kinetic energy
198 w(ix^d,e_)=w(ix^d,p_)*eos%inv_gamma_minus_1+&
199 half*(^c&w(ix^d,m^c_)**2+)*w(ix^d,rho_)
200 end if
201 ! Convert velocity to momentum
202 ^c&w(ix^d,m^c_)=w(ix^d,rho_)*w(ix^d,m^c_)\
203 {end do\}
204
205 if (hd_dust) then
206 call dust_to_conserved(ixi^l, ixo^l, w, x)
207 end if
208
210
211 end subroutine hd_to_conserved_origin
212
213 !> Transform conservative variables into primitive ones
214 subroutine hd_to_primitive_origin(ixI^L, ixO^L, w, x)
216 use mod_dust, only: dust_to_primitive
217 integer, intent(in) :: ixi^l, ixo^l
218 double precision, intent(inout) :: w(ixi^s, nw)
219 double precision, intent(in) :: x(ixi^s, 1:ndim)
220
221 double precision :: inv_rho
222 integer :: ix^d
223
224 timeeos0 = mpi_wtime() !> For monitoring cost of eos module
225
226 if (fix_small_values) then
227 call hd_handle_small_values(.false., w, x, ixi^l, ixo^l, 'hd_to_primitive')
228 end if
229
230 {do ix^db=ixomin^db,ixomax^db\}
231 inv_rho = 1.d0/w(ix^d,rho_)
232 ! Convert momentum to velocity
233 ^c&w(ix^d,m^c_)=w(ix^d,m^c_)*inv_rho\
234 ! Calculate pressure = (gamma-1) * (e-ek)
235 if(hd_energy) then
236 ! Compute pressure
237 w(ix^d,p_)=(eos%gamma_minus_1)*(w(ix^d,e_)&
238 -half*w(ix^d,rho_)*(^c&w(ix^d,m^c_)**2+))
239 end if
240 {end do\}
241
242 ! Convert dust momentum to dust velocity
243 if (hd_dust) then
244 call dust_to_primitive(ixi^l, ixo^l, w, x)
245 end if
246
248
249 end subroutine hd_to_primitive_origin
250
251 !> LTE primitive -> conserved conversion.
252 !>
253 !> On entry: rho_ = density, m_ = velocity, p_ = pressure.
254 !> On exit: rho_ = density (unchanged), m_ = momentum, e_ = total energy.
255 subroutine hd_to_conserved_lte(ixI^L, ixO^L, w, x)
257 use mod_dust, only: dust_to_conserved
258 integer, intent(in) :: ixi^l, ixo^l
259 double precision, intent(inout) :: w(ixi^s, nw)
260 double precision, intent(in) :: x(ixi^s, 1:ndim)
261
262 timeeos0 = mpi_wtime()
263
264 call p_to_e(ixi^l, ixo^l, w, x)
265
266 ! Convert velocity to momentum
267 ^c&w(ixo^s,m^c_)=w(ixo^s,rho_)*w(ixo^s,m^c_)\
268
269 if (hd_dust) then
270 call dust_to_conserved(ixi^l, ixo^l, w, x)
271 end if
272
274
275 end subroutine hd_to_conserved_lte
276
277 subroutine p_to_e(ixI^L, ixO^L, w, x)
278 !> Convert pressure to total energy: E = eint(rho, p) + KE.
279 !>
280 !> On entry: w(rho_) = density, w(m_) = velocity, w(p_) = pressure.
281 !> On exit: w(rho_) unchanged, w(m_) unchanged, w(e_) = total energy.
282 !>
283 !> Four paths for ionE (LTE with ionisation):
284 !> 1. FI bypass (p/rho > threshold): analytic eint = p/(gamma-1) + eion*nH
285 !> 2. Analytical Saha (eos_method='analytic'): direct Saha solve for T,y from p
286 !> 3. WB mode (hd_well_balanced): cached bisection on forward tables
287 !> 4. Standard: p2eint inverse table lookup (fast, ~0.01% round-trip error)
289 integer, intent(in) :: ixi^l, ixo^l
290 double precision, intent(inout) :: w(ixi^s, nw)
291 double precision, intent(in) :: x(ixi^s, 1:ndim)
292
293 integer :: ix^d
294 double precision :: p_to_eint, p_over_rho
295 double precision :: nh(ixi^s), nh_in(ixi^s), p_in(ixi^s)
296 double precision :: log_eint_mid, eint_total
297 double precision :: t_solve, y_solve, eint_nh_solve
298
299 if (eos%ionE) then
300 call eos%get_nH(w, x, ixi^l, ixo^l, nh)
301 nh_in(ixo^s) = dlog10(nh(ixo^s))
302 if (eos%p2eint_method /= 'bisect') then
303 p_in(ixo^s) = dlog10(w(ixo^s,p_)) - nh_in(ixo^s)
304 end if
305 endif
306
307 p_to_eint = eos%inv_gamma_minus_1
308 {do ix^db=ixomin^db,ixomax^db\}
309 if (hd_energy) then
310 if (eos%ionE) then
311 p_over_rho = w(ix^d,p_) / w(ix^d,rho_)
312 if (p_over_rho > eos%p_rho_FI_threshold) then
313 !> FI bypass: exact inverse of to_primitive
314 p_to_eint = eos%inv_gamma_minus_1 &
315 + eos%eion_per_nH * nh(ix^d) / w(ix^d,p_)
316 w(ix^d,e_) = w(ix^d,p_)*p_to_eint + &
317 half*(^c&w(ix^d,m^c_)**2+)*w(ix^d,rho_)
318 else if (eos%method == 'analytic') then
319 !> Analytical Saha: bisect for T from p, return eint directly
320 call saha_state_from_nh_p(nh(ix^d), w(ix^d,p_), &
321 t_solve, y_solve, eint_nh_solve)
322 w(ix^d,e_) = eint_nh_solve * nh(ix^d) + &
323 half*(^c&w(ix^d,m^c_)**2+)*w(ix^d,rho_)
324 else if (eos%p2eint_method == 'bisect') then
325 !> Bisection on forward p table: finds eint such that
326 !> p(nH, eint) = p_target exactly. More accurate than
327 !> the p2eint table in the ionisation zone.
328 call eint_from_p_bisect(nh_in(ix^d), &
329 dlog10(w(ix^d,p_)), log_eint_mid)
330 eint_total = nh(ix^d) * 10.0d0**log_eint_mid
331 eint_total = max(eint_total, &
332 nh(ix^d) * 10.0d0**eos%T%var2_min)
333 w(ix^d,e_) = eint_total + &
334 half*(^c&w(ix^d,m^c_)**2+)*w(ix^d,rho_)
335 else
336 !> Standard table lookup (fast, default)
337 p_to_eint = p2eint_from_nh_p(nh_in(ix^d), p_in(ix^d))
338 w(ix^d,e_) = w(ix^d,p_)*p_to_eint + &
339 half*(^c&w(ix^d,m^c_)**2+)*w(ix^d,rho_)
340 end if
341 else
342 w(ix^d,e_) = w(ix^d,p_)*p_to_eint + &
343 half*(^c&w(ix^d,m^c_)**2+)*w(ix^d,rho_)
344 end if
345 end if
346 {end do\}
347
348 end subroutine p_to_e
349
350 !> LTE conserved -> primitive conversion.
351 !>
352 !> On entry: rho_ = density, m_ = momentum, e_ = total energy.
353 !> On exit: rho_ = density (unchanged), m_ = velocity, p_ = pressure.
354 !>
355 !> Pressure is computed energy-consistently from actual eint via EoS
356 !> table lookups (T and ne/nH). Cannot use stored Ne_/Te_ because
357 !> they may be stale after AMR prolongation/coarsening.
358 subroutine hd_to_primitive_lte(ixI^L, ixO^L, w, x)
360 use mod_dust, only: dust_to_primitive
361 integer, intent(in) :: ixi^l, ixo^l
362 double precision, intent(inout) :: w(ixi^s, nw)
363 double precision, intent(in) :: x(ixi^s, 1:ndim)
364
365 double precision :: inv_rho
366 double precision :: nh(ixi^s)
367 double precision :: log_nh(ixi^s)
368 double precision :: eint_val, eint_in, t_loc, y_loc
369 integer :: ix^d
370
371 timeeos0 = mpi_wtime()
372
373 if (fix_small_values) then
374 call hd_handle_small_values(.false., w, x, ixi^l, ixo^l, 'hd_to_primitive_LTE')
375 end if
376
377 call eos%get_nH(w, x, ixi^l, ixo^l, nh)
378
379 ! Cache log10(nH) for all cells (used by table lookups for IonE)
380 if (eos%ionE) then
381 log_nh(ixo^s) = dlog10(nh(ixo^s))
382 end if
383
384 {do ix^db=ixomin^db,ixomax^db\}
385 inv_rho = 1.d0/w(ix^d,rho_)
386 ! Convert momentum to velocity
387 ^c&w(ix^d,m^c_)=w(ix^d,m^c_)*inv_rho\
388 ! Calculate pressure
389 if(hd_energy) then
390 if (eos%ionE) then
391 ! Energy-consistent pressure from actual eint via EoS tables.
392 ! Cannot use stored Ne_/Te_ because they may be stale after
393 ! AMR prolongation/coarsening (nonlinear EoS breaks averaging).
394 eint_val = w(ix^d,e_) - half*w(ix^d,rho_)*(^c&w(ix^d,m^c_)**2+)
395 ! Floor eint to prevent unphysical values
396 if (eos%method /= 'analytic') then
397 eint_val = max(eint_val, nh(ix^d) * 10.0d0**eos%T%var2_min)
398 end if
399 eint_val = max(eint_val, smalldouble)
400 if (eint_val * inv_rho > eos%eint_rho_FI_threshold) then
401 ! FI bypass: p = (gamma-1)*(eint - eion*nH)
402 w(ix^d,p_) = eos%gamma_minus_1 &
403 * (eint_val - eos%eion_per_nH * nh(ix^d))
404 else
405 ! Ionisation zone: single p/nH lookup
406 eint_in = dlog10(eint_val) - log_nh(ix^d)
407 w(ix^d,p_) = nh(ix^d) &
408 * p_nh_from_eint(log_nh(ix^d), eint_in)
409 end if
410 else
411 w(ix^d,p_)=(eos%gamma_minus_1)*(w(ix^d,e_)&
412 -half*w(ix^d,rho_)*(^c&w(ix^d,m^c_)**2+))
413 end if
414 end if
415 {end do\}
416
417 ! Convert dust momentum to dust velocity
418 if (hd_dust) then
419 call dust_to_primitive(ixi^l, ixo^l, w, x)
420 end if
421
423
424 end subroutine hd_to_primitive_lte
425
426 !> Calculate thermal pressure within ixO^L.
427 !> For energy runs delegates to eos%get_thermal_pressure; for no-energy
428 !> (isothermal) uses the adiabatic relation p = adiab * rho^gamma.
429 subroutine hd_get_pthermal(w, x, ixI^L, ixO^L, pth)
431 use mod_physics, only: phys_get_ei
434
435 integer, intent(in) :: ixi^l, ixo^l
436 double precision, intent(in) :: w(ixi^s, 1:nw)
437 double precision, intent(in) :: x(ixi^s, 1:ndim)
438 double precision, intent(out):: pth(ixi^s)
439 integer :: iw, ix^d
440 double precision :: nh(ixi^s), ei(ixi^s), tpi, rpi
441
442 if (hd_energy) then
443 if (eos%eos_type == 'LTE') then
444 ! LTE: p = nH * (1 + He + ne/nH) * T from stored state
445 call eos%get_nH(w, x, ixi^l, ixo^l, nh)
446 pth(ixo^s) = nh(ixo^s) * (1.0d0 + eos%He_abundance &
447 + (w(ixo^s,iw_ne) / nh(ixo^s))) * w(ixo^s,iw_te)
448 else if (eos%eos_type == 'PI' .and. eos%ionE) then
449 ! PI energy: invert eint (= e - KE, via phys_get_ei) -> p with
450 ! the ionisation backend (eint carries the ionisation energy).
451 ei(ixo^s) = phys_get_ei(w, ixi^l, ixo^l)
452 {do ix^db=ixomin^db,ixomax^db\}
453 call state_from_eint_pi(w(ix^d,rho_), ei(ix^d), &
454 tpi, pth(ix^d), rpi)
455 {end do\}
456 else
457 ! FI: p = (gamma-1) * (e - KE)
458 pth(ixo^s) = eos%gamma_minus_1 * phys_get_ei(w, ixi^l, ixo^l)
459 end if
460 else
461 if (.not. associated(usr_set_pthermal)) then
462 pth(ixo^s) = hd_adiab * w(ixo^s, rho_)**eos%gamma
463 else
464 call usr_set_pthermal(w,x,ixi^l,ixo^l,pth)
465 end if
466 end if
467
468 if (fix_small_values) then
469 {do ix^db= ixo^lim^db\}
470 if(pth(ix^d)<small_pressure) then
471 pth(ix^d)=small_pressure
472 endif
473 {enddo^d&\}
474 else if (check_small_values) then
475 {do ix^db= ixo^lim^db\}
476 if(pth(ix^d)<small_pressure) then
477 write(*,*) "Error: small value of gas pressure",pth(ix^d),&
478 " encountered when call hd_get_pthermal"
479 write(*,*) "Iteration: ", it, " Time: ", global_time
480 write(*,*) "Location: ", x(ix^d,:)
481 write(*,*) "Cell number: ", ix^d
482 do iw=1,nw
483 write(*,*) trim(cons_wnames(iw)),": ",w(ix^d,iw)
484 end do
485 if(trace_small_values) write(*,*) dsqrt(pth(ix^d)-bigdouble)
486 write(*,*) "Saving status at the previous time step"
487 crash=.true.
488 end if
489 {enddo^d&\}
490 end if
491
492 end subroutine hd_get_pthermal
493
494 !> Sound speed squared for FI (fully ionized / constant gamma) EoS.
495 !> Expects w in primitive form: w(p_) = pressure, w(rho_) = density.
496 subroutine hd_get_csound2_fi(w, x, ixI^L, ixO^L, cs2)
498 integer, intent(in) :: ixi^l, ixo^l
499 double precision, intent(in) :: w(ixi^s, nw)
500 double precision, intent(in) :: x(ixi^s, 1:ndim)
501 double precision, intent(out) :: cs2(ixi^s)
502
503 timeeos0 = mpi_wtime()
504
505 cs2(ixo^s) = eos%gamma * w(ixo^s, p_) / w(ixo^s, rho_)
506
507 timeeos_csound = timeeos_csound + (mpi_wtime()-timeeos0)
508
509 end subroutine hd_get_csound2_fi
510
511 !> Sound speed squared for LTE+IonE EoS.
512 !> Delegates Gamma_1 computation to hd_get_gamma1_LTE, then cs2 = Gamma_1 * p/rho.
513 subroutine hd_get_csound2_lte(w, x, ixI^L, ixO^L, cs2)
515 integer, intent(in) :: ixi^l, ixo^l
516 double precision, intent(in) :: w(ixi^s, nw)
517 double precision, intent(in) :: x(ixi^s, 1:ndim)
518 double precision, intent(out) :: cs2(ixi^s)
519 integer :: ix^d
520
521 timeeos0 = mpi_wtime()
522
523 call hd_get_gamma1_lte(w, x, ixi^l, ixo^l, cs2)
524 {do ix^db=ixomin^db,ixomax^db\}
525 cs2(ix^d) = cs2(ix^d) * w(ix^d, p_) / w(ix^d, rho_)
526 {end do\}
527
528 timeeos_csound = timeeos_csound + (mpi_wtime()-timeeos0)
529
530 end subroutine hd_get_csound2_lte
531
532 !> PI energy-mode routines (eos_type='PI', ionE=.true.)
533 !> Mirror the HD FI/LTE family; the eint<->p relation is delegated to the
534 !> portable scalar backend (mod_eos_PI). Same eq_state_units /
535 !> RR=1 normalisation as FI. Origin (total-energy) only, as in HD. eint
536 !> carries the ionisation-energy term, so p=(gamma-1)*eint no longer holds.
537
538 !> Primitive pressure -> total energy via backend (m_ is velocity here).
539 subroutine p_to_e_pi(ixI^L, ixO^L, w, x)
541 integer, intent(in) :: ixi^l, ixo^l
542 double precision, intent(inout) :: w(ixi^s, nw)
543 double precision, intent(in) :: x(ixi^s, 1:ndim)
544 double precision :: eint
545 integer :: ix^d
546
547 {do ix^db=ixomin^db,ixomax^db\}
548 if (hd_energy) then
549 call eint_from_rho_p_pi(w(ix^d,rho_), w(ix^d,p_), eint)
550 w(ix^d,e_) = eint + half*(^c&w(ix^d,m^c_)**2+)*w(ix^d,rho_)
551 end if
552 {end do\}
553 end subroutine p_to_e_pi
554
555 !> Primitive -> conserved (PI energy)
556 subroutine hd_to_conserved_pi(ixI^L, ixO^L, w, x)
558 use mod_dust, only: dust_to_conserved
559 integer, intent(in) :: ixi^l, ixo^l
560 double precision, intent(inout) :: w(ixi^s, nw)
561 double precision, intent(in) :: x(ixi^s, 1:ndim)
562
563 timeeos0 = mpi_wtime()
564 call p_to_e_pi(ixi^l, ixo^l, w, x)
565 ! Convert velocity to momentum
566 ^c&w(ixo^s,m^c_)=w(ixo^s,rho_)*w(ixo^s,m^c_)\
567 if (hd_dust) call dust_to_conserved(ixi^l, ixo^l, w, x)
569 end subroutine hd_to_conserved_pi
570
571 !> Conserved -> primitive (PI energy): KE removed -> eint, backend -> p.
572 subroutine hd_to_primitive_pi(ixI^L, ixO^L, w, x)
574 use mod_dust, only: dust_to_primitive
575 integer, intent(in) :: ixi^l, ixo^l
576 double precision, intent(inout) :: w(ixi^s, nw)
577 double precision, intent(in) :: x(ixi^s, 1:ndim)
578 double precision :: inv_rho, eint_val, t, rfac
579 integer :: ix^d
580
581 timeeos0 = mpi_wtime()
582
583 if (fix_small_values) then
584 call hd_handle_small_values(.false., w, x, ixi^l, ixo^l, &
585 'hd_to_primitive_PI')
586 end if
587
588 {do ix^db=ixomin^db,ixomax^db\}
589 inv_rho = 1.d0/w(ix^d,rho_)
590 ! Convert momentum to velocity
591 ^c&w(ix^d,m^c_)=w(ix^d,m^c_)*inv_rho\
592 if (hd_energy) then
593 eint_val = w(ix^d,e_) &
594 - half*w(ix^d,rho_)*(^c&w(ix^d,m^c_)**2+)
595 eint_val = max(eint_val, smalldouble)
596 call state_from_eint_pi(w(ix^d,rho_), eint_val, &
597 t, w(ix^d,p_), rfac)
598 end if
599 {end do\}
600
601 if (hd_dust) call dust_to_primitive(ixi^l, ixo^l, w, x)
603 end subroutine hd_to_primitive_pi
604
605 !> Adiabatic sound speed squared from primitive (rho, p).
606
607 !> Effective Gamma1 = cs2 * rho / p.
608
609 !> PI energy mode: refresh Te_ from gas internal energy via the backend
610 !> eint->T inversion. Uses HD's Te_ index (PI registers Te via
611 !> var_set_auxvar, so the generic iw_te is unset). phys_get_ei supplies
612 !> eint (KE removed).
613
614 !> PI no-energy R-factor from the stored Te_ (HD's index). Mirrors MHD's
615 !> Rfactor_from_temperature_ionization. The generic mod_eos version uses
616 !> the global iw_te, which PI leaves unset (var_set_auxvar) -- so HD needs
617 !> its own Te_-addressed routine, exactly as MHD does.
618
619 !> PI no-energy Te_ update (pth/(rho*R) with lagged iz_H from wCT(Te_)).
620 !> HD's Te_-addressed mirror of MHD's mhd_update_temperature.
621
622 !> Prominence (T,p) R-factor: recompute from (rho, pth) via the module's
623 !> prominence inversion (no-energy only -> pth=(gamma-1)*eint, R-indep).
624
625 !> Prominence (T,p) Te_ update from (rho, pth).
626
627 !> Return effective adiabatic index for LTE+IonE EoS.
628 !> Dispatches on gamma1_method and eos%method.
629 subroutine hd_get_gamma1_lte(w, x, ixI^L, ixO^L, gamma1)
631 integer, intent(in) :: ixi^l, ixo^l
632 double precision, intent(in) :: w(ixi^s, nw)
633 double precision, intent(in) :: x(ixi^s, 1:ndim)
634 double precision, intent(out) :: gamma1(ixi^s)
635
636 double precision :: nh_val, p_over_rho
637 integer :: ix^d
638
639 if (eos%gamma1_method == 'constant') then
640 gamma1(ixo^s) = eos%gamma
641 return
642 end if
643
644 {do ix^db=ixomin^db,ixomax^db\}
645 p_over_rho = w(ix^d, p_) / w(ix^d, rho_)
646 if (p_over_rho > eos%p_rho_FI_threshold) then
647 gamma1(ix^d) = eos%gamma
648 else
649 nh_val = w(ix^d, rho_) / eos%nH2rhoFactor
650 if (eos%method == 'analytic') then
651 if (iw_te > 0 .and. w(ix^d,iw_te) > 0.0d0) then
652 gamma1(ix^d) = saha_gamma1_from_nh_t(nh_val, w(ix^d,iw_te))
653 else
654 gamma1(ix^d) = eos%gamma
655 end if
656 else
657 gamma1(ix^d) = gamma1_from_nh_p(dlog10(nh_val), &
658 dlog10(w(ix^d, p_) / nh_val))
659 end if
660 end if
661 {end do\}
662
663 end subroutine hd_get_gamma1_lte
664
665 !> Rfactor = p/(rho*T) for constant ionisation degree (FI/PI no-energy).
666 !> Stays in the seam: RR is the physics module's gas-constant factor, not
667 !> visible to mod_eos. Rfactor_from_LTE lives in mod_eos (no RR).
668 subroutine rfactor_from_constant_ionization(w,x,ixI^L,ixO^L,Rfactor)
670 integer, intent(in) :: ixi^l, ixo^l
671 double precision, intent(in) :: w(ixi^s,1:nw)
672 double precision, intent(in) :: x(ixi^s,1:ndim)
673 double precision, intent(out):: rfactor(ixi^s)
674
675 rfactor(ixo^s)=rr
676
677 end subroutine rfactor_from_constant_ionization
678
679 !> Convert conserved (rho, rho*v, E) to prolong form (rho, v, T).
680 !> T is stored in the e_ slot. Interpolation in this space avoids
681 !> Jensen's inequality across the ionisation plateau.
682 subroutine hd_to_prolong_lte(ixI^L, ixO^L, w, x)
683 integer, intent(in) :: ixi^l, ixo^l
684 double precision, intent(inout) :: w(ixi^s, nw)
685 double precision, intent(in) :: x(ixi^s, 1:ndim)
686
687 double precision :: inv_rho, eint_val, nh_val, log_nh, t_loc, y_loc
688 integer :: ix^d
689
690 {do ix^db=ixomin^db,ixomax^db\}
691 inv_rho = 1.d0 / w(ix^d, rho_)
692 nh_val = w(ix^d, rho_) / eos%nH2rhoFactor
693 log_nh = dlog10(nh_val)
694
695 ! Convert momentum to velocity
696 ^c&w(ix^d,m^c_)=w(ix^d,m^c_)*inv_rho\
697
698 ! Compute eint = E - 0.5*rho*v^2
699 eint_val = w(ix^d, e_) &
700 - half * w(ix^d, rho_) * (^c&w(ix^d,m^c_)**2+)
701 ! Floor eint to prevent unphysical values
702 if (eos%method /= 'analytic') then
703 eint_val = max(eint_val, nh_val * 10.0d0**eos%T%var2_min)
704 end if
705 eint_val = max(eint_val, smalldouble)
706
707 ! Convert eint to T via EoS
708 if (eint_val * inv_rho > eos%eint_rho_FI_threshold) then
709 ! FI bypass: T = (gamma-1)*(eint - eion*nH) / (nH * n_per_nH_FI)
710 w(ix^d, e_) = eos%gamma_minus_1 &
711 * (eint_val - eos%eion_per_nH * nh_val) &
712 / (nh_val * eos%n_per_nH_FI)
713 else if (eos%method == 'analytic') then
714 ! Analytical Saha: solve for T from eint
715 call saha_t_from_nh_eint(nh_val, &
716 eint_val / nh_val, t_loc, y_loc)
717 w(ix^d, e_) = t_loc
718 else
719 ! Ionisation zone: T from table
720 w(ix^d, e_) = t_from_nh_eint(log_nh, &
721 dlog10(eint_val) - log_nh)
722 end if
723 {end do\}
724
725 end subroutine hd_to_prolong_lte
726
727 !> Convert prolong form (rho, v, T) back to conserved (rho, rho*v, E).
728 !> T is read from the e_ slot. Uses eint_nH_from_T for back-conversion.
729 subroutine hd_from_prolong_lte(ixI^L, ixO^L, w, x)
730 integer, intent(in) :: ixi^l, ixo^l
731 double precision, intent(inout) :: w(ixi^s, nw)
732 double precision, intent(in) :: x(ixi^s, 1:ndim)
733
734 double precision :: t_val, eint_val, t_fi, nh_val, log_nh, log_t_min
735 integer :: ix^d
736
737 t_fi = (eos%eint_rho_FI_threshold &
738 * eos%nH2rhoFactor - eos%eion_per_nH) &
739 * eos%gamma_minus_1 / eos%n_per_nH_FI
740
741 ! Floor for log_T when calling the (rho, T) inverse table.
742 ! Legacy 'tables' method populates eos%eint_from_T; entropy method
743 ! populates eos%eintT. Picking the wrong container leaves var2_min = 0
744 ! (uninitialised), which floors T at 10^0 = 1 code unit (= 10^6 K) —
745 ! that clobbers any cold cell going through AMR prolongation.
746 if (eos%method == 'entropy') then
747 log_t_min = eos%eintT%var2_min
748 else
749 log_t_min = eos%eint_from_T%var2_min
750 end if
751
752 {do ix^db=ixomin^db,ixomax^db\}
753 t_val = w(ix^d, e_) ! T stored in e_ slot
754 nh_val = w(ix^d, rho_) / eos%nH2rhoFactor
755 log_nh = dlog10(nh_val)
756
757 if (t_val > t_fi) then
758 ! FI: eint = nH*(n_per_nH*T/(gamma-1) + eion)
759 eint_val = nh_val &
760 * (eos%n_per_nH_FI * t_val * eos%inv_gamma_minus_1 &
761 + eos%eion_per_nH)
762 else if (eos%method == 'analytic') then
763 ! Analytical Saha: eint from T directly
764 eint_val = saha_eint_from_nh_t(nh_val, t_val) * nh_val
765 else
766 ! Ionisation zone: eint/nH from T table
767 eint_val = eint_nh_from_t(log_nh, &
768 dlog10(max(t_val, 10.0d0**log_t_min))) &
769 * nh_val
770 end if
771
772 ! E = eint + 0.5*rho*v^2
773 w(ix^d, e_) = eint_val &
774 + half * w(ix^d, rho_) * (^c&w(ix^d,m^c_)**2+)
775 ! Convert velocity to momentum
776 ^c&w(ix^d,m^c_)=w(ix^d,rho_)*w(ix^d,m^c_)\
777 {end do\}
778
779 end subroutine hd_from_prolong_lte
780
781end module mod_hd_eos
782!> Needs a line after to pass the preprocesor
subroutine, public mpistop(message)
Exit MPI-AMRVAC with an error message.
Module for including dust species, which interact with the gas through a drag force.
Definition mod_dust.t:3
subroutine, public dust_to_primitive(ixil, ixol, w, x)
Definition mod_dust.t:229
subroutine, public dust_to_conserved(ixil, ixol, w, x)
Definition mod_dust.t:209
EoS state container – the single thermodynamic authority for AMRVAC.
type(eos_container), allocatable, public eos
The single EoS state object, allocated in eos_init and shared (read-mostly) across all EoS sub-module...
Analytic H-only Saha EoS (eos_method == 'analytic').
double precision function, public saha_gamma1_from_nh_t(nh_code, t_code)
Look up Gamma1 from the analytical 2D table (nH, T axes in code units). For use when eosmethod == 'an...
subroutine, public saha_t_from_nh_eint(nh_code, eint_nh_code, t_out, y_out)
Temperature inversion: given (nH, eint/nH) in CODE UNITS, find T in CODE UNITS, by bisection (guarant...
subroutine, public saha_state_from_nh_p(nh_code, p_code, t_out, y_out, eint_nh_out)
Given (nH, p) in CODE UNITS, find T and y by solving p = nH*(1+y(T))*T_code. Uses bisection on T....
LTE (Saha-table) EoS kernels and finalise for the eos% family.
Definition mod_eos_LTE.t:12
double precision function, public p_nh_from_eint(log_nh, log_eint_nh)
p/nH from (log10 nH, log10 eint/nH) in code units. Returns (1+He+y)*T directly – single lookup replac...
double precision function, public p2eint_from_nh_p(nh, ponh)
Pressure-to-eint ratio from (log10 nH, log10 p/nH) in code units. Dispatches: analytic -> Saha solve ...
double precision function, public eint_nh_from_t(log_nh, log_t)
Internal energy per nH from (log10 nH, log10 T) in code units. Uses the bisection-built inverse table...
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)
Ionization fraction from (log10 nH, log10 eint/nH) in code units. Dispatches: analytic -> Saha quadra...
subroutine, public get_temperature_from_eint_fast_lte(w, x, ixil, ixol, res)
subroutine, public eos_get_eintt_grid(n_nh, lg_nh_min, lg_nh_max)
log_nH grid metadata of the (log_nH, log_T) inverse table (eint from T), choosing the container by me...
double precision function, public gamma1_from_nh_p(log_nh, log_p_nh)
Gamma_1 from pressure-indexed table: (log10 nH, log10 p/nH) -> Gamma_1. For 'entropy' the conversion ...
double precision function, public t_from_nh_eint(nh, eint_nh)
Temperature from (log10 nH, log10 eint/nH) in code units. Dispatches: analytic -> Saha bisection/Newt...
PI (partial-ionisation, eos_type='PI') arm of the eos% family.
Definition mod_eos_PI.t:27
double precision function, dimension(log_nh, log_p_nh), public p2eint_pi(log_nh, log_p_nh)
eint/p factor from pressure per H: maps p -> eint = p * (this).
Definition mod_eos_PI.t:336
double precision function, dimension(log_nh, log_t), public eint_from_t_pi(log_nh, log_t)
Internal energy per H from temperature: eint/nH(T).
Definition mod_eos_PI.t:329
double precision function, dimension(log_nh, log_eint_nh), public y_from_eint_pi(log_nh, log_eint_nh)
Electron-to-hydrogen ratio ne/nH from internal energy per H. ne/nH = iz_H + A_He*iz_He*(1+iz_He) (mat...
Definition mod_eos_PI.t:354
subroutine, public get_gamma1_pi(w, x, ixil, ixol, gamma1)
Effective Gamma1 = cs2 * rho / p for the same primitive state.
Definition mod_eos_PI.t:146
subroutine, public eint_from_rho_p_pi(rho, p, eint)
Primitive pressure -> GAS internal energy (prim -> conserved direction): invert (rho,...
Definition mod_eos_PI.t:299
double precision function, dimension(log_nh, log_eint_nh), public t_from_eint_pi(log_nh, log_eint_nh)
Temperature from internal energy per H.
Definition mod_eos_PI.t:345
subroutine, public state_from_eint_pi(rho, eint, t, p, rfactor, iz_h, iz_he)
Conserved gas internal energy -> temperature, thermal pressure, R. Inverse of p_eint_from_rho_T_PI....
Definition mod_eos_PI.t:274
Equation of state for AMRVAC, handled through a single eos_container object.
Definition mod_eos.t:30
subroutine, public eos_finalise()
Phase 'commit' (after units are known): finalise the dispatch for the loaded physics – wire the metho...
Definition mod_eos.t:265
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
logical fix_small_values
fix small values with average or replace methods
HD <-> EoS seam: binds the eos% authority into hydrodynamics.
Definition mod_hd_eos.t:15
subroutine, public hd_get_pthermal(w, x, ixil, ixol, pth)
Calculate thermal pressure within ixO^L. For energy runs delegates to eosget_thermal_pressure; for no...
Definition mod_hd_eos.t:430
procedure(sub_convert), pointer, public hd_to_conserved
Definition mod_hd_eos.t:35
subroutine, public hd_link_eos()
Link the appropriate EOS conversion routines based on the selected EoS type.
Definition mod_hd_eos.t:43
procedure(sub_convert), pointer, public hd_to_primitive
Definition mod_hd_eos.t:34
Hydrodynamics physics module.
Definition mod_hd_phys.t:2
integer, public, protected m
Definition mod_hd_phys.t:69
logical, public, protected hd_energy
Whether an energy equation is used.
Definition mod_hd_phys.t:14
logical, public, protected hd_dust
Whether dust is added.
Definition mod_hd_phys.t:32
integer, public, protected e_
Index of the energy density (-1 if not present)
Definition mod_hd_phys.t:75
double precision, public, protected rr
type(tc_fluid), allocatable, public tc_fl
Definition mod_hd_phys.t:24
integer, public, protected c
Indices of the momentum density for the form of better vectorization.
Definition mod_hd_phys.t:69
double precision, public hd_adiab
gamma is set in &eos_list and accessed via eosgamma
integer, public, protected rho_
Whether plasma is partially ionized.
Definition mod_hd_phys.t:63
subroutine, public hd_handle_small_values(primitive, w, x, ixil, ixol, subname)
type(fld_fluid), allocatable, public fld_fl
Radiation fluid object (gas-EoS callbacks for FLD), wired in hd_link_eos.
Definition mod_hd_phys.t:40
integer, public, protected c_
Definition mod_hd_phys.t:69
type(rc_fluid), allocatable, public rc_fl
Definition mod_hd_phys.t:29
integer, public, protected p_
Index of the gas pressure (-1 if not present) should equal e_.
Definition mod_hd_phys.t:78
This module defines the procedures of a physics module. It contains function pointers for the various...
Definition mod_physics.t:4
procedure(sub_check_params), pointer phys_bind_eos_to_source
Definition mod_physics.t:53
procedure(sub_convert), pointer phys_to_primitive
Definition mod_physics.t:52
procedure(sub_get_pthermal), pointer phys_get_gamma1
Definition mod_physics.t:78
procedure(sub_get_pthermal), pointer phys_get_pthermal
Definition mod_physics.t:77
procedure(sub_get_ei), pointer phys_get_ei
Definition mod_physics.t:68
procedure(sub_convert), pointer phys_to_prolong
Definition mod_physics.t:55
procedure(sub_convert), pointer phys_to_conserved
Definition mod_physics.t:51
procedure(sub_convert), pointer phys_from_prolong
Definition mod_physics.t:56
procedure(sub_get_rho), pointer phys_get_rho
Definition mod_physics.t:70
module radiative cooling – add optically thin radiative cooling
subroutine build_y_mod_table(fl)
===================================================================
Module for handling problematic values in simulations, such as negative pressures.
logical, public trace_small_values
trace small values in the source file using traceback flag of compiler
double precision timeeos0
Definition mod_timing.t:10
double precision timeeos_conv
Definition mod_timing.t:13
double precision timeeos_csound
Definition mod_timing.t:12
Module with all the methods that users can customize in AMRVAC.
procedure(rfactor), pointer usr_rfactor
procedure(hd_pthermal), pointer usr_set_pthermal