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/passage.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import List, Tuple, Union
|
|
1
|
+
from typing import List, Optional, Tuple, Union
|
|
2
2
|
import numpy as np
|
|
3
3
|
import numpy.typing as npt
|
|
4
4
|
from scipy.interpolate import PchipInterpolator, interp1d
|
|
@@ -30,7 +30,7 @@ class Passage:
|
|
|
30
30
|
|
|
31
31
|
def __init__(self,xhub:Union[npt.NDArray,List[float]],rhub:Union[npt.NDArray,List[float]],
|
|
32
32
|
xshroud:Union[npt.NDArray,List[float]],rshroud:Union[npt.NDArray,List[float]],
|
|
33
|
-
passageType:PassageType=PassageType.Axial):
|
|
33
|
+
passageType:PassageType=PassageType.Axial, zero_phi: bool = False):
|
|
34
34
|
"""_summary_
|
|
35
35
|
|
|
36
36
|
Args:
|
|
@@ -42,6 +42,7 @@ class Passage:
|
|
|
42
42
|
"""
|
|
43
43
|
assert len(xhub) == len(xshroud), "xHub and xShroud should be the same length"
|
|
44
44
|
assert len(rhub) == len(rshroud), "rHub and rShroud should be the same length"
|
|
45
|
+
self.zero_phi = zero_phi
|
|
45
46
|
|
|
46
47
|
hub_arc_len = xr_to_mprime(np.vstack([xhub,rhub]).transpose())[1]
|
|
47
48
|
self.hub_arc_len = hub_arc_len[-1]
|
|
@@ -81,15 +82,14 @@ class Passage:
|
|
|
81
82
|
r_streamline = t_streamline.copy()*0
|
|
82
83
|
x_streamline = t_streamline.copy()*0
|
|
83
84
|
for i,t in enumerate(t_streamline):
|
|
84
|
-
xhub = self.xhub(t)
|
|
85
|
-
rhub = self.rhub(t)
|
|
86
|
-
xshroud = self.xshroud(t)
|
|
87
|
-
rshroud = self.rshroud(t)
|
|
88
|
-
x_streamline[i] ,r_streamline[i] = line2D(
|
|
85
|
+
xhub = float(self.xhub(t))
|
|
86
|
+
rhub = float(self.rhub(t))
|
|
87
|
+
xshroud = float(self.xshroud(t))
|
|
88
|
+
rshroud = float(self.rshroud(t))
|
|
89
|
+
x_streamline[i] ,r_streamline[i] = line2D((xhub,rhub),(xshroud,rshroud)).get_point(t_radial)
|
|
89
90
|
return t_streamline,x_streamline,r_streamline
|
|
90
91
|
|
|
91
|
-
|
|
92
|
-
def streamline_curvature(x_streamline:npt.NDArray,r_streamline:npt.NDArray) -> Tuple[npt.NDArray,npt.NDArray,npt.NDArray]:
|
|
92
|
+
def streamline_curvature(self, x_streamline:npt.NDArray,r_streamline:npt.NDArray) -> Tuple[npt.NDArray,npt.NDArray,npt.NDArray]:
|
|
93
93
|
"""Hub and casing values of streamline angles of inclination and curvature
|
|
94
94
|
|
|
95
95
|
x_streamline[axial,radial]
|
|
@@ -113,14 +113,16 @@ class Passage:
|
|
|
113
113
|
phi = np.zeros(shape=x_streamline.shape)
|
|
114
114
|
r = np.zeros(shape=x_streamline.shape)
|
|
115
115
|
radius_curvature = np.zeros(shape=x_streamline.shape)
|
|
116
|
+
if self.zero_phi:
|
|
117
|
+
return phi, radius_curvature, r_streamline
|
|
116
118
|
# Have to make sure there isn't a divide by zero which could happen if there is a vertical line somewhere
|
|
117
119
|
indices = np.where(np.abs(np.diff(x_streamline))>np.finfo(float).eps)[0]
|
|
118
120
|
|
|
119
121
|
d_dx = FinDiff(0,x_streamline[indices[0]:indices[-1]],1)
|
|
120
122
|
d2_dx2 = FinDiff(0,x_streamline[indices[0]:indices[-1]],2)
|
|
121
123
|
|
|
122
|
-
dr_dx = d_dx(r_streamline[indices[0]:indices[-1]])
|
|
123
|
-
d2r_dx2 = d2_dx2(r_streamline[indices[0]:indices[-1]])
|
|
124
|
+
dr_dx = d_dx(r_streamline[indices[0]:indices[-1]]) # type: ignore
|
|
125
|
+
d2r_dx2 = d2_dx2(r_streamline[indices[0]:indices[-1]]) # type: ignore
|
|
124
126
|
|
|
125
127
|
radius_curvature[indices[0]:indices[-1]] = np.power((1+np.power(dr_dx,2)),1.5)
|
|
126
128
|
radius_curvature[indices[0]:indices[-1]] = np.divide(radius_curvature[indices[0]:indices[-1]], np.abs(d2r_dx2))
|
|
@@ -170,7 +172,7 @@ class Passage:
|
|
|
170
172
|
total_area += area
|
|
171
173
|
return total_area
|
|
172
174
|
|
|
173
|
-
def get_cutting_line(self, t_hub:float) -> Tuple[line2D,float,float]:
|
|
175
|
+
def get_cutting_line(self, t_hub:float,t_shroud:Optional[float]=None) -> Tuple[line2D,float,float]:
|
|
174
176
|
"""Gets the cutting line perpendicular to hub and shroud
|
|
175
177
|
|
|
176
178
|
Args:
|
|
@@ -181,65 +183,90 @@ class Passage:
|
|
|
181
183
|
|
|
182
184
|
cut (line2D): line from hub to shroud
|
|
183
185
|
t_hub (float): Percentage along hub arc length
|
|
184
|
-
t_shroud (float): t corresponding to intersection of bisector of hub
|
|
186
|
+
t_shroud (Optional[float]): t corresponding to intersection of bisector of hub. Defaults to None
|
|
185
187
|
|
|
186
188
|
"""
|
|
187
|
-
xhub = self.xhub(t_hub)
|
|
188
|
-
rhub = self.rhub(t_hub)
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
if self.passageType == PassageType.Centrifugal:
|
|
201
|
-
if np.abs(dr)>1e-6:
|
|
202
|
-
# Draw a line perpendicular to the hub.
|
|
203
|
-
# Find the intersection point to the shroud.
|
|
204
|
-
h = -dx/dr # Slope of perpendicular line
|
|
189
|
+
xhub = float(self.xhub(t_hub))
|
|
190
|
+
rhub = float(self.rhub(t_hub))
|
|
191
|
+
if t_shroud is None:
|
|
192
|
+
if t_hub>0 and t_hub<1:
|
|
193
|
+
dx = self.xhub(t_hub+0.0001) - self.xhub(t_hub-0.0001)
|
|
194
|
+
dr = self.rhub(t_hub+0.0001) - self.rhub(t_hub-0.0001)
|
|
195
|
+
elif t_hub>0:
|
|
196
|
+
dx = self.xhub(t_hub) - self.xhub(t_hub-0.0001)
|
|
197
|
+
dr = self.rhub(t_hub) - self.rhub(t_hub-0.0001)
|
|
198
|
+
else: # t_hub<1:
|
|
199
|
+
dx = self.xhub(t_hub+0.0001) - self.xhub(t_hub)
|
|
200
|
+
dr = self.rhub(t_hub+0.0001) - self.rhub(t_hub)
|
|
205
201
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
202
|
+
if self.passageType == PassageType.Centrifugal:
|
|
203
|
+
if np.abs(dr)>1e-6:
|
|
204
|
+
# Draw a line perpendicular to the hub.
|
|
205
|
+
# Find the intersection point to the shroud.
|
|
206
|
+
h = -dx/dr # Slope of perpendicular line
|
|
207
|
+
|
|
208
|
+
f = lambda t: h*(self.xshroud(t) - xhub)+rhub # line from hub to shroud
|
|
209
|
+
fun = lambda t: np.abs(f(t)-self.rshroud(t)) # find where it intersects
|
|
210
|
+
res = minimize_scalar(fun,bounds=[0,1],tol=1E-3)
|
|
211
|
+
t_shroud = res.x # type: ignore
|
|
212
|
+
else:
|
|
213
|
+
t_shroud = t_hub # Vertical line
|
|
210
214
|
else:
|
|
211
|
-
t_shroud = t_hub
|
|
212
|
-
|
|
213
|
-
|
|
215
|
+
t_shroud = t_hub
|
|
216
|
+
|
|
217
|
+
xshroud = float(self.xshroud(t_shroud))
|
|
218
|
+
rshroud = float(self.rshroud(t_shroud))
|
|
214
219
|
|
|
215
|
-
xshroud
|
|
216
|
-
rshroud = self.rshroud(t_shroud)
|
|
217
|
-
return line2D([xhub,rhub],[xshroud,rshroud]), t_hub, t_shroud
|
|
220
|
+
return line2D((xhub,rhub),(xshroud,rshroud)), t_hub, t_shroud # type: ignore
|
|
218
221
|
|
|
219
|
-
def get_xr_slice(self,t_span:float,percent_hub:Tuple[float,float],
|
|
220
|
-
|
|
221
|
-
|
|
222
|
+
def get_xr_slice(self, t_span: float, percent_hub: Tuple[float, float],
|
|
223
|
+
percent_shroud: Optional[Tuple[float, float]] = None, resolution: int = 100) -> npt.NDArray[np.float64]:
|
|
224
|
+
"""
|
|
225
|
+
Return the (x, r) coordinates of a *straight* streamline segment that
|
|
226
|
+
connects corresponding hub and shroud points, sampled uniformly along
|
|
227
|
+
each surface between the given percent limits.
|
|
228
|
+
|
|
229
|
+
The point returned on each connecting line is at parametric position
|
|
230
|
+
`t_span` in [0, 1], where 0 = hub point and 1 = shroud point.
|
|
231
|
+
|
|
222
232
|
Args:
|
|
223
|
-
t_span (
|
|
224
|
-
|
|
225
|
-
|
|
233
|
+
t_span: Interpolation parameter along each hub→shroud connector (0..1).
|
|
234
|
+
percent_hub: (start, end) fractional arc-length positions along the hub (0..1).
|
|
235
|
+
percent_shroud: Optional (start, end) along the shroud (0..1). If None,
|
|
236
|
+
the shroud uses the same normalized range as `percent_hub`.
|
|
237
|
+
resolution: Number of sample points along the streamwise direction.
|
|
226
238
|
|
|
227
239
|
Returns:
|
|
228
|
-
|
|
240
|
+
(resolution, 2) array of [x, r] coordinates.
|
|
229
241
|
"""
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
242
|
+
# ---- validation
|
|
243
|
+
if not (0.0 <= t_span <= 1.0):
|
|
244
|
+
raise ValueError("t_span must be in [0, 1].")
|
|
245
|
+
if resolution < 2:
|
|
246
|
+
raise ValueError("resolution must be >= 2.")
|
|
247
|
+
if not (0.0 <= percent_hub[0] <= 1.0 and 0.0 <= percent_hub[1] <= 1.0):
|
|
248
|
+
raise ValueError("percent_hub values must be in [0, 1].")
|
|
249
|
+
if percent_shroud is not None and not (
|
|
250
|
+
0.0 <= percent_shroud[0] <= 1.0 and 0.0 <= percent_shroud[1] <= 1.0
|
|
251
|
+
):
|
|
252
|
+
raise ValueError("percent_shroud values must be in [0, 1].")
|
|
253
|
+
|
|
254
|
+
# ---- parameterize along hub and shroud (use each surface's own length!)
|
|
255
|
+
t_hub = np.linspace(percent_hub[0], percent_hub[1], resolution) * self.hub_length
|
|
256
|
+
if percent_shroud is None:
|
|
257
|
+
t_shroud = np.linspace(percent_hub[0], percent_hub[1], resolution) * self.shroud_length
|
|
258
|
+
else:
|
|
259
|
+
t_shroud = np.linspace(percent_shroud[0], percent_shroud[1], resolution) * self.shroud_length
|
|
260
|
+
|
|
261
|
+
# ---- sample hub & shroud curves (x, r)
|
|
262
|
+
hub_pts = np.column_stack([self.xhub(t_hub), self.rhub(t_hub)]) # (N, 2)
|
|
263
|
+
shroud_pts = np.column_stack([self.xshroud(t_shroud), self.rshroud(t_shroud)]) # (N, 2)
|
|
264
|
+
|
|
265
|
+
# ---- vectorized interpolation along each connector: hub + t*(shroud - hub)
|
|
266
|
+
xr = hub_pts + (shroud_pts - hub_pts) * float(t_span) # (N, 2)
|
|
267
|
+
|
|
268
|
+
return xr.astype(np.float64, copy=False)
|
|
269
|
+
|
|
243
270
|
|
|
244
271
|
def get_m(self,t_span:float,resolution:int=100) -> npt.NDArray:
|
|
245
272
|
"""Meridional cooridnates
|
|
@@ -251,7 +278,7 @@ class Passage:
|
|
|
251
278
|
Returns:
|
|
252
279
|
npt.NDArray: _description_
|
|
253
280
|
"""
|
|
254
|
-
xr = self.get_xr_slice(t_span,(0,1),resolution)
|
|
281
|
+
xr = self.get_xr_slice(t_span=t_span,percent_hub=(0,1),resolution=resolution)
|
|
255
282
|
dx = np.diff(xr[:,0])
|
|
256
283
|
dr = np.diff(xr[:,1])
|
|
257
284
|
m = np.concat([[0],np.cumsum(np.sqrt(dx**2 + dr**2))])
|
|
@@ -269,7 +296,7 @@ class Passage:
|
|
|
269
296
|
(float) : returns the derivative
|
|
270
297
|
"""
|
|
271
298
|
m = self.get_m(t_span,resolution)
|
|
272
|
-
return PchipInterpolator(np.linspace(0,1,resolution),np.diff(m))(location)
|
|
299
|
+
return PchipInterpolator(np.linspace(0,1,resolution),np.diff(m))(location) # type: ignore
|
|
273
300
|
|
|
274
301
|
@property
|
|
275
302
|
def hub_length(self):
|
|
@@ -279,6 +306,14 @@ class Passage:
|
|
|
279
306
|
"""
|
|
280
307
|
return np.sum(np.sqrt(np.diff(self.xhub_pts)**2 + np.diff(self.rhub_pts)**2))
|
|
281
308
|
|
|
309
|
+
@property
|
|
310
|
+
def shroud_length(self):
|
|
311
|
+
"""returns the computed length of the shroud
|
|
312
|
+
Returns:
|
|
313
|
+
_type_: _description_
|
|
314
|
+
"""
|
|
315
|
+
return np.sum(np.sqrt(np.diff(self.xshroud_pts)**2 + np.diff(self.rshroud_pts)**2))
|
|
316
|
+
|
|
282
317
|
def plot_cuts(self,percent_axial:List[float]=[]):
|
|
283
318
|
"""_summary_
|
|
284
319
|
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""Convenience helpers for constructing BladeRow objects with minimal boilerplate."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Optional, Sequence, Any
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
from .bladerow import BladeRow
|
|
10
|
+
from .enums import RowType
|
|
11
|
+
|
|
12
|
+
__all__ = ["make_blade_row", "make_rotor_row", "make_stator_row"]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def make_blade_row(
|
|
16
|
+
*args: Any,
|
|
17
|
+
row_type: Optional[RowType] = None,
|
|
18
|
+
hub_location: Optional[float] = None,
|
|
19
|
+
shroud_location: Optional[float] = None,
|
|
20
|
+
stage_id: int = 0,
|
|
21
|
+
**attrs: Any,
|
|
22
|
+
) -> BladeRow:
|
|
23
|
+
"""Generic BladeRow factory that supports legacy calling styles.
|
|
24
|
+
|
|
25
|
+
Args mirror the original BladeRow constructor; any extra keyword arguments
|
|
26
|
+
are set as attributes on the created row for convenience.
|
|
27
|
+
"""
|
|
28
|
+
# Allow positional legacy (hub_location, row_type, stage_id, shroud_location)
|
|
29
|
+
pos = list(args)
|
|
30
|
+
# Legacy positional usage: (RowType, power=...) or (hub_loc, RowType, ...)
|
|
31
|
+
if pos and isinstance(pos[0], RowType):
|
|
32
|
+
row_type = pos.pop(0)
|
|
33
|
+
if hub_location is None and pos:
|
|
34
|
+
hub_location = pos.pop(0)
|
|
35
|
+
if row_type is None and pos and isinstance(pos[0], RowType):
|
|
36
|
+
row_type = pos.pop(0)
|
|
37
|
+
if shroud_location is None and pos:
|
|
38
|
+
shroud_location = pos.pop(0)
|
|
39
|
+
|
|
40
|
+
hub_location = 0.0 if hub_location is None else float(hub_location)
|
|
41
|
+
row_type = row_type if row_type is not None else RowType.Stator
|
|
42
|
+
|
|
43
|
+
row = BladeRow(hub_location=hub_location, row_type=row_type, stage_id=stage_id, shroud_location=shroud_location)
|
|
44
|
+
|
|
45
|
+
# Apply extra attributes (e.g., loss_function, beta2_metal, etc.)
|
|
46
|
+
for key, val in attrs.items():
|
|
47
|
+
setattr(row, key, val)
|
|
48
|
+
return row
|
|
49
|
+
|
|
50
|
+
def _maybe_set_pitch(row: BladeRow, pitch_to_chord: Optional[float], solidity: Optional[float]) -> None:
|
|
51
|
+
"""Apply pitch/solidity inputs to a blade row if provided."""
|
|
52
|
+
if pitch_to_chord is not None:
|
|
53
|
+
row.pitch_to_chord = pitch_to_chord
|
|
54
|
+
elif solidity is not None and solidity != 0:
|
|
55
|
+
row.pitch_to_chord = 1.0 / solidity
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def make_rotor_row(
|
|
59
|
+
hub_location: float,
|
|
60
|
+
metal_exit_angle_deg: Optional[float | Sequence[float]] = None,
|
|
61
|
+
loss_function: Optional[object] = None,
|
|
62
|
+
P0_ratio: float = 1.0,
|
|
63
|
+
pitch_to_chord: Optional[float] = None,
|
|
64
|
+
solidity: Optional[float] = None,
|
|
65
|
+
num_blades: Optional[int] = None,
|
|
66
|
+
axial_chord: Optional[float] = None,
|
|
67
|
+
) -> BladeRow:
|
|
68
|
+
"""Create a Rotor blade row with common inputs.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
hub_location: Streamwise position (0–1) for the row.
|
|
72
|
+
beta2_metal_deg: Exit metal angle(s) in degrees.
|
|
73
|
+
loss_function: Loss model to attach.
|
|
74
|
+
P0_ratio: Total-pressure ratio target across the row.
|
|
75
|
+
pitch_to_chord: Pitch-to-chord ratio (alternative to solidity).
|
|
76
|
+
solidity: Solidity (chord/pitch) if preferred over pitch_to_chord.
|
|
77
|
+
num_blades: Number of blades (used to derive pitch).
|
|
78
|
+
axial_chord: Axial chord length if known.
|
|
79
|
+
"""
|
|
80
|
+
row = BladeRow(hub_location=hub_location, row_type=RowType.Rotor)
|
|
81
|
+
row.P0_ratio = P0_ratio
|
|
82
|
+
row.P0_ratio_target = P0_ratio
|
|
83
|
+
_maybe_set_pitch(row, pitch_to_chord, solidity)
|
|
84
|
+
if num_blades is not None:
|
|
85
|
+
row.num_blades = num_blades
|
|
86
|
+
if axial_chord is not None:
|
|
87
|
+
row.axial_chord = axial_chord
|
|
88
|
+
if metal_exit_angle_deg is not None:
|
|
89
|
+
row.metal_exit_angle = np.atleast_1d(metal_exit_angle_deg)
|
|
90
|
+
if loss_function is not None:
|
|
91
|
+
row.loss_function = loss_function
|
|
92
|
+
return row
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def make_stator_row(
|
|
96
|
+
hub_location: float,
|
|
97
|
+
metal_exit_angle_deg: Optional[float | Sequence[float]] = None,
|
|
98
|
+
loss_function: Optional[object] = None,
|
|
99
|
+
P0_ratio: float = 1.0,
|
|
100
|
+
pitch_to_chord: Optional[float] = None,
|
|
101
|
+
solidity: Optional[float] = None,
|
|
102
|
+
num_blades: Optional[int] = None,
|
|
103
|
+
axial_chord: Optional[float] = None,
|
|
104
|
+
) -> BladeRow:
|
|
105
|
+
"""Create a Stator/IGV blade row with common inputs.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
hub_location: Streamwise position (0–1) for the row.
|
|
109
|
+
alpha2_metal_deg: Exit metal angle(s) in degrees.
|
|
110
|
+
loss_function: Loss model to attach.
|
|
111
|
+
P0_ratio: Total-pressure ratio target across the row.
|
|
112
|
+
pitch_to_chord: Pitch-to-chord ratio (alternative to solidity).
|
|
113
|
+
solidity: Solidity (chord/pitch) if preferred over pitch_to_chord.
|
|
114
|
+
num_blades: Number of blades (used to derive pitch).
|
|
115
|
+
axial_chord: Axial chord length if known.
|
|
116
|
+
"""
|
|
117
|
+
row = BladeRow(hub_location=hub_location, row_type=RowType.Stator)
|
|
118
|
+
row.P0_ratio = P0_ratio
|
|
119
|
+
row.P0_ratio_target = P0_ratio
|
|
120
|
+
_maybe_set_pitch(row, pitch_to_chord, solidity)
|
|
121
|
+
if num_blades is not None:
|
|
122
|
+
row.num_blades = num_blades
|
|
123
|
+
if axial_chord is not None:
|
|
124
|
+
row.axial_chord = axial_chord
|
|
125
|
+
if metal_exit_angle_deg is not None:
|
|
126
|
+
row.metal_exit_angle = np.atleast_1d(metal_exit_angle_deg)
|
|
127
|
+
if loss_function is not None:
|
|
128
|
+
row.loss_function = loss_function
|
|
129
|
+
return row
|
turbodesign/solve_radeq.py
CHANGED
|
@@ -1,31 +1,30 @@
|
|
|
1
1
|
from typing import List, Tuple
|
|
2
2
|
from .radeq import radeq
|
|
3
|
-
from .enums import LossType, RowType, PowerType
|
|
3
|
+
from .enums import LossType, RowType, PowerType
|
|
4
4
|
from .bladerow import BladeRow
|
|
5
|
-
from .
|
|
6
|
-
from .
|
|
5
|
+
from .turbine_math import compute_gas_constants
|
|
6
|
+
from .turbine_math import compute_quantities, compute_power
|
|
7
|
+
from .flow_math import compute_massflow
|
|
7
8
|
import numpy.typing as npt
|
|
8
9
|
import numpy as np
|
|
9
10
|
from scipy.interpolate import interp1d
|
|
10
11
|
from scipy.optimize import minimize_scalar
|
|
11
12
|
from .passage import Passage
|
|
12
13
|
|
|
13
|
-
def adjust_streamlines(blade_rows:List[BladeRow],passage:Passage):
|
|
14
|
+
def adjust_streamlines(blade_rows:List[BladeRow],passage:Passage,massflow_fraction:np.ndarray):
|
|
14
15
|
"""Adjust the streamlines to evenly divide the massflow
|
|
15
16
|
|
|
16
17
|
Args:
|
|
17
18
|
blade_rows (List[BladeRow]): List of blade rows
|
|
18
19
|
passage (Passage): passage object describing the hub and shroud
|
|
19
|
-
|
|
20
|
+
massflow_fraction (np.ndarray): array from 0 to 1 of how the massflow should be distributed from hub to shroud
|
|
20
21
|
"""
|
|
21
22
|
for row_index,row in enumerate(blade_rows):
|
|
22
23
|
print(f"Adjusting Streamlines to balance massflow Row: {row_index}")
|
|
23
|
-
massflow_fraction = np.linspace(0,1,len(row.percent_hub_shroud))
|
|
24
24
|
row.total_massflow = row.massflow[-1]
|
|
25
|
-
ideal_massflow_fraction = row.massflow[-1] * massflow_fraction
|
|
26
25
|
|
|
27
|
-
new_percent_streamline = interp1d(row.massflow,row.percent_hub_shroud)(
|
|
28
|
-
row.percent_hub_shroud[1:-1] = new_percent_streamline
|
|
26
|
+
new_percent_streamline = interp1d(row.massflow,row.percent_hub_shroud)(massflow_fraction[1:-1])
|
|
27
|
+
row.percent_hub_shroud[1:-1] = new_percent_streamline
|
|
29
28
|
|
|
30
29
|
cut_line, thub,_ = passage.get_cutting_line(row.percent_hub)
|
|
31
30
|
row.x,row.r = cut_line.get_point(row.percent_hub_shroud)
|
|
@@ -36,4 +35,4 @@ def adjust_streamlines(blade_rows:List[BladeRow],passage:Passage):
|
|
|
36
35
|
row.phi[i] = float(interp1d(t_streamline,phi)(row.percent_hub))
|
|
37
36
|
row.rm[i] = float(interp1d(t_streamline,rm)(row.percent_hub))
|
|
38
37
|
row.r[i] = float(interp1d(t_streamline,r)(row.percent_hub))
|
|
39
|
-
row.x[i] = float(interp1d(t_streamline,x_streamline)(row.percent_hub))
|
|
38
|
+
row.x[i] = float(interp1d(t_streamline,x_streamline)(row.percent_hub))
|