1C
2C
3C$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
4C EQSAHA
5C$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
6 SUBROUTINE EQSAHA(FX,TL,T,PL,P,DL,D,BETA,BETAI,BETA14,FXION,RMU,
7 * *AMU,EMU,LDERIV,LATMO,QDT,QDP,QCP,DELA,QDTT,QDTP,QAT,QAP,QCPT,QCPP,
8 * *KSAHA)
9C 10/14/26 MHP via Claude
10C code changed to convert COMMON/CONSTANTS/ into CONSTANTS_MOD.
11 USE CONSTANTS_MOD
12C 10/14/26 MHP via Claude
13C code changed to convert COMMON/IOUNITS/ into IOUNITS_MOD.
14 USE IOUNITS_MOD
15C 10/20/26 MHP via Claude
16C code changed to replace implicit typing with IMPLICIT NONE and
17C explicit declarations.
18 IMPLICIT NONE
19 REAL*8 AMU,BETA,BETA14,BETA16,BETAI,BETAUT,BTEMP,C11,C12,C12L,C13,
20 * * C13L,C21,C22,C23,C31,C32,C33,CR12,CR12L,CR13,CR13L,CR23,D,
21 * * DELA,DELTE,DELTEL,DELTX1,DELTX2,DELX1L,DIV,DL,E,EEP1,EMAX,
22 * * EMU,EP1,EP12,EP1E,ETEMP,FACT,FACT1,FACT2,FX,FXHE,FXION,FXS,
23 * * FXT,P,PDT,PDTQ,PL,QAP,QAT,QCP,QCPI,QCPP,QCPT,QDP,QDT,QDTP,
24 * * QDTT,QQUTP,QQUTT,QUT,R1,R1L,R1P,R1T,R2,R2P,R2T,R3,R3P,R3T,
25 * * RAMU,RMU,RMUB,SAHAK,SAHAT,SAHATT,SAHAU,SAHAX,SAHEK0,SAHEK1,
26 * * SAHET0,SAHET1,SAHEX0,SAHEX1,SAHEX2,SK0QT,SK1QT,SQEP,SQET,
27 * * SQETP,SQETT,STEMP,STEMP1,STEMP2,STEMP3,STEMP4,STEMP5,
28 * * STEMP6,STEMP7,STOL,STOLEE,SX0QP,SX0QT,SX1QP,SX1QT,SX1QTP,
29 * * SX1QTT,SX2QP,SX2QT,SX2QTP,SX2QTT,T,TEMP,TEMP8,TI,TL,UPSUM,
30 * * USUM,UTSUM
31 INTEGER I,ISAHA,KSAHA,MAXS,NZ,NZ0,NZ1
32 LOGICAL*4 LATMO,LDERIV,LFIN,LNOHE,LNOHE2
33
34
35C 9/25/26 MHP via Claude
36C code changed to consolidate all I/O logical unit numbers, previously
37C scattered across COMMON/LUOUT/, COMMON/LUNUM/, COMMON/ALEX06/,
38C COMMON/ACDPTH/, COMMON/NEWOPAC/, COMMON/LOPAL95/, COMMON/ATMOS2/,
39C COMMON/ALATM03/, COMMON/OPALEOS/, and COMMON/SCV2/, into a single
40C COMMON/IOUNITS/
41 DIMENSION FX(12),FXION(3)
42 DIMENSION SAHATT(11),FXT(11)
43 DIMENSION SAHAU(13),SAHAT(11),SAHAK(11),SAHAX(11),FXS(11)
44C 9/25/26 MHP via Claude
45C code changed to merge COMMON/CONST/, COMMON/CONST1/, COMMON/CONST2/,
46C and COMMON/CONST3/ into a single COMMON/CONSTANTS/
47 DATA SAHAT,SAHET0,SAHET1/59630.,69450.,88710.,91330.,94570.,
48 T T 130630.,157800.,158000.,168630.,183080.,250200.,285270.,631370./
49 DATA SAHAU/-.01D0,-.47D0,.60D0,.49D0,.08D0,.11D0,.0,-.04D0,.64D0,.99D0,1.03D0,.6D0,.0/
50 DATA NZ/11/
51 DATA MAXS/20/
52 DATA STOL/4.0D1/
53 DATA E,SAHEX1,SAHEX2/0.1D0,0.0D0,0.0D0/
54 DATA STOLEE/1.0D-09/
55 SAVE
56
57C 1 FXHE = FX(12)
58 FXHE = FX(12)
59 EMAX = 1.0D0 + FXHE
60 TI = 1.0D0/T
61C BEGIN SAHA ROUTINE
62C COMPUTE SAHA K'S
63 STEMP = 2.50D0*TL - PL + CMKH
64 NZ1 = 1
65 DO 11 I=1,NZ
66 SAHATT(I) = SAHAT(I)*TI
67 SAHAK(I) = CLN*(SAHAU(I) + STEMP) - SAHATT(I)
68 IF(SAHAK(I).LT.-STOL) GO TO 12
69 IF(SAHAK(I).GT.+STOL) GO TO 10
70 SAHAK(I) = BETAI*DEXP(SAHAK(I))
71 GO TO 11
72 10 10 SAHAK(I) = 1.0D16
73 NZ1 = I + 1
74 11 11 CONTINUE
75 I = NZ + 1
76 12 12 NZ0 = I - 1
77 NZ1 = MIN0(NZ,NZ1)
78 LNOHE = .TRUE.
79 LNOHE2= .TRUE.
80 IF(FXHE.LT.1.0D-10) GO TO 13
81 SAHEK0 = CLN*(SAHAU(12) + STEMP) - SAHET0/T
82 IF(SAHEK0.LT.-STOL) GO TO 13
83 LNOHE = .FALSE.
84 SAHEK0 = BETAI*DEXP(SAHEK0)
85 SAHEK1 = CLN*(SAHAU(13) + STEMP) - SAHET1/T
86 IF(SAHEK1.LT.-STOL) GO TO 14
87 LNOHE2= .FALSE.
88 SAHEK1 = BETAI*DEXP(SAHEK1)
89 GO TO 15
90 13 13 SAHEX1 = 0.0D0
91 14 14 SAHEX2 = 0.0D0
92 15 15 CONTINUE
93C BEGIN ITERATIONS FOR SOLUTION OF E
94 DO 28 ISAHA=1,MAXS
95 KSAHA = KSAHA + 1
96 EP1 = 1.0D0/(1.0D0 + E)
97 EP1E = EP1/E
98 EP12 = EP1*EP1
99 EEP1 = E*EP1
100 C33 = -E*(1.0D0 + E)
101 R3 = E
102 IF(NZ0.LE.0) GO TO 22
103 DO 21 I=1,NZ0
104 DIV = 1.0D0/(SAHAK(I) + EEP1)
105 TEMP8 = SAHAK(I)*DIV
106 SAHAX(I) = TEMP8
107 TEMP8 = TEMP8*FX(I)
108 R3 = R3 - TEMP8
109 TEMP8 = TEMP8*EEP1*DIV
110 C33 = C33 - TEMP8
111 FXS(I) = TEMP8
112 21 21 CONTINUE
113 22 22 C33 = C33*EP1E
114 R3 = R3 - FXHE*(SAHEX1 + SAHEX2+SAHEX2)
115 LFIN = DABS(R3).LT.STOLEE
116 IF(LNOHE) GO TO 25
117 C22 = SAHEK0 + EEP1
118 C23 = SAHEX1*EP12
119 C32 = FXHE
120 R2 = SAHEK0*(1.0D0-SAHEX1-SAHEX2) - SAHEX1*EEP1
121 IF(LNOHE2) GO TO 23
122 C11 = EEP1
123 C12 = -SAHEK1
124 C13 = SAHEX2*EP12
125 C21 = SAHEK0
126 C31 = 2.0D0*FXHE
127 R1 = SAHEX1*SAHEK1 - SAHEX2*EEP1
128C REDUCTION OF SAHA MATRIX TO DIAGONAL FORM
129 CR12 = - C21/C11
130 CR13 = - C31/C11
131 C22 = C22 + CR12*C12
132 C32 = C32 + CR13*C12
133 C23 = C23 + CR12*C13
134 C33 = C33 + CR13*C13
135 IF (R1.EQ.0.0D0) GO TO 210
136 R1L=DLOG10(DABS(R1))
137 IF (CR12.EQ.0.0D0) GO TO 200
138 CR12L=DLOG10(DABS(CR12))
139 FACT=CR12L+R1L
140 IF (FACT.LT.-38.0D0) GO TO 200
141 R2 = R2 + CR12*R1
142 200 200 CONTINUE
143 IF (CR13.EQ.0.0D0) GO TO 210
144 CR13L=DLOG10(DABS(CR13))
145 FACT=CR13L+R1L
146 IF (FACT.LT.-38.0D0) GO TO 210
147 R3 = R3 + CR13*R1
148 210 210 CONTINUE
149C ENTRY FOR NO FULLY IONIZED HELIUM (SAHEX2 = 0.0)
150 23 23 CR23 = - C32/C22
151 C33 = C33 + CR23*C23
152 R3 = R3 + CR23*R2
153C ENTRY FOR NEUTRAL HELIUM
154 25 25 DELTE = R3/C33
155 IF(LNOHE) GO TO 26
156 DELTX1 = (R2 - C23*DELTE)/C22
157 SAHEX1 = SAHEX1 + DELTX1
158 SAHEX1 = DMAX1(0.0D0,DMIN1(1.0D0,SAHEX1))
159 IF(LNOHE2) GO TO 26
160CCC STATEMENT RECALCULATED FOR DEC-20 SYSTEM
161 IF (C12.EQ.0.D0 .OR. DELTX1.EQ.0.D0) GO TO 100
162 C12L=DLOG10(DABS(C12))
163 DELX1L=DLOG10(DABS(DELTX1))
164 FACT1=C12L+DELX1L
165 IF (FACT1.GE.-38.0D0) GO TO 100
166 FACT1=-38.0D0
167 FACT1=DEXP(CLN*FACT1)*DSIGN(1.0D0,C12)*DSIGN(1.0D0,DELTX1)
168 GO TO 105
169 100 100 CONTINUE
170 FACT1=C12*DELTX1
171 105 105 CONTINUE
172 IF (C13.EQ.0.D0 .OR. DELTE.EQ.0.D0) GO TO 110
173 C13L=DLOG10(DABS(C13))
174 DELTEL=DLOG10(DABS(DELTE))
175 FACT2=C13L+DELTEL
176 IF (FACT2.GE.-38.0D0) GO TO 110
177 FACT2=-38.0D0
178 FACT2=DEXP(CLN*FACT2)*DSIGN(1.0D0,C13)*DSIGN(1.0D0,DELTE)
179 GO TO 115
180 110 110 CONTINUE
181 FACT2=C13*DELTE
182 115 115 CONTINUE
183 DELTX2=(R1-FACT1-FACT2)/C11
184 SAHEX2 = SAHEX2 + DELTX2
185 SAHEX2 = DMAX1(0.0D0,DMIN1(1.0D0,SAHEX2))
186 26 26 E = E + DELTE
187 E = DMAX1(1.0D-11,DMIN1(EMAX,E))
188 LFIN = LFIN .AND. DABS(DELTE).LT.STOLEE
189 IF(LFIN) GO TO 29
190 28 28 CONTINUE
191 WRITE(ISHORT,99) TL,PL,E,DELTE
192 99 99 FORMAT(' -----SAHA FAILURE (TL,PL)=',2F10.6,' (E,DE)=',2F20.12)
193C SYSTEM HAS BEEN SOLVED FOR E
194 29 29 EMU = E*AMU
195 RMU = CGAS*(EMU + AMU)
196 D = BETA*P/(RMU*T)
197 DL = DLOG10(D)
198 FXION(1) = 0.0D0
199 IF(NZ0.GE.7) FXION(1) = SAHAX(7)
200 FXION(2) = SAHEX1
201 FXION(3) = SAHEX2
202 IF(LATMO) GO TO 60
203C COMPUTE FIRST DERIVATIVES
204 RMUB = RMU*BETAI
205 R3T = 0.0D0
206 R3P = 0.0D0
207 IF(NZ0.LE.0) GO TO 31
208 DO 30 I=NZ1,NZ0
209 R3P = R3P - FXS(I)
210C KC 2025-05-30 fixed "DO termination statement which is not END DO or CONTINUE"
211C 30 R3T = R3T -FXS(I)*(2.5D0 + BETA14 + SAHATT(I))
212 R3T = R3T -FXS(I)*(2.5D0 + BETA14 + SAHATT(I))
213 30 30 CONTINUE
214 R3P = -BETAI*R3P
215 31 31 IF(LNOHE) GO TO 33
216 SAHEX0 = 1.0D0 - SAHEX1 - SAHEX2
217 SK0QT = 2.5D0 + BETA14 + SAHET0*TI
218 TEMP = SAHEK0*SAHEX0
219 R2T = TEMP*SK0QT
220 R2P = -TEMP*BETAI
221 IF(LNOHE2) GO TO 32
222 SK1QT = 2.5D0 + BETA14 + SAHET1*TI
223 TEMP = SAHEK1*SAHEX1
224 R1T = TEMP*SK1QT
225 R1P = -TEMP*BETAI
226 R2T = R2T + CR12*R1T
227 R2P = R2P + CR12*R1P
228 R3T = R3T + CR13*R1T
229 R3P = R3P + CR13*R1P
230 32 32 R3T = R3T + CR23*R2T
231 R3P = R3P + CR23*R2P
232 33 33 SQET = R3T/C33
233 SQEP = R3P/C33
234 QDT = -1.0D0 - BETA14 - EP1*SQET
235 QDP = BETAI - EP1*SQEP
236C COMPUTE INTERNAL ENERGY TEMPERATURE DERIVATIVE(QUT)
237 USUM = 0.0D0
238 IF(LNOHE) GO TO 35
239 SX1QT = (R2T - C23*SQET)/C22
240 SX1QP = (R2P - C23*SQEP)/C22
241 SX2QT = 0.0D0
242 SX2QP = 0.0D0
243 IF(LNOHE2) GO TO 34
244 SX2QT = (R1T - C12*SX1QT - C13*SQET)/C11
245 SX2QP = (R1P - C12*SX1QP - C13*SQEP)/C11
246 34 34 SX0QT = -(SX1QT + SX2QT)
247 SX0QP = -(SX1QP + SX2QP)
248 USUM = FXHE*(SX1QT*SAHET0 + SX2QT*(SAHET0 + SAHET1))
249 35 35 IF(NZ0.LE.0) GO TO 37
250 STEMP = 2.50D0 + BETA14 - EP1E*SQET
251 DO 36 I=NZ1,NZ0
252 FXT(I) = FXS(I)*(STEMP + SAHATT(I))
253C KC 2025-05-30 fixed "DO termination statement which is not END DO or CONTINUE"
254C 36 USUM = USUM + FXT(I)*SAHAT(I)
255 USUM = USUM + FXT(I)*SAHAT(I)
256 36 36 CONTINUE
257 37 37 CONTINUE
258 BETAUT = 0.75D0*(2.0D0 + BETA14)
259 RAMU = CGAS*AMU
260 QUT =RAMU*((1D0+E)*(BETAUT+3D0*BETAI*BETA14)+BETAUT*SQET+USUM*TI)
261C COMPUTE SPECIFIC HEAT(QCP) AND ADIABATIC GRADIENT(DELA)
262 PDT = P/(D*T)
263 PDTQ = PDT*QDT
264 QCP = QUT - PDTQ
265 QCPI = 1.0D0/QCP
266 DELA = -PDTQ*QCPI
267 IF(.NOT.LDERIV) GO TO 60
268C COMPUTE DERIVATIVES OF QDT,QCP,DELA
269 BETA16 = 4.0D0*BETAI*BETA14
270 R3T = 0.0D0
271 R3P = 0.0D0
272 UTSUM = 0.0D0
273 UPSUM = 0.0D0
274 IF(NZ0.LE.0) GO TO 42
275 ETEMP = (1.0D0+E+E)/(E*(1.0D0+E))**2
276 STEMP1 = 2.5D0 + BETA14 - SQET*EP1E
277 STEMP2 = -BETAI - SQEP*EP1E
278 STEMP3 = ETEMP*SQEP*SQET - BETAI*BETA14
279 STEMP4 = ETEMP*SQET**2 + BETA16
280 DO 41 I=NZ1,NZ0
281 STEMP5 = (STEMP1 + SAHATT(I))*(1.0D0 - 2.0D0*SAHAX(I))
282 STEMP6 = FXS(I)*(STEMP4 - SAHATT(I)) + FXT(I)*STEMP5
283 R3T = R3T - STEMP6
284 UTSUM = UTSUM + SAHAT(I)*(STEMP6 - FXT(I))
285 STEMP7 = FXS(I)*(STEMP3 + STEMP2*STEMP5)
286 R3P = R3P - STEMP7
287C KC 2025-05-30 fixed "DO termination statement which is not END DO or CONTINUE"
288C 41 UPSUM = UPSUM + SAHAT(I)*STEMP7
289 UPSUM = UPSUM + SAHAT(I)*STEMP7
290 41 41 CONTINUE
291 42 42 CONTINUE
292 IF(LNOHE) GO TO 44
293 STEMP = 2.0D0*SAHEX1*EP1*SQET
294 R2T = SAHEK0*(SAHEX0*(BETA16-SAHET0*TI+SK0QT**2)+2D0*SX0QT*SK0QT)
295 2 2 +EP12*(STEMP*SQET - 2.0D0*SX1QT*SQET)
296 R2P = SAHEK0*(SAHEX0*BETAI*(-BETA14-SK0QT)+SX0QP*SK0QT-BETAI*
297 2 2 SX0QT) + EP12*(STEMP*SQEP - SX1QP*SQET - SX1QT*SQEP)
298 IF(LNOHE2) GO TO 43
299 STEMP = 2.0D0*SAHEX2*EP1*SQET
300 R1T = SAHEK1*(SAHEX1*(BETA16-SAHET1*TI+SK1QT**2)+2D0*SX1QT*SK1QT)
301 1 1 +EP12*(STEMP*SQET - 2.0D0*SX2QT*SQET)
302 R1P = SAHEK1*(SAHEX1*BETAI*(-BETA14-SK1QT)+SX1QP*SK1QT-BETAI*
303 1 1 SX1QT) + EP12*(STEMP*SQEP - SX2QP*SQET - SX2QT*SQEP)
304 R2T = R2T + CR12*R1T
305 R2P = R2P + CR12*R1P
306 R3P = R3P + CR13*R1P
307 R3T = R3T + CR13*R1T
308 43 43 R3P = R3P + CR23*R2P
309 R3T = R3T + CR23*R2T
310 44 44 SQETT = R3T/C33
311 SQETP = R3P/C33
312 QDTT = -BETA16 + (EP1*SQET)**2 - EP1*SQETT
313 QDTP = BETAI*BETA14 + EP12*SQET*SQEP - EP1*SQETP
314 IF(NZ0.LE.0) GO TO 46
315 STEMP = EP1E*SQETT
316 STEMP1 = EP1E*SQETP
317 DO 45 I=NZ1,NZ0
318 UTSUM = UTSUM - FXS(I)*SAHAT(I)*STEMP
319C KC 2025-05-30 fixed "DO termination statement which is not END DO or CONTINUE"
320C 45 UPSUM = UPSUM - FXS(I)*SAHAT(I)*STEMP1
321 UPSUM = UPSUM - FXS(I)*SAHAT(I)*STEMP1
322 45 45 CONTINUE
323 46 46 CONTINUE
324 IF(LNOHE) GO TO 48
325 SX1QTT = (R2T - C23*SQETT)/C22
326 SX1QTP = (R2P - C23*SQETP)/C22
327 IF(LNOHE2) GO TO 47
328 SX2QTT = (R1T - C12*SX1QTT - C13*SQETT)/C11
329 SX2QTP = (R1P - C12*SX1QTP - C13*SQETP)/C11
330 UTSUM = UTSUM + FXHE*(SAHET0+SAHET1)*(SX2QTT - SX2QT)
331 UPSUM = UPSUM + FXHE*(SAHET0+SAHET1)*SX2QTP
332 47 47 UTSUM = UTSUM + FXHE*SAHET0*(SX1QTT - SX1QT)
333 UPSUM = UPSUM + FXHE*SAHET0*SX1QTP
334 48 48 CONTINUE
335 BTEMP = (1.0D0+E)*BETA14*BETAI*(24.0D0*BETAI - 9.0D0)
336 QQUTT = RAMU*(BTEMP+0.75D0*(2.0D0+BETA14*(8.0D0*BETAI+1D0))*SQET+
337 . . BETAUT*SQETT + UTSUM*TI)
338 QQUTP = RAMU*(-0.25D0*BTEMP+0.75D0*((2.0D0+BETA14*(4.0D0*BETAI
339 . . +1.0D0))*SQEP-BETA14*BETAI*SQET) +BETAUT*SQETP+UPSUM*TI)
340 QCPT = QCPI*(QQUTT + PDTQ*(1.0D0+QDT) - PDT*QDTT)
341 QCPP = QCPI*(QQUTP - PDTQ*(1.0D0-QDP) - PDT*QDTP)
342 QAT = -1.0D0 - QDT - QCPT + QDTT/QDT
343 QAP = 1.0D0 - QDP - QCPP + QDTP/QDT
344 60 60 CONTINUE
345
346 RETURN
347 END1"""Python version of eqsaha.f (YREC development, 7be4133): the Saha
2equation of state for partially ionized gas."""
3
4import math
5
6import numpy as np
7
8from pyrec import constants_mod
9from pyrec import iounits_mod
10
11# DATA: ionization temperatures chi/k (K) of the 11 species, in the order
12# Na, Al, Mg, Fe, Si, C, H, O, N, Ar, Ne (index 0 unused), and of He, He+
13# (Fortran lines 47–54).
14SAHAT = np.array([0.0, 59630., 69450., 88710., 91330., 94570., 130630.,
15 157800., 158000., 168630., 183080., 250200.])
16SAHET0 = 285270.
17SAHET1 = 631370.
18# DATA SAHAU: the constant u_i of each Saha equation (13 values: the 11
19# species, then He and He+), in double precision.
20# Question (see the page): the source of the constants u_i.
21SAHAU = np.array([0.0, -.01, -.47, .60, .49, .08, .11, .0, -.04, .64, .99,
22 1.03, .6, .0])
23NZ = 11
24MAXS = 20
25STOL = 4.0e1
26STOLEE = 1.0e-09
27
28# DATA E,SAHEX1,SAHEX2 and SAVE: the electrons per atom and the He+ and
29# He++ fractions, kept between calls as the next starting guess.
30# Question (see the page): the result can depend on the order of calls.
31e = 0.1
32sahex1 = 0.0
33sahex2 = 0.0
34
35
36def eqsaha(fx: np.ndarray, tl: float, t: float, pl: float, p: float, dl: float, d: float, beta: float, betai: float,
37 beta14: float, fxion: np.ndarray, rmu: float, amu: float, emu: float, lderiv: bool, latmo: bool, qdt: float,
38 qdp: float, qcp: float, dela: float, qdtt: float, qdtp: float, qat: float, qap: float, qcpt: float,
39 qcpp: float, ksaha: int) -> tuple[float, float, float, float, float, float, float, float, float, float,
40 float, float, float, float, int]:
41 """Solve the Saha equations for hydrogen, helium (two stages) and ten
42 metals by Newton iteration on the electrons per atom, and return the
43 density, the ionization fractions and the thermodynamic derivatives.
44
45 Translates: eqsaha.f#EQSAHA (7be4133)
46
47 Arguments: fx, the number fractions of the 12 species (index 0
48 unused); tl, t, pl, p (log T, T, log P, P); beta and its relatives
49 betai (1/beta) and beta14; amu; lderiv (also the second derivatives);
50 latmo (skip the derivatives); ksaha, the caller's iteration counter.
51 Array changed in place: fxion (H+, He+, He++ fractions, index 0
52 unused).
53
54 Returns (dl, d, rmu, emu, qdt, qdp, qcp, dela, qdtt, qdtp, qat, qap,
55 qcpt, qcpp, ksaha). With latmo, qdt ... qcpp are returned as passed in;
56 without lderiv, qdtt ... qcpp are.
57
58 """
59
60 global e, sahex1, sahex2
61 c = constants_mod
62 sahatt = np.zeros(NZ + 1)
63 sahak = np.zeros(NZ + 1)
64 sahax = np.zeros(NZ + 1)
65 fxs = np.zeros(NZ + 1)
66 fxt = np.zeros(NZ + 1)
67
68 # Fortran lines 58–60.
69 fxhe = fx[12]
70 emax = 1.0 + fxhe
71 ti = 1.0 / t
72
73 # The Saha K's (Fortran lines 63–77), in the order of increasing ionization temperature. The
74 # first species with an exponent below -STOL ends the list: it and all
75 # later ones count as neutral. Above +STOL, the species is fully
76 # ionized (K = 1e16) and left out of the derivative sums (nz1).
77 stemp = 2.50 * tl - pl + c.cmkh
78 nz1 = 1
79 for i in range(1, NZ + 1):
80 sahatt[i] = SAHAT[i] * ti
81 sahak[i] = c.cln * (SAHAU[i] + stemp) - sahatt[i]
82 if sahak[i] < -STOL:
83 break # go to 12
84 if sahak[i] > +STOL:
85 sahak[i] = 1.0e16 # label 10
86 nz1 = i + 1
87 continue
88 sahak[i] = betai * math.exp(sahak[i])
89 else:
90 i = NZ + 1
91 nz0 = i - 1 # label 12
92 nz1 = min(NZ, nz1)
93
94 # Helium: neutral (lnohe), or no He++ (lnohe2) (Fortran lines 78–92).
95 lnohe = True
96 lnohe2 = True
97 sahek0 = sahek1 = 0.0
98 if fxhe < 1.0e-10:
99 sahex1 = 0.0 # label 13
100 sahex2 = 0.0 # label 14
101 else:
102 sahek0 = c.cln * (SAHAU[12] + stemp) - SAHET0 / t
103 if sahek0 < -STOL:
104 sahex1 = 0.0
105 sahex2 = 0.0
106 else:
107 lnohe = False
108 sahek0 = betai * math.exp(sahek0)
109 sahek1 = c.cln * (SAHAU[13] + stemp) - SAHET1 / t
110 if sahek1 < -STOL:
111 sahex2 = 0.0
112 else:
113 lnohe2 = False
114 sahek1 = betai * math.exp(sahek1)
115
116 # Newton iterations for e, sahex1, sahex2 (the 3x3 system reduced by
117 # hand; Fortran lines 94–192).
118 cr12 = cr13 = cr23 = 0.0
119 c11 = c12 = c13 = c22 = c23 = 0.0
120 r1 = r2 = 0.0
121 for isaha in range(1, MAXS + 1):
122 ksaha = ksaha + 1
123 ep1 = 1.0 / (1.0 + e)
124 ep1e = ep1 / e
125 ep12 = ep1 * ep1
126 eep1 = e * ep1
127 c33 = -e * (1.0 + e)
128 r3 = e
129 for i in range(1, nz0 + 1):
130 div = 1.0 / (sahak[i] + eep1)
131 temp8 = sahak[i] * div
132 sahax[i] = temp8
133 temp8 = temp8 * fx[i]
134 r3 = r3 - temp8
135 temp8 = temp8 * eep1 * div
136 c33 = c33 - temp8
137 fxs[i] = temp8
138 c33 = c33 * ep1e # label 22
139 r3 = r3 - fxhe * (sahex1 + sahex2 + sahex2)
140 lfin = abs(r3) < STOLEE
141 if not lnohe:
142 c22 = sahek0 + eep1
143 c23 = sahex1 * ep12
144 c32 = fxhe
145 r2 = sahek0 * (1.0 - sahex1 - sahex2) - sahex1 * eep1
146 if not lnohe2:
147 c11 = eep1
148 c12 = -sahek1
149 c13 = sahex2 * ep12
150 c21 = sahek0
151 c31 = 2.0 * fxhe
152 r1 = sahex1 * sahek1 - sahex2 * eep1
153 # Reduction of the Saha matrix to diagonal form.
154 cr12 = -c21 / c11
155 cr13 = -c31 / c11
156 c22 = c22 + cr12 * c12
157 c32 = c32 + cr13 * c12
158 c23 = c23 + cr12 * c13
159 c33 = c33 + cr13 * c13
160 # Products below 1e-38 are skipped (an old underflow guard).
161 if r1 != 0.0:
162 r1l = math.log10(abs(r1))
163 if cr12 != 0.0:
164 fact = math.log10(abs(cr12)) + r1l
165 if not fact < -38.0:
166 r2 = r2 + cr12 * r1
167 if cr13 != 0.0: # label 200
168 fact = math.log10(abs(cr13)) + r1l
169 if not fact < -38.0:
170 r3 = r3 + cr13 * r1
171 # label 23: entry for no fully ionized helium
172 cr23 = -c32 / c22
173 c33 = c33 + cr23 * c23
174 r3 = r3 + cr23 * r2
175 # label 25: entry for neutral helium
176 delte = r3 / c33
177 if not lnohe:
178 deltx1 = (r2 - c23 * delte) / c22
179 sahex1 = sahex1 + deltx1
180 sahex1 = max(0.0, min(1.0, sahex1))
181 if not lnohe2:
182 # Products below 1e-38 are replaced by +-1e-38 ("statement
183 # recalculated for DEC-20 system").
184 fact1 = c12 * deltx1 # label 100
185 if c12 != 0.0 and deltx1 != 0.0:
186 if math.log10(abs(c12)) + math.log10(abs(deltx1)) < -38.0:
187 fact1 = (math.exp(c.cln * -38.0)
188 * math.copysign(1.0, c12)
189 * math.copysign(1.0, deltx1))
190 fact2 = c13 * delte # label 110
191 if c13 != 0.0 and delte != 0.0:
192 if math.log10(abs(c13)) + math.log10(abs(delte)) < -38.0:
193 fact2 = (math.exp(c.cln * -38.0)
194 * math.copysign(1.0, c13)
195 * math.copysign(1.0, delte))
196 deltx2 = (r1 - fact1 - fact2) / c11
197 sahex2 = sahex2 + deltx2
198 sahex2 = max(0.0, min(1.0, sahex2))
199 e = e + delte # label 26
200 e = max(1.0e-11, min(emax, e))
201 lfin = lfin and abs(delte) < STOLEE
202 if lfin:
203 break # go to 29
204 else:
205 # Question (see the page): the run goes on with the unconverged e.
206 iounits_mod.write(iounits_mod.ishort,
207 f" -----SAHA FAILURE (TL,PL)={tl:10.6f}{pl:10.6f}"
208 f" (E,DE)={e:20.12f}{delte:20.12f}") # format 99
209
210 # label 29: the system has been solved for e (Fortran lines 194–202).
211 emu = e * amu
212 rmu = c.cgas * (emu + amu)
213 d = beta * p / (rmu * t)
214 dl = math.log10(d)
215 fxion[1] = 0.0
216 if nz0 >= 7:
217 fxion[1] = sahax[7] # hydrogen is species 7
218 fxion[2] = sahex1
219 fxion[3] = sahex2
220 if latmo:
221 return (dl, d, rmu, emu, qdt, qdp, qcp, dela, qdtt, qdtp, qat, qap,
222 qcpt, qcpp, ksaha) # go to 60
223
224 # First derivatives: d ln E / d ln T (sqet) and d ln P (sqep)
225 # (Fortran lines 204–235).
226 rmub = rmu * betai # noqa: F841 (unused in the Fortran too)
227 r3t = 0.0
228 r3p = 0.0
229 if nz0 > 0:
230 for i in range(nz1, nz0 + 1):
231 r3p = r3p - fxs[i]
232 r3t = r3t - fxs[i] * (2.5 + beta14 + sahatt[i])
233 r3p = -betai * r3p
234 r1t = r1p = r2t = r2p = 0.0
235 sahex0 = sk0qt = sk1qt = 0.0
236 if not lnohe: # label 31
237 sahex0 = 1.0 - sahex1 - sahex2
238 sk0qt = 2.5 + beta14 + SAHET0 * ti
239 temp = sahek0 * sahex0
240 r2t = temp * sk0qt
241 r2p = -temp * betai
242 if not lnohe2:
243 sk1qt = 2.5 + beta14 + SAHET1 * ti
244 temp = sahek1 * sahex1
245 r1t = temp * sk1qt
246 r1p = -temp * betai
247 r2t = r2t + cr12 * r1t
248 r2p = r2p + cr12 * r1p
249 r3t = r3t + cr13 * r1t
250 r3p = r3p + cr13 * r1p
251 r3t = r3t + cr23 * r2t # label 32
252 r3p = r3p + cr23 * r2p
253 sqet = r3t / c33 # label 33
254 sqep = r3p / c33
255 qdt = -1.0 - beta14 - ep1 * sqet
256 qdp = betai - ep1 * sqep
257
258 # Internal energy temperature derivative (qut; Fortran lines 237–260).
259 usum = 0.0
260 sx0qt = sx0qp = sx1qt = sx1qp = sx2qt = sx2qp = 0.0
261 if not lnohe:
262 sx1qt = (r2t - c23 * sqet) / c22
263 sx1qp = (r2p - c23 * sqep) / c22
264 sx2qt = 0.0
265 sx2qp = 0.0
266 if not lnohe2:
267 sx2qt = (r1t - c12 * sx1qt - c13 * sqet) / c11
268 sx2qp = (r1p - c12 * sx1qp - c13 * sqep) / c11
269 sx0qt = -(sx1qt + sx2qt) # label 34
270 sx0qp = -(sx1qp + sx2qp)
271 usum = fxhe * (sx1qt * SAHET0 + sx2qt * (SAHET0 + SAHET1))
272 if nz0 > 0: # label 35
273 stemp = 2.50 + beta14 - ep1e * sqet
274 for i in range(nz1, nz0 + 1):
275 fxt[i] = fxs[i] * (stemp + sahatt[i])
276 usum = usum + fxt[i] * SAHAT[i]
277 betaut = 0.75 * (2.0 + beta14) # label 37
278 ramu = c.cgas * amu
279 qut = ramu * ((1.0 + e) * (betaut + 3.0 * betai * beta14)
280 + betaut * sqet + usum * ti)
281
282 # Specific heat (qcp) and adiabatic gradient (dela; Fortran lines
283 # 262–267).
284 pdt = p / (d * t)
285 pdtq = pdt * qdt
286 qcp = qut - pdtq
287 qcpi = 1.0 / qcp
288 dela = -pdtq * qcpi
289 if not lderiv:
290 return (dl, d, rmu, emu, qdt, qdp, qcp, dela, qdtt, qdtp, qat, qap,
291 qcpt, qcpp, ksaha) # go to 60
292
293 # Derivatives of qdt, qcp and dela: the second derivatives of E
294 # (Fortran lines 269–311).
295 beta16 = 4.0 * betai * beta14
296 r3t = 0.0
297 r3p = 0.0
298 utsum = 0.0
299 upsum = 0.0
300 if nz0 > 0:
301 etemp = (1.0 + e + e) / (e * (1.0 + e))**2
302 stemp1 = 2.5 + beta14 - sqet * ep1e
303 stemp2 = -betai - sqep * ep1e
304 stemp3 = etemp * sqep * sqet - betai * beta14
305 stemp4 = etemp * sqet**2 + beta16
306 for i in range(nz1, nz0 + 1):
307 stemp5 = (stemp1 + sahatt[i]) * (1.0 - 2.0 * sahax[i])
308 stemp6 = fxs[i] * (stemp4 - sahatt[i]) + fxt[i] * stemp5
309 r3t = r3t - stemp6
310 utsum = utsum + SAHAT[i] * (stemp6 - fxt[i])
311 stemp7 = fxs[i] * (stemp3 + stemp2 * stemp5)
312 r3p = r3p - stemp7
313 upsum = upsum + SAHAT[i] * stemp7
314 if not lnohe: # label 42
315 stemp = 2.0 * sahex1 * ep1 * sqet
316 r2t = (sahek0 * (sahex0 * (beta16 - SAHET0 * ti + sk0qt**2)
317 + 2.0 * sx0qt * sk0qt)
318 + ep12 * (stemp * sqet - 2.0 * sx1qt * sqet))
319 r2p = (sahek0 * (sahex0 * betai * (-beta14 - sk0qt) + sx0qp * sk0qt
320 - betai * sx0qt)
321 + ep12 * (stemp * sqep - sx1qp * sqet - sx1qt * sqep))
322 if not lnohe2:
323 stemp = 2.0 * sahex2 * ep1 * sqet
324 r1t = (sahek1 * (sahex1 * (beta16 - SAHET1 * ti + sk1qt**2)
325 + 2.0 * sx1qt * sk1qt)
326 + ep12 * (stemp * sqet - 2.0 * sx2qt * sqet))
327 r1p = (sahek1 * (sahex1 * betai * (-beta14 - sk1qt)
328 + sx1qp * sk1qt - betai * sx1qt)
329 + ep12 * (stemp * sqep - sx2qp * sqet - sx2qt * sqep))
330 r2t = r2t + cr12 * r1t
331 r2p = r2p + cr12 * r1p
332 r3p = r3p + cr13 * r1p
333 r3t = r3t + cr13 * r1t
334 r3p = r3p + cr23 * r2p # label 43
335 r3t = r3t + cr23 * r2t
336 sqett = r3t / c33 # label 44
337 sqetp = r3p / c33
338
339 # Fortran lines 312–343.
340 qdtt = -beta16 + (ep1 * sqet)**2 - ep1 * sqett
341 qdtp = betai * beta14 + ep12 * sqet * sqep - ep1 * sqetp
342 if nz0 > 0:
343 stemp = ep1e * sqett
344 stemp1 = ep1e * sqetp
345 for i in range(nz1, nz0 + 1):
346 utsum = utsum - fxs[i] * SAHAT[i] * stemp
347 upsum = upsum - fxs[i] * SAHAT[i] * stemp1
348 if not lnohe: # label 46
349 sx1qtt = (r2t - c23 * sqett) / c22
350 sx1qtp = (r2p - c23 * sqetp) / c22
351 if not lnohe2:
352 sx2qtt = (r1t - c12 * sx1qtt - c13 * sqett) / c11
353 sx2qtp = (r1p - c12 * sx1qtp - c13 * sqetp) / c11
354 utsum = utsum + fxhe * (SAHET0 + SAHET1) * (sx2qtt - sx2qt)
355 upsum = upsum + fxhe * (SAHET0 + SAHET1) * sx2qtp
356 utsum = utsum + fxhe * SAHET0 * (sx1qtt - sx1qt) # label 47
357 upsum = upsum + fxhe * SAHET0 * sx1qtp
358 btemp = (1.0 + e) * beta14 * betai * (24.0 * betai - 9.0) # label 48
359 qqutt = ramu * (btemp + 0.75 * (2.0 + beta14 * (8.0 * betai + 1.0)) * sqet
360 + betaut * sqett + utsum * ti)
361 qqutp = ramu * (-0.25 * btemp
362 + 0.75 * ((2.0 + beta14 * (4.0 * betai + 1.0)) * sqep
363 - beta14 * betai * sqet)
364 + betaut * sqetp + upsum * ti)
365 qcpt = qcpi * (qqutt + pdtq * (1.0 + qdt) - pdt * qdtt)
366 qcpp = qcpi * (qqutp - pdtq * (1.0 - qdp) - pdt * qdtp)
367 qat = -1.0 - qdt - qcpt + qdtt / qdt
368 qap = 1.0 - qdp - qcpp + qdtp / qdt
369 return (dl, d, rmu, emu, qdt, qdp, qcp, dela, qdtt, qdtp, qat, qap,
370 qcpt, qcpp, ksaha) # label 60