turbo-design 1.3.8__py3-none-any.whl → 1.3.10__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {turbo_design-1.3.8.dist-info → turbo_design-1.3.10.dist-info}/METADATA +2 -1
- turbo_design-1.3.10.dist-info/RECORD +46 -0
- {turbo_design-1.3.8.dist-info → turbo_design-1.3.10.dist-info}/WHEEL +1 -1
- turbodesign/__init__.py +57 -4
- turbodesign/agf.py +346 -0
- turbodesign/arrayfuncs.py +31 -1
- turbodesign/bladerow.py +238 -155
- turbodesign/compressor_math.py +386 -0
- turbodesign/compressor_spool.py +941 -0
- turbodesign/coolant.py +18 -6
- turbodesign/deviation/__init__.py +5 -0
- turbodesign/deviation/axial_compressor.py +3 -0
- turbodesign/deviation/carter_deviation.py +79 -0
- turbodesign/deviation/deviation_base.py +20 -0
- turbodesign/deviation/fixed_deviation.py +42 -0
- turbodesign/enums.py +5 -6
- turbodesign/flow_math.py +158 -0
- turbodesign/inlet.py +126 -56
- turbodesign/isentropic.py +59 -15
- turbodesign/loss/__init__.py +3 -1
- turbodesign/loss/compressor/OTAC_README.md +39 -0
- turbodesign/loss/compressor/__init__.py +54 -0
- turbodesign/loss/compressor/diffusion.py +61 -0
- turbodesign/loss/compressor/lieblein.py +1 -0
- turbodesign/loss/compressor/otac.py +799 -0
- turbodesign/loss/compressor/references/schobeiri-2012-shock-loss-model-for-transonic-and-supersonic-axial-compressors-with-curved-blades.pdf +0 -0
- turbodesign/loss/fixedpolytropic.py +27 -0
- turbodesign/loss/fixedpressureloss.py +30 -0
- turbodesign/loss/losstype.py +2 -30
- turbodesign/loss/turbine/TD2.py +25 -29
- turbodesign/loss/turbine/__init__.py +0 -1
- turbodesign/loss/turbine/ainleymathieson.py +6 -5
- turbodesign/loss/turbine/craigcox.py +6 -5
- turbodesign/loss/turbine/fixedefficiency.py +8 -7
- turbodesign/loss/turbine/kackerokapuu.py +7 -5
- turbodesign/loss/turbine/traupel.py +17 -16
- turbodesign/outlet.py +81 -22
- turbodesign/passage.py +98 -63
- turbodesign/row_factory.py +129 -0
- turbodesign/solve_radeq.py +9 -10
- turbodesign/{td_math.py → turbine_math.py} +144 -185
- turbodesign/turbine_spool.py +1219 -0
- turbo_design-1.3.8.dist-info/RECORD +0 -33
- turbodesign/compressorspool.py +0 -60
- turbodesign/loss/turbine/fixedpressureloss.py +0 -25
- turbodesign/rotor.py +0 -38
- turbodesign/spool.py +0 -317
- turbodesign/turbinespool.py +0 -543
turbodesign/bladerow.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from dataclasses import field, Field
|
|
1
|
+
from dataclasses import dataclass, field, Field
|
|
2
2
|
from typing import Any, Callable, List, Optional, Sequence, Tuple, Union
|
|
3
3
|
from .enums import RowType, PowerType
|
|
4
4
|
import numpy as np
|
|
@@ -9,45 +9,54 @@ from cantera import Solution, composite
|
|
|
9
9
|
from .coolant import Coolant
|
|
10
10
|
from pyturbo.helper import line2D
|
|
11
11
|
from pyturbo.aero.airfoil2D import Airfoil2D
|
|
12
|
-
from .loss import LossBaseClass
|
|
12
|
+
from .loss import LossBaseClass
|
|
13
|
+
from .deviation.deviation_base import DeviationBaseClass
|
|
13
14
|
from .passage import Passage
|
|
15
|
+
from .arrayfuncs import safe_interpolate
|
|
14
16
|
|
|
15
17
|
|
|
18
|
+
@dataclass(eq=False)
|
|
16
19
|
class BladeRow:
|
|
17
|
-
id:int = 0
|
|
18
|
-
stage_id:int = 0
|
|
20
|
+
id: int = 0
|
|
21
|
+
stage_id: int = 0
|
|
19
22
|
row_type: RowType = RowType.Stator
|
|
20
|
-
loss_function:LossBaseClass
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
loss_function: Optional[LossBaseClass] = None
|
|
24
|
+
deviation_function: Optional[DeviationBaseClass] = None
|
|
25
|
+
cutting_line: Optional[line2D] = None # Line perpendicular to the streamline
|
|
26
|
+
rp: float = 0.4 # Degree of Reaction
|
|
27
|
+
hub_location: float = 0.0
|
|
28
|
+
shroud_location: float = 0.0
|
|
29
|
+
|
|
24
30
|
# Fluid
|
|
25
31
|
R: float = 287.15 # Ideal Gas constant J/(Kg K)
|
|
26
32
|
gamma: float = 1.33 # Ratio of Cp/Cv
|
|
27
33
|
Cp: float = 1019 # Cp J/(Kg*K)
|
|
28
34
|
Cv: float = 1019/1.14 # Cv J/(Kg*K)
|
|
29
|
-
_coolant:Coolant = None #
|
|
30
|
-
mu:float = 0
|
|
31
|
-
|
|
32
|
-
total_massflow:float = 0 # Massflow spool + all upstream cooling flow [kg/s]
|
|
33
|
-
massflow:npt.NDArray = field(default_factory=lambda: np.array([0])) # Massflow per radii
|
|
34
|
-
total_massflow_no_coolant:float = 0 # Inlet massflow
|
|
35
|
+
_coolant: Optional[Coolant] = None # Coolant Fluid
|
|
36
|
+
mu: float = 0
|
|
37
|
+
|
|
38
|
+
total_massflow: float = 0 # Massflow spool + all upstream cooling flow [kg/s]
|
|
39
|
+
massflow: npt.NDArray = field(default_factory=lambda: np.array([0])) # Massflow per radii
|
|
40
|
+
total_massflow_no_coolant: float = 0 # Inlet massflow
|
|
41
|
+
massflow_target: Optional[npt.NDArray] = None # Custom massflow distribution for angle matching [kg/s]
|
|
35
42
|
# ----------------------------------
|
|
36
43
|
|
|
37
44
|
# Streamline Properties
|
|
38
|
-
percent_hub:float = 0 # Where blade row is defined along the hub.
|
|
45
|
+
percent_hub: float = 0 # Where blade row is defined along the hub.
|
|
39
46
|
percent_hub_shroud: npt.NDArray = field(default_factory=lambda: np.array([0])) # Percent streamline length from hub to shroud.
|
|
40
47
|
x: npt.NDArray = field(default_factory=lambda: np.array([0])) # x - coordinates (useful for computing axial chord)
|
|
41
48
|
r: npt.NDArray = field(default_factory=lambda: np.array([0])) # Radius - coordinates
|
|
42
49
|
m: npt.NDArray = field(default_factory=lambda: np.array([0])) # meridional
|
|
43
|
-
|
|
50
|
+
total_area: float = 0
|
|
51
|
+
area: npt.NDArray = field(default_factory=lambda: np.array([0]))
|
|
44
52
|
# Calculated massflow is the massflow computed after radial eq solver
|
|
45
53
|
calculated_massflow: float = 0
|
|
46
|
-
|
|
54
|
+
|
|
47
55
|
# Row Efficiency (calculated or specified)
|
|
48
|
-
eta_total:float = 0 # Total to Total
|
|
49
|
-
eta_static:float = 0 # Total to static
|
|
50
|
-
|
|
56
|
+
eta_total: float = 0 # Total to Total
|
|
57
|
+
eta_static: float = 0 # Total to static
|
|
58
|
+
eta_poly: float = 0 # Polytropic efficiency (per row if applicable)
|
|
59
|
+
stage_loading: float = 0 # stage loading how much work done per stage
|
|
51
60
|
|
|
52
61
|
alpha1: npt.NDArray = field(default_factory=lambda: np.array([0])) # Blade inlet absolute flow angle
|
|
53
62
|
alpha2: npt.NDArray = field(default_factory=lambda: np.array([0])) # Blade exit absolute flow angle
|
|
@@ -55,20 +64,21 @@ class BladeRow:
|
|
|
55
64
|
beta1: npt.NDArray = field(default_factory=lambda: np.array([0])) # Blade inlet relative flow angle
|
|
56
65
|
beta2: npt.NDArray = field(default_factory=lambda: np.array([0])) # Blade exit relative flow angle
|
|
57
66
|
|
|
58
|
-
|
|
59
|
-
|
|
67
|
+
deviation: npt.NDArray = field(default_factory=lambda: np.array([0]))
|
|
68
|
+
_beta1_metal: npt.NDArray = field(default_factory=lambda: np.array([0])) # blade inlet metal angle
|
|
69
|
+
beta1_metal_radii: npt.NDArray = field(default_factory=lambda: np.array([0])) # radii where metal angle is defined
|
|
60
70
|
|
|
61
|
-
_beta2_metal:npt.NDArray = field(default_factory=lambda: np.array([0])) # blade exit metal angle
|
|
62
|
-
beta2_metal_radii:npt.NDArray = field(default_factory=lambda: np.array([0])) # radii where metal angle is defined
|
|
71
|
+
_beta2_metal: npt.NDArray = field(default_factory=lambda: np.array([0])) # blade exit metal angle
|
|
72
|
+
beta2_metal_radii: npt.NDArray = field(default_factory=lambda: np.array([0])) # radii where metal angle is defined
|
|
63
73
|
|
|
64
|
-
beta1_fixed:bool = False # Geometry already defined. This affects the inlet flow angle
|
|
65
|
-
beta2_fixed:bool = False # Geometry already defined. This affects the exit flow angle
|
|
74
|
+
beta1_fixed: bool = False # Geometry already defined. This affects the inlet flow angle
|
|
75
|
+
beta2_fixed: bool = False # Geometry already defined. This affects the exit flow angle
|
|
66
76
|
|
|
67
77
|
# Velocities
|
|
68
78
|
Vm: npt.NDArray = field(default_factory=lambda: np.array([0])) # Meridional velocity
|
|
69
79
|
Vx: npt.NDArray = field(default_factory=lambda: np.array([0])) # Axial Velocity
|
|
70
80
|
Vt: npt.NDArray = field(default_factory=lambda: np.array([0])) # Tangential Velocity
|
|
71
|
-
Vr:npt.NDArray = field(default_factory=lambda: np.array([0])) # Radial velocity
|
|
81
|
+
Vr: npt.NDArray = field(default_factory=lambda: np.array([0])) # Radial velocity
|
|
72
82
|
V: npt.NDArray = field(default_factory=lambda: np.array([0])) # Absolute Velocity in 3D coordinate system
|
|
73
83
|
V2: npt.NDArray = field(default_factory=lambda: np.array([0])) # Absolute Velocity in Theta-Axial plane
|
|
74
84
|
M: npt.NDArray = field(default_factory=lambda: np.array([0])) # Mach Number
|
|
@@ -77,15 +87,17 @@ class BladeRow:
|
|
|
77
87
|
W: npt.NDArray = field(default_factory=lambda: np.array([0])) # Relative Velocity in Theta-Axial plane
|
|
78
88
|
Wt: npt.NDArray = field(default_factory=lambda: np.array([0])) # Relative Tangential Velocity
|
|
79
89
|
|
|
80
|
-
_rpm: float = 0
|
|
81
|
-
omega:float = 0 # angular velocity rad/s
|
|
90
|
+
_rpm: float = field(default=0, init=False, repr=False)
|
|
91
|
+
omega: float = 0 # angular velocity rad/s
|
|
82
92
|
|
|
83
|
-
P0_stator_inlet = field(default_factory=lambda: np.array([0])) # Every quantity is an exit quantity, This is used for efficiency calcs
|
|
84
|
-
T0_stator_inlet = field(default_factory=lambda: np.array([0])) # Every quantity is an exit quantity, This is used for efficiency calcs
|
|
93
|
+
P0_stator_inlet: npt.NDArray = field(default_factory=lambda: np.array([0])) # Every quantity is an exit quantity, This is used for efficiency calcs
|
|
94
|
+
T0_stator_inlet: npt.NDArray = field(default_factory=lambda: np.array([0])) # Every quantity is an exit quantity, This is used for efficiency calcs
|
|
85
95
|
P0: npt.NDArray = field(default_factory=lambda: np.array([0])) # Total Quantities
|
|
96
|
+
P0_is: npt.NDArray = field(default_factory=lambda: np.array([0]))
|
|
86
97
|
T0: npt.NDArray = field(default_factory=lambda: np.array([0]))
|
|
87
|
-
T0_is:npt.NDArray = field(default_factory=lambda: np.array([0]))
|
|
98
|
+
T0_is: npt.NDArray = field(default_factory=lambda: np.array([0]))
|
|
88
99
|
P0R: npt.NDArray = field(default_factory=lambda: np.array([0])) # Relative Total Pressure (Pa)
|
|
100
|
+
P0R_is: npt.NDArray = field(default_factory=lambda: np.array([0]))
|
|
89
101
|
T0R: npt.NDArray = field(default_factory=lambda: np.array([0]))
|
|
90
102
|
|
|
91
103
|
# Static Quantities
|
|
@@ -93,36 +105,49 @@ class BladeRow:
|
|
|
93
105
|
T: npt.NDArray = field(default_factory=lambda: np.array([0]))
|
|
94
106
|
T_is: npt.NDArray = field(default_factory=lambda: np.array([0]))
|
|
95
107
|
rho: npt.NDArray = field(default_factory=lambda: np.array([0]))
|
|
96
|
-
|
|
108
|
+
entropy_rise: npt.NDArray = field(default_factory=lambda: np.array([0]))
|
|
109
|
+
|
|
97
110
|
# Related to streamline curvature
|
|
98
|
-
phi:npt.NDArray = field(default_factory=lambda: np.array([0])) # Inclination angle x,r plane. AY td2.f
|
|
111
|
+
phi: npt.NDArray = field(default_factory=lambda: np.array([0])) # Inclination angle x,r plane. AY td2.f
|
|
99
112
|
rm: npt.NDArray = field(default_factory=lambda: np.array([0])) # Curvature
|
|
100
113
|
incli_curve_radii: npt.NDArray = field(default_factory=lambda: np.array([0])) # radius at which curvature was evaluated
|
|
101
|
-
mprime:npt.NDArray = field(default_factory=lambda: np.array([0])) # Mprime distance
|
|
102
|
-
|
|
103
|
-
Yp:
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
114
|
+
mprime: npt.NDArray = field(default_factory=lambda: np.array([0])) # Mprime distance
|
|
115
|
+
|
|
116
|
+
Yp: npt.NDArray = field(default_factory=lambda: np.array([0])) # Pressure loss
|
|
117
|
+
blockage: float = 0
|
|
118
|
+
flow_coefficient: float = 0 # Vm/U or similar nondimensional flow coefficient
|
|
119
|
+
power: float = 0 # Watts
|
|
120
|
+
power_mean: float = 0
|
|
121
|
+
power_distribution: npt.NDArray = field(default_factory=lambda: np.array([0])) # How power is divided by radius.
|
|
122
|
+
P0_P: float = 0 # Total to Static Pressure Ratio
|
|
123
|
+
P0_ratio: float = 0 # Total-to-total pressure ratio target (design input; may be overwritten in legacy diagnostics)
|
|
124
|
+
P0_ratio_target: float = 0 # Frozen design target for P0_ratio (never overwritten; used for initial guesses)
|
|
125
|
+
Power_Type: PowerType = PowerType.P0_P
|
|
126
|
+
euler_power: float = 0
|
|
127
|
+
Reynolds: float = 0
|
|
128
|
+
eta_poly: float = 0.0 # Optional per-row polytropic efficiency target
|
|
129
|
+
num_blades: int = 0
|
|
111
130
|
|
|
112
131
|
# Used for loss calculations
|
|
113
|
-
_blade_to_blade_gap:float = 0.025 # Gap between blade in terms of percent chord.
|
|
132
|
+
_blade_to_blade_gap: float = 0.025 # Gap between blade in terms of percent chord.
|
|
114
133
|
|
|
115
|
-
_aspect_ratio:float = 0.9 #
|
|
116
|
-
_pitch_to_chord:
|
|
134
|
+
_aspect_ratio: float = 0.9 #
|
|
135
|
+
_pitch_to_chord: npt.NDArray = field(default_factory=lambda: np.array([0.7])) # Pitch to chord ratio, used to determine number of blades and compute loss
|
|
117
136
|
|
|
118
|
-
_axial_chord:float = -1
|
|
119
|
-
_chord:
|
|
120
|
-
_stagger:
|
|
121
|
-
_te_s:float = 0.08
|
|
122
|
-
_tip_clearance:float = 0 # Clearance as a percentage of span or blade height
|
|
137
|
+
_axial_chord: float = -1
|
|
138
|
+
_chord: npt.NDArray = field(default_factory=lambda: np.array([-1.0]))
|
|
139
|
+
_stagger: npt.NDArray = field(default_factory=lambda: np.array([42.0]))
|
|
140
|
+
_te_s: float = 0.08
|
|
141
|
+
_tip_clearance: float = 0 # Clearance as a percentage of span or blade height
|
|
142
|
+
|
|
143
|
+
_inlet_to_outlet_pratio: list = field(default_factory=lambda: [0.06,0.95])
|
|
123
144
|
|
|
124
|
-
|
|
125
|
-
|
|
145
|
+
def __post_init__(self):
|
|
146
|
+
if self.shroud_location == 0:
|
|
147
|
+
self.shroud_location = self.hub_location
|
|
148
|
+
# Preserve any user-specified target ratio so later diagnostics can safely overwrite P0_ratio.
|
|
149
|
+
if self.P0_ratio_target == 0 and self.P0_ratio != 0:
|
|
150
|
+
self.P0_ratio_target = self.P0_ratio
|
|
126
151
|
|
|
127
152
|
@property
|
|
128
153
|
def inlet_to_outlet_pratio(self) -> Tuple[float,float]:
|
|
@@ -192,7 +217,7 @@ class BladeRow:
|
|
|
192
217
|
|
|
193
218
|
|
|
194
219
|
@property
|
|
195
|
-
def pitch_to_chord(self) ->
|
|
220
|
+
def pitch_to_chord(self) -> npt.NDArray:
|
|
196
221
|
"""Gets the pitch to chord ratio
|
|
197
222
|
|
|
198
223
|
Returns:
|
|
@@ -207,10 +232,10 @@ class BladeRow:
|
|
|
207
232
|
Args:
|
|
208
233
|
val (float): new pitch to chord ratio. Typically stators are 0.8 to 0.95. Rotors 0.7 to 0.8
|
|
209
234
|
"""
|
|
210
|
-
self._pitch_to_chord = val
|
|
235
|
+
self._pitch_to_chord = convert_to_ndarray(val)
|
|
211
236
|
|
|
212
237
|
@property
|
|
213
|
-
def solidity(self) ->
|
|
238
|
+
def solidity(self) -> npt.NDArray:
|
|
214
239
|
"""Inverse of pitch to chord ratio
|
|
215
240
|
|
|
216
241
|
Returns:
|
|
@@ -228,16 +253,28 @@ class BladeRow:
|
|
|
228
253
|
Returns:
|
|
229
254
|
float: solidity
|
|
230
255
|
"""
|
|
231
|
-
self._pitch_to_chord = 1/val
|
|
256
|
+
self._pitch_to_chord = 1/convert_to_ndarray(val)
|
|
232
257
|
|
|
233
258
|
@property
|
|
234
|
-
def
|
|
259
|
+
def metal_inlet_angle(self) -> npt.NDArray:
|
|
260
|
+
"""Blade metal inlet angle (degrees)."""
|
|
235
261
|
return np.degrees(self._beta1_metal)
|
|
236
262
|
|
|
237
263
|
@property
|
|
238
|
-
def
|
|
264
|
+
def beta1_metal(self) -> npt.NDArray:
|
|
265
|
+
"""Backward-compatible alias for metal_inlet_angle."""
|
|
266
|
+
return self.metal_inlet_angle
|
|
267
|
+
|
|
268
|
+
@property
|
|
269
|
+
def metal_exit_angle(self) -> npt.NDArray:
|
|
270
|
+
"""Blade metal exit angle (degrees)."""
|
|
239
271
|
return np.degrees(self._beta2_metal)
|
|
240
272
|
|
|
273
|
+
@property
|
|
274
|
+
def beta2_metal(self) -> npt.NDArray:
|
|
275
|
+
"""Backward-compatible alias for metal_exit_angle."""
|
|
276
|
+
return self.metal_exit_angle
|
|
277
|
+
|
|
241
278
|
@property
|
|
242
279
|
def stagger(self) -> float:
|
|
243
280
|
"""Average stagger angle
|
|
@@ -257,7 +294,7 @@ class BladeRow:
|
|
|
257
294
|
self._stagger = val
|
|
258
295
|
|
|
259
296
|
@property
|
|
260
|
-
def chord(self) ->
|
|
297
|
+
def chord(self) -> npt.NDArray:
|
|
261
298
|
"""Chord defined at mean radius
|
|
262
299
|
|
|
263
300
|
Returns:
|
|
@@ -286,14 +323,16 @@ class BladeRow:
|
|
|
286
323
|
else:
|
|
287
324
|
return self.pitch*np.sin(np.pi/2-self.beta2.mean())
|
|
288
325
|
|
|
326
|
+
_num_blades: float = 0
|
|
327
|
+
|
|
289
328
|
@property
|
|
290
|
-
def num_blades(self) ->float:
|
|
291
|
-
"""
|
|
329
|
+
def num_blades(self) -> float:
|
|
330
|
+
"""Configured number of blades (set during design/initialization)."""
|
|
331
|
+
return self._num_blades
|
|
292
332
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
return int(2*np.pi*self.r.mean() / self.pitch)
|
|
333
|
+
@num_blades.setter
|
|
334
|
+
def num_blades(self, val: float) -> None:
|
|
335
|
+
self._num_blades = val
|
|
297
336
|
|
|
298
337
|
@property
|
|
299
338
|
def camber(self) -> float:
|
|
@@ -330,54 +369,63 @@ class BladeRow:
|
|
|
330
369
|
"""
|
|
331
370
|
self._tip_clearance = val
|
|
332
371
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
def beta1_metal(self,beta1_metal:List[float],percent:List[float]=[]):
|
|
350
|
-
"""Sets the leading edge metal angle for the blade
|
|
351
|
-
|
|
352
|
-
Args:
|
|
353
|
-
beta1_metal (List[float]): blade leading edge angle
|
|
354
|
-
percent (List[float]): percent location of metal angles from hub to shroud.
|
|
355
|
-
"""
|
|
356
|
-
self._beta1_metal = np.radians(convert_to_ndarray(beta1_metal))
|
|
357
|
-
if len(percent) != len(beta1_metal):
|
|
358
|
-
percent = np.linspace(0,1,len(self._beta1_metal)).tolist() # type: ignore
|
|
372
|
+
# Backwards-compatible alias
|
|
373
|
+
@property
|
|
374
|
+
def location(self) -> float:
|
|
375
|
+
return self.hub_location
|
|
376
|
+
|
|
377
|
+
@location.setter
|
|
378
|
+
def location(self, val: float) -> None:
|
|
379
|
+
self.hub_location = val
|
|
380
|
+
|
|
381
|
+
@metal_inlet_angle.setter
|
|
382
|
+
def metal_inlet_angle(self, metal_inlet_angle: List[float], percent: List[float] = []):
|
|
383
|
+
"""Sets the leading edge metal angle for the blade (degrees)."""
|
|
384
|
+
arr = np.radians(convert_to_ndarray(metal_inlet_angle))
|
|
385
|
+
if len(percent) != len(metal_inlet_angle):
|
|
386
|
+
percent = np.linspace(0, 1, len(arr)).tolist() # type: ignore
|
|
387
|
+
self._beta1_metal = arr
|
|
359
388
|
self.beta1_metal_radii = convert_to_ndarray(percent)
|
|
360
389
|
self.beta1_fixed = True
|
|
361
|
-
self.beta1 = self.
|
|
390
|
+
self.beta1 = self.metal_inlet_angle.copy()
|
|
391
|
+
|
|
392
|
+
@beta1_metal.setter
|
|
393
|
+
def beta1_metal(self, beta1_metal: List[float], percent: List[float] = []):
|
|
394
|
+
"""Backward-compatible alias for metal_inlet_angle setter."""
|
|
395
|
+
self.metal_inlet_angle = beta1_metal
|
|
362
396
|
|
|
363
|
-
@
|
|
364
|
-
def
|
|
365
|
-
"""Sets the trailing edge metal angle for the blade
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
"""
|
|
372
|
-
self._beta2_metal = np.radians(convert_to_ndarray(beta2_metal))
|
|
373
|
-
if len(percent) != len(beta2_metal):
|
|
374
|
-
percent = np.linspace(0,1,len(self._beta2_metal)).tolist() # type: ignore
|
|
397
|
+
@metal_exit_angle.setter
|
|
398
|
+
def metal_exit_angle(self, metal_exit_angle: List[float], percent: List[float] = []):
|
|
399
|
+
"""Sets the trailing edge metal angle for the blade (degrees)."""
|
|
400
|
+
arr = np.radians(convert_to_ndarray(metal_exit_angle))
|
|
401
|
+
if len(percent) != len(metal_exit_angle):
|
|
402
|
+
percent = np.linspace(0, 1, len(arr)).tolist() # type: ignore
|
|
403
|
+
self._beta2_metal = arr
|
|
375
404
|
self.beta2_metal_radii = convert_to_ndarray(percent)
|
|
376
405
|
self.beta2_fixed = True
|
|
377
|
-
|
|
378
|
-
|
|
406
|
+
|
|
407
|
+
# Apply deviation if defined; deviation_function returns degrees
|
|
408
|
+
deviation_func = getattr(self, "deviation_function", None)
|
|
409
|
+
deviation_rad = 0.0
|
|
410
|
+
if callable(deviation_func):
|
|
411
|
+
try:
|
|
412
|
+
deviation_val = deviation_func(self, None)
|
|
413
|
+
deviation_rad = np.radians(deviation_val)
|
|
414
|
+
except Exception:
|
|
415
|
+
deviation_rad = 0.0
|
|
416
|
+
|
|
417
|
+
beta2_effective = self._beta2_metal + deviation_rad
|
|
379
418
|
if self.row_type == RowType.Stator:
|
|
380
|
-
self.alpha2 =
|
|
419
|
+
self.alpha2 = beta2_effective.copy()
|
|
420
|
+
self.beta2 = beta2_effective.copy()
|
|
421
|
+
else:
|
|
422
|
+
self.beta2 = beta2_effective.copy()
|
|
423
|
+
self.deviation = np.full_like(self.beta2, deviation_rad)
|
|
424
|
+
|
|
425
|
+
@beta2_metal.setter
|
|
426
|
+
def beta2_metal(self, beta2_metal: List[float], percent: List[float] = []):
|
|
427
|
+
"""Backward-compatible alias for metal_exit_angle setter."""
|
|
428
|
+
self.metal_exit_angle = beta2_metal
|
|
381
429
|
|
|
382
430
|
@property
|
|
383
431
|
def rpm(self):
|
|
@@ -416,14 +464,6 @@ class BladeRow:
|
|
|
416
464
|
self.loss_function = model
|
|
417
465
|
return
|
|
418
466
|
|
|
419
|
-
if isinstance(model, Sequence):
|
|
420
|
-
if len(model) == 0:
|
|
421
|
-
raise ValueError("At least one loss model must be provided.")
|
|
422
|
-
if not all(isinstance(m, LossBaseClass) for m in model):
|
|
423
|
-
raise TypeError("All entries must inherit LossBaseClass.")
|
|
424
|
-
self.loss_function = CompositeLossModel(model)
|
|
425
|
-
return
|
|
426
|
-
|
|
427
467
|
raise TypeError("Loss models must inherit LossBaseClass.")
|
|
428
468
|
|
|
429
469
|
@property
|
|
@@ -446,6 +486,39 @@ class BladeRow:
|
|
|
446
486
|
|
|
447
487
|
def __repr__(self):
|
|
448
488
|
return f"{self.row_type.name} P0:{np.mean(self.P0):0.2f} T0:{np.mean(self.T0):0.2f} P:{np.mean(self.P):0.2f} massflow:{np.mean(self.total_massflow_no_coolant):0.3f}"
|
|
489
|
+
|
|
490
|
+
def synchronize_blade_geometry(self) -> None:
|
|
491
|
+
"""Couple num_blades, pitch-to-chord/solidity, chord, and stagger.
|
|
492
|
+
|
|
493
|
+
Uses mean radius from interpolated streamlines to derive pitch, chord,
|
|
494
|
+
and stagger (axial chord / chord).
|
|
495
|
+
"""
|
|
496
|
+
if self.num_blades <= 0 or self.r.size == 0:
|
|
497
|
+
return
|
|
498
|
+
|
|
499
|
+
# Pitch from blade count and local radius
|
|
500
|
+
pitch = 2 * np.pi * self.r / self.num_blades
|
|
501
|
+
|
|
502
|
+
# Pitch-to-chord (or 1/solidity) may be scalar or spanwise; broadcast it
|
|
503
|
+
ptc = convert_to_ndarray(self.pitch_to_chord)
|
|
504
|
+
if ptc.size == 1:
|
|
505
|
+
ptc = ptc * np.ones_like(self.r, dtype=float)
|
|
506
|
+
else:
|
|
507
|
+
t_src = np.linspace(0, 1, ptc.size)
|
|
508
|
+
ptc = np.interp(self.percent_hub_shroud, t_src, ptc)
|
|
509
|
+
|
|
510
|
+
chord = pitch / np.maximum(ptc, 1e-9)
|
|
511
|
+
self._chord = chord
|
|
512
|
+
self._pitch_to_chord = ptc
|
|
513
|
+
|
|
514
|
+
axial = self.axial_chord if self.axial_chord > 0 else float(np.mean(chord))
|
|
515
|
+
if self.axial_chord <= 0:
|
|
516
|
+
self.axial_chord = axial
|
|
517
|
+
|
|
518
|
+
ratio = np.clip(axial / np.maximum(chord, 1e-9), -1.0, 1.0)
|
|
519
|
+
stagger_rad = np.arccos(ratio)
|
|
520
|
+
# Store stagger distribution in degrees
|
|
521
|
+
self._stagger = np.degrees(stagger_rad)
|
|
449
522
|
|
|
450
523
|
def to_dict(self):
|
|
451
524
|
|
|
@@ -487,15 +560,18 @@ class BladeRow:
|
|
|
487
560
|
"rho":self.rho.tolist(),
|
|
488
561
|
"mu":self.mu,
|
|
489
562
|
"Yp":self.Yp,
|
|
563
|
+
"flow_coefficient": self.flow_coefficient,
|
|
490
564
|
"Power":self.power,
|
|
491
565
|
"P0_P": self.P0_P,
|
|
492
566
|
"eta_total":self.eta_total,
|
|
493
567
|
"eta_static":self.eta_static,
|
|
568
|
+
"eta_poly": self.eta_poly,
|
|
494
569
|
"euler_power":self.euler_power,
|
|
495
570
|
"axial_chord":self.axial_chord,
|
|
496
571
|
"aspect_ratio":self.aspect_ratio,
|
|
497
572
|
"num_blades":self.num_blades,
|
|
498
|
-
"
|
|
573
|
+
"total_area": self.total_area,
|
|
574
|
+
"area": self.area.tolist(),
|
|
499
575
|
"radius":self.r.tolist(),
|
|
500
576
|
"x":self.x.tolist(),
|
|
501
577
|
"dx":self.x[-1]-self.x[0],
|
|
@@ -508,7 +584,7 @@ class BladeRow:
|
|
|
508
584
|
return data
|
|
509
585
|
|
|
510
586
|
#* Some functions related to blade row
|
|
511
|
-
def
|
|
587
|
+
def interpolate_streamline_quantities(row:BladeRow,passage:Passage,num_streamlines:int=3):
|
|
512
588
|
"""Interpolate all quantities onto the streamline and allocates variables.
|
|
513
589
|
Run this after setting some initial conditions
|
|
514
590
|
|
|
@@ -520,9 +596,17 @@ def interpolate_streamline_radii(row:BladeRow,passage:Passage,num_streamlines:in
|
|
|
520
596
|
Returns:
|
|
521
597
|
(BladeRow): new row object with quantities interpolated
|
|
522
598
|
"""
|
|
599
|
+
src_percent = convert_to_ndarray(row.percent_hub_shroud)
|
|
600
|
+
|
|
523
601
|
row.cutting_line,_,_ = passage.get_cutting_line(row.location)
|
|
524
|
-
|
|
525
|
-
|
|
602
|
+
t_span = np.array([0.5]) if num_streamlines <= 1 else np.linspace(0, 1, num_streamlines)
|
|
603
|
+
row.x, row.r = row.cutting_line.get_point(t_span)
|
|
604
|
+
if num_streamlines <= 1:
|
|
605
|
+
streamline_percent_length = np.array([0.5])
|
|
606
|
+
row.total_area = passage.get_area(row.location)
|
|
607
|
+
row.area = np.array([row.total_area])
|
|
608
|
+
else:
|
|
609
|
+
streamline_percent_length = np.sqrt((row.r-row.r[0])**2+(row.x-row.x[0])**2)/row.cutting_line.length
|
|
526
610
|
|
|
527
611
|
# Flow angles
|
|
528
612
|
row._beta1_metal = row._beta1_metal.default_factory() if type(row._beta1_metal) == Field else row._beta1_metal
|
|
@@ -534,51 +618,48 @@ def interpolate_streamline_radii(row:BladeRow,passage:Passage,num_streamlines:in
|
|
|
534
618
|
row._beta2_metal = interpolate_quantities(row._beta2_metal,row.beta2_metal_radii,streamline_percent_length)
|
|
535
619
|
row.beta1_metal_radii = streamline_percent_length
|
|
536
620
|
row.beta2_metal_radii = streamline_percent_length
|
|
621
|
+
row.deviation = streamline_percent_length * 0
|
|
537
622
|
|
|
538
|
-
|
|
539
|
-
row.percent_hub_shroud = streamline_percent_length
|
|
540
|
-
else:
|
|
541
|
-
row.percent_hub_shroud = streamline_percent_length # Reset the radii to streamline radii
|
|
623
|
+
row.mprime = interpolate_quantities(row.mprime, src_percent, streamline_percent_length)
|
|
542
624
|
|
|
543
|
-
row.alpha1 =
|
|
544
|
-
row.alpha2 =
|
|
545
|
-
row.beta1 =
|
|
546
|
-
row.beta2 =
|
|
625
|
+
row.alpha1 = safe_interpolate(row.alpha1, src_percent, streamline_percent_length, radians=False)
|
|
626
|
+
row.alpha2 = safe_interpolate(row.alpha2, src_percent, streamline_percent_length, radians=False)
|
|
627
|
+
row.beta1 = safe_interpolate(row.beta1, src_percent, streamline_percent_length, radians=False)
|
|
628
|
+
row.beta2 = safe_interpolate(row.beta2, src_percent, streamline_percent_length, radians=False)
|
|
547
629
|
|
|
548
630
|
# Velocities
|
|
549
|
-
row.Vm = interpolate_quantities(row.Vm,
|
|
550
|
-
row.Vx = interpolate_quantities(row.Vx,
|
|
551
|
-
row.Vt = interpolate_quantities(row.Vt,
|
|
552
|
-
row.Vr = interpolate_quantities(row.Vr,
|
|
553
|
-
row.V = interpolate_quantities(row.V,
|
|
554
|
-
row.V2 = interpolate_quantities(row.V2,
|
|
555
|
-
row.M = interpolate_quantities(row.M,
|
|
556
|
-
row.M_rel = interpolate_quantities(row.M_rel,
|
|
557
|
-
row.U = interpolate_quantities(row.U,
|
|
558
|
-
row.W = interpolate_quantities(row.W,
|
|
559
|
-
row.Wt = interpolate_quantities(row.Wt,
|
|
631
|
+
row.Vm = interpolate_quantities(row.Vm, src_percent, streamline_percent_length)
|
|
632
|
+
row.Vx = interpolate_quantities(row.Vx, src_percent, streamline_percent_length)
|
|
633
|
+
row.Vt = interpolate_quantities(row.Vt, src_percent, streamline_percent_length)
|
|
634
|
+
row.Vr = interpolate_quantities(row.Vr, src_percent, streamline_percent_length)
|
|
635
|
+
row.V = interpolate_quantities(row.V, src_percent, streamline_percent_length)
|
|
636
|
+
row.V2 = interpolate_quantities(row.V2, src_percent, streamline_percent_length)
|
|
637
|
+
row.M = interpolate_quantities(row.M, src_percent, streamline_percent_length)
|
|
638
|
+
row.M_rel = interpolate_quantities(row.M_rel, src_percent, streamline_percent_length)
|
|
639
|
+
row.U = interpolate_quantities(row.U, src_percent, streamline_percent_length)
|
|
640
|
+
row.W = interpolate_quantities(row.W, src_percent, streamline_percent_length)
|
|
641
|
+
row.Wt = interpolate_quantities(row.Wt, src_percent, streamline_percent_length)
|
|
560
642
|
|
|
561
643
|
# Total Quantities
|
|
562
|
-
row.T0 = interpolate_quantities(row.T0,
|
|
563
|
-
row.T0_is = interpolate_quantities(row.T0,
|
|
564
|
-
row.P0 = interpolate_quantities(row.P0,
|
|
565
|
-
row.
|
|
644
|
+
row.T0 = interpolate_quantities(row.T0, src_percent, streamline_percent_length)
|
|
645
|
+
row.T0_is = interpolate_quantities(row.T0, src_percent, streamline_percent_length) # For Turbines
|
|
646
|
+
row.P0 = interpolate_quantities(row.P0, src_percent, streamline_percent_length)
|
|
647
|
+
row.P0_is = interpolate_quantities(row.P0, src_percent, streamline_percent_length) # For Compressors
|
|
648
|
+
row.P0_stator_inlet = interpolate_quantities(row.P0_stator_inlet, src_percent, streamline_percent_length)
|
|
566
649
|
|
|
567
650
|
# Relative Quantities
|
|
568
|
-
row.P0R = interpolate_quantities(row.P0R,
|
|
569
|
-
row.
|
|
651
|
+
row.P0R = interpolate_quantities(row.P0R, src_percent, streamline_percent_length)
|
|
652
|
+
row.P0R_is = interpolate_quantities(row.P0, src_percent, streamline_percent_length)
|
|
653
|
+
row.T0R = interpolate_quantities(row.T0R, src_percent, streamline_percent_length)
|
|
570
654
|
|
|
571
655
|
# Static Quantities
|
|
572
|
-
row.P = interpolate_quantities(row.P,
|
|
573
|
-
row.T = interpolate_quantities(row.T,
|
|
574
|
-
row.T_is = interpolate_quantities(row.T_is,
|
|
575
|
-
row.rho = interpolate_quantities(row.rho,
|
|
656
|
+
row.P = interpolate_quantities(row.P, src_percent, streamline_percent_length)
|
|
657
|
+
row.T = interpolate_quantities(row.T, src_percent, streamline_percent_length)
|
|
658
|
+
row.T_is = interpolate_quantities(row.T_is, src_percent, streamline_percent_length)
|
|
659
|
+
row.rho = interpolate_quantities(row.rho, src_percent, streamline_percent_length)
|
|
660
|
+
row.entropy_rise = interpolate_quantities(row.entropy_rise, src_percent, streamline_percent_length)
|
|
576
661
|
|
|
577
|
-
|
|
578
|
-
# row.P0_fun = interp1d(row.percent_hub_shroud,row.P0)
|
|
579
|
-
# row.T0_fun = interp1d(row.percent_hub_shroud,row.T0)
|
|
580
|
-
# elif row.row_type == RowType.Outlet:
|
|
581
|
-
# row.P_fun = interp1d(row.percent_hub_shroud,row.P)
|
|
662
|
+
row.percent_hub_shroud = streamline_percent_length
|
|
582
663
|
|
|
583
664
|
return row
|
|
584
665
|
|
|
@@ -615,6 +696,8 @@ def interpolate_quantities(q:npt.NDArray,r:npt.NDArray,r2:npt.NDArray):
|
|
|
615
696
|
q2 = np.zeros(shape=r2.shape)
|
|
616
697
|
return q[0]+q2
|
|
617
698
|
else:
|
|
699
|
+
if len(r) != len(q):
|
|
700
|
+
r = np.linspace(0, 1, len(q))
|
|
618
701
|
return interp1d(r,q,kind='linear')(r2)
|
|
619
702
|
|
|
620
703
|
def compute_gas_constants(row:BladeRow,fluid:Optional[Solution]=None) -> None:
|