MPI-AMRVAC 3.2
The MPI - Adaptive Mesh Refinement - Versatile Advection Code (development version)
Loading...
Searching...
No Matches
mod_eos_pi_tables.t
Go to the documentation of this file.
1!=============================================================================
2!> PI (partial-ionisation) ionisation-degree backend for the eos% family.
3!>
4!> The physics library behind mod_eos_pi (the eos_type='PI' adapter): given the
5!> thermodynamic state it returns the hydrogen/helium ionisation degrees, the
6!> R-factor (mean inverse molecular weight) and -- in energy mode -- the
7!> ionisation energy. Standalone: depends only on mod_global_parameters /
8!> mod_constants / mod_comm_lib, never on mod_eos*, so eos% -> ionisation is
9!> acyclic. The analogue for PI of mod_eos_lte_tables for LTE.
10!>
11!> Three interchangeable tables (eos%pi_table), selected at init:
12!> chromosphere -- T-only H I/H fraction, Carlsson & Leenaarts (2012, A&A 539 A39)
13!> flare -- T-only H I/H fraction, Hong, Carlsson & Ding (2022, A&A 661 A77)
14!> prominence -- (T,p) H ionisation, Heinzel et al. (2015, A&A 579 A16) +
15!> Gunar et al. (2025, A&A 699 A89); model A, side illumination, L=500 km.
16!> Hydrogen ionisation is tabulated (interpolated to a 4000-point fine grid);
17!> helium is a semi-analytic Saha-like fit. The R-factor is
18!> R = (1 + iz_H + A_He*(1 + iz_He*(1 + iz_He))) / Rfactor_norm,
19!> with Rfactor_norm = 2 + 3*A_He set by the adapter so R -> 1 at full ionisation
20!> under the FI code-unit normalisation. Energy mode folds 13.6 eV per ionised H
21!> into eint. The map Y(T) = R(T)*T = p/rho is monotonic, so (rho,p) -> T inverts
22!> by bisection (T-only) or pressure-bisection (prominence); energy mode inverts
23!> eint -> T by Newton.
24!>
25!> Public API: ionization_degree_init (once, after units) + the state-query
26!> routines consumed by mod_eos_pi (get_state[_scalar], state_Tp, solve_p_Rfactor,
27!> get_state_from_eint, get_p_eint_from_rho_T, get_eps_derivative_T,
28!> get_csound2_T, the predicates and the legacy degree-from-temperature shim).
29!=============================================================================
31 implicit none
32 private
33 integer, parameter :: n_Tonly = 100
34 double precision :: HI_fraction_chromosphere(n_Tonly)
35 double precision :: T_H_fraction_K(n_Tonly)
36 double precision :: ionization_He_abundance = 0.1d0
37 double precision :: ionization_Rfactor_norm = 2.3d0
38 double precision, allocatable :: prom_T_table(:)
39 double precision, allocatable :: prom_p_table(:)
40 double precision, allocatable :: prom_T_eos_table(:)
41 double precision, allocatable :: prom_Y_eos_table(:,:)
42 double precision :: Te_table_min, Te_table_max, inv_Te_table_step
43 double precision :: Te_low_iz_He=5413.d0
44 double precision :: Y_table_min, Y_table_max
45 double precision :: ionization_H_energy_unit = 0.d0
46 !> He ionisation-energy units (code units per ionised electron), set in
47 !> ionization_degree_init when energy mode is on. The two stages match the
48 !> electron-count model used by the R-factor (electrons per He =
49 !> iz_He*(1+iz_He) = iz_He + iz_He**2): first stage iz_He at chi_HeI, second
50 !> stage iz_He**2 at chi_HeII. Zero in no-energy mode.
51 double precision :: ionization_HeI_energy_unit = 0.d0
52 double precision :: ionization_HeII_energy_unit = 0.d0
53 double precision, dimension(:), allocatable :: Rfactor_table
54 double precision, dimension(:), allocatable :: eps_ion_H_table
55 double precision, dimension(:), allocatable :: Te_H_table
56 double precision, dimension(:), allocatable :: iz_H_table
57 ! Y_table(i)=Rfactor_table(i)*Te_H_table(i)
58 double precision, dimension(:), allocatable :: Y_table
59 ! chromosphere: T-only ionization table based on
60 ! Carlsson & Leenaarts (2012, A&A, 539, A39).
61 integer, parameter :: ionization_mode_chromosphere = 1
62 ! prominence: pressure-dependent prominence ionization table based on
63 ! Heinzel et al. (2015, A&A, 579, A16) and
64 ! Gunar et al. (2025, A&A, 699, A89). The bundled table uses model A,
65 ! side illumination, and the L = 500 km voxel from the latter work.
66 integer, parameter :: ionization_mode_prominence = 2
67 ! flare: T-only hydrogen H I/H fraction table for flaring chromosphere
68 ! conditions from Hong, Carlsson & Ding (2022, A&A, 661, A77). It is
69 ! handled by the same T-only H-fraction machinery as the chromosphere
70 ! table.
71 integer, parameter :: ionization_mode_flare = 3
72 integer, parameter :: n_prom_T = 10
73 integer, parameter :: n_prom_p = 7
74 integer, parameter :: n_prom_eos = 4000
75 integer :: ionization_table_mode = ionization_mode_chromosphere
76 ! Initialize
78 ! Current rho,p -> complete EOS state
79 public :: ionization_get_state
81 ! Prescribed T,p -> Rfactor and optional ionization degrees
82 public :: ionization_state_tp
83 ! Prescribed rho,T -> consistent pthermal and Rfactor
85 ! T-only cooling callback and mode query
91 ! ionization_includes_energy, ionization_get_eps_derivative_T,
92 ! ionization_check_eint_table and ionization_state_from_temperature_scalar
93 ! are PRIVATE: only ever called inside this module (no external importer in
94 ! src/), so they are not part of the backend's public surface.
95 ! Legacy compatibility API (used by get_Rfactor_tonly_PI in mod_eos_pi)
97 logical :: ionization_initialized = .false.
98 logical :: ionization_include_energy = .false.
99 !> One-shot guard so a non-converged eint->T Newton inversion warns once
100 !> (rank 0) instead of spamming every offending cell every substep.
101 logical :: pi_newton_warned = .false.
102
103 double precision, parameter :: prom_T_K(n_prom_T) = (/ &
104 6000.d0, 7000.d0, 8000.d0, 9000.d0, 10000.d0, &
105 12000.d0, 15000.d0, 20000.d0, 25000.d0, 30000.d0 /)
106
107 double precision, parameter :: prom_p_cgs(n_prom_p) = (/ &
108 0.03d0, 0.05d0, 0.10d0, 0.30d0, 0.50d0, 0.70d0, 1.00d0 /)
109
110 ! First index: temperature. Second index: pressure.
111 ! Data are ordered one complete temperature column per pressure.
112 double precision, parameter :: &
113 prom_iz_H_data(n_prom_T,n_prom_p) = reshape((/ &
114 ! p = 0.03 dyn cm^-2
115 0.53d0, 0.59d0, 0.65d0, 0.73d0, 0.80d0, &
116 0.90d0, 0.95d0, 0.98d0, 0.99d0, 1.00d0, &
117 ! p = 0.05 dyn cm^-2
118 0.40d0, 0.47d0, 0.56d0, 0.69d0, 0.79d0, &
119 0.91d0, 0.96d0, 0.98d0, 0.99d0, 1.00d0, &
120 ! p = 0.10 dyn cm^-2
121 0.25d0, 0.32d0, 0.47d0, 0.67d0, 0.81d0, &
122 0.93d0, 0.97d0, 0.99d0, 0.99d0, 1.00d0, &
123 ! p = 0.30 dyn cm^-2
124 0.10d0, 0.17d0, 0.41d0, 0.69d0, 0.85d0, &
125 0.96d0, 0.98d0, 0.99d0, 1.00d0, 1.00d0, &
126 ! p = 0.50 dyn cm^-2
127 0.06d0, 0.14d0, 0.38d0, 0.70d0, 0.87d0, &
128 0.97d0, 0.99d0, 1.00d0, 1.00d0, 1.00d0, &
129 ! p = 0.70 dyn cm^-2
130 0.04d0, 0.12d0, 0.36d0, 0.70d0, 0.88d0, &
131 0.97d0, 0.99d0, 1.00d0, 1.00d0, 1.00d0, &
132 ! p = 1.00 dyn cm^-2
133 0.03d0, 0.10d0, 0.34d0, 0.71d0, 0.89d0, &
134 0.98d0, 0.99d0, 1.00d0, 1.00d0, 1.00d0 /), &
135 (/ n_prom_t, n_prom_p /))
136
137 ! H I/H population fraction from Hong, Carlsson & Ding
138 ! (2022, A&A, 661, A77). Its temperature grid is identical to
139 ! T_H_fraction_K.
140 double precision, parameter :: HI_fraction_flare(n_Tonly) = (/ &
141 9.9999877d-1, 9.9999877d-1, 9.9999877d-1, 9.9999877d-1, 9.9999877d-1, &
142 9.9999877d-1, 9.9999877d-1, 9.9999877d-1, 9.9999877d-1, 9.9999877d-1, &
143 9.9999877d-1, 9.9999890d-1, 9.9998024d-1, 9.9992560d-1, 9.9968205d-1, &
144 9.9899492d-1, 9.9682125d-1, 9.9069732d-1, 9.8273722d-1, 9.5165553d-1, &
145 8.8401513d-1, 7.5808722d-1, 6.3272201d-1, 4.7164997d-1, 1.6497967d-1, &
146 8.6151552d-2, 4.9787498d-2, 2.3629016d-2, 1.1010071d-2, 7.7663095d-3, &
147 5.7739222d-3, 4.1811404d-3, 3.4427999d-3, 2.9108183d-3, 2.2295092d-3, &
148 1.9451997d-3, 1.4754925d-3, 1.1849689d-3, 9.1381928d-4, 7.1369446d-4, &
149 6.3154203d-4, 5.4204957d-4, 4.6800799d-4, 4.1090740d-4, 3.4051796d-4, &
150 2.9397681d-4, 2.5497294d-4, 2.0900410d-4, 1.7267453d-4, 1.3474650d-4, &
151 1.0894726d-4, 8.6230407d-5, 7.0545733d-5, 5.8882317d-5, 4.9311732d-5, &
152 3.9687148d-5, 3.1954017d-5, 2.6460585d-5, 2.1367807d-5, 1.7836169d-5, &
153 1.4207015d-5, 1.2022741d-5, 9.9158901d-6, 8.5561393d-6, 7.1994135d-6, &
154 6.1326905d-6, 5.0944881d-6, 4.3131760d-6, 3.6080551d-6, 3.0571391d-6, &
155 2.5778758d-6, 2.1649433d-6, 1.7960119d-6, 1.4711565d-6, 1.1832833d-6, &
156 9.3858978d-7, 7.4227325d-7, 5.8536296d-7, 4.6590828d-7, 3.8339449d-7, &
157 3.2851748d-7, 2.8778528d-7, 2.5213506d-7, 2.2042234d-7, 1.9440791d-7, &
158 1.7116454d-7, 1.5143655d-7, 1.3390303d-7, 1.1879927d-7, 1.0569364d-7, &
159 9.4140342d-8, 8.3767251d-8, 7.4822295d-8, 6.6863923d-8, 5.9836421d-8, &
160 5.3685738d-8, 4.8177839d-8, 4.3423310d-8, 3.9109303d-8, 1.0188512d-8 /)
161
162 ! Common temperature grid for the T-only H-fraction tables. The
163 ! Carlsson & Leenaarts (2012) and Hong et al. (2022) grids coincide.
164 data t_h_fraction_k /2.1000005d+03, 2.2349495d+03, 2.3785708d+03, 2.5314199d+03, 2.6940928d+03, &
165 2.8672190d+03, 3.0514692d+03, 3.2475613d+03, 3.4562544d+03, 3.6783584d+03, &
166 3.9147351d+03, 4.1662998d+03, 4.4340322d+03, 4.7189697d+03, 5.0222153d+03, &
167 5.3449502d+03, 5.6884248d+03, 6.0539712d+03, 6.4430083d+03, 6.8570420d+03, &
168 7.2976860d+03, 7.7666460d+03, 8.2657383d+03, 8.7969062d+03, 9.3622090d+03, &
169 9.9638330d+03, 1.0604130d+04, 1.1285561d+04, 1.2010781d+04, 1.2782619d+04, &
170 1.3604041d+04, 1.4478250d+04, 1.5408652d+04, 1.6398826d+04, 1.7452648d+04, &
171 1.8574172d+04, 1.9767766d+04, 2.1038082d+04, 2.2390010d+04, 2.3828838d+04, &
172 2.5360100d+04, 2.6989764d+04, 2.8724182d+04, 3.0570023d+04, 3.2534518d+04, &
173 3.4625215d+04, 3.6850262d+04, 3.9218293d+04, 4.1738543d+04, 4.4420699d+04, &
174 4.7275266d+04, 5.0313219d+04, 5.3546391d+04, 5.6987395d+04, 6.0649453d+04, &
175 6.4546914d+04, 6.8694758d+04, 7.3109148d+04, 7.7807289d+04, 8.2807258d+04, &
176 8.8128625d+04, 9.3791852d+04, 9.9819000d+04, 1.0623346d+05, 1.1306024d+05, &
177 1.2032560d+05, 1.2805797d+05, 1.3628709d+05, 1.4504503d+05, 1.5436592d+05, &
178 1.6428561d+05, 1.7484295d+05, 1.8607852d+05, 1.9803609d+05, 2.1076208d+05, &
179 2.2430608d+05, 2.3872045d+05, 2.5406084d+05, 2.7038703d+05, 2.8776234d+05, &
180 3.0625456d+05, 3.2593475d+05, 3.4688000d+05, 3.6917081d+05, 3.9289406d+05, &
181 4.1814181d+05, 4.4501247d+05, 4.7360988d+05, 5.0404450d+05, 5.3643488d+05, &
182 5.7090725d+05, 6.0759431d+05, 6.4663956d+05, 6.8819319d+05, 7.3241712d+05, &
183 7.7948294d+05, 8.2957412d+05, 8.8288331d+05, 9.3961919d+05, 1.0000000d+06 /
184
185 ! Chromospheric H I/H fraction from
186 ! Carlsson, M., & Leenaarts, J. 2012, A&A, 539, A39.
187 data hi_fraction_chromosphere /1.0000000d+00, 1.0000000d+00, 1.0000000d+00, 1.0000000d+00, 1.0000000d+00, &
188 1.0000000d+00, 1.0000000d+00, 9.9999928d-01, 9.9999774d-01, 9.9999624d-01, &
189 9.9999428d-01, 9.9999219d-01, 9.9998862d-01, 9.9998313d-01, 9.9997944d-01, &
190 9.9995774d-01, 1.0000324d+00, 9.9822283d-01, 9.6391600d-01, 8.8995951d-01, &
191 8.0025935d-01, 7.4219799d-01, 7.1378660d-01, 6.9565988d-01, 6.7326778d-01, &
192 6.5032905d-01, 6.2487972d-01, 5.9925246d-01, 5.7028764d-01, 5.3673893d-01, &
193 4.9144468d-01, 4.4332290d-01, 3.9158964d-01, 3.4621361d-01, 2.7826238d-01, &
194 1.9633104d-01, 1.2593637d-01, 8.1383914d-02, 5.0944358d-02, 2.8770180d-02, &
195 1.4449068d-02, 7.6071853d-03, 4.3617180d-03, 2.7694483d-03, 1.8341533d-03, &
196 1.2467797d-03, 8.6159195d-04, 6.0232455d-04, 4.2862241d-04, 3.0666622d-04, &
197 2.2256430d-04, 1.6328457d-04, 1.2179965d-04, 9.2065704d-05, 7.0357783d-05, &
198 5.4168402d-05, 4.2026968d-05, 3.2807184d-05, 2.5801779d-05, 2.0414085d-05, &
199 1.6271379d-05, 1.3041737d-05, 1.0538992d-05, 8.5329248d-06, 6.9645471d-06, &
200 5.7069005d-06, 4.7155136d-06, 3.9203474d-06, 3.2734958d-06, 2.7415674d-06, &
201 2.2911979d-06, 1.9045801d-06, 1.5692771d-06, 1.2782705d-06, 1.0281080d-06, &
202 8.1567373d-07, 6.4200310d-07, 5.0668103d-07, 4.0577518d-07, 3.3245340d-07, &
203 2.8006056d-07, 2.4119515d-07, 2.1033340d-07, 1.8429903d-07, 1.6189058d-07, &
204 1.4264889d-07, 1.2616209d-07, 1.1193421d-07, 9.9408616d-08, 8.8335305d-08, &
205 7.8593857d-08, 7.0042269d-08, 6.2532941d-08, 5.5898361d-08, 5.0049657d-08, &
206 4.4888207d-08, 4.0317172d-08, 3.6192560d-08, 3.2330732d-08, 2.9387850d-08 /
207
208 contains
209
210 subroutine ionization_degree_init(He_abundance, Rfactor_norm, table_name, include_energy)
213 use mod_constants, only: const_ev
214 use mod_comm_lib, only: mpistop
215 double precision, intent(in) :: he_abundance, rfactor_norm
216 character(len=*), intent(in), optional :: table_name
217 character(len=32) :: selected_table
218 logical, intent(in), optional :: include_energy
219 double precision, parameter :: erg_to_joule = 1.0d-7
220 double precision :: energy_unit
221
222 if (ionization_initialized) then
223 call mpistop("ionization_degree_init called more than once")
224 end if
225
226 if (he_abundance < 0.d0) call mpistop("negative He abundance")
227 if (rfactor_norm <= 0.d0) call mpistop("invalid Rfactor normalization")
228
229 ionization_he_abundance = he_abundance
230 ionization_rfactor_norm = rfactor_norm
231 te_low_iz_he = 5413.d0/unit_temperature
232 ionization_include_energy = .false.
233 if (present(include_energy)) ionization_include_energy = include_energy
234
235 selected_table = "chromosphere"
236 if (present(table_name)) selected_table = trim(adjustl(table_name))
237
238 select case (trim(selected_table))
239 case ("chromosphere")
240 ionization_table_mode = ionization_mode_chromosphere
241 case ("flare")
242 ionization_table_mode = ionization_mode_flare
243 case ("prominence")
244 ionization_table_mode = ionization_mode_prominence
245 case default
246 call mpistop( &
247 "Unknown ionization table. Use 'chromosphere', 'flare', "// &
248 "or 'prominence'.")
249 end select
250
251 if (ionization_include_energy .and. &
253 call mpistop("Ionization energy is supported only for T-only tables "// &
254 "('chromosphere' and 'flare'), not for the pressure-dependent "// &
255 "'prominence' table.")
256 end if
257
258 if (ionization_include_energy) then
259 if (unit_numberdensity <= 0.d0 .or. unit_pressure <= 0.d0) then
260 call mpistop("invalid units for hydrogen ionization energy")
261 end if
262 ! eV -> code-units-per-particle factor (const_eV is erg/eV; in SI the
263 ! pressure unit is in joule, so convert erg->joule first).
264 if (si_unit) then
265 energy_unit = const_ev*erg_to_joule*unit_numberdensity/unit_pressure
266 else
268 end if
269 ionization_h_energy_unit = 13.6d0 * energy_unit ! chi_H
270 ionization_hei_energy_unit = 24.59d0 * energy_unit ! chi_He I
271 ionization_heii_energy_unit = 54.42d0 * energy_unit ! chi_He II
272 else
273 ionization_h_energy_unit = 0.d0
274 ionization_hei_energy_unit = 0.d0
275 ionization_heii_energy_unit = 0.d0
276 end if
277
278 select case (ionization_table_mode)
279 case (ionization_mode_chromosphere)
280 call ionization_build_temperature_table(hi_fraction_chromosphere)
281 case (ionization_mode_flare)
282 call ionization_build_temperature_table(hi_fraction_flare)
283 case (ionization_mode_prominence)
284 call ionization_build_prominence_table()
285 end select
286 ionization_initialized = .true.
287 end subroutine ionization_degree_init
288
289 subroutine ionization_build_temperature_table(HI_fraction)
291
292 double precision, intent(in) :: hi_fraction(n_tonly)
293 double precision :: fact1, fact2, fact3
294 double precision :: dl1, dl2, te_table_step
295 double precision :: iz_h_source(n_tonly)
296 integer :: i, j, n_interpolated_table
297 logical :: jump
298
299 n_interpolated_table=4000
300
301 ! The source tables store H I/H; the EOS uses H II/H.
302 iz_h_source=1.d0-hi_fraction
303
304 allocate(te_h_table(1:n_interpolated_table))
305 allocate(iz_h_table(1:n_interpolated_table))
306 te_h_table=zero
307 iz_h_table=zero
308
309 te_table_step = (t_h_fraction_k(n_tonly)-t_h_fraction_k(1)) &
310 /dble(n_interpolated_table-1)
311
312 te_h_table(1) = t_h_fraction_k(1)
313 iz_h_table(1) = iz_h_source(1)
314
315 te_h_table(n_interpolated_table) = t_h_fraction_k(n_tonly)
316 iz_h_table(n_interpolated_table) = iz_h_source(n_tonly)
317
318 do i=2,n_interpolated_table
319 te_h_table(i) = te_h_table(i-1)+te_table_step
320 do j=1,n_tonly-1
321 ! Second order interpolation except at the outer edge or a jump.
322 if(te_h_table(i) < t_h_fraction_k(j+1)) then
323 if(j.eq. n_tonly-1) then
324 fact1 = (te_h_table(i)-t_h_fraction_k(j+1)) &
325 /(t_h_fraction_k(j)-t_h_fraction_k(j+1))
326
327 fact2 = (te_h_table(i)-t_h_fraction_k(j)) &
328 /(t_h_fraction_k(j+1)-t_h_fraction_k(j))
329
330 iz_h_table(i) = iz_h_source(j)*fact1 + &
331 iz_h_source(j+1)*fact2
332 exit
333 else
334 dl1 = iz_h_source(j+1)-iz_h_source(j)
335 dl2 = iz_h_source(j+2)-iz_h_source(j+1)
336 jump = (max(dabs(dl1),dabs(dl2)) > &
337 2.d0*min(dabs(dl1),dabs(dl2)))
338 end if
339
340 if(jump) then
341 fact1 = (te_h_table(i)-t_h_fraction_k(j+1)) &
342 /(t_h_fraction_k(j)-t_h_fraction_k(j+1))
343
344 fact2 = (te_h_table(i)-t_h_fraction_k(j)) &
345 /(t_h_fraction_k(j+1)-t_h_fraction_k(j))
346
347 iz_h_table(i) = iz_h_source(j)*fact1 + &
348 iz_h_source(j+1)*fact2
349 exit
350 else
351 fact1 = ((te_h_table(i)-t_h_fraction_k(j+1)) &
352 * (te_h_table(i)-t_h_fraction_k(j+2))) &
353 / ((t_h_fraction_k(j)-t_h_fraction_k(j+1)) &
354 * (t_h_fraction_k(j)-t_h_fraction_k(j+2)))
355
356 fact2 = ((te_h_table(i)-t_h_fraction_k(j)) &
357 * (te_h_table(i)-t_h_fraction_k(j+2))) &
358 / ((t_h_fraction_k(j+1)-t_h_fraction_k(j)) &
359 * (t_h_fraction_k(j+1)-t_h_fraction_k(j+2)))
360
361 fact3 = ((te_h_table(i)-t_h_fraction_k(j)) &
362 * (te_h_table(i)-t_h_fraction_k(j+1))) &
363 / ((t_h_fraction_k(j+2)-t_h_fraction_k(j)) &
364 * (t_h_fraction_k(j+2)-t_h_fraction_k(j+1)))
365
366 iz_h_table(i) = iz_h_source(j)*fact1 + &
367 iz_h_source(j+1)*fact2 + iz_h_source(j+2)*fact3
368 exit
369 end if
370 end if
371 end do
372 end do
373
374 te_h_table=te_h_table/unit_temperature
375 te_table_min=te_h_table(1)
376 te_table_max=te_h_table(n_interpolated_table)
377 inv_te_table_step = &
378 dble(n_interpolated_table-1)/(te_table_max-te_table_min)
379
380 call ionization_build_eos_table()
381 end subroutine ionization_build_temperature_table
382
383 subroutine ionization_build_prominence_table()
385 use mod_comm_lib, only: mpistop
386
387 integer :: it, ip
388 double precision :: pressure_unit_cgs
389 double precision :: dt, rfactor
390
391 if (unit_temperature <= 0.d0 .or. unit_pressure <= 0.d0) then
392 call mpistop("invalid units for prominence ionization table")
393 end if
394
395 if (si_unit) then
396 ! unit_pressure is Pa; 1 Pa = 10 dyn cm^-2.
397 pressure_unit_cgs = 10.d0*unit_pressure
398 else
399 ! In cgs mode unit_pressure is already dyn cm^-2.
400 pressure_unit_cgs = unit_pressure
401 end if
402
403 allocate(prom_t_table(n_prom_t))
404 allocate(prom_p_table(n_prom_p))
405 allocate(prom_t_eos_table(n_prom_eos))
406 allocate(prom_y_eos_table(n_prom_eos,n_prom_p))
407
408 prom_t_table = prom_t_k/unit_temperature
409 prom_p_table = prom_p_cgs/pressure_unit_cgs
410
411 dt = (1.d6 - 2.1000005d3) / dble(n_prom_eos-1)
412 do it = 1, n_prom_eos
413 prom_t_eos_table(it) = &
414 (2.1000005d3 + dble(it-1)*dt)/unit_temperature
415 end do
416
417 do ip = 1, n_prom_p
418 do it = 1, n_prom_eos
419 call ionization_state_tp(prom_t_eos_table(it), prom_p_table(ip), rfactor)
420 prom_y_eos_table(it,ip) = &
421 rfactor*prom_t_eos_table(it)
422 end do
423 end do
424
425 call ionization_check_prominence_y_table()
426 end subroutine ionization_build_prominence_table
427
428 subroutine ionization_prominence_pressure_bracket(p, ip, weight)
429 double precision, intent(in) :: p
430 integer, intent(out) :: ip
431 double precision, intent(out) :: weight
432
433 integer :: j
434 double precision :: p_use
435
436 p_use = min(prom_p_table(n_prom_p), &
437 max(prom_p_table(1), p))
438
439 if (p_use <= prom_p_table(1)) then
440 ip = 1
441 weight = 0.d0
442 return
443 else if (p_use >= prom_p_table(n_prom_p)) then
444 ip = n_prom_p-1
445 weight = 1.d0
446 return
447 end if
448
449 do j = 1, n_prom_p-1
450 if (p_use <= prom_p_table(j+1)) then
451 ip = j
452 weight = (p_use-prom_p_table(j)) / &
453 (prom_p_table(j+1)-prom_p_table(j))
454 return
455 end if
456 end do
457 end subroutine ionization_prominence_pressure_bracket
458
459 !> Array interface for T -> ionization degrees. Used by get_Rfactor_tonly_PI
460 !> (mod_eos_pi) to build the no-energy chromosphere/flare R-factor block.
461 subroutine ionization_degree_from_temperature(ixI^L,ixO^L,Te,iz_H,iz_He)
463 integer, intent(in) :: ixi^l, ixo^l
464 double precision, intent(in) :: te(ixi^s)
465 double precision, intent(out) :: iz_h(ixo^s), iz_he(ixo^s)
466 integer :: ix^d
467 double precision :: rtmp
468
469 {do ix^db=ixomin^db,ixomax^db\}
471 te(ix^d), rtmp, iz_h(ix^d), iz_he(ix^d))
472 {end do\}
474
475 subroutine ionization_h_degree_temperature_scalar(T, iz_H)
476 use mod_comm_lib, only: mpistop
477
478 double precision, intent(in) :: t
479 double precision, intent(out) :: iz_h
480
481 integer :: i
482
483 if (.not. allocated(te_h_table) .or. &
484 .not. allocated(iz_h_table)) then
485 call mpistop("temperature-only ionization table is not initialized")
486 end if
487
488 if (t < te_table_min) then
489 iz_h = 0.d0
490 else if (t >= te_table_max) then
491 iz_h = 1.d0
492 else
493 i = int((t-te_table_min)*inv_te_table_step) + 1
494 i = max(1, min(size(te_h_table)-1, i))
495
496 iz_h = iz_h_table(i) + &
497 (t-te_h_table(i)) * &
498 (iz_h_table(i+1)-iz_h_table(i)) * &
499 inv_te_table_step
500 end if
501
502 iz_h = min(1.d0, max(0.d0, iz_h))
503 end subroutine ionization_h_degree_temperature_scalar
504
505 subroutine ionization_h_degree_prominence_scalar(T, p, iz_H)
506 use mod_comm_lib, only: mpistop
507
508 double precision, intent(in) :: t, p
509 double precision, intent(out) :: iz_h
510
511 integer :: it, ip, j
512 double precision :: weight_t, weight_p
513 double precision :: iz_low, iz_high
514
515 if (.not. allocated(prom_t_table) .or. &
516 .not. allocated(prom_p_table)) then
517 call mpistop("prominence ionization table is not initialized")
518 end if
519
520 call ionization_prominence_pressure_bracket(p, ip, weight_p)
521
522 if (t <= prom_t_table(1)) then
523 it = 1
524 weight_t = 0.d0
525 else if (t >= prom_t_table(n_prom_t)) then
526 iz_h = 1.d0
527 return
528 else
529 it = 1
530 do j = 1, n_prom_t-1
531 if (t <= prom_t_table(j+1)) then
532 it = j
533 exit
534 end if
535 end do
536
537 weight_t = (t-prom_t_table(it)) / &
538 (prom_t_table(it+1)-prom_t_table(it))
539 end if
540
541 if (it == 1 .and. weight_t == 0.d0) then
542 iz_h = (1.d0-weight_p)*prom_iz_h_data(1,ip) + &
543 weight_p*prom_iz_h_data(1,ip+1)
544 else
545 iz_low = (1.d0-weight_p)*prom_iz_h_data(it,ip) + &
546 weight_p*prom_iz_h_data(it,ip+1)
547
548 iz_high = (1.d0-weight_p)*prom_iz_h_data(it+1,ip) + &
549 weight_p*prom_iz_h_data(it+1,ip+1)
550
551 iz_h = (1.d0-weight_t)*iz_low + weight_t*iz_high
552 end if
553
554 iz_h = min(1.d0, max(0.d0, iz_h))
555 end subroutine ionization_h_degree_prominence_scalar
556
557 subroutine ionization_get_state_scalar(rho, p, T, Rfactor, iz_H, iz_He)
558 use mod_comm_lib, only: mpistop
559 double precision, intent(in) :: rho, p
560 double precision, intent(out) :: t, rfactor
561 double precision, intent(out), optional :: iz_h, iz_he
562
563 double precision :: y
564 double precision :: iz_h_loc, iz_he_loc
565
566 if (.not. ionization_initialized) then
567 call mpistop("ionization tables are not initialized")
568 end if
569
570 if (rho <= 0.d0 .or. p <= 0.d0) then
571 select case (ionization_table_mode)
572 case (ionization_mode_chromosphere, ionization_mode_flare)
573 t = te_h_table(1)
574 case (ionization_mode_prominence)
575 t = prom_t_eos_table(1)
576 end select
577 else
578 y = p/rho
579
580 select case (ionization_table_mode)
581 case (ionization_mode_chromosphere, ionization_mode_flare)
582 call ionization_invert_y_scalar(y, t)
583
584 case (ionization_mode_prominence)
585 call ionization_invert_y_prominence_scalar(y, p, t)
586
587 case default
588 call mpistop("invalid ionization table mode")
589 end select
590 end if
591
592 call ionization_state_tp( t, p, rfactor, iz_h_loc, iz_he_loc)
593
594 if (present(iz_h)) iz_h = iz_h_loc
595 if (present(iz_he)) iz_he = iz_he_loc
596 end subroutine ionization_get_state_scalar
597
598 subroutine ionization_he_degree_scalar(T, iz_He)
600
601 double precision, intent(in) :: t
602 double precision, intent(out) :: iz_he
603
604 if (t < te_low_iz_he) then
605 iz_he = 1.0084814d-4
606 else
607 iz_he = 1.d0 - &
608 10.d0**(0.322571d0-5.96d-5*t*unit_temperature)
609 end if
610
611 iz_he = min(1.d0, max(0.d0, iz_he))
612 end subroutine ionization_he_degree_scalar
613
614 subroutine ionization_state_tp(T, p, Rfactor, iz_H, iz_He)
615 use mod_comm_lib, only: mpistop
616
617 double precision, intent(in) :: t, p
618 double precision, intent(out) :: rfactor
619 double precision, intent(out), optional :: iz_h, iz_he
620
621 double precision :: iz_h_loc, iz_he_loc
622
623 select case (ionization_table_mode)
624 case (ionization_mode_chromosphere, ionization_mode_flare)
625 call ionization_h_degree_temperature_scalar(t, iz_h_loc)
626
627 case (ionization_mode_prominence)
628 call ionization_h_degree_prominence_scalar( &
629 t, p, iz_h_loc)
630
631 case default
632 call mpistop("invalid ionization table mode")
633 end select
634
635 call ionization_he_degree_scalar(t, iz_he_loc)
636
637 iz_h_loc = min(1.d0, max(0.d0, iz_h_loc))
638 iz_he_loc = min(1.d0, max(0.d0, iz_he_loc))
639
640 rfactor = ionization_rfactor_from_degrees( &
641 iz_h_loc, iz_he_loc)
642
643 if (present(iz_h)) iz_h = iz_h_loc
644 if (present(iz_he)) iz_he = iz_he_loc
645 end subroutine ionization_state_tp
646
647 subroutine ionization_solve_p_rfactor(rho, T, p, Rfactor)
648 use mod_comm_lib, only: mpistop
649
650 integer, parameter :: max_iter = 80
651 double precision, intent(in) :: rho, t
652 double precision, intent(out) :: p, rfactor
653
654 integer :: iter
655 double precision :: plo, phi, pmid
656 double precision :: rlo, rhi, rmid
657 double precision :: fhi, fmid, scale
658
659 if (.not. ionization_initialized) then
660 call mpistop("ionization pressure solver called before initialization")
661 end if
662 if (rho <= 0.d0 .or. t <= 0.d0) then
663 call mpistop("invalid rho or T in ionization pressure solver")
664 end if
665
668 p = rho*rfactor*t
669 return
670 end if
671
672 ! Low-pressure clamp branch
673 plo = prom_p_table(1)
674 call ionization_state_tp(t, plo, rlo)
675 p = rho*rlo*t
676 if (p <= plo) then
677 rfactor = rlo
678 return
679 end if
680
681 ! High-pressure clamp branch
682 phi = prom_p_table(n_prom_p)
683 call ionization_state_tp(t, phi, rhi)
684 p = rho*rhi*t
685 if (p >= phi) then
686 rfactor = rhi
687 return
688 end if
689
690 ! Bracket is guaranteed: the two clamp branches above return whenever
691 ! f(p)=p-rho*R(p)*T does NOT change sign across [plo,phi], so reaching here
692 ! implies f(plo)<0 and f(phi)>0 -- a sign change, hence a root for bisection.
693 do iter = 1, max_iter
694 pmid = 0.5d0*(plo+phi)
695 call ionization_state_tp(t, pmid, rmid)
696 fmid = pmid-rho*rmid*t
697 scale = max(abs(pmid), abs(rho*rmid*t), tiny(1.d0))
698
699 if (abs(fmid) <= 1.d-12*scale) then
700 p = pmid
701 rfactor = rmid
702 return
703 end if
704
705 if (fmid > 0.d0) then
706 phi = pmid
707 else
708 plo = pmid
709 end if
710 end do
711
712 call mpistop("pressure-dependent EOS solve did not converge")
713 end subroutine ionization_solve_p_rfactor
714
715 subroutine ionization_get_state(ixI^L, ixO^L, rho, p, T, Rfactor, iz_H, iz_He)
716 integer, intent(in) :: ixi^l, ixo^l
717 double precision, intent(in) :: rho(ixi^s), p(ixi^s)
718 double precision, intent(out) :: t(ixi^s), rfactor(ixi^s)
719 double precision, intent(out), optional :: iz_h(ixi^s), iz_he(ixi^s)
720
721 double precision :: iz_h_loc, iz_he_loc
722 integer :: ix^d
723
724 {do ix^db=ixomin^db,ixomax^db\}
725 call ionization_get_state_scalar(rho(ix^d), p(ix^d), t(ix^d), &
726 rfactor(ix^d), iz_h_loc, iz_he_loc)
727
728 if (present(iz_h)) iz_h(ix^d) = iz_h_loc
729 if (present(iz_he)) iz_he(ix^d) = iz_he_loc
730 {end do\}
731 end subroutine ionization_get_state
732
733 subroutine ionization_invert_y_scalar(y, T)
734 use mod_comm_lib, only: mpistop
735 double precision, intent(in) :: y
736 double precision, intent(out) :: t
737
738 integer :: ilo, ihi, imid, n
739 double precision :: denom
740
741 if (.not. allocated(y_table) .or. .not. allocated(rfactor_table)) then
742 call mpistop("ionization_invert_y_scalar: EOS tables are not initialized")
743 end if
744
745 n = size(y_table)
746
747 if (n < 2) then
748 call mpistop("ionization_invert_y_scalar: EOS table has fewer than 2 points")
749 end if
750
751 if (y <= 0.d0) then
752 t = te_h_table(1)
753 return
754 end if
755
756 if (y < y_table_min) then
757 if (rfactor_table(1) > 0.d0) then
758 t = max(tiny(1.d0), y/rfactor_table(1))
759 else
760 t = te_h_table(1)
761 end if
762 return
763 end if
764
765 if (y >= y_table_max) then
766 if (rfactor_table(n) > 0.d0) then
767 t = y/rfactor_table(n)
768 else
769 t = te_h_table(n)
770 end if
771 return
772 end if
773
774 ilo = 1
775 ihi = n
776
777 do while (ihi - ilo > 1)
778 imid = (ilo + ihi)/2
779 if (y >= y_table(imid)) then
780 ilo = imid
781 else
782 ihi = imid
783 end if
784 end do
785
786 denom = y_table(ilo+1) - y_table(ilo)
787
788 if (denom <= max(tiny(denom), 1.d-12*dabs(y_table(ilo)))) then
789 call mpistop("ionization_invert_y_scalar: non-increasing Y_table interval")
790 end if
791
792 t = te_h_table(ilo) + (y - y_table(ilo)) * &
793 (te_h_table(ilo+1) - te_h_table(ilo))/denom
794 end subroutine ionization_invert_y_scalar
795
796 subroutine ionization_state_from_temperature_scalar(T, Rfactor, iz_H, iz_He)
797 use mod_comm_lib, only: mpistop
798
799 double precision, intent(in) :: t
800 double precision, intent(out) :: rfactor
801 double precision, intent(out), optional :: iz_h, iz_he
802 double precision :: iz_h_loc, iz_he_loc
803
804 if (.not. ionization_is_temperature_only()) then
805 call mpistop("temperature-only ionization state requested "// &
806 "for a pressure-dependent table")
807 end if
808
809 select case (ionization_table_mode)
810 case (ionization_mode_chromosphere, ionization_mode_flare)
811 call ionization_h_degree_temperature_scalar(t, iz_h_loc)
812 end select
813 call ionization_he_degree_scalar(t, iz_he_loc)
814
815 rfactor = ionization_rfactor_from_degrees( &
816 iz_h_loc, iz_he_loc)
817
818 if (present(iz_h)) iz_h = iz_h_loc
819 if (present(iz_he)) iz_he = iz_he_loc
821
822 subroutine ionization_check_prominence_y_table()
823 use mod_comm_lib, only: mpistop
824 integer :: it, ip
825
826 do ip = 1, n_prom_p
827 do it = 1, n_prom_eos-1
828 if (prom_y_eos_table(it+1,ip) <= &
829 prom_y_eos_table(it,ip)) then
830 call mpistop("prominence EOS Y(T,p) is not monotonic")
831 end if
832 end do
833 end do
834 end subroutine ionization_check_prominence_y_table
835
836 subroutine ionization_invert_y_prominence_scalar(y, p, T)
837 use mod_comm_lib, only: mpistop
838
839 double precision, intent(in) :: y, p
840 double precision, intent(out) :: t
841
842 integer :: ip, ilo, ihi, imid
843 double precision :: wp, ylo, yhi, ymid
844 double precision :: ymin, ymax, rend, denom
845
846 if (.not. allocated(prom_y_eos_table)) then
847 call mpistop("prominence EOS inversion table is missing")
848 end if
849
850 call ionization_prominence_pressure_bracket(p, ip, wp)
851
852 ymin = (1.d0-wp)*prom_y_eos_table(1,ip) + &
853 wp*prom_y_eos_table(1,ip+1)
854
855 ymax = (1.d0-wp)*prom_y_eos_table(n_prom_eos,ip) + &
856 wp*prom_y_eos_table(n_prom_eos,ip+1)
857
858 if (y <= 0.d0) then
859 t = prom_t_eos_table(1)
860 return
861 end if
862
863 if (y < ymin) then
864 rend = ymin/prom_t_eos_table(1)
865 t = max(tiny(1.d0), y/rend)
866 return
867 end if
868
869 if (y >= ymax) then
870 rend = ymax/prom_t_eos_table(n_prom_eos)
871 t = y/rend
872 return
873 end if
874
875 ilo = 1
876 ihi = n_prom_eos
877
878 do while (ihi-ilo > 1)
879 imid = (ilo+ihi)/2
880 ymid = (1.d0-wp)*prom_y_eos_table(imid,ip) + &
881 wp*prom_y_eos_table(imid,ip+1)
882
883 if (y >= ymid) then
884 ilo = imid
885 else
886 ihi = imid
887 end if
888 end do
889
890 ylo = (1.d0-wp)*prom_y_eos_table(ilo,ip) + &
891 wp*prom_y_eos_table(ilo,ip+1)
892
893 yhi = (1.d0-wp)*prom_y_eos_table(ilo+1,ip) + &
894 wp*prom_y_eos_table(ilo+1,ip+1)
895
896 denom = yhi-ylo
897 if (denom <= max(tiny(denom),1.d-12*abs(ylo))) then
898 call mpistop("invalid prominence EOS inversion interval")
899 end if
900
901 t = prom_t_eos_table(ilo) + (y-ylo) * &
902 (prom_t_eos_table(ilo+1)-prom_t_eos_table(ilo)) / &
903 denom
904 end subroutine ionization_invert_y_prominence_scalar
905
906 subroutine ionization_build_eos_table()
907 use mod_comm_lib, only: mpistop
908 integer :: i, n
909 double precision :: iz_h, iz_he
910
911 if (.not. allocated(te_h_table) .or. .not. allocated(iz_h_table)) then
912 call mpistop("ionization_build_eos_table: ionization tables are not initialized")
913 end if
914
915 if (allocated(rfactor_table)) deallocate(rfactor_table)
916 if (allocated(y_table)) deallocate(y_table)
917 if (allocated(eps_ion_h_table)) deallocate(eps_ion_h_table)
918
919 n = size(te_h_table)
920
921 if (n < 2) then
922 call mpistop("ionization_build_eos_table: Te_H_table has fewer than 2 points")
923 end if
924
925 allocate(rfactor_table(1:n))
926 allocate(y_table(1:n))
927 if (ionization_include_energy) allocate(eps_ion_h_table(1:n))
928
929 do i = 1, n
931 te_h_table(i), rfactor_table(i), iz_h, iz_he)
932 y_table(i) = rfactor_table(i)*te_h_table(i)
933 if (ionization_include_energy) then
934 eps_ion_h_table(i) = ionization_eps_ion_of_degrees(iz_h, iz_he)
935 end if
936 end do
937
938 y_table_min = y_table(1)
939 y_table_max = y_table(n)
940
941 call ionization_check_y_table()
942 end subroutine ionization_build_eos_table
943
944 subroutine ionization_check_y_table()
945 use mod_comm_lib, only: mpistop
946 integer :: i
947
948 if (.not. allocated(y_table)) then
949 call mpistop("ionization_check_y_table: Y_table is not allocated")
950 end if
951
952 if (size(y_table) < 2) then
953 call mpistop("ionization_check_y_table: Y_table has fewer than 2 points")
954 end if
955
956 do i = 1, size(y_table)-1
957 if (y_table(i+1) <= y_table(i)) then
958 call mpistop("ionization_check_y_table: Y(T)=Rfactor(T)*T is not strictly increasing")
959 end if
960 end do
961 end subroutine ionization_check_y_table
962
963 double precision function ionization_rfactor_from_degrees(iz_H, iz_He)
964 double precision, intent(in) :: iz_h, iz_he
965
966 ionization_rfactor_from_degrees = &
967 (1.d0 + iz_h + ionization_he_abundance * &
968 (1.d0 + iz_he*(1.d0 + iz_he))) / ionization_rfactor_norm
969 end function ionization_rfactor_from_degrees
970
971 !> Ionisation energy per one H (code units) from the H/He degrees -- the energy
972 !> analogue of ionization_Rfactor_from_degrees, and the single source for the
973 !> ion-energy term in eint. The He electron-count model matches the R-factor:
974 !> electrons per He = iz_He*(1+iz_He) = iz_He (first stage, chi_HeI) + iz_He**2
975 !> (second stage, chi_HeII), so p and eint derive from the SAME iz_H,iz_He.
976 !> All *_energy_unit are 0 in no-energy mode, so this returns 0 there.
977 double precision function ionization_eps_ion_of_degrees(iz_H, iz_He) result(eps_ion)
978 double precision, intent(in) :: iz_h, iz_he
979
980 eps_ion = ionization_h_energy_unit*iz_h &
981 + ionization_he_abundance * ( ionization_hei_energy_unit*iz_he &
982 + ionization_heii_energy_unit*iz_he*iz_he )
983 end function ionization_eps_ion_of_degrees
984
987 ionization_table_mode == ionization_mode_chromosphere .or. &
988 ionization_table_mode == ionization_mode_flare
990
991 logical function ionization_includes_energy()
992 ionization_includes_energy = ionization_include_energy
993 end function ionization_includes_energy
994
995 double precision function ionization_eint_table_value(i, invgam)
996 integer, intent(in) :: i
997 double precision, intent(in) :: invgam
998
999 ionization_eint_table_value = &
1000 y_table(i)*invgam + eps_ion_h_table(i)
1001 end function ionization_eint_table_value
1002
1003 subroutine ionization_check_eint_table(invgam)
1004 use mod_comm_lib, only: mpistop
1005 double precision, intent(in) :: invgam
1006 integer :: i
1007 double precision :: eps1, eps2
1008
1009 if (.not. ionization_include_energy) return
1010 if (.not. allocated(eps_ion_h_table)) then
1011 call mpistop("ionization-energy table is not initialized")
1012 end if
1013 if (invgam <= 0.d0) then
1014 call mpistop("invalid inverse gamma minus one for ionization EOS")
1015 end if
1016
1017 do i = 1, size(y_table)-1
1018 eps1 = ionization_eint_table_value(i, invgam)
1019 eps2 = ionization_eint_table_value(i+1, invgam)
1020 if (eps2 <= eps1) then
1021 call mpistop("ionization specific-energy table is not monotonic")
1022 end if
1023 end do
1024 end subroutine ionization_check_eint_table
1025
1026 subroutine ionization_invert_eint_scalar(eps, invgam, T)
1027 use mod_comm_lib, only: mpistop
1028 use mod_global_parameters, only: mype
1029 ! eps: internal energy per one H
1030 double precision, intent(in) :: eps, invgam
1031 double precision, intent(out) :: t
1032
1033 integer :: ilo, ihi, imid, n, iter
1034 double precision :: eps_min, eps_max, eps_lo, eps_hi, denom
1035 double precision :: eps_eval, slope, rfactor, iz_h, iz_he, tnew
1036
1037 if (.not. ionization_include_energy .or. &
1038 .not. allocated(eps_ion_h_table)) then
1039 call mpistop("ionization-energy EOS is not initialized")
1040 end if
1041 if (invgam <= 0.d0) then
1042 call mpistop("invalid inverse gamma minus one in energy inversion")
1043 end if
1044
1045 n = size(y_table)
1046 eps_min = ionization_eint_table_value(1, invgam)
1047 eps_max = ionization_eint_table_value(n, invgam)
1048
1049 if (eps < eps_min) then
1050 ! Below the tabulated eion energy range this is floor/illegal-state
1051 ! recovery, not a strictly conservative EOS inversion.
1052 t = max(tiny(1.d0), &
1053 (eps-eps_ion_h_table(1))/(rfactor_table(1)*invgam))
1054 return
1055 end if
1056
1057 if (eps >= eps_max) then
1058 t = max(tiny(1.d0), &
1059 (eps-eps_ion_h_table(n))/(rfactor_table(n)*invgam))
1060 return
1061 end if
1062
1063 ilo = 1
1064 ihi = n
1065 do while (ihi-ilo > 1)
1066 imid = (ilo+ihi)/2
1067 if (eps >= ionization_eint_table_value(imid, invgam)) then
1068 ilo = imid
1069 else
1070 ihi = imid
1071 end if
1072 end do
1073
1074 eps_lo = ionization_eint_table_value(ilo, invgam)
1075 eps_hi = ionization_eint_table_value(ilo+1, invgam)
1076 denom = eps_hi-eps_lo
1077 if (denom <= max(tiny(denom), 1.d-12*abs(eps_lo))) then
1078 call mpistop("invalid ionization-energy inversion interval")
1079 end if
1080
1081 t = te_h_table(ilo) + (eps-eps_lo) * &
1082 (te_h_table(ilo+1)-te_h_table(ilo))/denom
1083
1084 ! The tabulated value supplies a close initial guess. Correct it against
1085 ! the actual interpolated ionization state so rho,eint -> T and
1086 ! rho,T -> eint remain mutually consistent inside each table interval.
1087 slope = denom/(te_h_table(ilo+1)-te_h_table(ilo))
1088 do iter = 1, 3
1090 t, rfactor, iz_h, iz_he)
1091 eps_eval = rfactor*t*invgam + ionization_eps_ion_of_degrees(iz_h, iz_he)
1092 if (abs(eps_eval-eps) <= &
1093 1.d-12*max(abs(eps), tiny(1.d0))) exit
1094 tnew = t-(eps_eval-eps)/slope
1095 t = max(te_h_table(ilo), min(te_h_table(ilo+1), tnew))
1096 end do
1097 if (iter > 3 .and. .not. pi_newton_warned .and. mype == 0) then
1098 pi_newton_warned = .true.
1099 write(*,*) "WARNING: PI eint->T Newton inversion did not fully converge ", &
1100 "in 3 iterations; using the last iterate (interval-clamped). ", &
1101 "Further occurrences are not reported."
1102 end if
1103 end subroutine ionization_invert_eint_scalar
1104
1106 rho, eint, invgam, T, p, Rfactor, iz_H, iz_He)
1107 use mod_comm_lib, only: mpistop
1108 double precision, intent(in) :: rho, eint, invgam
1109 double precision, intent(out) :: t, p, rfactor
1110 double precision, intent(out), optional :: iz_h, iz_he
1111 double precision :: iz_h_loc, iz_he_loc
1112
1113 if (.not. ionization_include_energy) &
1114 call mpistop("ionization-energy EOS is not enabled")
1115 if (.not. ionization_is_temperature_only()) &
1116 call mpistop("ionization-energy EOS requires a T-only table")
1117 if (rho <= 0.d0 .or. eint <= 0.d0) &
1118 call mpistop("invalid rho or eint in ionization EOS")
1119 call ionization_invert_eint_scalar(eint/rho, invgam, t)
1121 t, rfactor, iz_h_loc, iz_he_loc)
1122 p = rho*rfactor*t
1123 if (present(iz_h)) iz_h = iz_h_loc
1124 if (present(iz_he)) iz_he = iz_he_loc
1125 end subroutine ionization_get_state_from_eint
1126
1128 rho, T, invgam, p, eint, Rfactor, iz_H, iz_He)
1129 use mod_comm_lib, only: mpistop
1130 double precision, intent(in) :: rho, t, invgam
1131 double precision, intent(out) :: p, eint, rfactor
1132 double precision, intent(out), optional :: iz_h, iz_he
1133 double precision :: iz_h_loc, iz_he_loc
1134
1135 if (rho <= 0.d0 .or. t <= 0.d0 .or. invgam <= 0.d0) then
1136 call mpistop("invalid state in rho,T ionization EOS mapping")
1137 end if
1138
1139 if (ionization_include_energy) then
1140 if (.not. ionization_is_temperature_only()) then
1141 call mpistop("ionization energy requires a T-only ionization table")
1142 end if
1144 t, rfactor, iz_h_loc, iz_he_loc)
1145 p = rho*rfactor*t
1146 eint = rho*(rfactor*t*invgam + &
1147 ionization_eps_ion_of_degrees(iz_h_loc, iz_he_loc))
1148 else
1149 call ionization_solve_p_rfactor(rho, t, p, rfactor)
1150 call ionization_state_tp(t, p, rfactor, iz_h_loc, iz_he_loc)
1151 eint = p*invgam
1152 end if
1153
1154 if (present(iz_h)) iz_h = iz_h_loc
1155 if (present(iz_he)) iz_he = iz_he_loc
1157
1158 subroutine ionization_get_eps_derivative_t( &
1159 T, invgam, eps, deps_dT, dq_dT_out)
1160 use mod_comm_lib, only: mpistop
1161 double precision, intent(in) :: t, invgam
1162 double precision, intent(out) :: eps, deps_dt
1163 double precision, intent(out), optional :: dq_dt_out
1164
1165 integer :: i, n
1166 double precision :: rfactor, iz_h, iz_he, q, dq_dt, depsion_dt
1167
1168 if (.not. ionization_include_energy) then
1169 call mpistop("ionization-energy derivative requested while disabled")
1170 end if
1171 if (.not. ionization_is_temperature_only()) then
1172 call mpistop("ionization-energy derivative requires a T-only table")
1173 end if
1174 if (t <= 0.d0 .or. invgam <= 0.d0) then
1175 call mpistop("invalid state in ionization-energy derivative")
1176 end if
1177
1178 n = size(te_h_table)
1179 call ionization_state_from_temperature_scalar(t, rfactor, iz_h, iz_he)
1180 q = rfactor*t
1181 eps = q*invgam + ionization_eps_ion_of_degrees(iz_h, iz_he)
1182
1183 if (t < te_table_min) then
1184 dq_dt = rfactor_table(1)
1185 depsion_dt = 0.d0
1186 else if (t >= te_table_max) then
1187 dq_dt = rfactor_table(n)
1188 depsion_dt = 0.d0
1189 else
1190 i = int((t-te_table_min)*inv_te_table_step) + 1
1191 i = max(1, min(n-1, i))
1192 dq_dt = (y_table(i+1)-y_table(i)) / &
1193 (te_h_table(i+1)-te_h_table(i))
1194 depsion_dt = (eps_ion_h_table(i+1)-eps_ion_h_table(i)) / &
1195 (te_h_table(i+1)-te_h_table(i))
1196 end if
1197
1198 deps_dt = dq_dt*invgam + depsion_dt
1199 if (deps_dt <= 0.d0) then
1200 call mpistop("non-positive heat capacity in ionization EOS")
1201 end if
1202 if (present(dq_dt_out)) dq_dt_out = dq_dt
1203 end subroutine ionization_get_eps_derivative_t
1204
1205 subroutine ionization_get_csound2_t(T, invgam, csound2)
1206 double precision, intent(in) :: t, invgam
1207 double precision, intent(out) :: csound2
1208
1209 double precision :: rfactor, q, eps, deps_dt, dq_dt
1210
1211 call ionization_get_eps_derivative_t( &
1212 t, invgam, eps, deps_dt, dq_dt)
1214 q = rfactor*t
1215 csound2 = q*(1.d0+dq_dt/deps_dt)
1216 end subroutine ionization_get_csound2_t
1217
1218end module mod_eos_pi_tables
subroutine, public mpistop(message)
Exit MPI-AMRVAC with an error message.
Module for physical and numeric constants.
double precision, parameter const_ev
PI (partial-ionisation) ionisation-degree backend for the eos% family.
logical function, public ionization_is_temperature_only()
subroutine, public ionization_get_state(ixil, ixol, rho, p, t, rfactor, iz_h, iz_he)
subroutine, public ionization_get_state_scalar(rho, p, t, rfactor, iz_h, iz_he)
subroutine, public ionization_get_p_eint_from_rho_t(rho, t, invgam, p, eint, rfactor, iz_h, iz_he)
subroutine, public ionization_degree_init(he_abundance, rfactor_norm, table_name, include_energy)
subroutine, public ionization_state_from_temperature_scalar(t, rfactor, iz_h, iz_he)
subroutine, public ionization_solve_p_rfactor(rho, t, p, rfactor)
subroutine, public ionization_degree_from_temperature(ixil, ixol, te, iz_h, iz_he)
Array interface for T -> ionization degrees. Used by get_Rfactor_tonly_PI (mod_eos_pi) to build the n...
subroutine, public ionization_get_state_from_eint(rho, eint, invgam, t, p, rfactor, iz_h, iz_he)
subroutine, public ionization_state_tp(t, p, rfactor, iz_h, iz_he)
subroutine, public ionization_get_csound2_t(t, invgam, csound2)
This module contains definitions of global parameters and variables and some generic functions/subrou...
integer it
Number of time steps taken.
double precision unit_numberdensity
Physical scaling factor for number density.
double precision unit_pressure
Physical scaling factor for pressure.
integer mype
The rank of the current MPI task.
double precision dt
global time step
double precision, dimension(:), allocatable, parameter d
double precision unit_temperature
Physical scaling factor for temperature.
logical si_unit
Use SI units (.true.) or use cgs units (.false.).