turbo-design 1.1.4__tar.gz → 1.3.0__tar.gz
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.1.4 → turbo_design-1.3.0}/PKG-INFO +3 -2
- {turbo_design-1.1.4 → turbo_design-1.3.0}/pyproject.toml +2 -2
- {turbo_design-1.1.4 → turbo_design-1.3.0}/turbodesign/bladerow.py +28 -24
- {turbo_design-1.1.4 → turbo_design-1.3.0}/turbodesign/coolant.py +2 -2
- {turbo_design-1.1.4 → turbo_design-1.3.0}/turbodesign/inlet.py +50 -22
- {turbo_design-1.1.4 → turbo_design-1.3.0}/turbodesign/outlet.py +2 -1
- {turbo_design-1.1.4 → turbo_design-1.3.0}/turbodesign/passage.py +62 -6
- {turbo_design-1.1.4 → turbo_design-1.3.0}/turbodesign/radeq.py +36 -7
- {turbo_design-1.1.4 → turbo_design-1.3.0}/turbodesign/solve_radeq.py +7 -6
- {turbo_design-1.1.4 → turbo_design-1.3.0}/turbodesign/spool.py +17 -15
- {turbo_design-1.1.4 → turbo_design-1.3.0}/turbodesign/td_math.py +29 -26
- {turbo_design-1.1.4 → turbo_design-1.3.0}/turbodesign/turbinespool.py +122 -128
- {turbo_design-1.1.4 → turbo_design-1.3.0}/turbodesign/__init__.py +0 -0
- {turbo_design-1.1.4 → turbo_design-1.3.0}/turbodesign/arrayfuncs.py +0 -0
- {turbo_design-1.1.4 → turbo_design-1.3.0}/turbodesign/cantera_gas/co2.yaml +0 -0
- {turbo_design-1.1.4 → turbo_design-1.3.0}/turbodesign/compressorspool.py +0 -0
- {turbo_design-1.1.4 → turbo_design-1.3.0}/turbodesign/enums.py +0 -0
- {turbo_design-1.1.4 → turbo_design-1.3.0}/turbodesign/isentropic.py +0 -0
- {turbo_design-1.1.4 → turbo_design-1.3.0}/turbodesign/loss/__init__.py +0 -0
- {turbo_design-1.1.4 → turbo_design-1.3.0}/turbodesign/loss/compressor/__init__.py +0 -0
- {turbo_design-1.1.4 → turbo_design-1.3.0}/turbodesign/loss/losstype.py +0 -0
- {turbo_design-1.1.4 → turbo_design-1.3.0}/turbodesign/loss/turbine/TD2.py +0 -0
- {turbo_design-1.1.4 → turbo_design-1.3.0}/turbodesign/loss/turbine/__init__.py +0 -0
- {turbo_design-1.1.4 → turbo_design-1.3.0}/turbodesign/loss/turbine/ainleymathieson.py +0 -0
- {turbo_design-1.1.4 → turbo_design-1.3.0}/turbodesign/loss/turbine/craigcox.py +0 -0
- {turbo_design-1.1.4 → turbo_design-1.3.0}/turbodesign/loss/turbine/fixedefficiency.py +0 -0
- {turbo_design-1.1.4 → turbo_design-1.3.0}/turbodesign/loss/turbine/fixedpressureloss.py +0 -0
- {turbo_design-1.1.4 → turbo_design-1.3.0}/turbodesign/loss/turbine/kackerokapuu.py +0 -0
- {turbo_design-1.1.4 → turbo_design-1.3.0}/turbodesign/loss/turbine/traupel.py +0 -0
- {turbo_design-1.1.4 → turbo_design-1.3.0}/turbodesign/lossinterp.py +0 -0
- {turbo_design-1.1.4 → turbo_design-1.3.0}/turbodesign/rotor.py +0 -0
- {turbo_design-1.1.4 → turbo_design-1.3.0}/turbodesign/stage.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
2
|
Name: turbo-design
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: TurboDesign is a library used to design turbines and compressors using radial equilibrium.
|
|
5
5
|
Author: Paht Juangphanich
|
|
6
6
|
Author-email: paht.juangphanich@nasa.gov
|
|
@@ -10,6 +10,7 @@ Classifier: Programming Language :: Python :: 3.9
|
|
|
10
10
|
Classifier: Programming Language :: Python :: 3.10
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.11
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
13
14
|
Requires-Dist: cantera
|
|
14
15
|
Requires-Dist: findiff
|
|
15
16
|
Requires-Dist: matplotlib
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "turbo-design"
|
|
3
|
-
version = "1.
|
|
3
|
+
version = "1.3.0"
|
|
4
4
|
description = "TurboDesign is a library used to design turbines and compressors using radial equilibrium."
|
|
5
5
|
authors = ["Paht Juangphanich <paht.juangphanich@nasa.gov>"]
|
|
6
6
|
packages = [
|
|
@@ -18,7 +18,7 @@ matplotlib = "*"
|
|
|
18
18
|
pandas = "*"
|
|
19
19
|
python = ">=3.9"
|
|
20
20
|
|
|
21
|
-
[
|
|
21
|
+
[poetry.group.dev.dependencies]
|
|
22
22
|
python = ">=3.9"
|
|
23
23
|
|
|
24
24
|
[build-system]
|
|
@@ -26,7 +26,6 @@ class BladeRow:
|
|
|
26
26
|
Cp: float = 1019 # Cp J/(Kg*K)
|
|
27
27
|
Cv: float = 1019/1.14 # Cv J/(Kg*K)
|
|
28
28
|
_coolant:Coolant = None # type: ignore # Coolant Fluid
|
|
29
|
-
fluid: composite.Solution = Solution("air.yaml")
|
|
30
29
|
mu:float = 0
|
|
31
30
|
|
|
32
31
|
total_massflow:float = 0 # Massflow spool + all upstream cooling flow [kg/s]
|
|
@@ -35,10 +34,11 @@ class BladeRow:
|
|
|
35
34
|
# ----------------------------------
|
|
36
35
|
|
|
37
36
|
# Streamline Properties
|
|
38
|
-
|
|
37
|
+
percent_hub:float = 0 # Where blade row is defined along the hub.
|
|
39
38
|
percent_hub_shroud: npt.NDArray = field(default_factory=lambda: np.array([0])) # Percent streamline length from hub to shroud.
|
|
40
39
|
x: npt.NDArray = field(default_factory=lambda: np.array([0])) # x - coordinates (useful for computing axial chord)
|
|
41
40
|
r: npt.NDArray = field(default_factory=lambda: np.array([0])) # Radius - coordinates
|
|
41
|
+
m: npt.NDArray = field(default_factory=lambda: np.array([0])) # meridional
|
|
42
42
|
area:float = 0
|
|
43
43
|
# Calculated massflow is the massflow computed after radial eq solver
|
|
44
44
|
calculated_massflow: float = 0
|
|
@@ -121,6 +121,7 @@ class BladeRow:
|
|
|
121
121
|
_tip_clearance:float = 0 # Clearance as a percentage of span or blade height
|
|
122
122
|
|
|
123
123
|
_inlet_to_outlet_pratio = [0.06,0.95]
|
|
124
|
+
location:float = 0 # Percent along hub where bladerow is defined
|
|
124
125
|
|
|
125
126
|
@property
|
|
126
127
|
def inlet_to_outlet_pratio(self) -> Tuple[float,float]:
|
|
@@ -328,18 +329,18 @@ class BladeRow:
|
|
|
328
329
|
"""
|
|
329
330
|
self._tip_clearance = val
|
|
330
331
|
|
|
331
|
-
def __init__(self,
|
|
332
|
+
def __init__(self,location:float,row_type:RowType=RowType.Stator,stage_id:int = 0):
|
|
332
333
|
"""Initializes the blade row to be a particular type
|
|
333
334
|
|
|
334
335
|
Args:
|
|
335
|
-
|
|
336
|
+
location (float): Location of the blade row as a percentage of hub length
|
|
336
337
|
row_type (RowType): Specifies the Type. Defaults to RowType.Stator
|
|
337
338
|
power (float, optional): power . Defaults to 0.
|
|
338
339
|
P0_P (float, optional): Total to Static Pressure Ratio
|
|
339
340
|
stage_id (int, optional): ID of the stage so if you have 9 stages, the id could be 9. It's used to separate the stages. Each stage will have it's own unique degree of reaction
|
|
340
341
|
"""
|
|
341
342
|
self.row_type = row_type
|
|
342
|
-
self.
|
|
343
|
+
self.location = location
|
|
343
344
|
self.Yp = 0 # Loss
|
|
344
345
|
self.stage_id = stage_id
|
|
345
346
|
|
|
@@ -437,7 +438,7 @@ class BladeRow:
|
|
|
437
438
|
self._te_s = val
|
|
438
439
|
|
|
439
440
|
def __repr__(self):
|
|
440
|
-
return f"{self.row_type.name
|
|
441
|
+
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}"
|
|
441
442
|
|
|
442
443
|
def to_dict(self):
|
|
443
444
|
|
|
@@ -512,7 +513,7 @@ def interpolate_streamline_radii(row:BladeRow,passage:Passage,num_streamlines:in
|
|
|
512
513
|
Returns:
|
|
513
514
|
(BladeRow): new row object with quantities interpolated
|
|
514
515
|
"""
|
|
515
|
-
row.cutting_line,_,_ = passage.get_cutting_line(row.
|
|
516
|
+
row.cutting_line,_,_ = passage.get_cutting_line(row.location)
|
|
516
517
|
row.x,row.r = row.cutting_line.get_point(np.linspace(0,1,num_streamlines))
|
|
517
518
|
streamline_percent_length = np.sqrt((row.r-row.r[0])**2+(row.x-row.x[0])**2)/row.cutting_line.length
|
|
518
519
|
|
|
@@ -609,21 +610,24 @@ def interpolate_quantities(q:npt.NDArray,r:npt.NDArray,r2:npt.NDArray):
|
|
|
609
610
|
else:
|
|
610
611
|
return interp1d(r,q,kind='linear')(r2)
|
|
611
612
|
|
|
612
|
-
def compute_gas_constants(row:BladeRow):
|
|
613
|
-
"""
|
|
614
|
-
|
|
613
|
+
def compute_gas_constants(row:BladeRow,fluid:Solution=None) -> None:
|
|
614
|
+
"""Updates the Cp, Gamma, and density for a blade row. If fluid is not specified then only density and viscosity is updated.
|
|
615
|
+
|
|
616
|
+
Args:
|
|
617
|
+
row (BladeRow): _description_
|
|
618
|
+
fluid (Solution, optional): _description_. Defaults to None.
|
|
619
|
+
|
|
620
|
+
Returns:
|
|
621
|
+
(BladeRow): updated row
|
|
615
622
|
"""
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
# row.rho[i] = P/(T*row.R)
|
|
628
|
-
# i+=1
|
|
629
|
-
return row
|
|
623
|
+
if fluid:
|
|
624
|
+
Tm = row.T.mean()
|
|
625
|
+
Pm = row.P.mean()
|
|
626
|
+
fluid.TP = Tm,Pm
|
|
627
|
+
row.Cp = fluid.cp
|
|
628
|
+
row.Cv = fluid.cv
|
|
629
|
+
row.R = row.Cp-row.Cv
|
|
630
|
+
row.gamma = row.Cp/row.Cv
|
|
631
|
+
# Use Ideal Gas
|
|
632
|
+
row.rho = row.P/(row.T*row.R)
|
|
633
|
+
row.mu = sutherland(row.T) # type: ignore
|
|
@@ -3,8 +3,8 @@ from cantera import Solution
|
|
|
3
3
|
|
|
4
4
|
@dataclass
|
|
5
5
|
class Coolant:
|
|
6
|
-
fluid:Solution = field(default = Solution('air.yaml')) # cantera solution
|
|
7
6
|
T0:float = field(default=900) # Kelvin
|
|
8
7
|
P0:float = field(default=50*101325) # Pascal
|
|
9
|
-
massflow_percentage:float = field(default=0.03) # Fraction of total massflow going through compressor
|
|
8
|
+
massflow_percentage:float = field(default=0.03) # Fraction of total massflow going through compressor
|
|
9
|
+
Cp:float = field(default=1000) # J/K
|
|
10
10
|
|
|
@@ -16,57 +16,85 @@ class Inlet(BladeRow):
|
|
|
16
16
|
(BladeRow): Defines the properties of the blade row
|
|
17
17
|
"""
|
|
18
18
|
fun: interp1d
|
|
19
|
-
|
|
19
|
+
|
|
20
|
+
def __init__(self,M:float,T0:Union[float,List[float]],
|
|
21
|
+
P0:Union[float,List[float]],
|
|
22
|
+
location:float=0,
|
|
23
|
+
beta:Union[float,List[float]]=[0],
|
|
24
|
+
percent_radii:Union[float,List[float]]=[0.5]):
|
|
20
25
|
"""Initializes the inlet station.
|
|
21
26
|
Uses the beta and exit mach number to predict a value for Vm
|
|
22
27
|
|
|
23
28
|
Args:
|
|
24
29
|
M (float): Mach number at the inlet plane
|
|
25
|
-
beta (Union[float,List[float]]): exit relative flow angle
|
|
26
30
|
T0 (Union[float,List[float]]): Total Temperature Array
|
|
27
31
|
P0 (Union[float,List[float]]): Total Pressure Array
|
|
28
32
|
percent_radii (Union[float,List[float]]): Radius where total pressure and temperature are defined
|
|
29
|
-
|
|
30
|
-
axial_location (float): Axial Location as a percentage of hub length
|
|
33
|
+
location (float): Location as a percentage of hub length
|
|
31
34
|
beta (Union[float,List[float]], optional): Inlet flow angle in relative direction. Defaults to [].
|
|
35
|
+
|
|
32
36
|
"""
|
|
33
|
-
super().__init__(row_type=RowType.Inlet,
|
|
34
|
-
self.loss_function = None
|
|
37
|
+
super().__init__(row_type=RowType.Inlet,location=location,stage_id=-1)
|
|
35
38
|
self.beta1 = convert_to_ndarray(beta)
|
|
36
39
|
self.M = convert_to_ndarray(M)
|
|
37
40
|
self.T0 = convert_to_ndarray(T0)
|
|
38
41
|
self.P0 = convert_to_ndarray(P0)
|
|
39
42
|
self.percent_hub_shroud = convert_to_ndarray(percent_radii)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def initialize_fluid(self,fluid:Solution=None,R:float=287.15,gamma:float=1.4,Cp:float=1024):
|
|
46
|
+
"""Initialize the inlet using the fluid. This function should be called by a class that inherits from spool
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
fluid (Solution, optional): Cantera fluid object. Defaults to None.
|
|
50
|
+
R (float, optional): Ideal Gas Constant. Defaults to 287.15 J/(Kg K) for air
|
|
51
|
+
gamma (float, optional): _description_. Defaults to 1.4.
|
|
52
|
+
Cp (float, optional): _description_. Defaults to 1024 J/(Kg K).
|
|
53
|
+
|
|
54
|
+
"""
|
|
55
|
+
self.loss_function = None
|
|
56
|
+
|
|
40
57
|
# if it's inlet alpha and beta are the same, relative flow angle = absolute.
|
|
41
|
-
self.beta2 = np.radians(convert_to_ndarray(
|
|
42
|
-
self.alpha1 = np.radians(convert_to_ndarray(
|
|
43
|
-
fluid.TP = self.T0.mean(),self.P0.mean()
|
|
44
|
-
self.gamma = fluid.cp/fluid.cv
|
|
58
|
+
self.beta2 = np.radians(convert_to_ndarray(self.beta1))
|
|
59
|
+
self.alpha1 = np.radians(convert_to_ndarray(self.beta1))
|
|
45
60
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
61
|
+
if fluid:
|
|
62
|
+
fluid.TP = self.T0.mean(),self.P0.mean()
|
|
63
|
+
self.gamma = fluid.cp/fluid.cv
|
|
64
|
+
self.T = self.T0 * 1/(1 + (self.gamma-1) * self.M**2)
|
|
65
|
+
self.P = self.P0 * 1/(1 + (self.gamma-1) * self.M**2)**(self.gamma/(self.gamma-1))
|
|
66
|
+
fluid.TP = self.T.mean(),self.P.mean()
|
|
67
|
+
self.rho = convert_to_ndarray([fluid.density])
|
|
68
|
+
else:
|
|
69
|
+
self.Cp = Cp
|
|
70
|
+
self.gamma = gamma
|
|
71
|
+
self.R = R
|
|
72
|
+
self.T = self.T0 * 1/(1 + (self.gamma-1) * self.M**2)
|
|
73
|
+
self.P = self.P0 * 1/(1 + (self.gamma-1) * self.M**2)**(self.gamma/(self.gamma-1))
|
|
74
|
+
self.rho = self.P/(self.R*self.T)
|
|
75
|
+
|
|
51
76
|
self.rpm = 0
|
|
52
|
-
|
|
53
77
|
self.beta1_metal = [0]
|
|
54
78
|
self.beta2_metal = [0]
|
|
55
|
-
self.P0_fun = interp1d(self.percent_hub_shroud,P0)
|
|
56
|
-
self.T0_fun = interp1d(self.percent_hub_shroud,T0)
|
|
79
|
+
self.P0_fun = interp1d(self.percent_hub_shroud,self.P0)
|
|
80
|
+
self.T0_fun = interp1d(self.percent_hub_shroud,self.T0)
|
|
57
81
|
self.mprime = [0]
|
|
58
|
-
|
|
59
82
|
|
|
60
83
|
def initialize_velocity(self,passage:Passage,num_streamlines:int):
|
|
61
84
|
"""Initialize velocity calculations. Assumes streamlines and inclination angles have been calculated
|
|
62
|
-
|
|
85
|
+
Call this before performing calculations
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
passage (Passage): Passage object
|
|
89
|
+
num_streamlines (int): number of streamlines
|
|
90
|
+
|
|
63
91
|
"""
|
|
64
92
|
# Perform Calculations on Velocity
|
|
65
93
|
Vm_prev = 0; Vm_err = 0
|
|
66
94
|
t,x,radius = passage.get_streamline(self.percent_hub_shroud)
|
|
67
95
|
radius = radius[0]
|
|
68
96
|
|
|
69
|
-
cutline,_,_ = passage.get_cutting_line(self.
|
|
97
|
+
cutline,_,_ = passage.get_cutting_line(self.location)
|
|
70
98
|
self.x,self.r = cutline.get_point(np.linspace(0,1,num_streamlines))
|
|
71
99
|
for _ in range(10):
|
|
72
100
|
T0_T = (1+(self.gamma-1)/2 * self.M**2)
|
|
@@ -84,7 +112,7 @@ class Inlet(BladeRow):
|
|
|
84
112
|
self.V = np.sqrt(self.Vm**2 + self.Vt**2)
|
|
85
113
|
self.Vr = self.Vm * np.sin(self.phi)
|
|
86
114
|
|
|
87
|
-
|
|
115
|
+
compute_gas_constants(self)
|
|
88
116
|
rho_mean = self.rho.mean()
|
|
89
117
|
for i in range(len(self.massflow)-1):
|
|
90
118
|
tube_massflow = self.massflow[i+1]-self.massflow[i]
|
|
@@ -12,7 +12,7 @@ from scipy.interpolate import interp1d
|
|
|
12
12
|
class Outlet(BladeRow):
|
|
13
13
|
P_fun:interp1d
|
|
14
14
|
|
|
15
|
-
def __init__(self,P:Union[float,List[float]],percent_radii:List[float],num_streamlines:int=3):
|
|
15
|
+
def __init__(self,P:Union[float,List[float]],percent_radii:List[float],num_streamlines:int=3,location:float=1):
|
|
16
16
|
"""Initialize the outlet
|
|
17
17
|
|
|
18
18
|
Args:
|
|
@@ -26,6 +26,7 @@ class Outlet(BladeRow):
|
|
|
26
26
|
self.P_fun = interp1d(self.percent_hub_shroud,self.P)
|
|
27
27
|
self.row_type = RowType.Outlet
|
|
28
28
|
self.loss_function = None
|
|
29
|
+
self.location = location
|
|
29
30
|
|
|
30
31
|
|
|
31
32
|
def transfer_quantities(self,upstream:BladeRow):
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from typing import List, Tuple
|
|
2
2
|
import numpy as np
|
|
3
3
|
import numpy.typing as npt
|
|
4
|
-
from scipy.interpolate import PchipInterpolator
|
|
4
|
+
from scipy.interpolate import PchipInterpolator, interp1d
|
|
5
5
|
from pyturbo.helper import line2D
|
|
6
6
|
from .enums import PassageType
|
|
7
7
|
from scipy.optimize import minimize_scalar
|
|
@@ -142,7 +142,31 @@ class Passage:
|
|
|
142
142
|
|
|
143
143
|
return phi, rm, r
|
|
144
144
|
|
|
145
|
-
def
|
|
145
|
+
def get_area(self,t_hub:float) -> float:
|
|
146
|
+
"""Get Area
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
t_hub (float): Percent arc length along the hub
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
float: Area
|
|
153
|
+
"""
|
|
154
|
+
n = 100
|
|
155
|
+
line = self.get_cutting_line(t_hub)[0]
|
|
156
|
+
x,r = line.get_point(np.linspace(0,1,n))
|
|
157
|
+
total_area = 0
|
|
158
|
+
for j in range(1,n):
|
|
159
|
+
if np.abs((x[-1]-x[0]))<1E-12: # Axial Machines
|
|
160
|
+
total_area += np.pi*(r[j]**2-r[j-1]**2)
|
|
161
|
+
else: # Radial Machines
|
|
162
|
+
dx = x[j]-x[j-1]
|
|
163
|
+
S = (r[j]-r[j-1])
|
|
164
|
+
C = np.sqrt(1+((r[j]-r[j-1])/dx)**2)
|
|
165
|
+
area = 2*np.pi*C*(S/2*dx**2+r[j-1]*dx)
|
|
166
|
+
total_area += area
|
|
167
|
+
return total_area
|
|
168
|
+
|
|
169
|
+
def get_cutting_line(self, t_hub:float) -> Tuple[line2D,float,float]:
|
|
146
170
|
"""Gets the cutting line perpendicular to hub and shroud
|
|
147
171
|
|
|
148
172
|
Args:
|
|
@@ -188,17 +212,18 @@ class Passage:
|
|
|
188
212
|
rshroud = self.rshroud(t_shroud)
|
|
189
213
|
return line2D([xhub,rhub],[xshroud,rshroud]), t_hub, t_shroud
|
|
190
214
|
|
|
191
|
-
def get_xr_slice(self,t_span:float,
|
|
215
|
+
def get_xr_slice(self,t_span:float,percent_hub:Tuple[float,float],resolution:int=100):
|
|
192
216
|
"""Returns the xr coordinates of a streamline, a line that is parallel to both hub and shroud
|
|
193
217
|
|
|
194
218
|
Args:
|
|
195
219
|
t_span (float): _description_
|
|
196
|
-
|
|
220
|
+
meridional_location (float): _description_
|
|
221
|
+
resolution (int): number of points to resolve
|
|
197
222
|
|
|
198
223
|
Returns:
|
|
199
224
|
np.NDArray: _description_
|
|
200
225
|
"""
|
|
201
|
-
t_hub = np.linspace(
|
|
226
|
+
t_hub = np.linspace(percent_hub[0],percent_hub[1],resolution)
|
|
202
227
|
t_hub = convert_to_ndarray(t_hub)*self.hub_length
|
|
203
228
|
|
|
204
229
|
shroud_pts_cyl = np.vstack([self.xshroud(t_hub),self.rshroud(t_hub)]).transpose()
|
|
@@ -209,8 +234,39 @@ class Passage:
|
|
|
209
234
|
for j in range(n):
|
|
210
235
|
l = line2D(hub_pts_cyl[j,:],shroud_pts_cyl[j,:])
|
|
211
236
|
xr[j,0],xr[j,1] = l.get_point(t_span)
|
|
237
|
+
|
|
212
238
|
return xr
|
|
213
|
-
|
|
239
|
+
|
|
240
|
+
def get_m(self,t_span:float,resolution:int=100) -> npt.NDArray:
|
|
241
|
+
"""Meridional cooridnates
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
t_span (float): _description_
|
|
245
|
+
resolution (int, optional): _description_. Defaults to 100.
|
|
246
|
+
|
|
247
|
+
Returns:
|
|
248
|
+
npt.NDArray: _description_
|
|
249
|
+
"""
|
|
250
|
+
xr = self.get_xr_slice(t_span,(0,1),resolution)
|
|
251
|
+
dx = np.diff(xr[:,0])
|
|
252
|
+
dr = np.diff(xr[:,1])
|
|
253
|
+
m = np.concat([[0],np.cumsum(np.sqrt(dx**2 + dr**2))])
|
|
254
|
+
return m
|
|
255
|
+
|
|
256
|
+
def get_dm(self,t_span:float,location:float,resolution:int=1000) -> float:
|
|
257
|
+
"""return the derivative in the meridional direction at a particular point
|
|
258
|
+
|
|
259
|
+
Args:
|
|
260
|
+
t_span (float): percent span
|
|
261
|
+
location (float): hub location of the blade
|
|
262
|
+
resolution (int, optional): number of points to represent the hub curve. Defaults to 1000.
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
(float) : returns the derivative
|
|
266
|
+
"""
|
|
267
|
+
m = self.get_m(t_span,resolution)
|
|
268
|
+
return PchipInterpolator(np.linspace(0,1,resolution),np.diff(m))(location)
|
|
269
|
+
|
|
214
270
|
@property
|
|
215
271
|
def hub_length(self):
|
|
216
272
|
"""returns the computed length of the hub
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
from scipy.interpolate import interp1d
|
|
1
|
+
from scipy.interpolate import interp1d,PchipInterpolator
|
|
2
2
|
from scipy.integrate import odeint
|
|
3
|
-
import numpy as np
|
|
3
|
+
import numpy as np
|
|
4
|
+
import numdifftools as nd
|
|
4
5
|
from .bladerow import BladeRow
|
|
5
6
|
from .enums import RowType
|
|
6
7
|
|
|
7
8
|
|
|
8
|
-
def radeq(row:BladeRow,upstream:BladeRow) -> BladeRow:
|
|
9
|
+
def radeq(row:BladeRow,upstream:BladeRow,downstream:BladeRow=None) -> BladeRow:
|
|
9
10
|
"""Solves the radial equilibrium equation for axial machines and returns the convergence.
|
|
10
11
|
|
|
11
12
|
Note:
|
|
@@ -14,7 +15,8 @@ def radeq(row:BladeRow,upstream:BladeRow) -> BladeRow:
|
|
|
14
15
|
Args:
|
|
15
16
|
row (BladeRow): Current row
|
|
16
17
|
upstream (BladeRow): Previous row
|
|
17
|
-
|
|
18
|
+
downstream (BladeRow): Next row
|
|
19
|
+
|
|
18
20
|
Returns:
|
|
19
21
|
BladeRow: current row with T0, P0, and Vm calculated
|
|
20
22
|
"""
|
|
@@ -57,8 +59,30 @@ def radeq(row:BladeRow,upstream:BladeRow) -> BladeRow:
|
|
|
57
59
|
# Estimations
|
|
58
60
|
dVm_dr = float(interp1d(row_radius, np.gradient(row.Vm, row_radius))(r))
|
|
59
61
|
dVt_dr = dVm_dr*np.tan(alpha)
|
|
60
|
-
dVr_dr = dVm_dr*np.sin(phi)
|
|
61
|
-
|
|
62
|
+
# dVr_dr = dVm_dr*np.sin(phi)
|
|
63
|
+
|
|
64
|
+
up_Vm = interp1d(row_radius, upstream.Vm)(r)
|
|
65
|
+
if downstream:
|
|
66
|
+
if downstream.row_type == RowType.Outlet:
|
|
67
|
+
down_Vm = Vm
|
|
68
|
+
else:
|
|
69
|
+
down_Vm = interp1d(row_radius, downstream.Vm)(r)
|
|
70
|
+
else:
|
|
71
|
+
down_Vm = Vm
|
|
72
|
+
up_m = interp1d(row_radius, upstream.m)(r)
|
|
73
|
+
|
|
74
|
+
# Get a rough guess of dVm/dm
|
|
75
|
+
if downstream!=None:
|
|
76
|
+
down_m = interp1d(row_radius, downstream.m)(r)
|
|
77
|
+
row_m = interp1d(row_radius, row.m)(r)
|
|
78
|
+
if down_m != row_m:
|
|
79
|
+
func_Vm_m = PchipInterpolator([up_m, row_m, down_m],[up_Vm, Vm, down_Vm])
|
|
80
|
+
else:
|
|
81
|
+
func_Vm_m = PchipInterpolator([up_m, row_m],[up_Vm, Vm])
|
|
82
|
+
else:
|
|
83
|
+
func_Vm_m = PchipInterpolator([up_m, row_m],[up_Vm, Vm])
|
|
84
|
+
dVm_dm = func_Vm_m.derivative()(row_m)
|
|
85
|
+
|
|
62
86
|
# Upstream
|
|
63
87
|
dT0up_dr = float(interp1d(upstream.percent_hub_shroud, np.gradient(upstream.T0,up_radius))((r-row_radius[0])/(row_radius[-1]-row_radius[0]))) # use percentage to get the T0 upstream value
|
|
64
88
|
dP0up_dr = float(interp1d(upstream.percent_hub_shroud, np.gradient(upstream.P0,up_radius))((r-row_radius[0])/(row_radius[-1]-row_radius[0]))) # use percentage to get the T0 upstream value
|
|
@@ -79,7 +103,12 @@ def radeq(row:BladeRow,upstream:BladeRow) -> BladeRow:
|
|
|
79
103
|
raise Exception("Invalid value of C {C}, change reduce alpha or Vm")
|
|
80
104
|
B = (1-C)**(gamma/(gamma-1))
|
|
81
105
|
A = P0 * gamma/(gamma-1) * (1-C)**(1/(gamma-1))
|
|
82
|
-
|
|
106
|
+
|
|
107
|
+
epsilon = 1e-10 # or another small threshold
|
|
108
|
+
if abs(rm) > epsilon:
|
|
109
|
+
dVm_dr = Cp*T0/(Vm*A*(1+np.tan(alpha)**2)) * (rho*(Vt**2/r + Vm**2/rm * np.cos(phi) - Vr*dVm_dm) - B*dP0_dr) + Vm/(2*T0) *dT0_dr # Eqn 6
|
|
110
|
+
else:
|
|
111
|
+
dVm_dr = Cp*T0/(Vm*A*(1+np.tan(alpha)**2)) * (rho*(Vt**2/r - Vr*dVm_dm) - B*dP0_dr) + Vm/(2*T0) *dT0_dr # Eqn 6
|
|
83
112
|
|
|
84
113
|
ydot = np.array([dP0_dr,dT0_dr,dVm_dr])
|
|
85
114
|
|
|
@@ -18,7 +18,8 @@ def adjust_streamlines(blade_rows:List[BladeRow],passage:Passage):
|
|
|
18
18
|
passage (Passage): passage object describing the hub and shroud
|
|
19
19
|
|
|
20
20
|
"""
|
|
21
|
-
for
|
|
21
|
+
for row_index,row in enumerate(blade_rows):
|
|
22
|
+
print(f"Adjusting Streamlines to balance massflow Row: {row_index}")
|
|
22
23
|
massflow_fraction = np.linspace(0,1,len(row.percent_hub_shroud))
|
|
23
24
|
row.total_massflow = row.massflow[-1]
|
|
24
25
|
ideal_massflow_fraction = row.massflow[-1] * massflow_fraction
|
|
@@ -26,13 +27,13 @@ def adjust_streamlines(blade_rows:List[BladeRow],passage:Passage):
|
|
|
26
27
|
new_percent_streamline = interp1d(row.massflow,row.percent_hub_shroud)(ideal_massflow_fraction[1:-1])
|
|
27
28
|
row.percent_hub_shroud[1:-1] = new_percent_streamline
|
|
28
29
|
|
|
29
|
-
cut_line, thub,_ = passage.get_cutting_line(row.
|
|
30
|
+
cut_line, thub,_ = passage.get_cutting_line(row.percent_hub)
|
|
30
31
|
row.x,row.r = cut_line.get_point(row.percent_hub_shroud)
|
|
31
32
|
# Radii may have shifted, recompute Ay and rm
|
|
32
33
|
for i,tr in enumerate(row.percent_hub_shroud):
|
|
33
34
|
t_streamline, x_streamline, r_streamline = passage.get_streamline(tr)
|
|
34
35
|
phi, rm, r = passage.streamline_curvature(x_streamline,r_streamline)
|
|
35
|
-
row.phi[i] = float(interp1d(t_streamline,phi)(row.
|
|
36
|
-
row.rm[i] = float(interp1d(t_streamline,rm)(row.
|
|
37
|
-
row.r[i] = float(interp1d(t_streamline,r)(row.
|
|
38
|
-
row.x[i] = float(interp1d(t_streamline,x_streamline)(row.
|
|
36
|
+
row.phi[i] = float(interp1d(t_streamline,phi)(row.percent_hub))
|
|
37
|
+
row.rm[i] = float(interp1d(t_streamline,rm)(row.percent_hub))
|
|
38
|
+
row.r[i] = float(interp1d(t_streamline,r)(row.percent_hub))
|
|
39
|
+
row.x[i] = float(interp1d(t_streamline,x_streamline)(row.percent_hub))
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# type: ignore[arg-type, reportUnknownArgumentType]
|
|
2
2
|
from dataclasses import field
|
|
3
3
|
import json
|
|
4
|
-
from typing import Dict, List, Union
|
|
4
|
+
from typing import Dict, List, Union, Optional
|
|
5
5
|
import matplotlib.pyplot as plt
|
|
6
6
|
from .bladerow import BladeRow
|
|
7
7
|
import numpy as np
|
|
@@ -34,7 +34,7 @@ class Spool:
|
|
|
34
34
|
def __init__(self,passage:Passage,
|
|
35
35
|
massflow:float,rows=List[BladeRow],
|
|
36
36
|
num_streamlines:int=3,
|
|
37
|
-
fluid:Solution=
|
|
37
|
+
fluid:Solution=None,
|
|
38
38
|
rpm:float=-1,
|
|
39
39
|
massflow_constraint:MassflowConstraint=MassflowConstraint.MatchMassFlow):
|
|
40
40
|
"""Initializes a Spool
|
|
@@ -50,7 +50,7 @@ class Spool:
|
|
|
50
50
|
massflow (float): massflow at spool inlet
|
|
51
51
|
rows (List[BladeRow], optional): List of blade rows. Defaults to List[BladeRow].
|
|
52
52
|
num_streamlines (int, optional): number of streamlines. Defaults to 3.
|
|
53
|
-
|
|
53
|
+
fluid (ct.Solution, optional): cantera gas solution. Defaults to None, fluid is set by bladerow cp
|
|
54
54
|
rpm (float, optional): RPM for the entire spool Optional, you can also set rpm of the blade rows individually. Defaults to -1.
|
|
55
55
|
massflow_constraint (MassflowConstraint, optional): MatchMassflow - Matches the massflow defined in the spool. BalanceMassflow - Balances the massflow between BladeRows, matches the lowest massflow.
|
|
56
56
|
"""
|
|
@@ -70,13 +70,12 @@ class Spool:
|
|
|
70
70
|
2. Counter Rotation: We either use the RPM already persecribed for each blade row.
|
|
71
71
|
'''
|
|
72
72
|
if (type(self.blade_rows[i]) != Inlet) and (type(self.blade_rows[i]) != Outlet):
|
|
73
|
-
self.blade_rows[i].fluid = self.fluid
|
|
74
73
|
self.blade_rows[i].rpm = rpm
|
|
75
|
-
self.blade_rows[i].axial_chord = self.blade_rows[i].
|
|
74
|
+
self.blade_rows[i].axial_chord = self.blade_rows[i].location * self.passage.hub_length
|
|
76
75
|
|
|
77
76
|
|
|
78
77
|
@property
|
|
79
|
-
def fluid(self):
|
|
78
|
+
def fluid(self) -> Optional[Solution]:
|
|
80
79
|
return self._fluid
|
|
81
80
|
|
|
82
81
|
@fluid.setter
|
|
@@ -159,13 +158,15 @@ class Spool:
|
|
|
159
158
|
row.phi = np.zeros((self.num_streamlines,))
|
|
160
159
|
row.rm = np.zeros((self.num_streamlines,))
|
|
161
160
|
row.r = np.zeros((self.num_streamlines,))
|
|
161
|
+
row.m = np.zeros((self.num_streamlines,))
|
|
162
162
|
|
|
163
163
|
t_radial = np.linspace(0,1,self.num_streamlines)
|
|
164
164
|
self.calculate_streamline_curvature(row,t_radial)
|
|
165
|
-
|
|
165
|
+
|
|
166
166
|
# Set the loss function if it's not set
|
|
167
|
-
if (row
|
|
168
|
-
row.loss_function
|
|
167
|
+
if (type(row)!= Inlet and type(row) != Outlet):
|
|
168
|
+
if row.loss_function == None:
|
|
169
|
+
row.loss_function = TD2()
|
|
169
170
|
|
|
170
171
|
def calculate_streamline_curvature(self,row:BladeRow,t_radial:Union[List[float],npt.NDArray]):
|
|
171
172
|
"""Called to calculate new streamline curvature
|
|
@@ -177,9 +178,10 @@ class Spool:
|
|
|
177
178
|
for i,tr in enumerate(t_radial):
|
|
178
179
|
t_streamline, x_streamline, r_streamline = self.passage.get_streamline(tr)
|
|
179
180
|
phi, rm, r = self.passage.streamline_curvature(x_streamline,r_streamline)
|
|
180
|
-
row.phi[i] = float(interp1d(t_streamline,phi)(row.
|
|
181
|
-
row.rm[i] = float(interp1d(t_streamline,rm)(row.
|
|
182
|
-
row.r[i] = float(interp1d(t_streamline,r)(row.
|
|
181
|
+
row.phi[i] = float(interp1d(t_streamline,phi)(row.location))
|
|
182
|
+
row.rm[i] = float(interp1d(t_streamline,rm)(row.location))
|
|
183
|
+
row.r[i] = float(interp1d(t_streamline,r)(row.location))
|
|
184
|
+
row.m[i] = float(interp1d(t_streamline,self.passage.get_m(tr,resolution=len(t_streamline)))(row.location))
|
|
183
185
|
|
|
184
186
|
def solve(self):
|
|
185
187
|
raise NotImplementedError('Solve is not implemented')
|
|
@@ -214,10 +216,10 @@ class Spool:
|
|
|
214
216
|
else: # i>0
|
|
215
217
|
upstream = self.blade_rows[i-1]
|
|
216
218
|
if upstream.row_type== RowType.Inlet:
|
|
217
|
-
cut_line1,_,_ = self.passage.get_cutting_line((row.
|
|
219
|
+
cut_line1,_,_ = self.passage.get_cutting_line((row.location*hub_length +(0.5*row.blade_to_blade_gap*row.axial_chord) - row.axial_chord)/hub_length)
|
|
218
220
|
else:
|
|
219
|
-
cut_line1,_,_ = self.passage.get_cutting_line((upstream.
|
|
220
|
-
cut_line2,_,_ = self.passage.get_cutting_line((row.
|
|
221
|
+
cut_line1,_,_ = self.passage.get_cutting_line((upstream.location*hub_length)/hub_length)
|
|
222
|
+
cut_line2,_,_ = self.passage.get_cutting_line((row.location*hub_length-(0.5*row.blade_to_blade_gap*row.axial_chord))/hub_length)
|
|
221
223
|
|
|
222
224
|
if self.blade_rows[i].row_type == RowType.Stator:
|
|
223
225
|
x1,r1 = cut_line1.get_point(np.linspace(0,1,10))
|