-
Notifications
You must be signed in to change notification settings - Fork 0
/
Isentropic_gas.py
385 lines (292 loc) · 9.06 KB
/
Isentropic_gas.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
""" Isentropic properties. """
from __future__ import absolute_import, division
import numpy as np
import scipy as sp
from scipy.optimize import bisect, newton
from skaero.util.decorators import implicit
def mach_angle(M):
r"""Returns Mach angle given supersonic Mach number.
.. math::
\mu = \arcsin{\left ( \frac{1}{M} \right )}
Parameters
----------
M : float
Mach number.
Returns
-------
mu : float
Mach angle.
Raises
------
ValueError
If given Mach number is subsonic.
"""
try:
with np.errstate(invalid="raise"):
mu = np.arcsin(1 / M)
except FloatingPointError:
raise ValueError("Mach number must be supersonic")
return mu
def mach_from_area_ratio(A_Astar, fl=None):
"""Computes the Mach number given an area ratio asuming isentropic flow.
Uses the relation between Mach number and area ratio for isentropic flow,
and returns both the subsonic and the supersonic solution.
Parameters
----------
A_Astar : float
Cross sectional area.
fl : IsentropicFlow, optional
Isentropic flow object, default flow with gamma = 7 / 5.
Returns
-------
out : tuple of floats
Subsonic and supersonic Mach number solution of the equation.
Raises
------
ValueError
If the area ratio is less than 1.0 (the critical area is always the
minimum).
"""
if not fl:
fl = IsentropicFlow(gamma=1.4)
eq = implicit(fl.A_Astar)
if A_Astar < 1.0:
raise ValueError("Area ratio must be greater than 1")
elif A_Astar == 1.0:
M_sub = M_sup = 1.0
else:
M_sub = bisect(eq, 0.0, 1.0, args=(A_Astar,))
M_sup = newton(eq, 2.0, args=(A_Astar,))
return M_sub, M_sup
def mach_from_nu(nu, in_radians=True, gamma=1.4):
r"""Computes the Mach number given a Prandtl-Meyer angle, :math:`\nu`.
Uses the relation between Mach number and Prandtl-Meyer angle for
isentropic flow, to iteratively compute and return the Mach number.
Parameters
----------
nu : float
Prandtl-Meyer angle, by default in radians.
in_radians : bool, optional
When set as False, converts nu from degrees to radians.
gamma : float, optional
Specific heat ratio.
Returns
-------
M : float
Mach number corresponding to :math:`\nu`.
Raises
------
ValueError
If :math:`\nu` is 0 or negative or above the theoretical maxima based on
:math:`\gamma`.
"""
if not in_radians:
nu = np.radians(nu)
nu_max = np.pi / 2.0 * (np.sqrt((gamma + 1.0) / (gamma - 1.0)) - 1)
if nu <= 0.0 or nu >= nu_max:
raise ValueError(
"Prandtl-Meyer angle must be between (0, %f) radians." % nu_max
)
eq = implicit(PrandtlMeyerExpansion.nu)
M = newton(eq, 2.0, args=(nu,))
return M
class IsentropicFlow(object):
"""Class representing an isentropic gas flow.
Isentropic flow is characterized by:
* Viscous and heat conductivity effects are negligible.
* No chemical or radioactive heat production.
"""
def __init__(self, gamma=1.4):
"""Constructor of IsentropicFlow.
Parameters
----------
gamma : float, optional
Specific heat ratio, default 7 / 5.
"""
self.gamma = gamma
def p_p0(self, M):
r"""Pressure ratio from Mach number.
.. math::
\left ( \frac{P}{P_{0}} \right ) = \left ( \frac{T}{T_{0}} \right )^{\frac{\gamma}{(\gamma - 1)}}
Parameters
----------
M : array_like
Mach number.
Returns
-------
p_p0 : array_like
Pressure ratio.
"""
M = np.asanyarray(M)
p_p0 = self.T_T0(M) ** (self.gamma / (self.gamma - 1))
return p_p0
def rho_rho0(self, M):
r"""Density ratio from Mach number.
.. math::
\left ( \frac{\rho}{\rho_{0}} \right ) = \left ( \frac{T}{T_{0}} \right )^{\frac{1}{(\gamma - 1)}}
Parameters
----------
M : array_like
Mach number.
Returns
-------
rho_rho0 : array_like
Density ratio.
"""
M = np.asanyarray(M)
rho_rho0 = self.T_T0(M) ** (1 / (self.gamma - 1))
return rho_rho0
def T_T0(self, M):
r"""Temperature ratio from Mach number.
.. math::
\left ( \frac{T}{T_{0}} \right ) = \left (1 + \frac{\gamma - 1}{2}M^{2} \right )^{-1}
Parameters
----------
M : array_like
Mach number.
Returns
-------
T_T0 : array_like
Temperature ratio.
"""
M = np.asanyarray(M)
T_T0 = 1 / (1 + (self.gamma - 1) * M * M / 2)
return T_T0
def A_Astar(self, M):
"""Area ratio from Mach number.
Duct area divided by critial area given Mach number.
Parameters
----------
M : array_like
Mach number.
Returns
-------
A_Astar : array_like
Area ratio.
"""
M = np.asanyarray(M)
# If there is any zero entry, NumPy array division gives infinity,
# which is correct.
with np.errstate(divide="ignore"):
A_Astar = (2 / self.T_T0(M) / (self.gamma + 1)) ** (
(self.gamma + 1) / (2 * (self.gamma - 1))
) / M
return A_Astar
def a_a0(self, M):
""" Speed of sound ratio from Mach number.
Parameters
----------
M: array_like
Mach number.
Returns
-------
a_a0: array_like
Speed of sound ratio.
"""
M = np.asarray(M)
a_a0 = self.T_T0(M) ** 0.5
return a_a0
class PrandtlMeyerExpansion(object):
"""Class representing a Prandtl-Meyer expansion fan.
"""
@staticmethod
def nu(M, gamma=1.4):
r"""Prandtl-Meyer angle for a given Mach number.
The result is given by evaluating the Prandtl-Meyer function.
.. math::
\nu = \sqrt{\frac{\gamma + 1}{\gamma - 1}} \tan^{-1}\left [ \sqrt{\frac{\gamma - 1}{\gamma + 1}(M^{2} - 1)} \right ] - \tan^{-1}(\sqrt{M^{2} - 1})
Parameters
----------
M : float
Mach number.
gamma : float, optional
Specific heat ratio, default 7 / 5.
Returns
-------
nu : float
Prandtl-Meyer angle, in radians.
Raises
------
ValueError
If Mach number is subsonic.
"""
try:
with np.errstate(invalid="raise"):
sgpgm = np.sqrt((gamma + 1) / (gamma - 1))
nu = sgpgm * np.arctan(np.sqrt(M * M - 1) / sgpgm) - np.arctan(
np.sqrt(M * M - 1)
)
except FloatingPointError:
raise ValueError("Mach number must be supersonic")
return nu
def __init__(self, M_1, theta, fl=None, gamma=1.4):
"""Constructor of PrandtlMeyerExpansion.
Parameters
----------
M_1 : float
Upstream Mach number.
theta : float
Deflection angle, in radians.
fl : IsentropicFlow, optional.
Flow to be expanded
gamma : float, optional
Specific heat ratio, default value = 7 / 5.
Raises
------
ValueError
If given Mach number is subsonic.
"""
if not fl:
fl = IsentropicFlow(gamma=gamma)
nu_max = PrandtlMeyerExpansion.nu(np.inf, fl.gamma) - PrandtlMeyerExpansion.nu(
M_1, fl.gamma
)
if theta > nu_max:
raise ValueError(
"Deflection angle must be lower than maximum {:.2f}°".format(
np.degrees(nu_max)
)
)
self.M_1 = M_1
self.theta = theta
self.fl = fl
@property
def nu_1(self):
"""Upstream Prandtl-Meyer angle."""
return PrandtlMeyerExpansion.nu(self.M_1, self.fl.gamma)
@property
def nu_2(self):
"""Downstream Prandtl-Meyer angle."""
return self.nu_1 + self.theta
@property
def M_2(self):
"""Downstream Mach number.
"""
return mach_from_nu(nu=self.nu_2, gamma=self.fl.gamma)
@property
def mu_1(self):
"""Angle of forward Mach line.
"""
return mach_angle(self.M_1)
@property
def mu_2(self):
"""Angle of rearward Mach line.
"""
return mach_angle(self.M_2)
@property
def p2_p1(self):
"""Pressure ratio across the expansion fan.
"""
p2_p1 = self.fl.p_p0(self.M_2) / self.fl.p_p0(self.M_1)
return p2_p1
@property
def T2_T1(self):
"""Temperature ratio across the expansion fan.
"""
T2_T1 = self.fl.T_T0(self.M_2) / self.fl.T_T0(self.M_1)
return T2_T1
@property
def rho2_rho1(self):
"""Density ratio across the expansion fan.
"""
return self.p2_p1 / self.T2_T1