turbo-design 1.3.7__py3-none-any.whl → 1.3.9__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.
Potentially problematic release.
This version of turbo-design might be problematic. Click here for more details.
- {turbo_design-1.3.7.dist-info → turbo_design-1.3.9.dist-info}/METADATA +2 -1
- turbo_design-1.3.9.dist-info/RECORD +46 -0
- {turbo_design-1.3.7.dist-info → turbo_design-1.3.9.dist-info}/WHEEL +1 -1
- turbodesign/__init__.py +57 -4
- turbodesign/agf.py +346 -0
- turbodesign/arrayfuncs.py +31 -1
- turbodesign/bladerow.py +237 -155
- turbodesign/compressor_math.py +374 -0
- turbodesign/compressor_spool.py +837 -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 +159 -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/radeq.py +3 -2
- turbodesign/row_factory.py +129 -0
- turbodesign/solve_radeq.py +9 -10
- turbodesign/{td_math.py → turbine_math.py} +125 -175
- turbodesign/turbine_spool.py +984 -0
- turbo_design-1.3.7.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,53 @@ 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
|
|
35
41
|
# ----------------------------------
|
|
36
42
|
|
|
37
43
|
# Streamline Properties
|
|
38
|
-
percent_hub:float = 0 # Where blade row is defined along the hub.
|
|
44
|
+
percent_hub: float = 0 # Where blade row is defined along the hub.
|
|
39
45
|
percent_hub_shroud: npt.NDArray = field(default_factory=lambda: np.array([0])) # Percent streamline length from hub to shroud.
|
|
40
46
|
x: npt.NDArray = field(default_factory=lambda: np.array([0])) # x - coordinates (useful for computing axial chord)
|
|
41
47
|
r: npt.NDArray = field(default_factory=lambda: np.array([0])) # Radius - coordinates
|
|
42
48
|
m: npt.NDArray = field(default_factory=lambda: np.array([0])) # meridional
|
|
43
|
-
|
|
49
|
+
total_area: float = 0
|
|
50
|
+
area: npt.NDArray = field(default_factory=lambda: np.array([0]))
|
|
44
51
|
# Calculated massflow is the massflow computed after radial eq solver
|
|
45
52
|
calculated_massflow: float = 0
|
|
46
|
-
|
|
53
|
+
|
|
47
54
|
# Row Efficiency (calculated or specified)
|
|
48
|
-
eta_total:float = 0 # Total to Total
|
|
49
|
-
eta_static:float = 0 # Total to static
|
|
50
|
-
|
|
55
|
+
eta_total: float = 0 # Total to Total
|
|
56
|
+
eta_static: float = 0 # Total to static
|
|
57
|
+
eta_poly: float = 0 # Polytropic efficiency (per row if applicable)
|
|
58
|
+
stage_loading: float = 0 # stage loading how much work done per stage
|
|
51
59
|
|
|
52
60
|
alpha1: npt.NDArray = field(default_factory=lambda: np.array([0])) # Blade inlet absolute flow angle
|
|
53
61
|
alpha2: npt.NDArray = field(default_factory=lambda: np.array([0])) # Blade exit absolute flow angle
|
|
@@ -55,20 +63,21 @@ class BladeRow:
|
|
|
55
63
|
beta1: npt.NDArray = field(default_factory=lambda: np.array([0])) # Blade inlet relative flow angle
|
|
56
64
|
beta2: npt.NDArray = field(default_factory=lambda: np.array([0])) # Blade exit relative flow angle
|
|
57
65
|
|
|
58
|
-
|
|
59
|
-
|
|
66
|
+
deviation: npt.NDArray = field(default_factory=lambda: np.array([0]))
|
|
67
|
+
_beta1_metal: npt.NDArray = field(default_factory=lambda: np.array([0])) # blade inlet metal angle
|
|
68
|
+
beta1_metal_radii: npt.NDArray = field(default_factory=lambda: np.array([0])) # radii where metal angle is defined
|
|
60
69
|
|
|
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
|
|
70
|
+
_beta2_metal: npt.NDArray = field(default_factory=lambda: np.array([0])) # blade exit metal angle
|
|
71
|
+
beta2_metal_radii: npt.NDArray = field(default_factory=lambda: np.array([0])) # radii where metal angle is defined
|
|
63
72
|
|
|
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
|
|
73
|
+
beta1_fixed: bool = False # Geometry already defined. This affects the inlet flow angle
|
|
74
|
+
beta2_fixed: bool = False # Geometry already defined. This affects the exit flow angle
|
|
66
75
|
|
|
67
76
|
# Velocities
|
|
68
77
|
Vm: npt.NDArray = field(default_factory=lambda: np.array([0])) # Meridional velocity
|
|
69
78
|
Vx: npt.NDArray = field(default_factory=lambda: np.array([0])) # Axial Velocity
|
|
70
79
|
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
|
|
80
|
+
Vr: npt.NDArray = field(default_factory=lambda: np.array([0])) # Radial velocity
|
|
72
81
|
V: npt.NDArray = field(default_factory=lambda: np.array([0])) # Absolute Velocity in 3D coordinate system
|
|
73
82
|
V2: npt.NDArray = field(default_factory=lambda: np.array([0])) # Absolute Velocity in Theta-Axial plane
|
|
74
83
|
M: npt.NDArray = field(default_factory=lambda: np.array([0])) # Mach Number
|
|
@@ -77,15 +86,17 @@ class BladeRow:
|
|
|
77
86
|
W: npt.NDArray = field(default_factory=lambda: np.array([0])) # Relative Velocity in Theta-Axial plane
|
|
78
87
|
Wt: npt.NDArray = field(default_factory=lambda: np.array([0])) # Relative Tangential Velocity
|
|
79
88
|
|
|
80
|
-
_rpm: float = 0
|
|
81
|
-
omega:float = 0 # angular velocity rad/s
|
|
89
|
+
_rpm: float = field(default=0, init=False, repr=False)
|
|
90
|
+
omega: float = 0 # angular velocity rad/s
|
|
82
91
|
|
|
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
|
|
92
|
+
P0_stator_inlet: npt.NDArray = field(default_factory=lambda: np.array([0])) # Every quantity is an exit quantity, This is used for efficiency calcs
|
|
93
|
+
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
94
|
P0: npt.NDArray = field(default_factory=lambda: np.array([0])) # Total Quantities
|
|
95
|
+
P0_is: npt.NDArray = field(default_factory=lambda: np.array([0]))
|
|
86
96
|
T0: npt.NDArray = field(default_factory=lambda: np.array([0]))
|
|
87
|
-
T0_is:npt.NDArray = field(default_factory=lambda: np.array([0]))
|
|
97
|
+
T0_is: npt.NDArray = field(default_factory=lambda: np.array([0]))
|
|
88
98
|
P0R: npt.NDArray = field(default_factory=lambda: np.array([0])) # Relative Total Pressure (Pa)
|
|
99
|
+
P0R_is: npt.NDArray = field(default_factory=lambda: np.array([0]))
|
|
89
100
|
T0R: npt.NDArray = field(default_factory=lambda: np.array([0]))
|
|
90
101
|
|
|
91
102
|
# Static Quantities
|
|
@@ -93,36 +104,49 @@ class BladeRow:
|
|
|
93
104
|
T: npt.NDArray = field(default_factory=lambda: np.array([0]))
|
|
94
105
|
T_is: npt.NDArray = field(default_factory=lambda: np.array([0]))
|
|
95
106
|
rho: npt.NDArray = field(default_factory=lambda: np.array([0]))
|
|
96
|
-
|
|
107
|
+
entropy_rise: npt.NDArray = field(default_factory=lambda: np.array([0]))
|
|
108
|
+
|
|
97
109
|
# Related to streamline curvature
|
|
98
|
-
phi:npt.NDArray = field(default_factory=lambda: np.array([0])) # Inclination angle x,r plane. AY td2.f
|
|
110
|
+
phi: npt.NDArray = field(default_factory=lambda: np.array([0])) # Inclination angle x,r plane. AY td2.f
|
|
99
111
|
rm: npt.NDArray = field(default_factory=lambda: np.array([0])) # Curvature
|
|
100
112
|
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
|
-
|
|
113
|
+
mprime: npt.NDArray = field(default_factory=lambda: np.array([0])) # Mprime distance
|
|
114
|
+
|
|
115
|
+
Yp: npt.NDArray = field(default_factory=lambda: np.array([0])) # Pressure loss
|
|
116
|
+
blockage: float = 0
|
|
117
|
+
flow_coefficient: float = 0 # Vm/U or similar nondimensional flow coefficient
|
|
118
|
+
power: float = 0 # Watts
|
|
119
|
+
power_mean: float = 0
|
|
120
|
+
power_distribution: npt.NDArray = field(default_factory=lambda: np.array([0])) # How power is divided by radius.
|
|
121
|
+
P0_P: float = 0 # Total to Static Pressure Ratio
|
|
122
|
+
P0_ratio: float = 0 # Total-to-total pressure ratio target (design input; may be overwritten in legacy diagnostics)
|
|
123
|
+
P0_ratio_target: float = 0 # Frozen design target for P0_ratio (never overwritten; used for initial guesses)
|
|
124
|
+
Power_Type: PowerType = PowerType.P0_P
|
|
125
|
+
euler_power: float = 0
|
|
126
|
+
Reynolds: float = 0
|
|
127
|
+
eta_poly: float = 0.0 # Optional per-row polytropic efficiency target
|
|
128
|
+
num_blades: int = 0
|
|
111
129
|
|
|
112
130
|
# Used for loss calculations
|
|
113
|
-
_blade_to_blade_gap:float = 0.025 # Gap between blade in terms of percent chord.
|
|
131
|
+
_blade_to_blade_gap: float = 0.025 # Gap between blade in terms of percent chord.
|
|
114
132
|
|
|
115
|
-
_aspect_ratio:float = 0.9 #
|
|
116
|
-
_pitch_to_chord:
|
|
133
|
+
_aspect_ratio: float = 0.9 #
|
|
134
|
+
_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
135
|
|
|
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
|
|
136
|
+
_axial_chord: float = -1
|
|
137
|
+
_chord: npt.NDArray = field(default_factory=lambda: np.array([-1.0]))
|
|
138
|
+
_stagger: npt.NDArray = field(default_factory=lambda: np.array([42.0]))
|
|
139
|
+
_te_s: float = 0.08
|
|
140
|
+
_tip_clearance: float = 0 # Clearance as a percentage of span or blade height
|
|
141
|
+
|
|
142
|
+
_inlet_to_outlet_pratio: list = field(default_factory=lambda: [0.06,0.95])
|
|
123
143
|
|
|
124
|
-
|
|
125
|
-
|
|
144
|
+
def __post_init__(self):
|
|
145
|
+
if self.shroud_location == 0:
|
|
146
|
+
self.shroud_location = self.hub_location
|
|
147
|
+
# Preserve any user-specified target ratio so later diagnostics can safely overwrite P0_ratio.
|
|
148
|
+
if self.P0_ratio_target == 0 and self.P0_ratio != 0:
|
|
149
|
+
self.P0_ratio_target = self.P0_ratio
|
|
126
150
|
|
|
127
151
|
@property
|
|
128
152
|
def inlet_to_outlet_pratio(self) -> Tuple[float,float]:
|
|
@@ -192,7 +216,7 @@ class BladeRow:
|
|
|
192
216
|
|
|
193
217
|
|
|
194
218
|
@property
|
|
195
|
-
def pitch_to_chord(self) ->
|
|
219
|
+
def pitch_to_chord(self) -> npt.NDArray:
|
|
196
220
|
"""Gets the pitch to chord ratio
|
|
197
221
|
|
|
198
222
|
Returns:
|
|
@@ -207,10 +231,10 @@ class BladeRow:
|
|
|
207
231
|
Args:
|
|
208
232
|
val (float): new pitch to chord ratio. Typically stators are 0.8 to 0.95. Rotors 0.7 to 0.8
|
|
209
233
|
"""
|
|
210
|
-
self._pitch_to_chord = val
|
|
234
|
+
self._pitch_to_chord = convert_to_ndarray(val)
|
|
211
235
|
|
|
212
236
|
@property
|
|
213
|
-
def solidity(self) ->
|
|
237
|
+
def solidity(self) -> npt.NDArray:
|
|
214
238
|
"""Inverse of pitch to chord ratio
|
|
215
239
|
|
|
216
240
|
Returns:
|
|
@@ -228,16 +252,28 @@ class BladeRow:
|
|
|
228
252
|
Returns:
|
|
229
253
|
float: solidity
|
|
230
254
|
"""
|
|
231
|
-
self._pitch_to_chord = 1/val
|
|
255
|
+
self._pitch_to_chord = 1/convert_to_ndarray(val)
|
|
232
256
|
|
|
233
257
|
@property
|
|
234
|
-
def
|
|
258
|
+
def metal_inlet_angle(self) -> npt.NDArray:
|
|
259
|
+
"""Blade metal inlet angle (degrees)."""
|
|
235
260
|
return np.degrees(self._beta1_metal)
|
|
236
261
|
|
|
237
262
|
@property
|
|
238
|
-
def
|
|
263
|
+
def beta1_metal(self) -> npt.NDArray:
|
|
264
|
+
"""Backward-compatible alias for metal_inlet_angle."""
|
|
265
|
+
return self.metal_inlet_angle
|
|
266
|
+
|
|
267
|
+
@property
|
|
268
|
+
def metal_exit_angle(self) -> npt.NDArray:
|
|
269
|
+
"""Blade metal exit angle (degrees)."""
|
|
239
270
|
return np.degrees(self._beta2_metal)
|
|
240
271
|
|
|
272
|
+
@property
|
|
273
|
+
def beta2_metal(self) -> npt.NDArray:
|
|
274
|
+
"""Backward-compatible alias for metal_exit_angle."""
|
|
275
|
+
return self.metal_exit_angle
|
|
276
|
+
|
|
241
277
|
@property
|
|
242
278
|
def stagger(self) -> float:
|
|
243
279
|
"""Average stagger angle
|
|
@@ -257,7 +293,7 @@ class BladeRow:
|
|
|
257
293
|
self._stagger = val
|
|
258
294
|
|
|
259
295
|
@property
|
|
260
|
-
def chord(self) ->
|
|
296
|
+
def chord(self) -> npt.NDArray:
|
|
261
297
|
"""Chord defined at mean radius
|
|
262
298
|
|
|
263
299
|
Returns:
|
|
@@ -286,14 +322,16 @@ class BladeRow:
|
|
|
286
322
|
else:
|
|
287
323
|
return self.pitch*np.sin(np.pi/2-self.beta2.mean())
|
|
288
324
|
|
|
325
|
+
_num_blades: float = 0
|
|
326
|
+
|
|
289
327
|
@property
|
|
290
|
-
def num_blades(self) ->float:
|
|
291
|
-
"""
|
|
328
|
+
def num_blades(self) -> float:
|
|
329
|
+
"""Configured number of blades (set during design/initialization)."""
|
|
330
|
+
return self._num_blades
|
|
292
331
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
return int(2*np.pi*self.r.mean() / self.pitch)
|
|
332
|
+
@num_blades.setter
|
|
333
|
+
def num_blades(self, val: float) -> None:
|
|
334
|
+
self._num_blades = val
|
|
297
335
|
|
|
298
336
|
@property
|
|
299
337
|
def camber(self) -> float:
|
|
@@ -330,54 +368,63 @@ class BladeRow:
|
|
|
330
368
|
"""
|
|
331
369
|
self._tip_clearance = val
|
|
332
370
|
|
|
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
|
|
371
|
+
# Backwards-compatible alias
|
|
372
|
+
@property
|
|
373
|
+
def location(self) -> float:
|
|
374
|
+
return self.hub_location
|
|
375
|
+
|
|
376
|
+
@location.setter
|
|
377
|
+
def location(self, val: float) -> None:
|
|
378
|
+
self.hub_location = val
|
|
379
|
+
|
|
380
|
+
@metal_inlet_angle.setter
|
|
381
|
+
def metal_inlet_angle(self, metal_inlet_angle: List[float], percent: List[float] = []):
|
|
382
|
+
"""Sets the leading edge metal angle for the blade (degrees)."""
|
|
383
|
+
arr = np.radians(convert_to_ndarray(metal_inlet_angle))
|
|
384
|
+
if len(percent) != len(metal_inlet_angle):
|
|
385
|
+
percent = np.linspace(0, 1, len(arr)).tolist() # type: ignore
|
|
386
|
+
self._beta1_metal = arr
|
|
359
387
|
self.beta1_metal_radii = convert_to_ndarray(percent)
|
|
360
388
|
self.beta1_fixed = True
|
|
361
|
-
self.beta1 = self.
|
|
389
|
+
self.beta1 = self.metal_inlet_angle.copy()
|
|
390
|
+
|
|
391
|
+
@beta1_metal.setter
|
|
392
|
+
def beta1_metal(self, beta1_metal: List[float], percent: List[float] = []):
|
|
393
|
+
"""Backward-compatible alias for metal_inlet_angle setter."""
|
|
394
|
+
self.metal_inlet_angle = beta1_metal
|
|
362
395
|
|
|
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
|
|
396
|
+
@metal_exit_angle.setter
|
|
397
|
+
def metal_exit_angle(self, metal_exit_angle: List[float], percent: List[float] = []):
|
|
398
|
+
"""Sets the trailing edge metal angle for the blade (degrees)."""
|
|
399
|
+
arr = np.radians(convert_to_ndarray(metal_exit_angle))
|
|
400
|
+
if len(percent) != len(metal_exit_angle):
|
|
401
|
+
percent = np.linspace(0, 1, len(arr)).tolist() # type: ignore
|
|
402
|
+
self._beta2_metal = arr
|
|
375
403
|
self.beta2_metal_radii = convert_to_ndarray(percent)
|
|
376
404
|
self.beta2_fixed = True
|
|
377
|
-
|
|
378
|
-
|
|
405
|
+
|
|
406
|
+
# Apply deviation if defined; deviation_function returns degrees
|
|
407
|
+
deviation_func = getattr(self, "deviation_function", None)
|
|
408
|
+
deviation_rad = 0.0
|
|
409
|
+
if callable(deviation_func):
|
|
410
|
+
try:
|
|
411
|
+
deviation_val = deviation_func(self, None)
|
|
412
|
+
deviation_rad = np.radians(deviation_val)
|
|
413
|
+
except Exception:
|
|
414
|
+
deviation_rad = 0.0
|
|
415
|
+
|
|
416
|
+
beta2_effective = self._beta2_metal + deviation_rad
|
|
379
417
|
if self.row_type == RowType.Stator:
|
|
380
|
-
self.alpha2 =
|
|
418
|
+
self.alpha2 = beta2_effective.copy()
|
|
419
|
+
self.beta2 = beta2_effective.copy()
|
|
420
|
+
else:
|
|
421
|
+
self.beta2 = beta2_effective.copy()
|
|
422
|
+
self.deviation = np.full_like(self.beta2, deviation_rad)
|
|
423
|
+
|
|
424
|
+
@beta2_metal.setter
|
|
425
|
+
def beta2_metal(self, beta2_metal: List[float], percent: List[float] = []):
|
|
426
|
+
"""Backward-compatible alias for metal_exit_angle setter."""
|
|
427
|
+
self.metal_exit_angle = beta2_metal
|
|
381
428
|
|
|
382
429
|
@property
|
|
383
430
|
def rpm(self):
|
|
@@ -416,14 +463,6 @@ class BladeRow:
|
|
|
416
463
|
self.loss_function = model
|
|
417
464
|
return
|
|
418
465
|
|
|
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
466
|
raise TypeError("Loss models must inherit LossBaseClass.")
|
|
428
467
|
|
|
429
468
|
@property
|
|
@@ -446,6 +485,39 @@ class BladeRow:
|
|
|
446
485
|
|
|
447
486
|
def __repr__(self):
|
|
448
487
|
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}"
|
|
488
|
+
|
|
489
|
+
def synchronize_blade_geometry(self) -> None:
|
|
490
|
+
"""Couple num_blades, pitch-to-chord/solidity, chord, and stagger.
|
|
491
|
+
|
|
492
|
+
Uses mean radius from interpolated streamlines to derive pitch, chord,
|
|
493
|
+
and stagger (axial chord / chord).
|
|
494
|
+
"""
|
|
495
|
+
if self.num_blades <= 0 or self.r.size == 0:
|
|
496
|
+
return
|
|
497
|
+
|
|
498
|
+
# Pitch from blade count and local radius
|
|
499
|
+
pitch = 2 * np.pi * self.r / self.num_blades
|
|
500
|
+
|
|
501
|
+
# Pitch-to-chord (or 1/solidity) may be scalar or spanwise; broadcast it
|
|
502
|
+
ptc = convert_to_ndarray(self.pitch_to_chord)
|
|
503
|
+
if ptc.size == 1:
|
|
504
|
+
ptc = ptc * np.ones_like(self.r, dtype=float)
|
|
505
|
+
else:
|
|
506
|
+
t_src = np.linspace(0, 1, ptc.size)
|
|
507
|
+
ptc = np.interp(self.percent_hub_shroud, t_src, ptc)
|
|
508
|
+
|
|
509
|
+
chord = pitch / np.maximum(ptc, 1e-9)
|
|
510
|
+
self._chord = chord
|
|
511
|
+
self._pitch_to_chord = ptc
|
|
512
|
+
|
|
513
|
+
axial = self.axial_chord if self.axial_chord > 0 else float(np.mean(chord))
|
|
514
|
+
if self.axial_chord <= 0:
|
|
515
|
+
self.axial_chord = axial
|
|
516
|
+
|
|
517
|
+
ratio = np.clip(axial / np.maximum(chord, 1e-9), -1.0, 1.0)
|
|
518
|
+
stagger_rad = np.arccos(ratio)
|
|
519
|
+
# Store stagger distribution in degrees
|
|
520
|
+
self._stagger = np.degrees(stagger_rad)
|
|
449
521
|
|
|
450
522
|
def to_dict(self):
|
|
451
523
|
|
|
@@ -487,15 +559,18 @@ class BladeRow:
|
|
|
487
559
|
"rho":self.rho.tolist(),
|
|
488
560
|
"mu":self.mu,
|
|
489
561
|
"Yp":self.Yp,
|
|
562
|
+
"flow_coefficient": self.flow_coefficient,
|
|
490
563
|
"Power":self.power,
|
|
491
564
|
"P0_P": self.P0_P,
|
|
492
565
|
"eta_total":self.eta_total,
|
|
493
566
|
"eta_static":self.eta_static,
|
|
567
|
+
"eta_poly": self.eta_poly,
|
|
494
568
|
"euler_power":self.euler_power,
|
|
495
569
|
"axial_chord":self.axial_chord,
|
|
496
570
|
"aspect_ratio":self.aspect_ratio,
|
|
497
571
|
"num_blades":self.num_blades,
|
|
498
|
-
"
|
|
572
|
+
"total_area": self.total_area,
|
|
573
|
+
"area": self.area.tolist(),
|
|
499
574
|
"radius":self.r.tolist(),
|
|
500
575
|
"x":self.x.tolist(),
|
|
501
576
|
"dx":self.x[-1]-self.x[0],
|
|
@@ -508,7 +583,7 @@ class BladeRow:
|
|
|
508
583
|
return data
|
|
509
584
|
|
|
510
585
|
#* Some functions related to blade row
|
|
511
|
-
def
|
|
586
|
+
def interpolate_streamline_quantities(row:BladeRow,passage:Passage,num_streamlines:int=3):
|
|
512
587
|
"""Interpolate all quantities onto the streamline and allocates variables.
|
|
513
588
|
Run this after setting some initial conditions
|
|
514
589
|
|
|
@@ -520,9 +595,17 @@ def interpolate_streamline_radii(row:BladeRow,passage:Passage,num_streamlines:in
|
|
|
520
595
|
Returns:
|
|
521
596
|
(BladeRow): new row object with quantities interpolated
|
|
522
597
|
"""
|
|
598
|
+
src_percent = convert_to_ndarray(row.percent_hub_shroud)
|
|
599
|
+
|
|
523
600
|
row.cutting_line,_,_ = passage.get_cutting_line(row.location)
|
|
524
|
-
|
|
525
|
-
|
|
601
|
+
t_span = np.array([0.5]) if num_streamlines <= 1 else np.linspace(0, 1, num_streamlines)
|
|
602
|
+
row.x, row.r = row.cutting_line.get_point(t_span)
|
|
603
|
+
if num_streamlines <= 1:
|
|
604
|
+
streamline_percent_length = np.array([0.5])
|
|
605
|
+
row.total_area = passage.get_area(row.location)
|
|
606
|
+
row.area = np.array([row.total_area])
|
|
607
|
+
else:
|
|
608
|
+
streamline_percent_length = np.sqrt((row.r-row.r[0])**2+(row.x-row.x[0])**2)/row.cutting_line.length
|
|
526
609
|
|
|
527
610
|
# Flow angles
|
|
528
611
|
row._beta1_metal = row._beta1_metal.default_factory() if type(row._beta1_metal) == Field else row._beta1_metal
|
|
@@ -534,51 +617,48 @@ def interpolate_streamline_radii(row:BladeRow,passage:Passage,num_streamlines:in
|
|
|
534
617
|
row._beta2_metal = interpolate_quantities(row._beta2_metal,row.beta2_metal_radii,streamline_percent_length)
|
|
535
618
|
row.beta1_metal_radii = streamline_percent_length
|
|
536
619
|
row.beta2_metal_radii = streamline_percent_length
|
|
620
|
+
row.deviation = streamline_percent_length * 0
|
|
537
621
|
|
|
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
|
|
622
|
+
row.mprime = interpolate_quantities(row.mprime, src_percent, streamline_percent_length)
|
|
542
623
|
|
|
543
|
-
row.alpha1 =
|
|
544
|
-
row.alpha2 =
|
|
545
|
-
row.beta1 =
|
|
546
|
-
row.beta2 =
|
|
624
|
+
row.alpha1 = safe_interpolate(row.alpha1, src_percent, streamline_percent_length, radians=False)
|
|
625
|
+
row.alpha2 = safe_interpolate(row.alpha2, src_percent, streamline_percent_length, radians=False)
|
|
626
|
+
row.beta1 = safe_interpolate(row.beta1, src_percent, streamline_percent_length, radians=False)
|
|
627
|
+
row.beta2 = safe_interpolate(row.beta2, src_percent, streamline_percent_length, radians=False)
|
|
547
628
|
|
|
548
629
|
# 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,
|
|
630
|
+
row.Vm = interpolate_quantities(row.Vm, src_percent, streamline_percent_length)
|
|
631
|
+
row.Vx = interpolate_quantities(row.Vx, src_percent, streamline_percent_length)
|
|
632
|
+
row.Vt = interpolate_quantities(row.Vt, src_percent, streamline_percent_length)
|
|
633
|
+
row.Vr = interpolate_quantities(row.Vr, src_percent, streamline_percent_length)
|
|
634
|
+
row.V = interpolate_quantities(row.V, src_percent, streamline_percent_length)
|
|
635
|
+
row.V2 = interpolate_quantities(row.V2, src_percent, streamline_percent_length)
|
|
636
|
+
row.M = interpolate_quantities(row.M, src_percent, streamline_percent_length)
|
|
637
|
+
row.M_rel = interpolate_quantities(row.M_rel, src_percent, streamline_percent_length)
|
|
638
|
+
row.U = interpolate_quantities(row.U, src_percent, streamline_percent_length)
|
|
639
|
+
row.W = interpolate_quantities(row.W, src_percent, streamline_percent_length)
|
|
640
|
+
row.Wt = interpolate_quantities(row.Wt, src_percent, streamline_percent_length)
|
|
560
641
|
|
|
561
642
|
# 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.
|
|
643
|
+
row.T0 = interpolate_quantities(row.T0, src_percent, streamline_percent_length)
|
|
644
|
+
row.T0_is = interpolate_quantities(row.T0, src_percent, streamline_percent_length) # For Turbines
|
|
645
|
+
row.P0 = interpolate_quantities(row.P0, src_percent, streamline_percent_length)
|
|
646
|
+
row.P0_is = interpolate_quantities(row.P0, src_percent, streamline_percent_length) # For Compressors
|
|
647
|
+
row.P0_stator_inlet = interpolate_quantities(row.P0_stator_inlet, src_percent, streamline_percent_length)
|
|
566
648
|
|
|
567
649
|
# Relative Quantities
|
|
568
|
-
row.P0R = interpolate_quantities(row.P0R,
|
|
569
|
-
row.
|
|
650
|
+
row.P0R = interpolate_quantities(row.P0R, src_percent, streamline_percent_length)
|
|
651
|
+
row.P0R_is = interpolate_quantities(row.P0, src_percent, streamline_percent_length)
|
|
652
|
+
row.T0R = interpolate_quantities(row.T0R, src_percent, streamline_percent_length)
|
|
570
653
|
|
|
571
654
|
# 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,
|
|
655
|
+
row.P = interpolate_quantities(row.P, src_percent, streamline_percent_length)
|
|
656
|
+
row.T = interpolate_quantities(row.T, src_percent, streamline_percent_length)
|
|
657
|
+
row.T_is = interpolate_quantities(row.T_is, src_percent, streamline_percent_length)
|
|
658
|
+
row.rho = interpolate_quantities(row.rho, src_percent, streamline_percent_length)
|
|
659
|
+
row.entropy_rise = interpolate_quantities(row.entropy_rise, src_percent, streamline_percent_length)
|
|
576
660
|
|
|
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)
|
|
661
|
+
row.percent_hub_shroud = streamline_percent_length
|
|
582
662
|
|
|
583
663
|
return row
|
|
584
664
|
|
|
@@ -615,6 +695,8 @@ def interpolate_quantities(q:npt.NDArray,r:npt.NDArray,r2:npt.NDArray):
|
|
|
615
695
|
q2 = np.zeros(shape=r2.shape)
|
|
616
696
|
return q[0]+q2
|
|
617
697
|
else:
|
|
698
|
+
if len(r) != len(q):
|
|
699
|
+
r = np.linspace(0, 1, len(q))
|
|
618
700
|
return interp1d(r,q,kind='linear')(r2)
|
|
619
701
|
|
|
620
702
|
def compute_gas_constants(row:BladeRow,fluid:Optional[Solution]=None) -> None:
|