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
turbodesign/outlet.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from typing import List, Union
|
|
3
|
+
from .enums import RowType
|
|
4
|
+
from .bladerow import BladeRow, compute_gas_constants, interpolate_quantities
|
|
5
|
+
from .arrayfuncs import convert_to_ndarray
|
|
6
|
+
import numpy as np
|
|
7
|
+
import numpy.typing as npt
|
|
8
|
+
import copy
|
|
9
|
+
from scipy.interpolate import interp1d
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Outlet(BladeRow):
|
|
13
|
+
P_fun:interp1d
|
|
14
|
+
|
|
15
|
+
def __init__(self,P:Union[float,List[float]],percent_radii:List[float],num_streamlines:int=3):
|
|
16
|
+
"""Initialize the outlet
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
P (Union[float,List[float]]): List of static pressure profile at outlet
|
|
20
|
+
percent_radii (List[float]): Percent Radius from 0 to 1 or just put 0.5 and it uses all of it
|
|
21
|
+
"""
|
|
22
|
+
self.P = convert_to_ndarray(P)
|
|
23
|
+
self.percent_hub_shroud = convert_to_ndarray(percent_radii)
|
|
24
|
+
if len(self.percent_hub_shroud)==1:
|
|
25
|
+
self.percent_hub_shroud = np.arange(0,1,num_streamlines)
|
|
26
|
+
self.P_fun = interp1d(self.percent_hub_shroud,self.P)
|
|
27
|
+
self.row_type = RowType.Outlet
|
|
28
|
+
self.loss_function = None
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def transfer_quantities(self,upstream:BladeRow):
|
|
32
|
+
"""Transfer quantities from upstream row to outlet while maintaining the outlet static pressure
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
upstream (BladeRow): Upstream row, for turbines this is a rotor, for compressors this is a stator.
|
|
36
|
+
"""
|
|
37
|
+
self.__dict__ = upstream.__dict__.copy() # Copies P and hub shroud percentage
|
|
38
|
+
self.P_fun = interp1d(self.percent_hub_shroud,self.P)
|
|
39
|
+
self.row_type = RowType.Outlet
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def get_static_pressure(self,percent_hub_shroud:Union[float,npt.NDArray]):
|
|
43
|
+
"""Returns the static pressure at a certain percent hub_shroud
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
percent_hub_shroud (Union[float,npt.NDArray]): _description_
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
_type_: _description_
|
|
50
|
+
"""
|
|
51
|
+
if type(percent_hub_shroud) == float:
|
|
52
|
+
return float(self.P_fun(percent_hub_shroud))
|
|
53
|
+
else:
|
|
54
|
+
return self.P_fun(percent_hub_shroud)
|
|
55
|
+
|
|
56
|
+
|
turbodesign/passage.py
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
from typing import List, Tuple
|
|
2
|
+
import numpy as np
|
|
3
|
+
import numpy.typing as npt
|
|
4
|
+
from scipy.interpolate import interp1d
|
|
5
|
+
from pyturbo.helper import line2D
|
|
6
|
+
from .enums import PassageType
|
|
7
|
+
from scipy.optimize import minimize_scalar
|
|
8
|
+
from findiff import FinDiff
|
|
9
|
+
from pyturbo.helper import convert_to_ndarray
|
|
10
|
+
import matplotlib.pyplot as plt
|
|
11
|
+
|
|
12
|
+
class Passage:
|
|
13
|
+
xhub:interp1d
|
|
14
|
+
rhub:interp1d
|
|
15
|
+
xhub_pts:npt.NDArray
|
|
16
|
+
rhub_pts:npt.NDArray
|
|
17
|
+
|
|
18
|
+
xshroud:interp1d
|
|
19
|
+
rshroud:interp1d
|
|
20
|
+
xshroud_pts:npt.NDArray
|
|
21
|
+
rshroud_pts:npt.NDArray
|
|
22
|
+
|
|
23
|
+
n:int
|
|
24
|
+
passageType:PassageType
|
|
25
|
+
|
|
26
|
+
x_streamlines:npt.NDArray
|
|
27
|
+
r_streamlines:npt.NDArray
|
|
28
|
+
|
|
29
|
+
def __init__(self,xhub:List[float],rhub:List[float],
|
|
30
|
+
xshroud:List[float],rshroud:List[float],
|
|
31
|
+
passageType:PassageType=PassageType.Axial):
|
|
32
|
+
"""_summary_
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
xhub (List[float]): xhub coordinates
|
|
36
|
+
rhub (List[float]): rhub coordinates
|
|
37
|
+
xshroud (List[float]): xshroud coordinates
|
|
38
|
+
rshroud (List[float]): rshroud coordinates
|
|
39
|
+
passageType (PassageType, optional): Axial or Centrifugal. Defaults to PassageType.Axial.
|
|
40
|
+
"""
|
|
41
|
+
assert len(xhub) == len(xshroud), "xHub and xShroud should be the same length"
|
|
42
|
+
assert len(rhub) == len(rshroud), "rHub and rShroud should be the same length"
|
|
43
|
+
|
|
44
|
+
self.n = len(xhub)
|
|
45
|
+
t_streamline = np.linspace(0,1,len(xhub))
|
|
46
|
+
self.xhub = interp1d(t_streamline,xhub)
|
|
47
|
+
self.rhub = interp1d(t_streamline,rhub)
|
|
48
|
+
self.xshroud = interp1d(t_streamline,xshroud)
|
|
49
|
+
self.rshroud = interp1d(t_streamline,rshroud)
|
|
50
|
+
|
|
51
|
+
self.xhub_pts = convert_to_ndarray(xhub)
|
|
52
|
+
self.rhub_pts = convert_to_ndarray(rhub)
|
|
53
|
+
self.xshroud_pts = convert_to_ndarray(xshroud)
|
|
54
|
+
self.rshroud_pts = convert_to_ndarray(rshroud)
|
|
55
|
+
|
|
56
|
+
self.passageType = passageType
|
|
57
|
+
|
|
58
|
+
def get_streamline(self,t_radial:float) -> Tuple[npt.NDArray,npt.NDArray, npt.NDArray]:
|
|
59
|
+
"""Gets the streamline at a certain percent radius
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
t_radial (float): _description_
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
Tuple containing:
|
|
66
|
+
|
|
67
|
+
t_streamline (npt.NDArray): Non dimensional length of the streamline
|
|
68
|
+
x_streamline (npt.NDArray): x-coordinate along the streamline
|
|
69
|
+
r_streamline (npt.NDArray): r-coordinate along the streamline
|
|
70
|
+
"""
|
|
71
|
+
t_streamline = np.linspace(0,1,self.n)
|
|
72
|
+
# first streamline is at the hub
|
|
73
|
+
r_streamline = t_streamline.copy()*0
|
|
74
|
+
x_streamline = t_streamline.copy()*0
|
|
75
|
+
for i,t in enumerate(t_streamline):
|
|
76
|
+
xhub = self.xhub(t)
|
|
77
|
+
rhub = self.rhub(t)
|
|
78
|
+
xshroud = self.xshroud(t)
|
|
79
|
+
rshroud = self.rshroud(t)
|
|
80
|
+
x_streamline[i],r_streamline[i] = line2D([xhub,rhub],[xshroud,rshroud]).get_point(t_radial)
|
|
81
|
+
|
|
82
|
+
return t_streamline,x_streamline,r_streamline
|
|
83
|
+
|
|
84
|
+
@staticmethod
|
|
85
|
+
def streamline_curvature(x_streamline:npt.NDArray,r_streamline:npt.NDArray) -> Tuple[npt.NDArray,npt.NDArray,npt.NDArray]:
|
|
86
|
+
"""Hub and casing values of streamline angles of inclination and curvature
|
|
87
|
+
|
|
88
|
+
x_streamline[axial,radial]
|
|
89
|
+
r_streamline[axial,radial]
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
x_streamline (np.ndarray): Axial position as a matrix with shape [number of stations, number of x-positions]
|
|
93
|
+
r_streamline (np.ndarray): Annulus Radii of streamlines arranged with shape [number of stations, number of x-positions]
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
Tuple: containing
|
|
97
|
+
|
|
98
|
+
*phi* (np.ndarray): Array containing angles of inclination for each radi at each station. Rows = radius, columns = station
|
|
99
|
+
*rm* (np.ndarray): Array containing the curvature for each station and streamline
|
|
100
|
+
*r* (np.ndarray): Annulus radius
|
|
101
|
+
|
|
102
|
+
References:
|
|
103
|
+
https://stackoverflow.com/questions/28269379/curve-curvature-in-numpy
|
|
104
|
+
|
|
105
|
+
"""
|
|
106
|
+
phi = np.zeros(shape=x_streamline.shape)
|
|
107
|
+
rm = phi.copy()
|
|
108
|
+
r = phi.copy()
|
|
109
|
+
|
|
110
|
+
d_dx = FinDiff(0,x_streamline,1)
|
|
111
|
+
d2_dx2 = FinDiff(0,x_streamline,2)
|
|
112
|
+
dr_dx = d_dx(r_streamline)
|
|
113
|
+
d2r_dx2 = d2_dx2(r_streamline)
|
|
114
|
+
|
|
115
|
+
radius_curvature = np.power((1+np.power(dr_dx,2)),1.5)
|
|
116
|
+
radius_curvature = np.divide(radius_curvature, np.abs(d2r_dx2))
|
|
117
|
+
radius_curvature = np.nan_to_num(radius_curvature,nan=0)
|
|
118
|
+
|
|
119
|
+
rm = radius_curvature # https://www.cuemath.com/radius-of-curvature-formula/ should be 1/curvature
|
|
120
|
+
phi = np.arctan(dr_dx)
|
|
121
|
+
r = r_streamline
|
|
122
|
+
|
|
123
|
+
return phi, rm, r
|
|
124
|
+
|
|
125
|
+
def get_cutting_line(self, t_hub:float) -> line2D:
|
|
126
|
+
"""Gets the cutting line between hub and shroud
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
t_hub (float): percentage along the axial direction
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
(Tuple) containing:
|
|
133
|
+
|
|
134
|
+
cut (line2D): line from hub to shroud
|
|
135
|
+
t_hub (float): t corresponding to xhub location
|
|
136
|
+
t_shroud (float): t corresponding to intersection of bisector of hub
|
|
137
|
+
|
|
138
|
+
"""
|
|
139
|
+
xhub = self.xhub(t_hub)
|
|
140
|
+
rhub = self.rhub(t_hub)
|
|
141
|
+
|
|
142
|
+
if t_hub>0 and t_hub<1:
|
|
143
|
+
dx = self.xhub(t_hub+0.0001) - self.xhub(t_hub-0.0001)
|
|
144
|
+
dr = self.rhub(t_hub+0.0001) - self.rhub(t_hub-0.0001)
|
|
145
|
+
elif t_hub>0:
|
|
146
|
+
dx = self.xhub(t_hub) - self.xhub(t_hub-0.0001)
|
|
147
|
+
dr = self.rhub(t_hub) - self.rhub(t_hub-0.0001)
|
|
148
|
+
elif t_hub<1:
|
|
149
|
+
dx = self.xhub(t_hub+0.0001) - self.xhub(t_hub)
|
|
150
|
+
dr = self.rhub(t_hub+0.0001) - self.rhub(t_hub)
|
|
151
|
+
|
|
152
|
+
if self.passageType == PassageType.Centrifugal:
|
|
153
|
+
if np.abs(dr)>1e-6:
|
|
154
|
+
# Draw a line perpendicular to the hub.
|
|
155
|
+
# Find the intersection point to the shroud.
|
|
156
|
+
h = -dx/dr # Slope of perpendicular line
|
|
157
|
+
|
|
158
|
+
f = lambda t: h*(self.xshroud(t) - xhub)+rhub # line from hub to shroud
|
|
159
|
+
fun = lambda t: np.abs(f(t)-self.rshroud(t)) # find where it intersects
|
|
160
|
+
res = minimize_scalar(fun,bounds=[0,1],tol=1E-3)
|
|
161
|
+
t_shroud = res.x
|
|
162
|
+
else:
|
|
163
|
+
t_shroud = t_hub # Vertical line
|
|
164
|
+
else:
|
|
165
|
+
t_shroud = t_hub
|
|
166
|
+
|
|
167
|
+
xshroud = self.xshroud(t_shroud)
|
|
168
|
+
rshroud = self.rshroud(t_shroud)
|
|
169
|
+
return line2D([xhub,rhub],[xshroud,rshroud]), t_hub, t_shroud
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
@property
|
|
173
|
+
def hub_length(self):
|
|
174
|
+
"""returns the computed length of the hub
|
|
175
|
+
Returns:
|
|
176
|
+
_type_: _description_
|
|
177
|
+
"""
|
|
178
|
+
return np.sum(np.sqrt(np.diff(self.xhub_pts)**2 + np.diff(self.rhub_pts)**2))
|
|
179
|
+
|
|
180
|
+
def plot_cuts(self,percent_axial:List[float]=[]):
|
|
181
|
+
"""_summary_
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
percent_axial (List[float], optional): _description_. Defaults to [].
|
|
185
|
+
"""
|
|
186
|
+
|
|
187
|
+
plt.figure(num=1,clear=True,dpi=150,figsize=(15,10))
|
|
188
|
+
plt.plot(self.xhub_pts,self.rhub_pts,label='hub',linestyle='solid',linewidth=2,color='black')
|
|
189
|
+
plt.plot(self.xshroud_pts,self.rshroud_pts,label='hub',linestyle='solid',linewidth=2,color='black')
|
|
190
|
+
for p in percent_axial:
|
|
191
|
+
cut,_,_ = self.get_cutting_line(p)
|
|
192
|
+
x,r = cut.get_point(np.linspace(0,1,10))
|
|
193
|
+
plt.plot(x,r,label=f'{p}',linestyle='dashed')
|
|
194
|
+
|
|
195
|
+
plt.legend()
|
|
196
|
+
plt.axis('scaled')
|
|
197
|
+
plt.show()
|
|
198
|
+
|
turbodesign/radeq.py
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
from scipy.interpolate import interp1d
|
|
2
|
+
from scipy.integrate import odeint
|
|
3
|
+
import numpy as np
|
|
4
|
+
from .bladerow import BladeRow
|
|
5
|
+
from .enums import RowType
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def radeq(row:BladeRow,upstream:BladeRow) -> BladeRow:
|
|
9
|
+
"""Solves the radial equilibrium equation for axial machines and returns the convergence.
|
|
10
|
+
|
|
11
|
+
Note:
|
|
12
|
+
This function will give you T0, P0, Vm as a function of the radius.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
row (BladeRow): Current row
|
|
16
|
+
upstream (BladeRow): Previous row
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
BladeRow: current row with T0, P0, and Vm calculated
|
|
20
|
+
"""
|
|
21
|
+
row_radius = row.r # Use these for gradient
|
|
22
|
+
up_radius = upstream.r
|
|
23
|
+
|
|
24
|
+
def ode_radeq_streamtube(r:np.ndarray,y:np.ndarray):
|
|
25
|
+
"""Solves the radial equilibrium equation for a streamtube
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
r (np.ndarray): radius not as a percent
|
|
29
|
+
y (np.ndarray): Array containing [P0,Vt,VtU,T0]
|
|
30
|
+
|
|
31
|
+
"""
|
|
32
|
+
P0 = y[0]
|
|
33
|
+
T0 = y[1]
|
|
34
|
+
Vm = y[2]
|
|
35
|
+
if r>row_radius[-1]:
|
|
36
|
+
return [0,0,0]
|
|
37
|
+
elif r<row_radius[0]:
|
|
38
|
+
return [0,0,0]
|
|
39
|
+
|
|
40
|
+
Cp = row.Cp
|
|
41
|
+
phi = interp1d(row_radius, row.phi)(r)
|
|
42
|
+
alpha = interp1d(row_radius, row.alpha2)(r)
|
|
43
|
+
rm = interp1d(row_radius, row.rm)(r)
|
|
44
|
+
rho = row.rho.mean()
|
|
45
|
+
|
|
46
|
+
if (row.row_type == RowType.Rotor):
|
|
47
|
+
omega = row.rpm*np.pi/30
|
|
48
|
+
U = omega*r
|
|
49
|
+
else:
|
|
50
|
+
omega = 0
|
|
51
|
+
U = 0
|
|
52
|
+
gamma = row.gamma
|
|
53
|
+
|
|
54
|
+
# Solve the Radial Equlibrium
|
|
55
|
+
Vt = Vm*np.cos(phi)*np.tan(alpha)
|
|
56
|
+
Vr = Vm*np.sin(phi)
|
|
57
|
+
# Estimations
|
|
58
|
+
dVm_dr = float(interp1d(row_radius, np.gradient(row.Vm, row_radius))(r))
|
|
59
|
+
dVt_dr = dVm_dr*np.cos(phi)*np.tan(alpha)
|
|
60
|
+
dVr_dr = 0 #dVm_dr*np.sin(phi)
|
|
61
|
+
|
|
62
|
+
# Upstream
|
|
63
|
+
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
|
+
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
|
|
65
|
+
|
|
66
|
+
dP0_dr = float(interp1d(row.percent_hub_shroud, np.gradient(row.P0,row_radius))((r-row_radius[0])/(row_radius[-1]-row_radius[0]))) # use percentage to get
|
|
67
|
+
|
|
68
|
+
U_up = float(interp1d(upstream.percent_hub_shroud,up_radius*omega)((r-row_radius[0])/(row_radius[-1]-row_radius[0]))) # use percentage to get the T0 upstream value
|
|
69
|
+
dVtup_dr = float(interp1d(upstream.percent_hub_shroud,np.gradient(upstream.Vt,up_radius))((r-row_radius[0])/(row_radius[-1]-row_radius[0])))
|
|
70
|
+
Vtup = float(interp1d(upstream.percent_hub_shroud,upstream.Vt)((r-row_radius[0])/(row_radius[-1]-row_radius[0])))
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
dT0_dr = dT0up_dr - 1/row.Cp*(U_up*dVtup_dr + Vtup*omega - (U*dVt_dr+Vt*omega)) # Eqn 8
|
|
74
|
+
# if row.loss_function.LossType == LossType.Pressure: # type: ignore
|
|
75
|
+
# dP0_dr = dP0up_dr-row.Yp*(dP0up_dr - dP_dr) # Eqn 9
|
|
76
|
+
|
|
77
|
+
C = Vm**2*(1+np.cos(phi)**2 * np.tan(alpha)**2)/(2*Cp*T0)
|
|
78
|
+
B = (1-C)**(gamma/(gamma-1))
|
|
79
|
+
A = P0 * gamma/(gamma-1) * (1-C)**(1/(gamma-1))
|
|
80
|
+
dVm_dr = 1/(2*Vm*A) * (rho*(Vt/r - Vm**2/rm * np.cos(phi) - Vr*dVr_dr) - dP0_dr*B) + 1/(2*T0) *dT0_dr # Eqn 6
|
|
81
|
+
|
|
82
|
+
ydot = np.array([dP0_dr,dT0_dr,dVm_dr])
|
|
83
|
+
|
|
84
|
+
return ydot
|
|
85
|
+
|
|
86
|
+
T0 = row.T0
|
|
87
|
+
|
|
88
|
+
P0 = row.P0
|
|
89
|
+
Vm = row.Vm
|
|
90
|
+
|
|
91
|
+
# Estimate the Vt based on a given turning angle
|
|
92
|
+
mean_radius = row_radius.mean()
|
|
93
|
+
tip_radius = row_radius[-1]
|
|
94
|
+
hub_radius = row_radius[0]
|
|
95
|
+
|
|
96
|
+
T0m = interp1d(row.percent_hub_shroud,T0)(0.5);
|
|
97
|
+
P0m = interp1d(row.percent_hub_shroud,P0)(0.5); Vmm = interp1d(row.percent_hub_shroud,Vm)(0.5)
|
|
98
|
+
# We are solving for the values of these quantities at row exit
|
|
99
|
+
ics = np.array([P0m,T0m,Vmm])
|
|
100
|
+
|
|
101
|
+
rm_to_tip = np.linspace(mean_radius,tip_radius)
|
|
102
|
+
res1 = odeint(ode_radeq_streamtube, ics, rm_to_tip, tfirst=True)
|
|
103
|
+
|
|
104
|
+
rm_to_hub = np.flip(np.linspace(hub_radius,mean_radius))
|
|
105
|
+
res2 = odeint(ode_radeq_streamtube, ics, rm_to_hub, tfirst=True)
|
|
106
|
+
|
|
107
|
+
res2 = np.flipud(res2)
|
|
108
|
+
res = np.concatenate([res2[:-1,:],res1])
|
|
109
|
+
r = np.concatenate([np.flip(rm_to_hub)[:-1], rm_to_tip])
|
|
110
|
+
|
|
111
|
+
P0_new = interp1d(r,res[:,0])(row_radius)
|
|
112
|
+
T0_new = interp1d(r,res[:,1])(row_radius)
|
|
113
|
+
Vm_new = interp1d(r,res[:,2])(row_radius)
|
|
114
|
+
|
|
115
|
+
row.P0 = P0_new
|
|
116
|
+
row.T0 = T0_new
|
|
117
|
+
row.Vm = Vm_new
|
|
118
|
+
if row.row_type == RowType.Rotor:
|
|
119
|
+
# U(VT1-VT2) = Power/massflow; VT2 = VT1 - Power/massflow
|
|
120
|
+
row.Vt = upstream.Vt-row.power/(row.total_massflow*row.U)
|
|
121
|
+
row.alpha2 = np.arctan2(row.Vt,row.Vx)
|
|
122
|
+
elif row.row_type == RowType.Stator:
|
|
123
|
+
row.Vt = row.Vm*np.cos(row.phi)*np.tan(row.alpha2)
|
|
124
|
+
row.Vr = row.Vm*np.sin(row.phi)
|
|
125
|
+
row.Vx = row.Vm*np.cos(row.phi)
|
|
126
|
+
|
|
127
|
+
return row
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def radeq_normalized(row:BladeRow,upstream:BladeRow) -> BladeRow:
|
|
131
|
+
"""Solves the radial equilibrium equation for axial or radial machines and returns the convergence.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
row (BladeRow): Current row
|
|
135
|
+
upstream (BladeRow): Previous row
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
BladeRow: current row with Vt, T0, P0, and Vm calculated
|
|
139
|
+
"""
|
|
140
|
+
_,row_radius = row.streamline.get_point(row.percent_hub_shroud) # Use these for gradient
|
|
141
|
+
_,up_radius = upstream.streamline.get_point(upstream.percent_hub_shroud)
|
|
142
|
+
|
|
143
|
+
def ode_radeq_streamtube(t:np.ndarray,y:np.ndarray):
|
|
144
|
+
"""Solves the radial equilibrium equation for a streamtube
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
t (np.ndarray): percent from hub to shroud
|
|
148
|
+
y (np.ndarray): Array containing [P0,Vt,VtU,T0]
|
|
149
|
+
|
|
150
|
+
"""
|
|
151
|
+
P0 = y[0]
|
|
152
|
+
T0 = y[1]
|
|
153
|
+
Vm = y[2]
|
|
154
|
+
if t>1:
|
|
155
|
+
return [0,0,0]
|
|
156
|
+
elif t<0:
|
|
157
|
+
return [0,0,0]
|
|
158
|
+
|
|
159
|
+
_,r = row.streamline.get_point()
|
|
160
|
+
Cp = row.Cp
|
|
161
|
+
# Interpolate angle of inclination (phi), exit flow angle (alpha), radius of curvature (rm) at a particular percentage from hub to shroud
|
|
162
|
+
phi = interp1d(row.percent_hub_shroud, row.phi)(t)
|
|
163
|
+
alpha = interp1d(row.percent_hub_shroud, row.alpha2)(t)
|
|
164
|
+
rm = interp1d(row.percent_hub_shroud,row.rm)(t)
|
|
165
|
+
rho = interp1d(row.percent_hub_shroud,row.rho)(t)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
if (row.row_type == RowType.Rotor):
|
|
169
|
+
omega = row.rpm*np.pi/30
|
|
170
|
+
U = omega*r
|
|
171
|
+
else:
|
|
172
|
+
omega = 0
|
|
173
|
+
U = 0
|
|
174
|
+
gamma = row.gamma
|
|
175
|
+
|
|
176
|
+
# Solve the Radial Equlibrium
|
|
177
|
+
Vt = Vm*np.cos(phi)*np.tan(alpha)
|
|
178
|
+
Vr = float(interp1d(row.percent_hub_shroud, row.Vr)(t))
|
|
179
|
+
# Estimations: need the radius of the streamline to compute gradients
|
|
180
|
+
dVm_dr = interp1d(row_radius,np.gradient(row.Vm, row_radius))(r)
|
|
181
|
+
dVt_dr = dVm_dr*np.cos(phi)*np.tan(alpha)
|
|
182
|
+
|
|
183
|
+
dVm_dm = interp1d(row_radius, np.gradient(Vm, r)) + interp1d(x, np.gradient(Vm, r))
|
|
184
|
+
|
|
185
|
+
# Upstream: We interpolate the gradient based on the percentage from hub to shroud
|
|
186
|
+
dT0up_dr = interp1d(upstream.percent_hub_shroud,
|
|
187
|
+
np.gradient(upstream.T0,up_radius))(t)
|
|
188
|
+
dP0up_dr = interp1d(upstream.percent_hub_shroud,
|
|
189
|
+
np.gradient(upstream.P0,up_radius))(t)
|
|
190
|
+
dP0_dr = interp1d(upstream.percent_hub_shroud,
|
|
191
|
+
np.gradient(upstream.P0,up_radius))(t)
|
|
192
|
+
|
|
193
|
+
U_up = interp1d(upstream.percent_hub_shroud,up_radius*omega)(t) # use percentage to get the T0 upstream value
|
|
194
|
+
dVtup_dr = interp1d(upstream.percent_hub_shroud,np.gradient(upstream.Vt,up_radius))(t)
|
|
195
|
+
Vtup = interp1d(upstream.percent_hub_shroud,upstream.Vt)(t)
|
|
196
|
+
|
|
197
|
+
dP_dr = interp1d(row.percent_hub_shroud,np.gradient(row.P,row_radius))(t)
|
|
198
|
+
dT0_dr = dT0up_dr - 1/row.Cp*(U_up*dVtup_dr + Vtup*omega - (U*dVt_dr+Vt*omega)) # Eqn 8
|
|
199
|
+
# if row.loss_function.LossType == LossType.Pressure: # type: ignore
|
|
200
|
+
# dP0_dr = dP0up_dr-row.Yp*(dP0up_dr - dP_dr) # Eqn 9
|
|
201
|
+
|
|
202
|
+
C = Vm**2*(1+np.cos(phi)**2 * np.tan(alpha)**2)/(2*Cp*T0)
|
|
203
|
+
B = (1-C)**(gamma/(gamma-1))
|
|
204
|
+
A = P0 * gamma/(gamma-1) * (1-C)**(1/(gamma-1))
|
|
205
|
+
dVm_dr = 1/(2*Vm*A) * (rho*(Vt/r - Vm**2/rm * np.cos(phi)-Vr*dVm_dm) - dP0_dr*B) + 1/(2*T0) *dT0_dr # Eqn 6
|
|
206
|
+
|
|
207
|
+
ydot = np.array([dP0_dr,dT0_dr,dVm_dr])
|
|
208
|
+
|
|
209
|
+
return ydot
|
|
210
|
+
|
|
211
|
+
T0 = row.T0
|
|
212
|
+
P0 = row.P0
|
|
213
|
+
Vm = row.Vm
|
|
214
|
+
|
|
215
|
+
# Estimate the Vt based on a given turning angle
|
|
216
|
+
_, mean_radius = row.streamline.get_point(0.5)
|
|
217
|
+
_, tip_radius = row.streamline.get_point(1)
|
|
218
|
+
_, hub_radius = row.streamline.get_point(0)
|
|
219
|
+
|
|
220
|
+
T0m = interp1d(row.percent_hub_shroud,T0)(0.5);
|
|
221
|
+
P0m = interp1d(row.percent_hub_shroud,P0)(0.5);
|
|
222
|
+
Vmm = interp1d(row.percent_hub_shroud,Vm)(0.5)
|
|
223
|
+
# We are solving for the values of these quantities at row exit
|
|
224
|
+
ics = np.array([P0m,T0m,Vmm])
|
|
225
|
+
|
|
226
|
+
mid_to_tip = np.linspace(0,1)
|
|
227
|
+
res1 = odeint(ode_radeq_streamtube, ics, mid_to_tip, tfirst=True) # Results
|
|
228
|
+
|
|
229
|
+
mid_to_hub = np.flip(np.linspace(hub_radius,mean_radius))
|
|
230
|
+
res2 = odeint(ode_radeq_streamtube, ics, mid_to_hub, tfirst=True) # Results
|
|
231
|
+
|
|
232
|
+
res2 = np.flipud(res2)
|
|
233
|
+
res = np.concatenate([res2[:-1,:],res1]) # Combine the results
|
|
234
|
+
t = np.concatenate([np.flip(mid_to_hub)[:-1], mid_to_tip])
|
|
235
|
+
|
|
236
|
+
P0_new = interp1d(t,res[:,0])(row.percent_hub_shroud)
|
|
237
|
+
T0_new = interp1d(t,res[:,1])(row.percent_hub_shroud)
|
|
238
|
+
Vm_new = interp1d(t,res[:,2])(row.percent_hub_shroud)
|
|
239
|
+
|
|
240
|
+
row.P0 = P0_new
|
|
241
|
+
row.T0 = T0_new
|
|
242
|
+
row.Vm = Vm_new
|
|
243
|
+
if row.row_type == RowType.Rotor:
|
|
244
|
+
# U(VT1-VT2) = Power/massflow; VT2 = VT1 - Power/massflow
|
|
245
|
+
row.Vt = upstream.Vt-row.power/(row.total_massflow*row.U)
|
|
246
|
+
row.alpha2 = np.arctan2(row.Vt,row.Vx)
|
|
247
|
+
elif row.row_type == RowType.Stator:
|
|
248
|
+
row.Vt = row.Vm*np.cos(row.phi)*np.tan(row.alpha2)
|
|
249
|
+
row.Vr = row.Vm*np.sin(row.phi)
|
|
250
|
+
row.Vx = row.Vm*np.cos(row.phi)
|
|
251
|
+
|
|
252
|
+
return row
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
|
turbodesign/rotor.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from .enums import RowType
|
|
3
|
+
from .bladerow import BladeRow
|
|
4
|
+
from .arrayfuncs import convert_to_ndarray
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
class Rotor(BladeRow):
|
|
8
|
+
"""Station defined at rotor exit
|
|
9
|
+
|
|
10
|
+
Inherits:
|
|
11
|
+
(BladeRow): Defines the properties of the blade row
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
beta2:np.ndarray = field(default_factory=lambda: np.array([72])) # Degrees
|
|
15
|
+
M_rel:np.ndarray = field(default_factory=lambda: np.array([1.2])) # Relative mach number
|
|
16
|
+
power:float = 0 # Watts
|
|
17
|
+
|
|
18
|
+
def __init__(self,power:float=50000):
|
|
19
|
+
"""Initialize the Rotor
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
coolant_P0 (float, optional): Coolant massflow used to cool the rotor internal cooling, tip leakage, hub disk leakage. Defaults to 0.
|
|
23
|
+
coolant_T0 (float, optional): _description_. Defaults to 500.
|
|
24
|
+
power (float, optional): _description_. Defaults to 50000.
|
|
25
|
+
"""
|
|
26
|
+
self.row_type = RowType.RotorExit
|
|
27
|
+
self.power = power
|
|
28
|
+
super().__init__(self)
|
|
29
|
+
|
|
30
|
+
def initialize_rotor_exit(self,beta:float,M_exit:float):
|
|
31
|
+
"""Uses the beta and exit mach number to predict a value for Vm
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
beta (float): exit relative flow angle
|
|
35
|
+
M_exit (float): exit relative mach number
|
|
36
|
+
"""
|
|
37
|
+
self.beta2 = convert_to_ndarray(beta)
|
|
38
|
+
self.M_rel = convert_to_ndarray(M_exit)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from typing import List, Tuple
|
|
2
|
+
from .radeq import radeq, radeq_normalized
|
|
3
|
+
from .enums import LossType, RowType, PowerType, MassflowConstraint
|
|
4
|
+
from .bladerow import BladeRow
|
|
5
|
+
from .td_math import compute_gas_constants
|
|
6
|
+
from .td_math import compute_quantities, compute_power, compute_massflow
|
|
7
|
+
import numpy.typing as npt
|
|
8
|
+
import numpy as np
|
|
9
|
+
from scipy.interpolate import interp1d
|
|
10
|
+
from scipy.optimize import minimize_scalar
|
|
11
|
+
from .passage import Passage
|
|
12
|
+
|
|
13
|
+
def adjust_streamlines(blade_rows:List[BladeRow],passage:Passage):
|
|
14
|
+
"""Adjust the streamlines to evenly divide the massflow
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
blade_rows (List[BladeRow]): List of blade rows
|
|
18
|
+
passage (Passage): passage object describing the hub and shroud
|
|
19
|
+
|
|
20
|
+
"""
|
|
21
|
+
for _,row in enumerate(blade_rows):
|
|
22
|
+
massflow_fraction = np.linspace(0,1,len(row.percent_hub_shroud))
|
|
23
|
+
row.total_massflow = row.massflow[-1]
|
|
24
|
+
ideal_massflow_fraction = row.massflow[-1] * massflow_fraction
|
|
25
|
+
|
|
26
|
+
new_percent_streamline = interp1d(row.massflow,row.percent_hub_shroud)(ideal_massflow_fraction[1:-1])
|
|
27
|
+
row.percent_hub_shroud[1:-1] = new_percent_streamline
|
|
28
|
+
|
|
29
|
+
cut_line, thub,_ = passage.get_cutting_line(row.axial_location)
|
|
30
|
+
row.x,row.r = cut_line.get_point(row.percent_hub_shroud)
|
|
31
|
+
# Radii may have shifted, recompute Ay and rm
|
|
32
|
+
for i,tr in enumerate(row.percent_hub_shroud):
|
|
33
|
+
t_streamline, x_streamline, r_streamline = passage.get_streamline(tr)
|
|
34
|
+
phi, rm, r = passage.streamline_curvature(x_streamline,r_streamline)
|
|
35
|
+
row.phi[i] = float(interp1d(t_streamline,phi)(row.axial_location))
|
|
36
|
+
row.rm[i] = float(interp1d(t_streamline,rm)(row.axial_location))
|
|
37
|
+
row.r[i] = float(interp1d(t_streamline,r)(row.axial_location))
|