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.
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))