turbo-design 1.0.0__py2.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.0.0.dist-info/METADATA +25 -0
- turbo_design-1.0.0.dist-info/RECORD +33 -0
- turbo_design-1.0.0.dist-info/WHEEL +4 -0
- turbodesign/__init__.py +9 -0
- turbodesign/arrayfuncs.py +19 -0
- turbodesign/bladerow.py +614 -0
- turbodesign/cantera_gas/co2.yaml +36 -0
- turbodesign/compressorspool.py +60 -0
- turbodesign/coolant.py +10 -0
- turbodesign/enums.py +36 -0
- turbodesign/inlet.py +146 -0
- turbodesign/isentropic.py +82 -0
- turbodesign/loss/__init__.py +1 -0
- turbodesign/loss/compressor/__init__.py +1 -0
- turbodesign/loss/losstype.py +25 -0
- turbodesign/loss/turbine/TD2.py +142 -0
- turbodesign/loss/turbine/__init__.py +8 -0
- turbodesign/loss/turbine/ainleymathieson.py +118 -0
- turbodesign/loss/turbine/craigcox.py +189 -0
- turbodesign/loss/turbine/fixedefficiency.py +29 -0
- turbodesign/loss/turbine/fixedpressureloss.py +25 -0
- turbodesign/loss/turbine/kackerokapuu.py +124 -0
- turbodesign/loss/turbine/traupel.py +95 -0
- turbodesign/lossinterp.py +178 -0
- turbodesign/outlet.py +56 -0
- turbodesign/passage.py +198 -0
- turbodesign/radeq.py +255 -0
- turbodesign/rotor.py +38 -0
- turbodesign/solve_radeq.py +37 -0
- turbodesign/spool.py +289 -0
- turbodesign/stage.py +7 -0
- turbodesign/td_math.py +388 -0
- turbodesign/turbinespool.py +466 -0
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
from cantera.composite import Solution
|
|
3
|
+
from .bladerow import BladeRow, interpolate_streamline_radii
|
|
4
|
+
from .enums import RowType, MassflowConstraint, LossType, PassageType
|
|
5
|
+
from .spool import Spool
|
|
6
|
+
import json
|
|
7
|
+
from .passage import Passage
|
|
8
|
+
from scipy.interpolate import interp1d
|
|
9
|
+
import numpy as np
|
|
10
|
+
import numpy.typing as npt
|
|
11
|
+
from .td_math import inlet_calc,rotor_calc, stator_calc, compute_massflow, compute_power, compute_gas_constants
|
|
12
|
+
from .solve_radeq import adjust_streamlines, radeq
|
|
13
|
+
from scipy.optimize import minimize_scalar, minimize, fmin_slsqp
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TurbineSpool(Spool):
|
|
17
|
+
def __init__(self,passage:Passage,
|
|
18
|
+
massflow:float,rows:List[BladeRow],
|
|
19
|
+
num_streamlines:int=3,
|
|
20
|
+
fluid:Solution=Solution('air.yaml'),
|
|
21
|
+
rpm:float=-1,
|
|
22
|
+
massflow_constraint:MassflowConstraint=MassflowConstraint.MatchMassFlow):
|
|
23
|
+
"""Initializes a Turbine Spool
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
passage (Passage): Passage defining hub and shroud
|
|
27
|
+
massflow (float): massflow at spool inlet
|
|
28
|
+
rows (List[BladeRow], optional): List of blade rows. Defaults to List[BladeRow].
|
|
29
|
+
num_streamlines (int, optional): number of streamlines. Defaults to 3.
|
|
30
|
+
fluid (ct.Solution, optional): cantera gas solution. Defaults to ct.Solution('air.yaml').
|
|
31
|
+
rpm (float, optional): RPM for the entire spool Optional, you can also set rpm of the blade rows individually. Defaults to -1.
|
|
32
|
+
massflow_constraint (MassflowConstraint, optional): MatchMassflow - Matches the massflow defined in the spool. BalanceMassflow - Balances the massflow between BladeRows, matches the lowest massflow.
|
|
33
|
+
|
|
34
|
+
"""
|
|
35
|
+
super().__init__(passage, massflow, rows,num_streamlines, fluid, rpm)
|
|
36
|
+
self.massflow_constraint = massflow_constraint
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
def initialize_quantities(self):
|
|
40
|
+
"""Initializes the massflow throughout the rows
|
|
41
|
+
"""
|
|
42
|
+
# Massflow from inlet already defined
|
|
43
|
+
|
|
44
|
+
# Inlet
|
|
45
|
+
W0 = self.massflow
|
|
46
|
+
inlet = self.blade_rows[0]
|
|
47
|
+
if self.blade_rows[0].row_type == RowType.Inlet:
|
|
48
|
+
self.blade_rows[0].massflow = np.linspace(0,1,self.num_streamlines)*W0
|
|
49
|
+
self.blade_rows[0].total_massflow = W0
|
|
50
|
+
self.blade_rows[0].total_massflow_no_coolant = W0
|
|
51
|
+
|
|
52
|
+
interpolate_streamline_radii(self.blade_rows[0],self.passage)
|
|
53
|
+
|
|
54
|
+
# Set the gas to total values for now
|
|
55
|
+
self.blade_rows[0].fluid.TP = self.blade_rows[0].T0.mean(), self.blade_rows[0].P0.mean()
|
|
56
|
+
self.blade_rows[0].Cp = self.blade_rows[0].fluid.cp
|
|
57
|
+
self.blade_rows[0].Cv = self.blade_rows[0].fluid.cv
|
|
58
|
+
self.blade_rows[0].R = self.blade_rows[0].Cp-self.blade_rows[0].Cv
|
|
59
|
+
self.blade_rows[0].gamma = self.blade_rows[0].Cp/self.blade_rows[0].Cv
|
|
60
|
+
self.blade_rows[0].rho[:] = self.blade_rows[0].fluid.density
|
|
61
|
+
inlet_calc(self.blade_rows[0])
|
|
62
|
+
|
|
63
|
+
for row in self.blade_rows:
|
|
64
|
+
interpolate_streamline_radii(row,self.passage)
|
|
65
|
+
|
|
66
|
+
outlet = self.blade_rows[-1]
|
|
67
|
+
for j in range(self.num_streamlines):
|
|
68
|
+
P0 = inlet.get_total_pressure(inlet.percent_hub_shroud[j])
|
|
69
|
+
percents = np.zeros(shape=(len(self.blade_rows)-2)) + 0.3
|
|
70
|
+
percents[-1] = 1
|
|
71
|
+
Ps_range = outlet_pressure(percents=percents,inletP0=inlet.P0[j],outletP=outlet.P[j])
|
|
72
|
+
for i in range(1,len(self.blade_rows)-1):
|
|
73
|
+
self.blade_rows[i].P[j] = Ps_range[i-1]
|
|
74
|
+
|
|
75
|
+
# Pass T0 and P0 to all the other blade_rows
|
|
76
|
+
for i in range(1,len(self.blade_rows)-1):
|
|
77
|
+
upstream = self.blade_rows[i-1] # Inlet conditions solved before this step
|
|
78
|
+
if i+1<len(self.blade_rows):
|
|
79
|
+
downstream = self.blade_rows[i+1]
|
|
80
|
+
else:
|
|
81
|
+
downstream = None
|
|
82
|
+
|
|
83
|
+
row = self.blade_rows[i]
|
|
84
|
+
if (row.coolant is not None):
|
|
85
|
+
T0c = self.blade_rows[i].coolant.fluid.T
|
|
86
|
+
P0c = self.blade_rows[i].coolant.fluid.P
|
|
87
|
+
W0c = self.blade_rows[i].coolant.massflow_percentage * self.massflow
|
|
88
|
+
Cpc = self.blade_rows[i].coolant.fluid.cp
|
|
89
|
+
else:
|
|
90
|
+
T0c = 100
|
|
91
|
+
P0c = 0
|
|
92
|
+
W0c = 0
|
|
93
|
+
Cpc = 0
|
|
94
|
+
|
|
95
|
+
T0 = upstream.T0
|
|
96
|
+
P0 = upstream.P0
|
|
97
|
+
Cp = upstream.Cp
|
|
98
|
+
|
|
99
|
+
T0 = (W0*Cp*T0 + W0c*Cpc*T0c)/(Cpc * W0c + Cp*W0)
|
|
100
|
+
P0 = (W0*Cp*P0 + W0c*Cpc*P0c)/(Cpc * W0c + Cp*W0)
|
|
101
|
+
Cp = (W0*Cp + W0c*Cpc)/(W0c + W0) # Weighted
|
|
102
|
+
|
|
103
|
+
if row.row_type == RowType.Stator:
|
|
104
|
+
T0 = upstream.T0
|
|
105
|
+
else:
|
|
106
|
+
T0 = upstream.T0 - row.power / (Cp*(W0 + W0c))
|
|
107
|
+
|
|
108
|
+
W0 += W0c
|
|
109
|
+
row.T0 = T0
|
|
110
|
+
row.P0 = P0
|
|
111
|
+
row.Cp = Cp
|
|
112
|
+
row.total_massflow = W0
|
|
113
|
+
row.massflow = np.linspace(0,1,self.num_streamlines)*row.total_massflow
|
|
114
|
+
# Pass Quantities: rho, P0, T0
|
|
115
|
+
|
|
116
|
+
row.rho = upstream.rho
|
|
117
|
+
row.gamma = upstream.gamma
|
|
118
|
+
row.R = upstream.R
|
|
119
|
+
|
|
120
|
+
if row.row_type == RowType.Stator:
|
|
121
|
+
stator_calc(row,upstream,downstream)
|
|
122
|
+
elif row.row_type == RowType.Rotor:
|
|
123
|
+
rotor_calc(row,upstream)
|
|
124
|
+
compute_massflow(row)
|
|
125
|
+
compute_power(row,upstream)
|
|
126
|
+
|
|
127
|
+
def solve(self):
|
|
128
|
+
"""
|
|
129
|
+
Solve for the exit flow angles to match the massflow distribution at the stage exit
|
|
130
|
+
"""
|
|
131
|
+
self.initialize_streamlines()
|
|
132
|
+
self.initialize_quantities()
|
|
133
|
+
|
|
134
|
+
if self.massflow_constraint ==MassflowConstraint.MatchMassFlow:
|
|
135
|
+
self.__match_massflow()
|
|
136
|
+
elif self.massflow_constraint == MassflowConstraint.BalanceMassFlow:
|
|
137
|
+
self.__balance_massflow()
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def __match_massflow(self):
|
|
141
|
+
""" Matches the massflow between streamtubes by changing exit angles. Doesn't use radial equilibrium.
|
|
142
|
+
"""
|
|
143
|
+
for _ in range(3):
|
|
144
|
+
# Step 1: Solve a blade row for exit angle to maintain massflow
|
|
145
|
+
for i in range(len(self.blade_rows)):
|
|
146
|
+
row = self.blade_rows[i]
|
|
147
|
+
# Upstream Row
|
|
148
|
+
if i == 0:
|
|
149
|
+
upstream = self.blade_rows[i]
|
|
150
|
+
else:
|
|
151
|
+
upstream = self.blade_rows[i-1]
|
|
152
|
+
if i<len(self.blade_rows)-1:
|
|
153
|
+
downstream = self.blade_rows[i+1]
|
|
154
|
+
else:
|
|
155
|
+
downstream = None
|
|
156
|
+
|
|
157
|
+
if row.row_type == RowType.Stator:
|
|
158
|
+
bounds = [0,80]
|
|
159
|
+
elif row.row_type == RowType.Rotor:
|
|
160
|
+
bounds = [-80,0]
|
|
161
|
+
if row.row_type != RowType.Inlet:
|
|
162
|
+
for j in range(1,self.num_streamlines):
|
|
163
|
+
res = minimize_scalar(massflow_loss_function, bounds=bounds,args=(j,row,upstream,downstream),tol=1E-2)
|
|
164
|
+
if row.row_type == RowType.Rotor:
|
|
165
|
+
row.beta2[j] = np.radians(res.x)
|
|
166
|
+
# Initialize the value at the hub to not upset the mean
|
|
167
|
+
row.beta2[0] = 1/(len(row.beta2)-1)*row.beta2[1:].sum()
|
|
168
|
+
elif row.row_type == RowType.Stator:
|
|
169
|
+
row.alpha2[j] = np.radians(res.x)
|
|
170
|
+
row.alpha2[0] = 1/(len(row.alpha2)-1)*row.alpha2[1:].sum()
|
|
171
|
+
upstream = compute_gas_constants(upstream)
|
|
172
|
+
row = compute_gas_constants(row)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
# Step 3: Adjust streamlines to evenly divide massflow
|
|
176
|
+
adjust_streamlines(self.blade_rows,self.passage)
|
|
177
|
+
|
|
178
|
+
def __balance_massflow(self):
|
|
179
|
+
""" Balances the massflow between rows. Use radial equilibrium.
|
|
180
|
+
|
|
181
|
+
Types of stages:
|
|
182
|
+
1. Stator - Rotor | Stator - Rotor
|
|
183
|
+
2. Rotor | Stator - Rotor | Stator - Rotor
|
|
184
|
+
3. Stator - Rotor | CounterRotating | Stator - Rotor
|
|
185
|
+
4. Rotor-Counter Rotating | Stator - Rotor
|
|
186
|
+
5. Counter Rotating - Rotor | Stator - Rotor
|
|
187
|
+
|
|
188
|
+
Steps:
|
|
189
|
+
1. Split the blade rows into stages stator-rotor pairs or rotor rotor or rotor
|
|
190
|
+
2. Change degree of reaction to match the total massflow
|
|
191
|
+
3. Adjust the streamlines for each blade row to balance the massflow
|
|
192
|
+
"""
|
|
193
|
+
|
|
194
|
+
# Balance the massflow between Stages
|
|
195
|
+
def balance_massflows(x0:List[float],blade_rows:List[List[BladeRow]],P0:npt.NDArray,P:npt.NDArray):
|
|
196
|
+
total_massflow = list(); massflow_stage = list()
|
|
197
|
+
stage_ids = list(set([row.stage_id for row in self.blade_rows if row.stage_id>=0])); s = 0
|
|
198
|
+
sign = 1
|
|
199
|
+
for j in range(self.num_streamlines):
|
|
200
|
+
Ps_range = outlet_pressure(x0,P0[j],P[j])
|
|
201
|
+
for i in range(1,len(blade_rows)-1):
|
|
202
|
+
blade_rows[i].P[j] = Ps_range[i-1]
|
|
203
|
+
blade_rows[-1].P = P
|
|
204
|
+
calculate_massflows(blade_rows,True)
|
|
205
|
+
for row in blade_rows[1:]:
|
|
206
|
+
total_massflow.append(row.total_massflow_no_coolant)
|
|
207
|
+
for s in stage_ids:
|
|
208
|
+
for row in blade_rows:
|
|
209
|
+
if row.stage_id == s and row.row_type == RowType.Rotor:
|
|
210
|
+
massflow_stage.append(sign*row.total_massflow_no_coolant)
|
|
211
|
+
sign*=-1
|
|
212
|
+
if len(stage_ids) % 2 == 1:
|
|
213
|
+
massflow_stage.append(massflow_stage[-1]*sign)
|
|
214
|
+
print(x0)
|
|
215
|
+
return np.std(total_massflow)*2 # + abs(sum(massflow_stage)) # Equation 28
|
|
216
|
+
|
|
217
|
+
# Break apart the rows to stages
|
|
218
|
+
outlet_P=list(); outlet_P_guess = list()
|
|
219
|
+
|
|
220
|
+
for i in range(1,len(self.blade_rows)-2):
|
|
221
|
+
outlet_P.append(self.blade_rows[i].inlet_to_outlet_pratio)
|
|
222
|
+
outlet_P_guess.append(np.mean(self.blade_rows[i].inlet_to_outlet_pratio))
|
|
223
|
+
|
|
224
|
+
if len(outlet_P) == 1:
|
|
225
|
+
res1 = minimize_scalar(fun=balance_massflows,args=(self.blade_rows[:-1],self.blade_rows[0].P0,self.blade_rows[-1].P),
|
|
226
|
+
bounds=outlet_P[0],tol=0.001,options={'disp': True})
|
|
227
|
+
x = res1.x
|
|
228
|
+
else:
|
|
229
|
+
x = fmin_slsqp(func=balance_massflows,args=(self.blade_rows[:-1],self.blade_rows[0].P0,self.blade_rows[-1].P),
|
|
230
|
+
bounds=outlet_P, x0=outlet_P_guess,epsilon=0.001,iter=100) # ,tol=0.001,options={'disp': True})
|
|
231
|
+
|
|
232
|
+
# Adjust the inlet: Set the massflow
|
|
233
|
+
self.blade_rows[0].massflow = np.linspace(0,1,self.num_streamlines)*self.blade_rows[1].total_massflow_no_coolant
|
|
234
|
+
inlet_calc(self.blade_rows[0]) # adjust the inlet to match massflow
|
|
235
|
+
for _ in range(3):
|
|
236
|
+
adjust_streamlines(self.blade_rows[:-1],self.passage)
|
|
237
|
+
self.blade_rows[-1].transfer_quantities(self.blade_rows[-2])
|
|
238
|
+
self.blade_rows[-1].P = self.blade_rows[-1].get_static_pressure(self.blade_rows[-1].percent_hub_shroud)
|
|
239
|
+
err = balance_massflows(x,self.blade_rows[:-1],self.blade_rows[0].P0,self.blade_rows[-1].P)
|
|
240
|
+
if err>5E-2:
|
|
241
|
+
print(f"Massflow is not convergenced error:{err}")
|
|
242
|
+
else:
|
|
243
|
+
print(f"Massflow converged to less than 0.05kg/s error:{err}")
|
|
244
|
+
|
|
245
|
+
def export_properties(self,filename:str="turbine_spool.json"):
|
|
246
|
+
"""Export the spool object to json
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
filename (str, optional): name of export file. Defaults to "spool.json".
|
|
250
|
+
"""
|
|
251
|
+
blade_rows = list()
|
|
252
|
+
degree_of_reaction = list()
|
|
253
|
+
total_total_efficiency = list()
|
|
254
|
+
total_static_efficiency = list()
|
|
255
|
+
stage_loading = list()
|
|
256
|
+
euler_power = list()
|
|
257
|
+
|
|
258
|
+
x_streamline = np.zeros((self.num_streamlines,len(self.blade_rows)))
|
|
259
|
+
r_streamline = np.zeros((self.num_streamlines,len(self.blade_rows)))
|
|
260
|
+
massflow = list()
|
|
261
|
+
for indx,row in enumerate(self.blade_rows):
|
|
262
|
+
blade_rows.append(row.to_dict()) # Appending data
|
|
263
|
+
if row.row_type == RowType.Rotor:
|
|
264
|
+
# Calculation for these are specific to Turbines
|
|
265
|
+
degree_of_reaction.append(((self.blade_rows[indx-1].P- row.P)/(self.blade_rows[indx-2].P-row.P)).mean())
|
|
266
|
+
|
|
267
|
+
total_total_efficiency.append(row.eta_total)
|
|
268
|
+
total_static_efficiency.append(row.eta_static)
|
|
269
|
+
|
|
270
|
+
stage_loading.append(row.stage_loading)
|
|
271
|
+
euler_power.append(row.euler_power)
|
|
272
|
+
|
|
273
|
+
if row.row_type!=RowType.Inlet and row.row_type!=RowType.Outlet:
|
|
274
|
+
massflow.append(row.massflow[-1])
|
|
275
|
+
|
|
276
|
+
for j,p in enumerate(row.percent_hub_shroud):
|
|
277
|
+
t,x,r = self.passage.get_streamline(p)
|
|
278
|
+
x_streamline[j,indx] = float(interp1d(t,x)(row.axial_location))
|
|
279
|
+
r_streamline[j,indx] = float(interp1d(t,r)(row.axial_location))
|
|
280
|
+
|
|
281
|
+
data = {
|
|
282
|
+
"blade_rows": blade_rows,
|
|
283
|
+
"massflow":np.mean(massflow),
|
|
284
|
+
"rpm":self.rpm,
|
|
285
|
+
"r_streamline":r_streamline.tolist(),
|
|
286
|
+
"x_streamline":x_streamline.tolist(),
|
|
287
|
+
"rhub":self.passage.rhub_pts.tolist(),
|
|
288
|
+
"rshroud":self.passage.rshroud_pts.tolist(),
|
|
289
|
+
"xhub":self.passage.xhub_pts.tolist(),
|
|
290
|
+
"xshroud":self.passage.xshroud_pts.tolist(),
|
|
291
|
+
"num_streamlines":self.num_streamlines,
|
|
292
|
+
"total-total_efficiency":total_total_efficiency,
|
|
293
|
+
"total-static_efficiency":total_static_efficiency,
|
|
294
|
+
"stage_loading":stage_loading,
|
|
295
|
+
"degree_of_reaction":degree_of_reaction
|
|
296
|
+
}
|
|
297
|
+
# Dump all the Python objects into a single JSON file.
|
|
298
|
+
class NumpyEncoder(json.JSONEncoder):
|
|
299
|
+
def default(self, obj):
|
|
300
|
+
if isinstance(obj, np.ndarray):
|
|
301
|
+
return obj.tolist()
|
|
302
|
+
return super().default(obj)
|
|
303
|
+
|
|
304
|
+
with open(filename, "w") as f:
|
|
305
|
+
json.dump(data, f, indent=4,cls=NumpyEncoder)
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def calculate_massflows(blade_rows:List[BladeRow],calculate_vm:bool=False):
|
|
309
|
+
"""Calculates the massflow
|
|
310
|
+
|
|
311
|
+
Args:
|
|
312
|
+
blade_rows (List[BladeRow]): _description_
|
|
313
|
+
passage (Passage): _description_
|
|
314
|
+
calculate_vm (bool, optional): _description_. Defaults to False.
|
|
315
|
+
"""
|
|
316
|
+
for p in range(3):
|
|
317
|
+
for i in range(1,len(blade_rows)):
|
|
318
|
+
row = blade_rows[i]
|
|
319
|
+
# Upstream Row
|
|
320
|
+
if i == 0:
|
|
321
|
+
upstream = blade_rows[i]
|
|
322
|
+
else:
|
|
323
|
+
upstream = blade_rows[i-1]
|
|
324
|
+
if i<len(blade_rows)-1:
|
|
325
|
+
downstream = blade_rows[i+1]
|
|
326
|
+
else:
|
|
327
|
+
downstream = None
|
|
328
|
+
|
|
329
|
+
# Pressure loss = shift in entropy which affects the total pressure of the row
|
|
330
|
+
if row.row_type == RowType.Inlet:
|
|
331
|
+
row.Yp = 0
|
|
332
|
+
else:
|
|
333
|
+
if row.loss_function.loss_type == LossType.Pressure:
|
|
334
|
+
row.Yp = row.loss_function(row,upstream)
|
|
335
|
+
for _ in range(2):
|
|
336
|
+
if row.row_type == RowType.Rotor:
|
|
337
|
+
rotor_calc(row,upstream,calculate_vm=True)
|
|
338
|
+
# Finds Equilibrium between Vm, P0, T0
|
|
339
|
+
row = radeq(row,upstream)
|
|
340
|
+
row = compute_gas_constants(row)
|
|
341
|
+
rotor_calc(row,upstream,calculate_vm=False)
|
|
342
|
+
elif row.row_type == RowType.Stator:
|
|
343
|
+
stator_calc(row,upstream,downstream,calculate_vm=True)
|
|
344
|
+
# Finds Equilibrium between Vm, P0, T0
|
|
345
|
+
row = radeq(row,upstream)
|
|
346
|
+
row = compute_gas_constants(row)
|
|
347
|
+
stator_calc(row,upstream,downstream,calculate_vm=False)
|
|
348
|
+
row = compute_gas_constants(row)
|
|
349
|
+
compute_massflow(row)
|
|
350
|
+
compute_power(row,upstream)
|
|
351
|
+
|
|
352
|
+
elif row.loss_function.loss_type == LossType.Enthalpy:
|
|
353
|
+
if row.row_type == RowType.Rotor:
|
|
354
|
+
row.Yp = 0
|
|
355
|
+
rotor_calc(row,upstream,calculate_vm=calculate_vm)
|
|
356
|
+
eta_total = float(row.loss_function(row,upstream))
|
|
357
|
+
def find_yp(Yp,row,upstream):
|
|
358
|
+
row.Yp = Yp
|
|
359
|
+
rotor_calc(row,upstream,calculate_vm=True)
|
|
360
|
+
row = radeq(row,upstream)
|
|
361
|
+
row = compute_gas_constants(row)
|
|
362
|
+
rotor_calc(row,upstream,calculate_vm=False)
|
|
363
|
+
return abs(row.eta_total - eta_total)
|
|
364
|
+
|
|
365
|
+
res = minimize_scalar(find_yp,bounds=[0,0.6],args=(row,upstream))
|
|
366
|
+
row.Yp = res.x
|
|
367
|
+
elif row.row_type == RowType.Stator:
|
|
368
|
+
row.Yp = 0
|
|
369
|
+
stator_calc(row,upstream,downstream,calculate_vm=True)
|
|
370
|
+
row = radeq(row,upstream)
|
|
371
|
+
row = compute_gas_constants(row)
|
|
372
|
+
stator_calc(row,upstream,downstream,calculate_vm=False)
|
|
373
|
+
row = compute_gas_constants(row)
|
|
374
|
+
compute_massflow(row)
|
|
375
|
+
compute_power(row,upstream)
|
|
376
|
+
|
|
377
|
+
def massflow_loss_function(exit_angle:float,index:int,row:BladeRow,upstream:BladeRow,downstream:BladeRow=None):
|
|
378
|
+
"""Finds the blade exit angles that balance the massflow throughout the stage
|
|
379
|
+
|
|
380
|
+
Args:
|
|
381
|
+
exit_angle (float): exit flow angle of the rotor row
|
|
382
|
+
index (int): streamline index for the current row
|
|
383
|
+
row (BladeRow): current blade row
|
|
384
|
+
upstream (BladeRow): upstream blade row
|
|
385
|
+
downstream (BladeRow): downstream blade row
|
|
386
|
+
|
|
387
|
+
Returns:
|
|
388
|
+
float: massflow loss
|
|
389
|
+
"""
|
|
390
|
+
# Pressure loss = shift in entropy which affects the total pressure of the row
|
|
391
|
+
if row.row_type == RowType.Inlet:
|
|
392
|
+
row.Yp = 0
|
|
393
|
+
else:
|
|
394
|
+
if row.loss_function.loss_type == LossType.Pressure:
|
|
395
|
+
row.Yp = row.loss_function(row,upstream)
|
|
396
|
+
if row.row_type == RowType.Rotor:
|
|
397
|
+
row.beta2[index] = np.radians(exit_angle)
|
|
398
|
+
rotor_calc(row,upstream)
|
|
399
|
+
elif row.row_type == RowType.Stator:
|
|
400
|
+
row.alpha2[index] = np.radians(exit_angle)
|
|
401
|
+
stator_calc(row,upstream,downstream)
|
|
402
|
+
upstream = compute_gas_constants(upstream)
|
|
403
|
+
row = compute_gas_constants(row)
|
|
404
|
+
elif row.loss_function.loss_type == LossType.Enthalpy:
|
|
405
|
+
# Search for pressure loss that results in the correct total temperature drop
|
|
406
|
+
if row.row_type == RowType.Rotor:
|
|
407
|
+
row.Yp = 0
|
|
408
|
+
row.beta2[index] = np.radians(exit_angle)
|
|
409
|
+
rotor_calc(row,upstream)
|
|
410
|
+
T0_drop = row.loss_function(row,upstream)
|
|
411
|
+
T0_target = row.T0.mean()-T0_drop
|
|
412
|
+
def find_yp(Yp):
|
|
413
|
+
row.Yp = Yp
|
|
414
|
+
rotor_calc(row,upstream)
|
|
415
|
+
upstream = compute_gas_constants(upstream)
|
|
416
|
+
row = compute_gas_constants(row)
|
|
417
|
+
return abs(row.T0.mean() - T0_target)
|
|
418
|
+
res = minimize_scalar(find_yp,bounds=[0,0.6])
|
|
419
|
+
row.Yp = res.x
|
|
420
|
+
elif row.row_type == RowType.Stator:
|
|
421
|
+
row.Yp = 0
|
|
422
|
+
row.alpha2[index] = np.radians(exit_angle)
|
|
423
|
+
stator_calc(row,upstream,downstream)
|
|
424
|
+
upstream = compute_gas_constants(upstream)
|
|
425
|
+
row = compute_gas_constants(row)
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
# if use_radeq:
|
|
429
|
+
# row = radeq(row,upstream) # Finds Equilibrium between Vm, P0, T0
|
|
430
|
+
|
|
431
|
+
compute_massflow(row)
|
|
432
|
+
compute_power(row,upstream)
|
|
433
|
+
|
|
434
|
+
if row.row_type!=RowType.Inlet:
|
|
435
|
+
if row.row_type == RowType.Rotor:
|
|
436
|
+
T3_is = upstream.T0 * (1/row.P0_P)**((row.gamma-1)/row.gamma)
|
|
437
|
+
else:
|
|
438
|
+
T3_is = upstream.T0 * (row.P0/row.P)**((row.gamma-1)/row.gamma)
|
|
439
|
+
a = np.sqrt(row.gamma*row.R*T3_is)
|
|
440
|
+
T03_is = T3_is * (1+(row.gamma-1)/2*(row.Vm/a)**2)
|
|
441
|
+
row.eta_total = (upstream.T0.mean() - row.T0.mean())/(upstream.T0.mean()-T03_is.mean())
|
|
442
|
+
|
|
443
|
+
return np.abs(row.total_massflow*index/(len(row.massflow)-1) - row.massflow[index])
|
|
444
|
+
|
|
445
|
+
def outlet_pressure(percents:List[float],inletP0:float,outletP:float) -> npt.NDArray:
|
|
446
|
+
"""Given a list of percents from 0 to 1 for each row, output each row's outlet static pressure
|
|
447
|
+
|
|
448
|
+
Args:
|
|
449
|
+
percents (List[float]): List of floats as percents [[0 to 1],[0 to 1]]
|
|
450
|
+
inletP0 (float): Inlet Total Pressure
|
|
451
|
+
outletP (float): Outlet Static Pressure
|
|
452
|
+
|
|
453
|
+
Returns:
|
|
454
|
+
npt.NDArray: Array of static pressures
|
|
455
|
+
"""
|
|
456
|
+
maxP = inletP0
|
|
457
|
+
minP = outletP
|
|
458
|
+
if isinstance(percents, float):
|
|
459
|
+
Ps = [percents*(minP-maxP)+maxP]
|
|
460
|
+
else:
|
|
461
|
+
Ps = np.zeros(shape=(len(percents),1)); i = 0
|
|
462
|
+
for p in percents:
|
|
463
|
+
Ps[i] = p*(minP-maxP)+maxP
|
|
464
|
+
maxP = Ps[i]
|
|
465
|
+
i+=1
|
|
466
|
+
return Ps
|