turbo-design 1.1.4__py3-none-any.whl → 1.3.0__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.1.4.dist-info → turbo_design-1.3.0.dist-info}/METADATA +3 -2
- {turbo_design-1.1.4.dist-info → turbo_design-1.3.0.dist-info}/RECORD +13 -13
- {turbo_design-1.1.4.dist-info → turbo_design-1.3.0.dist-info}/WHEEL +1 -1
- turbodesign/bladerow.py +28 -24
- turbodesign/coolant.py +2 -2
- turbodesign/inlet.py +50 -22
- turbodesign/outlet.py +2 -1
- turbodesign/passage.py +62 -6
- turbodesign/radeq.py +36 -7
- turbodesign/solve_radeq.py +7 -6
- turbodesign/spool.py +17 -15
- turbodesign/td_math.py +29 -26
- turbodesign/turbinespool.py +122 -128
|
@@ -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,11 +1,11 @@
|
|
|
1
1
|
turbodesign/__init__.py,sha256=N8Nu0I1vrlEHYJZZ7yhhg-FtbNbLyrgjy7hoicygUqg,325
|
|
2
2
|
turbodesign/arrayfuncs.py,sha256=GHIlTyLfeNsNCQQoh5m1aXK3iyvU6RjdYQipkqpU_ps,519
|
|
3
|
-
turbodesign/bladerow.py,sha256=
|
|
3
|
+
turbodesign/bladerow.py,sha256=v4tX27aOL7iuN0ILWYbtSbANWZ_7_LIxksu4ux1aQ2o,24959
|
|
4
4
|
turbodesign/cantera_gas/co2.yaml,sha256=M2o_RzxV9B9rDkgkXJC-l3voKraFZguTZuKKt4F7S_c,887
|
|
5
5
|
turbodesign/compressorspool.py,sha256=z8ZVczJ-EdZvIqqZArC6UdwC5MbU2SZh_MT2CGq5avY,2600
|
|
6
|
-
turbodesign/coolant.py,sha256=
|
|
6
|
+
turbodesign/coolant.py,sha256=evDtUFOYhfZKTVAsDWzxRzUX20gTvfjj0uybaWg4CsI,427
|
|
7
7
|
turbodesign/enums.py,sha256=T4aFrGMnx9fgZkrExMBIY2IbCueA0utqr_afk-TUe9Q,1188
|
|
8
|
-
turbodesign/inlet.py,sha256=
|
|
8
|
+
turbodesign/inlet.py,sha256=L_IHE8zzm3uFMSow0iO-VxI71SnyQZwRhJMlJDcwZ4o,7329
|
|
9
9
|
turbodesign/isentropic.py,sha256=YLTmNx69e_M5fTuLOmk_KvaQ6ABv0aZsityfBihJOmI,2003
|
|
10
10
|
turbodesign/loss/__init__.py,sha256=ZJomzXa6ElduFruURukCrFwJQXMWS2aW8JSaVQ-M2r8,46
|
|
11
11
|
turbodesign/loss/compressor/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
@@ -19,15 +19,15 @@ turbodesign/loss/turbine/fixedpressureloss.py,sha256=jbm8dt8wtyN1RaOgpgYZsvwM0-0
|
|
|
19
19
|
turbodesign/loss/turbine/kackerokapuu.py,sha256=hUmZdyA1hAhN3_xbwBhka2dyk0Aq9rqq62S2ghuKSOk,5071
|
|
20
20
|
turbodesign/loss/turbine/traupel.py,sha256=aZxFE9JDcCfi1gy-fqS1SxjjlhuDG-H-g3LPurty1M0,4164
|
|
21
21
|
turbodesign/lossinterp.py,sha256=B2KEobp-nD9jwD6UINgBmTlH9kKyWg3UNvXxqfUsr-k,6198
|
|
22
|
-
turbodesign/outlet.py,sha256
|
|
23
|
-
turbodesign/passage.py,sha256=
|
|
24
|
-
turbodesign/radeq.py,sha256=
|
|
22
|
+
turbodesign/outlet.py,sha256=-0XpeEmTQpFjUQNYQxOp5WuHkF7cXfx59hxy7BmaxgY,2119
|
|
23
|
+
turbodesign/passage.py,sha256=Ur6zZCzI9Y7HW0o5LrDq9yeatRain1mBLZBcTOzgjEU,11996
|
|
24
|
+
turbodesign/radeq.py,sha256=3XuSf_KtOqddJclcxh9v1V2YWMvbWfd0YxcHGkic7hg,11361
|
|
25
25
|
turbodesign/rotor.py,sha256=tHl9o5H4aQ6Etd4gqa8Ime1UK7k0de4GLt5Yb1sJdGs,1376
|
|
26
|
-
turbodesign/solve_radeq.py,sha256=
|
|
27
|
-
turbodesign/spool.py,sha256=
|
|
26
|
+
turbodesign/solve_radeq.py,sha256=mGVBHmc20pAXEDKGzyiU3BVUsmY9gJl7zhbtHZqxARg,1956
|
|
27
|
+
turbodesign/spool.py,sha256=XKvYABvfigLulGLHH23X7k6aL6wvfuqgfLkKCMK9X8c,14338
|
|
28
28
|
turbodesign/stage.py,sha256=UP45sDKDLsAkO_WfDWJ6kqXU7cYKh_4QO01QZnSN1oQ,166
|
|
29
|
-
turbodesign/td_math.py,sha256=
|
|
30
|
-
turbodesign/turbinespool.py,sha256=
|
|
31
|
-
turbo_design-1.
|
|
32
|
-
turbo_design-1.
|
|
33
|
-
turbo_design-1.
|
|
29
|
+
turbodesign/td_math.py,sha256=NMKgHkc36LCrr4bZxjxx7TcA4ztaRps1cIsm1tucMrk,16069
|
|
30
|
+
turbodesign/turbinespool.py,sha256=I5e1l8SsvwsQZZkOv7ric83-uNvT9MExwCimJEAhaI8,25194
|
|
31
|
+
turbo_design-1.3.0.dist-info/METADATA,sha256=qSAB19ivwujv2mpnh8XwkwdSuVNIbiYNMYozn6ccPW0,734
|
|
32
|
+
turbo_design-1.3.0.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
|
|
33
|
+
turbo_design-1.3.0.dist-info/RECORD,,
|
turbodesign/bladerow.py
CHANGED
|
@@ -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
|
turbodesign/coolant.py
CHANGED
|
@@ -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
|
|
turbodesign/inlet.py
CHANGED
|
@@ -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]
|
turbodesign/outlet.py
CHANGED
|
@@ -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):
|
turbodesign/passage.py
CHANGED
|
@@ -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
|
turbodesign/radeq.py
CHANGED
|
@@ -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
|
|
turbodesign/solve_radeq.py
CHANGED
|
@@ -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))
|
turbodesign/spool.py
CHANGED
|
@@ -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))
|
turbodesign/td_math.py
CHANGED
|
@@ -6,6 +6,7 @@ from .bladerow import BladeRow, compute_gas_constants
|
|
|
6
6
|
from .enums import RowType, LossType
|
|
7
7
|
from scipy.integrate import trapezoid
|
|
8
8
|
from .passage import Passage
|
|
9
|
+
from .isentropic import IsenP
|
|
9
10
|
|
|
10
11
|
def T0_coolant_weighted_average(row:BladeRow) -> float:
|
|
11
12
|
"""Calculate the new weighted Total Temperature array considering coolant
|
|
@@ -17,11 +18,12 @@ def T0_coolant_weighted_average(row:BladeRow) -> float:
|
|
|
17
18
|
Returns:
|
|
18
19
|
float: Total Temperature drop
|
|
19
20
|
"""
|
|
20
|
-
|
|
21
|
+
|
|
21
22
|
massflow = row.massflow
|
|
22
23
|
total_massflow_no_coolant = row.total_massflow_no_coolant
|
|
23
24
|
Cp = row.Cp
|
|
24
|
-
|
|
25
|
+
|
|
26
|
+
Cpc = row.coolant.Cp
|
|
25
27
|
T0c = row.coolant.T0
|
|
26
28
|
massflow_coolant = row.coolant.massflow_percentage*total_massflow_no_coolant*row.massflow[1:]/row.massflow[-1]
|
|
27
29
|
if massflow_coolant.mean()>0:
|
|
@@ -35,8 +37,9 @@ def T0_coolant_weighted_average(row:BladeRow) -> float:
|
|
|
35
37
|
else:
|
|
36
38
|
T0R = row.T0R
|
|
37
39
|
T0R_new = T0R.copy()
|
|
40
|
+
Cp = row.Cp
|
|
38
41
|
T0R_new[1:] = (massflow[1:]*Cp*T0R[1:] + massflow_coolant*Cpc*T0c) \
|
|
39
|
-
/(massflow[1:]*
|
|
42
|
+
/(massflow[1:]*Cp + massflow_coolant*Cpc)
|
|
40
43
|
T0R_new[0] = T0R_new[1]
|
|
41
44
|
|
|
42
45
|
T = T0R_new - row.W**2/(2*Cp) # Dont change the velocity triangle but adjust the static temperature
|
|
@@ -61,7 +64,7 @@ def compute_massflow(row:BladeRow) -> None:
|
|
|
61
64
|
for j in range(1,len(row.percent_hub_shroud)):
|
|
62
65
|
Vm = (row.Vm[j]+row.Vm[j-1])/2
|
|
63
66
|
rho = (row.rho[j]+row.rho[j-1])/2
|
|
64
|
-
if np.abs((row.x[j]-row.x[j-1]))<1E-
|
|
67
|
+
if np.abs((row.x[j]-row.x[j-1]))<1E-5: # Axial Machines
|
|
65
68
|
total_area += np.pi*(row.r[j]**2-row.r[j-1]**2)
|
|
66
69
|
massflow[j] = Vm * rho * np.pi* (row.r[j]**2-row.r[j-1]**2) + massflow[j-1]
|
|
67
70
|
else: # Radial Machines
|
|
@@ -77,6 +80,7 @@ def compute_massflow(row:BladeRow) -> None:
|
|
|
77
80
|
massflow += massflow_fraction*row.coolant.massflow_percentage*row.total_massflow_no_coolant # Take into account the coolant massflow
|
|
78
81
|
row.massflow = massflow
|
|
79
82
|
row.calculated_massflow = massflow[-1]
|
|
83
|
+
row.total_massflow = massflow[-1]
|
|
80
84
|
row.area = total_area
|
|
81
85
|
|
|
82
86
|
def compute_reynolds(rows:List[BladeRow],passage:Passage):
|
|
@@ -89,7 +93,7 @@ def compute_reynolds(rows:List[BladeRow],passage:Passage):
|
|
|
89
93
|
|
|
90
94
|
for i in range(1,len(rows)):
|
|
91
95
|
row = rows[i]
|
|
92
|
-
xr = passage.get_xr_slice(0.5,[rows[i-1].
|
|
96
|
+
xr = passage.get_xr_slice(0.5,[rows[i-1].location,row.percent_hub])
|
|
93
97
|
dx = np.diff(xr[:,0])
|
|
94
98
|
dr = np.diff(xr[:,1])
|
|
95
99
|
c = np.sum(np.sqrt(dx**2+dr**2))
|
|
@@ -107,8 +111,6 @@ def compute_reynolds(rows:List[BladeRow],passage:Passage):
|
|
|
107
111
|
row.axial_chord = max(c,1E-12) # Axial chord
|
|
108
112
|
# row.num_blades = int(2*np.pi*row.r.mean() / row.pitch_to_chord * row.axial_chord)
|
|
109
113
|
|
|
110
|
-
|
|
111
|
-
|
|
112
114
|
def compute_power(row:BladeRow,upstream:BladeRow) -> None:
|
|
113
115
|
"""Calculates the power
|
|
114
116
|
|
|
@@ -125,11 +127,14 @@ def compute_power(row:BladeRow,upstream:BladeRow) -> None:
|
|
|
125
127
|
row.T_is = 0
|
|
126
128
|
row.T0_is = 0
|
|
127
129
|
else:
|
|
128
|
-
|
|
129
|
-
row.
|
|
130
|
+
P0_P = (upstream.P0/row.P).mean()
|
|
131
|
+
row.T_is = upstream.T0 * (1/P0_P)**((row.gamma-1)/row.gamma)
|
|
132
|
+
a = np.sqrt(row.gamma*row.R*row.T_is)
|
|
133
|
+
row.T0_is = row.T_is * (1+(row.gamma-1)/2*(row.V/a)**2)
|
|
134
|
+
|
|
130
135
|
row.power = row.massflow[-1] * row.Cp * (upstream.T0.mean() - row.T0.mean())
|
|
131
|
-
row.eta_static = row.power/ (row.massflow[-1]*row.Cp*(upstream.T0.mean()-row.
|
|
132
|
-
row.eta_total =
|
|
136
|
+
row.eta_static = row.power/ (row.massflow[-1]*row.Cp*(upstream.T0.mean()-row.T_is.mean()))
|
|
137
|
+
row.eta_total = (upstream.T0.mean() - row.T0.mean()) / (upstream.T0.mean() - row.T0_is.mean())
|
|
133
138
|
row.stage_loading = row.Cp*(upstream.T0.mean() - row.T0.mean())/row.U.mean()**2
|
|
134
139
|
row.euler_power = row.massflow[-1]* (upstream.U*upstream.Vt - row.U*row.Vt).mean()
|
|
135
140
|
|
|
@@ -150,7 +155,7 @@ def compute_quantities(row:BladeRow,upstream:BladeRow):
|
|
|
150
155
|
if row.row_type == RowType.Rotor:
|
|
151
156
|
Cp_avg = (row.Cp+upstream.Cp)/2
|
|
152
157
|
# Factor any coolant added and changes in streamline radius
|
|
153
|
-
row.T0R = upstream.T0R - T0_coolant_weighted_average(row) - (upstream.U**2-row.U**2)/(2*Cp_avg)
|
|
158
|
+
row.T0R = upstream.T0R - T0_coolant_weighted_average(row) # - (upstream.U**2-row.U**2)/(2*Cp_avg)
|
|
154
159
|
row.P = upstream.P0_stator_inlet/row.P0_P
|
|
155
160
|
|
|
156
161
|
if row.loss_function.loss_type == LossType.Pressure:
|
|
@@ -225,20 +230,21 @@ def stator_calc(row:BladeRow,upstream:BladeRow,downstream:BladeRow=None,calculat
|
|
|
225
230
|
|
|
226
231
|
# Static Pressure is assumed
|
|
227
232
|
row.P0 = upstream.P0 - row.Yp*(upstream.P0-row.P)
|
|
228
|
-
|
|
233
|
+
|
|
229
234
|
if downstream is not None:
|
|
235
|
+
row.P0_P = row.P0/downstream.P
|
|
230
236
|
row.rp = (row.P-downstream.P)/(upstream.P0-downstream.P)
|
|
231
237
|
|
|
232
238
|
if calculate_vm:
|
|
233
239
|
row.M = ((row.P0/row.P)**((row.gamma-1)/row.gamma) - 1) * 2/(row.gamma-1)
|
|
234
240
|
row.M = np.sqrt(row.M)
|
|
235
|
-
T0_T = (1+(row.gamma-1)/2 * row.M
|
|
241
|
+
T0_T = (1+(row.gamma-1)/2 * row.M**2)
|
|
236
242
|
row.T0 = upstream.T0 - T0_coolant_weighted_average(row)
|
|
237
243
|
row.T = row.T0/T0_T
|
|
238
244
|
row.V = row.M*np.sqrt(row.gamma*row.R*row.T)
|
|
245
|
+
row.Vm = row.V*np.cos(row.alpha2)
|
|
239
246
|
row.Vx = row.Vm*np.cos(row.phi)
|
|
240
|
-
row.Vr = row.
|
|
241
|
-
row.Vm = np.sqrt(row.Vx**2+row.Vr**2)
|
|
247
|
+
row.Vr = row.Vm*np.sin(row.phi)
|
|
242
248
|
row.Vt = row.Vm*np.tan(row.alpha2)
|
|
243
249
|
else: # We know Vm, P0, T0, P
|
|
244
250
|
row.Vx = row.Vm*np.cos(row.phi)
|
|
@@ -254,6 +260,7 @@ def stator_calc(row:BladeRow,upstream:BladeRow,downstream:BladeRow=None,calculat
|
|
|
254
260
|
row.beta1 = upstream.beta2
|
|
255
261
|
row.rho = row.P/(row.R*row.T)
|
|
256
262
|
row.U = row.omega*row.r
|
|
263
|
+
row.Wt = row.Vt-row.U
|
|
257
264
|
row.P0_stator_inlet = upstream.P0
|
|
258
265
|
|
|
259
266
|
def rotor_calc(row:BladeRow,upstream:BladeRow,calculate_vm:bool=True):
|
|
@@ -297,7 +304,7 @@ def rotor_calc(row:BladeRow,upstream:BladeRow,calculate_vm:bool=True):
|
|
|
297
304
|
row.W = np.sqrt(2*row.Cp*(row.T0R-row.T)) #! nan popups here a lot for radial machines
|
|
298
305
|
if np.isnan(np.sum(row.W)):
|
|
299
306
|
# Need to adjust T
|
|
300
|
-
|
|
307
|
+
raise ValueError(f'nan detected: check flow path. Turbine inlet cut should be horizontal')
|
|
301
308
|
row.Vr = row.W*np.sin(row.phi)
|
|
302
309
|
row.Vm = row.W*np.cos(row.beta2)
|
|
303
310
|
row.Wt = row.W*np.sin(row.beta2)
|
|
@@ -327,11 +334,6 @@ def rotor_calc(row:BladeRow,upstream:BladeRow,calculate_vm:bool=True):
|
|
|
327
334
|
|
|
328
335
|
row.M_rel = row.W/np.sqrt(row.gamma*row.R*row.T)
|
|
329
336
|
row.T0 = row.T+row.V**2/(2*row.Cp)
|
|
330
|
-
|
|
331
|
-
T3_is = upstream.T0 * (1/row.P0_P)**((row.gamma-1)/row.gamma)
|
|
332
|
-
a = np.sqrt(row.gamma*row.R*T3_is)
|
|
333
|
-
T03_is = T3_is * (1+(row.gamma-1)/2*(row.V/a)**2)
|
|
334
|
-
row.eta_total = (upstream.T0.mean() - row.T0.mean())/(upstream.T0.mean()-T03_is.mean())
|
|
335
337
|
|
|
336
338
|
def inlet_calc(row:BladeRow):
|
|
337
339
|
"""Calculates the conditions for the Inlet
|
|
@@ -339,12 +341,13 @@ def inlet_calc(row:BladeRow):
|
|
|
339
341
|
Args:
|
|
340
342
|
row (BladeRow): _description_
|
|
341
343
|
"""
|
|
344
|
+
|
|
342
345
|
area = row.Vm.copy()*0
|
|
343
346
|
# Estimate the density
|
|
344
347
|
row.T = row.T0
|
|
345
|
-
row.P = row.P0
|
|
348
|
+
row.P = row.P0
|
|
346
349
|
row.rho = row.P/(row.T*row.R)
|
|
347
|
-
total_area = 0
|
|
350
|
+
total_area = 0
|
|
348
351
|
for iter in range(5): # Lets converge the Mach and Total and Static pressures
|
|
349
352
|
for j in range(1,len(row.percent_hub_shroud)):
|
|
350
353
|
rho = row.rho[j]
|
|
@@ -361,9 +364,9 @@ def inlet_calc(row:BladeRow):
|
|
|
361
364
|
row.Vm[j] = tube_massflow/(rho*area[j])
|
|
362
365
|
avg_mach = np.mean(row.M)
|
|
363
366
|
if np.mean(row.M)>0.5:
|
|
364
|
-
|
|
367
|
+
raise ValueError(f"High inlet mach can lead to errors iter:{iter} Mach:{avg_mach}")
|
|
365
368
|
if np.mean(row.M)<0.01:
|
|
366
|
-
|
|
369
|
+
raise ValueError(f"Unusually slow flow:{iter} Mach:{avg_mach}")
|
|
367
370
|
row.Vm[0] = 1/(len(row.Vm)-1)*row.Vm[1:].sum() # Initialize the value at the hub to not upset the mean
|
|
368
371
|
row.Vr = row.Vm*np.sin(row.phi)
|
|
369
372
|
row.Vt = row.Vm*np.cos(row.phi)*np.tan(row.alpha2)
|
turbodesign/turbinespool.py
CHANGED
|
@@ -10,17 +10,18 @@ import numpy as np
|
|
|
10
10
|
import numpy.typing as npt
|
|
11
11
|
from .td_math import inlet_calc,rotor_calc, stator_calc, compute_massflow, compute_power, compute_gas_constants, compute_reynolds
|
|
12
12
|
from .solve_radeq import adjust_streamlines, radeq
|
|
13
|
-
from scipy.optimize import minimize_scalar,
|
|
13
|
+
from scipy.optimize import minimize_scalar, differential_evolution, fmin_slsqp
|
|
14
14
|
from .inlet import Inlet
|
|
15
15
|
from .outlet import Outlet
|
|
16
16
|
|
|
17
17
|
class TurbineSpool(Spool):
|
|
18
|
+
|
|
18
19
|
def __init__(self,passage:Passage,
|
|
19
20
|
massflow:float,rows:List[BladeRow],
|
|
20
21
|
num_streamlines:int=3,
|
|
21
22
|
fluid:Solution=Solution('air.yaml'),
|
|
22
23
|
rpm:float=-1,
|
|
23
|
-
|
|
24
|
+
massflow_constraint:MassflowConstraint=MassflowConstraint.MatchMassFlow):
|
|
24
25
|
"""Initializes a Turbine Spool
|
|
25
26
|
|
|
26
27
|
Args:
|
|
@@ -45,21 +46,22 @@ class TurbineSpool(Spool):
|
|
|
45
46
|
# Inlet
|
|
46
47
|
W0 = self.massflow
|
|
47
48
|
inlet = self.blade_rows[0]
|
|
48
|
-
if self.
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
self.blade_rows[
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
if self.fluid:
|
|
50
|
+
inlet.initialize_fluid(self.fluid)
|
|
51
|
+
else:
|
|
52
|
+
inlet.initialize_fluid(R=self.blade_rows[1].R,
|
|
53
|
+
gamma=self.blade_rows[1].gamma,
|
|
54
|
+
Cp=self.blade_rows[1].Cp)
|
|
55
|
+
|
|
56
|
+
inlet.total_massflow = W0
|
|
57
|
+
inlet.total_massflow_no_coolant = W0
|
|
58
|
+
inlet.massflow = np.linspace(0,1,self.num_streamlines)*W0
|
|
59
|
+
|
|
60
|
+
inlet.initialize_velocity(self.passage,self.num_streamlines)
|
|
61
|
+
interpolate_streamline_radii(inlet,self.passage,self.num_streamlines)
|
|
54
62
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
self.blade_rows[0].Cp = self.blade_rows[0].fluid.cp
|
|
58
|
-
self.blade_rows[0].Cv = self.blade_rows[0].fluid.cv
|
|
59
|
-
self.blade_rows[0].R = self.blade_rows[0].Cp-self.blade_rows[0].Cv
|
|
60
|
-
self.blade_rows[0].gamma = self.blade_rows[0].Cp/self.blade_rows[0].Cv
|
|
61
|
-
self.blade_rows[0].rho[:] = self.blade_rows[0].fluid.density
|
|
62
|
-
inlet_calc(self.blade_rows[0])
|
|
63
|
+
compute_gas_constants(inlet,self.fluid)
|
|
64
|
+
inlet_calc(inlet)
|
|
63
65
|
|
|
64
66
|
for row in self.blade_rows:
|
|
65
67
|
interpolate_streamline_radii(row,self.passage,self.num_streamlines)
|
|
@@ -83,10 +85,10 @@ class TurbineSpool(Spool):
|
|
|
83
85
|
|
|
84
86
|
row = self.blade_rows[i]
|
|
85
87
|
if (row.coolant is not None):
|
|
86
|
-
T0c = self.blade_rows[i].coolant.
|
|
87
|
-
P0c = self.blade_rows[i].coolant.
|
|
88
|
+
T0c = self.blade_rows[i].coolant.T0
|
|
89
|
+
P0c = self.blade_rows[i].coolant.P0
|
|
88
90
|
W0c = self.blade_rows[i].coolant.massflow_percentage * self.massflow
|
|
89
|
-
Cpc = self.blade_rows[i].coolant.
|
|
91
|
+
Cpc = self.blade_rows[i].coolant.Cp
|
|
90
92
|
else:
|
|
91
93
|
T0c = 100
|
|
92
94
|
P0c = 0
|
|
@@ -120,10 +122,12 @@ class TurbineSpool(Spool):
|
|
|
120
122
|
|
|
121
123
|
if row.row_type == RowType.Stator:
|
|
122
124
|
stator_calc(row,upstream,downstream)
|
|
125
|
+
compute_massflow(row)
|
|
123
126
|
elif row.row_type == RowType.Rotor:
|
|
124
127
|
rotor_calc(row,upstream)
|
|
125
128
|
compute_massflow(row)
|
|
126
129
|
compute_power(row,upstream)
|
|
130
|
+
|
|
127
131
|
|
|
128
132
|
def solve(self):
|
|
129
133
|
"""
|
|
@@ -133,9 +137,9 @@ class TurbineSpool(Spool):
|
|
|
133
137
|
self.initialize_quantities()
|
|
134
138
|
|
|
135
139
|
if self.massflow_constraint ==MassflowConstraint.MatchMassFlow:
|
|
136
|
-
self.__match_massflow()
|
|
140
|
+
self.__match_massflow() # Matches massflow by changing turning angle
|
|
137
141
|
elif self.massflow_constraint == MassflowConstraint.BalanceMassFlow:
|
|
138
|
-
self.__balance_massflow()
|
|
142
|
+
self.__balance_massflow() # Balances massflow by changing row exit static pressure
|
|
139
143
|
|
|
140
144
|
|
|
141
145
|
def __match_massflow(self):
|
|
@@ -161,7 +165,7 @@ class TurbineSpool(Spool):
|
|
|
161
165
|
bounds = [-80,0]
|
|
162
166
|
if row.row_type != RowType.Inlet:
|
|
163
167
|
for j in range(1,self.num_streamlines):
|
|
164
|
-
res = minimize_scalar(massflow_loss_function, bounds=bounds,args=(j,row,upstream,downstream),tol=1E-
|
|
168
|
+
res = minimize_scalar(massflow_loss_function, bounds=bounds,args=(j,row,upstream,downstream),tol=1E-3)
|
|
165
169
|
if row.row_type == RowType.Rotor:
|
|
166
170
|
row.beta2[j] = np.radians(res.x)
|
|
167
171
|
# Initialize the value at the hub to not upset the mean
|
|
@@ -169,14 +173,41 @@ class TurbineSpool(Spool):
|
|
|
169
173
|
elif row.row_type == RowType.Stator:
|
|
170
174
|
row.alpha2[j] = np.radians(res.x)
|
|
171
175
|
row.alpha2[0] = 1/(len(row.alpha2)-1)*row.alpha2[1:].sum()
|
|
172
|
-
|
|
173
|
-
|
|
176
|
+
compute_gas_constants(upstream,self.fluid)
|
|
177
|
+
compute_gas_constants(row,self.fluid)
|
|
174
178
|
|
|
175
179
|
|
|
176
180
|
# Step 3: Adjust streamlines to evenly divide massflow
|
|
177
181
|
adjust_streamlines(self.blade_rows,self.passage)
|
|
178
182
|
compute_reynolds(self.blade_rows,self.passage)
|
|
179
|
-
|
|
183
|
+
|
|
184
|
+
@staticmethod # Private static method
|
|
185
|
+
def __massflow_std__(blade_rows:List[BladeRow]):
|
|
186
|
+
"""Returns the standard deviation of the massflow
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
blade_rows (List[BladeRow]): List of blade rows
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
_type_: _description_
|
|
193
|
+
"""
|
|
194
|
+
total_massflow = list(); s = 0; massflow_stage = list()
|
|
195
|
+
stage_ids = list(set([row.stage_id for row in blade_rows if row.stage_id>=0]))
|
|
196
|
+
for row in blade_rows[1:-1]: # Ignore inlet and outlet
|
|
197
|
+
total_massflow.append(row.total_massflow_no_coolant)
|
|
198
|
+
sign = 1
|
|
199
|
+
for s in stage_ids:
|
|
200
|
+
for row in blade_rows:
|
|
201
|
+
if row.stage_id == s and row.row_type == RowType.Rotor:
|
|
202
|
+
massflow_stage.append(sign*row.total_massflow_no_coolant)
|
|
203
|
+
sign*=-1
|
|
204
|
+
if len(stage_ids) % 2 == 1:
|
|
205
|
+
massflow_stage.append(massflow_stage[-1]*sign)
|
|
206
|
+
deviation = np.std(total_massflow)*2
|
|
207
|
+
if deviation>1.0:
|
|
208
|
+
print("high massflow deviation detected")
|
|
209
|
+
return np.std(total_massflow)*2 # + abs(sum(massflow_stage)) # Equation 28
|
|
210
|
+
|
|
180
211
|
def __balance_massflow(self):
|
|
181
212
|
""" Balances the massflow between rows. Use radial equilibrium.
|
|
182
213
|
|
|
@@ -193,24 +224,7 @@ class TurbineSpool(Spool):
|
|
|
193
224
|
3. Adjust the streamlines for each blade row to balance the massflow
|
|
194
225
|
"""
|
|
195
226
|
|
|
196
|
-
|
|
197
|
-
total_massflow = list(); s = 0; massflow_stage = list()
|
|
198
|
-
stage_ids = list(set([row.stage_id for row in self.blade_rows if row.stage_id>=0]))
|
|
199
|
-
for row in blade_rows[1:]:
|
|
200
|
-
total_massflow.append(row.total_massflow_no_coolant)
|
|
201
|
-
|
|
202
|
-
sign = 1
|
|
203
|
-
for s in stage_ids:
|
|
204
|
-
for row in blade_rows:
|
|
205
|
-
if row.stage_id == s and row.row_type == RowType.Rotor:
|
|
206
|
-
massflow_stage.append(sign*row.total_massflow_no_coolant)
|
|
207
|
-
sign*=-1
|
|
208
|
-
if len(stage_ids) % 2 == 1:
|
|
209
|
-
massflow_stage.append(massflow_stage[-1]*sign)
|
|
210
|
-
deviation = np.std(total_massflow)*2
|
|
211
|
-
if deviation>1.0:
|
|
212
|
-
print("high massflow deviation detected")
|
|
213
|
-
return np.std(total_massflow)*2 # + abs(sum(massflow_stage)) # Equation 28
|
|
227
|
+
|
|
214
228
|
|
|
215
229
|
# Balance the massflow between Stages
|
|
216
230
|
def balance_massflows(x0:List[float],blade_rows:List[List[BladeRow]],P0:npt.NDArray,P:npt.NDArray,balance_mean_pressure:bool=True):
|
|
@@ -232,27 +246,29 @@ class TurbineSpool(Spool):
|
|
|
232
246
|
Returns:
|
|
233
247
|
_type_: _description_
|
|
234
248
|
"""
|
|
249
|
+
blade_rows_backup = copy.deepcopy(blade_rows)
|
|
235
250
|
# try:
|
|
236
251
|
if balance_mean_pressure:
|
|
237
|
-
for j in range(self.num_streamlines):
|
|
252
|
+
for j in range(self.num_streamlines):
|
|
238
253
|
Ps_range = outlet_pressure(x0,P0[j],P[j])
|
|
239
|
-
for i in range(1,len(blade_rows)-
|
|
254
|
+
for i in range(1,len(blade_rows)-2):
|
|
240
255
|
blade_rows[i].P[j] = Ps_range[i-1]
|
|
241
|
-
blade_rows[-
|
|
256
|
+
blade_rows[-2].P = P
|
|
242
257
|
else:
|
|
243
258
|
for i in range(1,len(blade_rows)-1):
|
|
244
259
|
for j in range(self.num_streamlines):
|
|
245
260
|
blade_rows[i].P[j] = P[j]*x0[(i-1)*self.num_streamlines+j] # x0 size = num_streamlines -1
|
|
246
|
-
|
|
247
|
-
calculate_massflows(blade_rows,True)
|
|
261
|
+
# try:
|
|
262
|
+
calculate_massflows(blade_rows,True,self.fluid)
|
|
248
263
|
print(x0)
|
|
249
|
-
return
|
|
250
|
-
# except:
|
|
251
|
-
#
|
|
252
|
-
#
|
|
253
|
-
#
|
|
254
|
-
#
|
|
255
|
-
|
|
264
|
+
return self.__massflow_std__(blade_rows)
|
|
265
|
+
# except Exception as e:
|
|
266
|
+
# print(e)
|
|
267
|
+
# finally:
|
|
268
|
+
# blade_rows = blade_rows_backup
|
|
269
|
+
# return np.inf # Return a high error
|
|
270
|
+
|
|
271
|
+
|
|
256
272
|
# Break apart the rows to stages
|
|
257
273
|
outlet_P=list(); outlet_P_guess = list() # Outlet P is the bounds, outlet_p_guess is the guessed values
|
|
258
274
|
|
|
@@ -260,60 +276,38 @@ class TurbineSpool(Spool):
|
|
|
260
276
|
outlet_P.append(self.blade_rows[i].inlet_to_outlet_pratio)
|
|
261
277
|
outlet_P_guess.append(np.mean(self.blade_rows[i].inlet_to_outlet_pratio))
|
|
262
278
|
|
|
263
|
-
print(
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
279
|
+
print(f"Looping to converge massflow")
|
|
280
|
+
past_err = -100; loop_iter = 0; err = 0.001
|
|
281
|
+
while np.abs((err-past_err)/err)>0.05:
|
|
282
|
+
if len(outlet_P) == 1:
|
|
283
|
+
# x = balance_massflows(0.658,self.blade_rows[:-1],self.blade_rows[0].P0,self.blade_rows[-1].P)
|
|
284
|
+
res = minimize_scalar(fun=balance_massflows,args=(self.blade_rows,self.blade_rows[0].P0,self.blade_rows[-1].P),bounds=outlet_P[0],tol=0.001,options={'disp': True},method='bounded')
|
|
285
|
+
x = res.x
|
|
286
|
+
print(x)
|
|
287
|
+
else:
|
|
288
|
+
x = fmin_slsqp(func=balance_massflows,args=(self.blade_rows,self.blade_rows[0].P0,self.blade_rows[-1].P),
|
|
289
|
+
bounds=outlet_P, x0=outlet_P_guess,epsilon=0.001,iter=100) # ,tol=0.001,options={'disp': True})
|
|
290
|
+
outlet_P_guess = x
|
|
272
291
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
292
|
+
# Adjust the inlet: Set the massflow
|
|
293
|
+
self.blade_rows[0].massflow = np.linspace(0,1,self.num_streamlines)*self.blade_rows[1].total_massflow_no_coolant
|
|
294
|
+
self.blade_rows[0].total_massflow_no_coolant = self.blade_rows[1].total_massflow_no_coolant
|
|
295
|
+
self.blade_rows[0].total_massflow = np.linspace(0,1,self.num_streamlines)*self.blade_rows[1].total_massflow_no_coolant
|
|
296
|
+
self.blade_rows[0].calculated_massflow = self.blade_rows[0].total_massflow_no_coolant
|
|
297
|
+
inlet_calc(self.blade_rows[0]) # adjust the inlet to match massflow
|
|
276
298
|
|
|
277
|
-
|
|
278
|
-
for _ in range(2):
|
|
299
|
+
if self.adjust_streamlines:
|
|
279
300
|
adjust_streamlines(self.blade_rows[:-1],self.passage)
|
|
280
|
-
|
|
281
|
-
self.blade_rows[-1].P = self.blade_rows[-1].get_static_pressure(self.blade_rows[-1].percent_hub_shroud)
|
|
282
|
-
balance_massflows(x,self.blade_rows[:-1],self.blade_rows[0].P0,self.blade_rows[-1].P)
|
|
283
|
-
else:
|
|
301
|
+
|
|
284
302
|
self.blade_rows[-1].transfer_quantities(self.blade_rows[-2])
|
|
285
303
|
self.blade_rows[-1].P = self.blade_rows[-1].get_static_pressure(self.blade_rows[-1].percent_hub_shroud)
|
|
286
|
-
|
|
287
|
-
|
|
304
|
+
|
|
305
|
+
err = self.__massflow_std__(self.blade_rows)
|
|
306
|
+
print(f"Loop {loop_iter} massflow convergenced error:{err}")
|
|
288
307
|
|
|
289
308
|
# calculate Reynolds number
|
|
290
309
|
compute_reynolds(self.blade_rows,self.passage)
|
|
291
310
|
|
|
292
|
-
# finetune = True
|
|
293
|
-
# if finetune:
|
|
294
|
-
# print('Finetune static pressure between stages')
|
|
295
|
-
# self.blade_rows[-1].transfer_quantities(self.blade_rows[-2])
|
|
296
|
-
# self.blade_rows[-1].P = self.blade_rows[-1].get_static_pressure(self.blade_rows[-1].percent_hub_shroud)
|
|
297
|
-
# P = [row.P for row in self.blade_rows] # Average static pressures
|
|
298
|
-
# bounds = []; guess = []
|
|
299
|
-
# for i in range(1,len(self.blade_rows)-1): # set the bounds
|
|
300
|
-
# for j in range(self.num_streamlines):
|
|
301
|
-
# bounds.append([0.8,1.2]) # vary by +- 20%
|
|
302
|
-
# guess.append(1)
|
|
303
|
-
|
|
304
|
-
# x = fmin_slsqp(func=balance_massflows,args=(self.blade_rows[:-1],self.blade_rows[0].P0,P,False),
|
|
305
|
-
# bounds=bounds, x0=guess,epsilon=0.001,iter=200)
|
|
306
|
-
# P = [row.P for row in self.blade_rows] # Average static pressures
|
|
307
|
-
|
|
308
|
-
# for _ in range(2):
|
|
309
|
-
# adjust_streamlines(self.blade_rows[:-1],self.passage)
|
|
310
|
-
# self.blade_rows[-1].transfer_quantities(self.blade_rows[-2])
|
|
311
|
-
# self.blade_rows[-1].P = self.blade_rows[-1].get_static_pressure(self.blade_rows[-1].percent_hub_shroud)
|
|
312
|
-
# balance_massflows(x,self.blade_rows[:-1],self.blade_rows[0].P0,P,False)
|
|
313
|
-
|
|
314
|
-
# err = calculate_error(self.blade_rows[:-1])
|
|
315
|
-
# print(f"Massflow convergenced error after finetuning:{err}")
|
|
316
|
-
|
|
317
311
|
|
|
318
312
|
def export_properties(self,filename:str="turbine_spool.json"):
|
|
319
313
|
"""Export the spool object to json
|
|
@@ -327,7 +321,7 @@ class TurbineSpool(Spool):
|
|
|
327
321
|
total_static_efficiency = list()
|
|
328
322
|
stage_loading = list()
|
|
329
323
|
euler_power = list()
|
|
330
|
-
|
|
324
|
+
enthalpy_power = list()
|
|
331
325
|
x_streamline = np.zeros((self.num_streamlines,len(self.blade_rows)))
|
|
332
326
|
r_streamline = np.zeros((self.num_streamlines,len(self.blade_rows)))
|
|
333
327
|
massflow = list()
|
|
@@ -342,14 +336,14 @@ class TurbineSpool(Spool):
|
|
|
342
336
|
|
|
343
337
|
stage_loading.append(row.stage_loading)
|
|
344
338
|
euler_power.append(row.euler_power)
|
|
345
|
-
|
|
339
|
+
enthalpy_power.append(row.power)
|
|
346
340
|
if row.row_type!=RowType.Inlet and row.row_type!=RowType.Outlet:
|
|
347
341
|
massflow.append(row.massflow[-1])
|
|
348
342
|
|
|
349
343
|
for j,p in enumerate(row.percent_hub_shroud):
|
|
350
344
|
t,x,r = self.passage.get_streamline(p)
|
|
351
|
-
x_streamline[j,indx] = float(interp1d(t,x)(row.
|
|
352
|
-
r_streamline[j,indx] = float(interp1d(t,r)(row.
|
|
345
|
+
x_streamline[j,indx] = float(interp1d(t,x)(row.percent_hub))
|
|
346
|
+
r_streamline[j,indx] = float(interp1d(t,r)(row.percent_hub))
|
|
353
347
|
|
|
354
348
|
Pratio_Total_Total = np.mean(self.blade_rows[0].P0 / self.blade_rows[-2].P0)
|
|
355
349
|
Pratio_Total_Static = np.mean(self.blade_rows[0].P0 / self.blade_rows[-2].P)
|
|
@@ -368,6 +362,8 @@ class TurbineSpool(Spool):
|
|
|
368
362
|
"xhub":self.passage.xhub_pts.tolist(),
|
|
369
363
|
"xshroud":self.passage.xshroud_pts.tolist(),
|
|
370
364
|
"num_streamlines":self.num_streamlines,
|
|
365
|
+
"euler_power": euler_power,
|
|
366
|
+
"enthalpy_power":enthalpy_power,
|
|
371
367
|
"total-total_efficiency":total_total_efficiency,
|
|
372
368
|
"total-static_efficiency":total_static_efficiency,
|
|
373
369
|
"stage_loading":stage_loading,
|
|
@@ -389,7 +385,7 @@ class TurbineSpool(Spool):
|
|
|
389
385
|
json.dump(data, f, indent=4,cls=NumpyEncoder)
|
|
390
386
|
|
|
391
387
|
|
|
392
|
-
def calculate_massflows(blade_rows:List[BladeRow],calculate_vm:bool=False):
|
|
388
|
+
def calculate_massflows(blade_rows:List[BladeRow],calculate_vm:bool=False,fluid:Solution=None):
|
|
393
389
|
"""Calculates the massflow
|
|
394
390
|
|
|
395
391
|
Args:
|
|
@@ -398,7 +394,7 @@ def calculate_massflows(blade_rows:List[BladeRow],calculate_vm:bool=False):
|
|
|
398
394
|
calculate_vm (bool, optional): _description_. Defaults to False.
|
|
399
395
|
"""
|
|
400
396
|
for p in range(3):
|
|
401
|
-
for i in range(1,len(blade_rows)):
|
|
397
|
+
for i in range(1,len(blade_rows)-1):
|
|
402
398
|
row = blade_rows[i]
|
|
403
399
|
# Upstream Row
|
|
404
400
|
if i == 0:
|
|
@@ -407,8 +403,6 @@ def calculate_massflows(blade_rows:List[BladeRow],calculate_vm:bool=False):
|
|
|
407
403
|
upstream = blade_rows[i-1]
|
|
408
404
|
if i<len(blade_rows)-1:
|
|
409
405
|
downstream = blade_rows[i+1]
|
|
410
|
-
else:
|
|
411
|
-
downstream = None
|
|
412
406
|
|
|
413
407
|
# Pressure loss = shift in entropy which affects the total pressure of the row
|
|
414
408
|
if row.row_type == RowType.Inlet:
|
|
@@ -420,16 +414,16 @@ def calculate_massflows(blade_rows:List[BladeRow],calculate_vm:bool=False):
|
|
|
420
414
|
if row.row_type == RowType.Rotor:
|
|
421
415
|
rotor_calc(row,upstream,calculate_vm=True)
|
|
422
416
|
# Finds Equilibrium between Vm, P0, T0
|
|
423
|
-
row = radeq(row,upstream)
|
|
424
|
-
|
|
417
|
+
row = radeq(row,upstream,downstream)
|
|
418
|
+
compute_gas_constants(row,fluid)
|
|
425
419
|
rotor_calc(row,upstream,calculate_vm=False)
|
|
426
420
|
elif row.row_type == RowType.Stator:
|
|
427
421
|
stator_calc(row,upstream,downstream,calculate_vm=True)
|
|
428
422
|
# Finds Equilibrium between Vm, P0, T0
|
|
429
|
-
row = radeq(row,upstream)
|
|
430
|
-
|
|
423
|
+
row = radeq(row,upstream,downstream)
|
|
424
|
+
compute_gas_constants(row,fluid)
|
|
431
425
|
stator_calc(row,upstream,downstream,calculate_vm=False)
|
|
432
|
-
|
|
426
|
+
compute_gas_constants(row,fluid)
|
|
433
427
|
compute_massflow(row)
|
|
434
428
|
compute_power(row,upstream)
|
|
435
429
|
|
|
@@ -442,7 +436,7 @@ def calculate_massflows(blade_rows:List[BladeRow],calculate_vm:bool=False):
|
|
|
442
436
|
row.Yp = Yp
|
|
443
437
|
rotor_calc(row,upstream,calculate_vm=True)
|
|
444
438
|
row = radeq(row,upstream)
|
|
445
|
-
|
|
439
|
+
compute_gas_constants(row,fluid)
|
|
446
440
|
rotor_calc(row,upstream,calculate_vm=False)
|
|
447
441
|
return abs(row.eta_total - eta_total)
|
|
448
442
|
|
|
@@ -452,13 +446,13 @@ def calculate_massflows(blade_rows:List[BladeRow],calculate_vm:bool=False):
|
|
|
452
446
|
row.Yp = 0
|
|
453
447
|
stator_calc(row,upstream,downstream,calculate_vm=True)
|
|
454
448
|
row = radeq(row,upstream)
|
|
455
|
-
row = compute_gas_constants(row)
|
|
449
|
+
row = compute_gas_constants(row,fluid)
|
|
456
450
|
stator_calc(row,upstream,downstream,calculate_vm=False)
|
|
457
|
-
row = compute_gas_constants(row)
|
|
451
|
+
row = compute_gas_constants(row,fluid)
|
|
458
452
|
compute_massflow(row)
|
|
459
453
|
compute_power(row,upstream)
|
|
460
454
|
|
|
461
|
-
def massflow_loss_function(exit_angle:float,index:int,row:BladeRow,upstream:BladeRow,downstream:BladeRow=None):
|
|
455
|
+
def massflow_loss_function(exit_angle:float,index:int,row:BladeRow,upstream:BladeRow,downstream:BladeRow=None,fluid:Solution=None):
|
|
462
456
|
"""Finds the blade exit angles that balance the massflow throughout the stage
|
|
463
457
|
|
|
464
458
|
Args:
|
|
@@ -483,8 +477,8 @@ def massflow_loss_function(exit_angle:float,index:int,row:BladeRow,upstream:Blad
|
|
|
483
477
|
elif row.row_type == RowType.Stator:
|
|
484
478
|
row.alpha2[index] = np.radians(exit_angle)
|
|
485
479
|
stator_calc(row,upstream,downstream)
|
|
486
|
-
upstream = compute_gas_constants(upstream)
|
|
487
|
-
row = compute_gas_constants(row)
|
|
480
|
+
upstream = compute_gas_constants(upstream,fluid)
|
|
481
|
+
row = compute_gas_constants(row,fluid)
|
|
488
482
|
elif row.loss_function.loss_type == LossType.Enthalpy:
|
|
489
483
|
# Search for pressure loss that results in the correct total temperature drop
|
|
490
484
|
if row.row_type == RowType.Rotor:
|
|
@@ -496,8 +490,8 @@ def massflow_loss_function(exit_angle:float,index:int,row:BladeRow,upstream:Blad
|
|
|
496
490
|
def find_yp(Yp):
|
|
497
491
|
row.Yp = Yp
|
|
498
492
|
rotor_calc(row,upstream)
|
|
499
|
-
upstream = compute_gas_constants(upstream)
|
|
500
|
-
row = compute_gas_constants(row)
|
|
493
|
+
upstream = compute_gas_constants(upstream,fluid)
|
|
494
|
+
row = compute_gas_constants(row,fluid)
|
|
501
495
|
return abs(row.T0.mean() - T0_target)
|
|
502
496
|
res = minimize_scalar(find_yp,bounds=[0,0.6])
|
|
503
497
|
row.Yp = res.x
|
|
@@ -505,8 +499,8 @@ def massflow_loss_function(exit_angle:float,index:int,row:BladeRow,upstream:Blad
|
|
|
505
499
|
row.Yp = 0
|
|
506
500
|
row.alpha2[index] = np.radians(exit_angle)
|
|
507
501
|
stator_calc(row,upstream,downstream)
|
|
508
|
-
upstream = compute_gas_constants(upstream)
|
|
509
|
-
row = compute_gas_constants(row)
|
|
502
|
+
upstream = compute_gas_constants(upstream,fluid)
|
|
503
|
+
row = compute_gas_constants(row,fluid)
|
|
510
504
|
|
|
511
505
|
|
|
512
506
|
# if use_radeq:
|
|
@@ -516,12 +510,12 @@ def massflow_loss_function(exit_angle:float,index:int,row:BladeRow,upstream:Blad
|
|
|
516
510
|
compute_power(row,upstream)
|
|
517
511
|
|
|
518
512
|
if row.row_type!=RowType.Inlet:
|
|
519
|
-
if row.row_type == RowType.Rotor:
|
|
520
|
-
|
|
521
|
-
else:
|
|
522
|
-
|
|
513
|
+
# if row.row_type == RowType.Rotor:
|
|
514
|
+
T3_is = upstream.T0 * (1/row.P0_P)**((row.gamma-1)/row.gamma)
|
|
515
|
+
# else:
|
|
516
|
+
# T3_is = upstream.T0 * (row.P0/row.P)**((row.gamma-1)/row.gamma)
|
|
523
517
|
a = np.sqrt(row.gamma*row.R*T3_is)
|
|
524
|
-
T03_is = T3_is * (1+(row.gamma-1)/2*(row.
|
|
518
|
+
T03_is = T3_is * (1+(row.gamma-1)/2*(row.V/a)**2)
|
|
525
519
|
row.eta_total = (upstream.T0.mean() - row.T0.mean())/(upstream.T0.mean()-T03_is.mean())
|
|
526
520
|
|
|
527
521
|
return np.abs(row.total_massflow*index/(len(row.massflow)-1) - row.massflow[index])
|