turbo-design 1.2.1__tar.gz → 1.3.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of turbo-design might be problematic. Click here for more details.

Files changed (32) hide show
  1. {turbo_design-1.2.1 → turbo_design-1.3.1}/PKG-INFO +1 -1
  2. {turbo_design-1.2.1 → turbo_design-1.3.1}/pyproject.toml +1 -1
  3. {turbo_design-1.2.1 → turbo_design-1.3.1}/turbodesign/bladerow.py +9 -7
  4. {turbo_design-1.2.1 → turbo_design-1.3.1}/turbodesign/inlet.py +4 -4
  5. {turbo_design-1.2.1 → turbo_design-1.3.1}/turbodesign/outlet.py +2 -1
  6. {turbo_design-1.2.1 → turbo_design-1.3.1}/turbodesign/passage.py +39 -7
  7. {turbo_design-1.2.1 → turbo_design-1.3.1}/turbodesign/radeq.py +33 -9
  8. {turbo_design-1.2.1 → turbo_design-1.3.1}/turbodesign/solve_radeq.py +5 -5
  9. {turbo_design-1.2.1 → turbo_design-1.3.1}/turbodesign/spool.py +12 -10
  10. {turbo_design-1.2.1 → turbo_design-1.3.1}/turbodesign/td_math.py +5 -5
  11. {turbo_design-1.2.1 → turbo_design-1.3.1}/turbodesign/turbinespool.py +86 -100
  12. {turbo_design-1.2.1 → turbo_design-1.3.1}/turbodesign/__init__.py +0 -0
  13. {turbo_design-1.2.1 → turbo_design-1.3.1}/turbodesign/arrayfuncs.py +0 -0
  14. {turbo_design-1.2.1 → turbo_design-1.3.1}/turbodesign/cantera_gas/co2.yaml +0 -0
  15. {turbo_design-1.2.1 → turbo_design-1.3.1}/turbodesign/compressorspool.py +0 -0
  16. {turbo_design-1.2.1 → turbo_design-1.3.1}/turbodesign/coolant.py +0 -0
  17. {turbo_design-1.2.1 → turbo_design-1.3.1}/turbodesign/enums.py +0 -0
  18. {turbo_design-1.2.1 → turbo_design-1.3.1}/turbodesign/isentropic.py +0 -0
  19. {turbo_design-1.2.1 → turbo_design-1.3.1}/turbodesign/loss/__init__.py +0 -0
  20. {turbo_design-1.2.1 → turbo_design-1.3.1}/turbodesign/loss/compressor/__init__.py +0 -0
  21. {turbo_design-1.2.1 → turbo_design-1.3.1}/turbodesign/loss/losstype.py +0 -0
  22. {turbo_design-1.2.1 → turbo_design-1.3.1}/turbodesign/loss/turbine/TD2.py +0 -0
  23. {turbo_design-1.2.1 → turbo_design-1.3.1}/turbodesign/loss/turbine/__init__.py +0 -0
  24. {turbo_design-1.2.1 → turbo_design-1.3.1}/turbodesign/loss/turbine/ainleymathieson.py +0 -0
  25. {turbo_design-1.2.1 → turbo_design-1.3.1}/turbodesign/loss/turbine/craigcox.py +0 -0
  26. {turbo_design-1.2.1 → turbo_design-1.3.1}/turbodesign/loss/turbine/fixedefficiency.py +0 -0
  27. {turbo_design-1.2.1 → turbo_design-1.3.1}/turbodesign/loss/turbine/fixedpressureloss.py +0 -0
  28. {turbo_design-1.2.1 → turbo_design-1.3.1}/turbodesign/loss/turbine/kackerokapuu.py +0 -0
  29. {turbo_design-1.2.1 → turbo_design-1.3.1}/turbodesign/loss/turbine/traupel.py +0 -0
  30. {turbo_design-1.2.1 → turbo_design-1.3.1}/turbodesign/lossinterp.py +0 -0
  31. {turbo_design-1.2.1 → turbo_design-1.3.1}/turbodesign/rotor.py +0 -0
  32. {turbo_design-1.2.1 → turbo_design-1.3.1}/turbodesign/stage.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: turbo-design
3
- Version: 1.2.1
3
+ Version: 1.3.1
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
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "turbo-design"
3
- version = "1.2.1"
3
+ version = "1.3.1"
4
4
  description = "TurboDesign is a library used to design turbines and compressors using radial equilibrium."
5
5
  authors = ["Paht Juangphanich <paht.juangphanich@nasa.gov>"]
6
6
  packages = [
@@ -34,10 +34,11 @@ class BladeRow:
34
34
  # ----------------------------------
35
35
 
36
36
  # Streamline Properties
37
- axial_location:float = 0 # Where blade row is defined along the hub.
37
+ percent_hub:float = 0 # Where blade row is defined along the hub.
38
38
  percent_hub_shroud: npt.NDArray = field(default_factory=lambda: np.array([0])) # Percent streamline length from hub to shroud.
39
39
  x: npt.NDArray = field(default_factory=lambda: np.array([0])) # x - coordinates (useful for computing axial chord)
40
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
41
42
  area:float = 0
42
43
  # Calculated massflow is the massflow computed after radial eq solver
43
44
  calculated_massflow: float = 0
@@ -120,6 +121,7 @@ class BladeRow:
120
121
  _tip_clearance:float = 0 # Clearance as a percentage of span or blade height
121
122
 
122
123
  _inlet_to_outlet_pratio = [0.06,0.95]
124
+ location:float = 0 # Percent along hub where bladerow is defined
123
125
 
124
126
  @property
125
127
  def inlet_to_outlet_pratio(self) -> Tuple[float,float]:
@@ -327,18 +329,18 @@ class BladeRow:
327
329
  """
328
330
  self._tip_clearance = val
329
331
 
330
- def __init__(self,axial_location:float,row_type:RowType=RowType.Stator,stage_id:int = 0):
332
+ def __init__(self,location:float,row_type:RowType=RowType.Stator,stage_id:int = 0):
331
333
  """Initializes the blade row to be a particular type
332
334
 
333
335
  Args:
334
- axial_location (float): Location of the blade row as a percentage of the total axial length
336
+ location (float): Location of the blade row as a percentage of hub length
335
337
  row_type (RowType): Specifies the Type. Defaults to RowType.Stator
336
338
  power (float, optional): power . Defaults to 0.
337
339
  P0_P (float, optional): Total to Static Pressure Ratio
338
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
339
341
  """
340
342
  self.row_type = row_type
341
- self.axial_location = axial_location
343
+ self.location = location
342
344
  self.Yp = 0 # Loss
343
345
  self.stage_id = stage_id
344
346
 
@@ -511,7 +513,7 @@ def interpolate_streamline_radii(row:BladeRow,passage:Passage,num_streamlines:in
511
513
  Returns:
512
514
  (BladeRow): new row object with quantities interpolated
513
515
  """
514
- row.cutting_line,_,_ = passage.get_cutting_line(row.axial_location)
516
+ row.cutting_line,_,_ = passage.get_cutting_line(row.location)
515
517
  row.x,row.r = row.cutting_line.get_point(np.linspace(0,1,num_streamlines))
516
518
  streamline_percent_length = np.sqrt((row.r-row.r[0])**2+(row.x-row.x[0])**2)/row.cutting_line.length
517
519
 
@@ -622,8 +624,8 @@ def compute_gas_constants(row:BladeRow,fluid:Solution=None) -> None:
622
624
  Tm = row.T.mean()
623
625
  Pm = row.P.mean()
624
626
  fluid.TP = Tm,Pm
625
- row.Cp = row.fluid.cp
626
- row.Cv = row.fluid.cv
627
+ row.Cp = fluid.cp
628
+ row.Cv = fluid.cv
627
629
  row.R = row.Cp-row.Cv
628
630
  row.gamma = row.Cp/row.Cv
629
631
  # Use Ideal Gas
@@ -19,7 +19,7 @@ class Inlet(BladeRow):
19
19
 
20
20
  def __init__(self,M:float,T0:Union[float,List[float]],
21
21
  P0:Union[float,List[float]],
22
- axial_location:float=0,
22
+ location:float=0,
23
23
  beta:Union[float,List[float]]=[0],
24
24
  percent_radii:Union[float,List[float]]=[0.5]):
25
25
  """Initializes the inlet station.
@@ -30,11 +30,11 @@ class Inlet(BladeRow):
30
30
  T0 (Union[float,List[float]]): Total Temperature Array
31
31
  P0 (Union[float,List[float]]): Total Pressure Array
32
32
  percent_radii (Union[float,List[float]]): Radius where total pressure and temperature are defined
33
- axial_location (float): Axial Location as a percentage of hub length
33
+ location (float): Location as a percentage of hub length
34
34
  beta (Union[float,List[float]], optional): Inlet flow angle in relative direction. Defaults to [].
35
35
 
36
36
  """
37
- super().__init__(row_type=RowType.Inlet,axial_location=axial_location,stage_id=-1)
37
+ super().__init__(row_type=RowType.Inlet,location=location,stage_id=-1)
38
38
  self.beta1 = convert_to_ndarray(beta)
39
39
  self.M = convert_to_ndarray(M)
40
40
  self.T0 = convert_to_ndarray(T0)
@@ -94,7 +94,7 @@ class Inlet(BladeRow):
94
94
  t,x,radius = passage.get_streamline(self.percent_hub_shroud)
95
95
  radius = radius[0]
96
96
 
97
- cutline,_,_ = passage.get_cutting_line(self.axial_location)
97
+ cutline,_,_ = passage.get_cutting_line(self.location)
98
98
  self.x,self.r = cutline.get_point(np.linspace(0,1,num_streamlines))
99
99
  for _ in range(10):
100
100
  T0_T = (1+(self.gamma-1)/2 * self.M**2)
@@ -12,7 +12,7 @@ from scipy.interpolate import interp1d
12
12
  class Outlet(BladeRow):
13
13
  P_fun:interp1d
14
14
 
15
- def __init__(self,P:Union[float,List[float]],percent_radii:List[float],num_streamlines:int=3):
15
+ def __init__(self,P:Union[float,List[float]],percent_radii:List[float],num_streamlines:int=3,location:float=1):
16
16
  """Initialize the outlet
17
17
 
18
18
  Args:
@@ -26,6 +26,7 @@ class Outlet(BladeRow):
26
26
  self.P_fun = interp1d(self.percent_hub_shroud,self.P)
27
27
  self.row_type = RowType.Outlet
28
28
  self.loss_function = None
29
+ self.location = location
29
30
 
30
31
 
31
32
  def transfer_quantities(self,upstream:BladeRow):
@@ -1,7 +1,7 @@
1
1
  from typing import List, Tuple
2
2
  import numpy as np
3
3
  import numpy.typing as npt
4
- from scipy.interpolate import PchipInterpolator
4
+ from scipy.interpolate import PchipInterpolator, interp1d
5
5
  from pyturbo.helper import line2D
6
6
  from .enums import PassageType
7
7
  from scipy.optimize import minimize_scalar
@@ -141,7 +141,7 @@ class Passage:
141
141
  r = r_streamline
142
142
 
143
143
  return phi, rm, r
144
-
144
+
145
145
  def get_area(self,t_hub:float) -> float:
146
146
  """Get Area
147
147
 
@@ -166,7 +166,7 @@ class Passage:
166
166
  total_area += area
167
167
  return total_area
168
168
 
169
- def get_cutting_line(self, t_hub:float) -> line2D:
169
+ def get_cutting_line(self, t_hub:float) -> Tuple[line2D,float,float]:
170
170
  """Gets the cutting line perpendicular to hub and shroud
171
171
 
172
172
  Args:
@@ -212,17 +212,18 @@ class Passage:
212
212
  rshroud = self.rshroud(t_shroud)
213
213
  return line2D([xhub,rhub],[xshroud,rshroud]), t_hub, t_shroud
214
214
 
215
- def get_xr_slice(self,t_span:float,axial_location:Tuple[float,float]):
215
+ def get_xr_slice(self,t_span:float,percent_hub:Tuple[float,float],resolution:int=100):
216
216
  """Returns the xr coordinates of a streamline, a line that is parallel to both hub and shroud
217
217
 
218
218
  Args:
219
219
  t_span (float): _description_
220
- axial_location (float): _description_
220
+ meridional_location (float): _description_
221
+ resolution (int): number of points to resolve
221
222
 
222
223
  Returns:
223
224
  np.NDArray: _description_
224
225
  """
225
- t_hub = np.linspace(axial_location[0],axial_location[1],100)
226
+ t_hub = np.linspace(percent_hub[0],percent_hub[1],resolution)
226
227
  t_hub = convert_to_ndarray(t_hub)*self.hub_length
227
228
 
228
229
  shroud_pts_cyl = np.vstack([self.xshroud(t_hub),self.rshroud(t_hub)]).transpose()
@@ -233,8 +234,39 @@ class Passage:
233
234
  for j in range(n):
234
235
  l = line2D(hub_pts_cyl[j,:],shroud_pts_cyl[j,:])
235
236
  xr[j,0],xr[j,1] = l.get_point(t_span)
237
+
236
238
  return xr
237
-
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
+
238
270
  @property
239
271
  def hub_length(self):
240
272
  """returns the computed length of the hub
@@ -1,11 +1,12 @@
1
- from scipy.interpolate import interp1d
1
+ from scipy.interpolate import interp1d,PchipInterpolator
2
2
  from scipy.integrate import odeint
3
- import numpy as np
3
+ import numpy as np
4
+ import numdifftools as nd
4
5
  from .bladerow import BladeRow
5
6
  from .enums import RowType
6
7
 
7
8
 
8
- def radeq(row:BladeRow,upstream:BladeRow) -> BladeRow:
9
+ def radeq(row:BladeRow,upstream:BladeRow,downstream:BladeRow=None) -> BladeRow:
9
10
  """Solves the radial equilibrium equation for axial machines and returns the convergence.
10
11
 
11
12
  Note:
@@ -14,7 +15,8 @@ def radeq(row:BladeRow,upstream:BladeRow) -> BladeRow:
14
15
  Args:
15
16
  row (BladeRow): Current row
16
17
  upstream (BladeRow): Previous row
17
-
18
+ downstream (BladeRow): Next row
19
+
18
20
  Returns:
19
21
  BladeRow: current row with T0, P0, and Vm calculated
20
22
  """
@@ -57,8 +59,30 @@ def radeq(row:BladeRow,upstream:BladeRow) -> BladeRow:
57
59
  # Estimations
58
60
  dVm_dr = float(interp1d(row_radius, np.gradient(row.Vm, row_radius))(r))
59
61
  dVt_dr = dVm_dr*np.tan(alpha)
60
- dVr_dr = dVm_dr*np.sin(phi)
61
-
62
+ # dVr_dr = dVm_dr*np.sin(phi)
63
+
64
+ up_Vm = interp1d(row_radius, upstream.Vm)(r)
65
+ if downstream:
66
+ if downstream.row_type == RowType.Outlet:
67
+ down_Vm = Vm
68
+ else:
69
+ down_Vm = interp1d(row_radius, downstream.Vm)(r)
70
+ else:
71
+ down_Vm = Vm
72
+ up_m = interp1d(row_radius, upstream.m)(r)
73
+
74
+ # Get a rough guess of dVm/dm
75
+ if downstream!=None:
76
+ down_m = interp1d(row_radius, downstream.m)(r)
77
+ row_m = interp1d(row_radius, row.m)(r)
78
+ if down_m != row_m:
79
+ func_Vm_m = PchipInterpolator([up_m, row_m, down_m],[up_Vm, Vm, down_Vm])
80
+ else:
81
+ func_Vm_m = PchipInterpolator([up_m, row_m],[up_Vm, Vm])
82
+ else:
83
+ func_Vm_m = PchipInterpolator([up_m, row_m],[up_Vm, Vm])
84
+ dVm_dm = func_Vm_m.derivative()(row_m)
85
+
62
86
  # Upstream
63
87
  dT0up_dr = float(interp1d(upstream.percent_hub_shroud, np.gradient(upstream.T0,up_radius))((r-row_radius[0])/(row_radius[-1]-row_radius[0]))) # use percentage to get the T0 upstream value
64
88
  dP0up_dr = float(interp1d(upstream.percent_hub_shroud, np.gradient(upstream.P0,up_radius))((r-row_radius[0])/(row_radius[-1]-row_radius[0]))) # use percentage to get the T0 upstream value
@@ -78,13 +102,13 @@ def radeq(row:BladeRow,upstream:BladeRow) -> BladeRow:
78
102
  if (C>1) & ((gamma/(gamma-1))<2):
79
103
  raise Exception("Invalid value of C {C}, change reduce alpha or Vm")
80
104
  B = (1-C)**(gamma/(gamma-1))
81
- A = P0 * gamma/(gamma-1) * (1-C)**(1/(gamma-1))
105
+ A = P0 * gamma/(gamma-1) * (1-C)**(1/(gamma-1)) * (1+np.tan(alpha)**2)/(2*Cp)
82
106
 
83
107
  epsilon = 1e-10 # or another small threshold
84
108
  if abs(rm) > epsilon:
85
- 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
109
+ dVm_dr = Vm/(2*T0**2) * dT0_dr - rho/(2*A*Vm) * (Vt**2/r + Vm/rm*np.cos(phi)-Vr*dVm_dm) + B/(2*A*Vm) * dP0_dr # Eqn 21
86
110
  else:
87
- dVm_dr = 1/(2*Vm*A) * (rho*(Vt/r - Vr*dVr_dr) - dP0_dr*B) + 1/(2*T0) *dT0_dr # Eqn 6
111
+ dVm_dr = Vm/(2*T0**2) * dT0_dr - rho/(2*A*Vm) * (Vt**2/r) + B/(2*A*Vm) * dP0_dr # Eqn 21, modified
88
112
 
89
113
  ydot = np.array([dP0_dr,dT0_dr,dVm_dr])
90
114
 
@@ -27,13 +27,13 @@ def adjust_streamlines(blade_rows:List[BladeRow],passage:Passage):
27
27
  new_percent_streamline = interp1d(row.massflow,row.percent_hub_shroud)(ideal_massflow_fraction[1:-1])
28
28
  row.percent_hub_shroud[1:-1] = new_percent_streamline
29
29
 
30
- cut_line, thub,_ = passage.get_cutting_line(row.axial_location)
30
+ cut_line, thub,_ = passage.get_cutting_line(row.percent_hub)
31
31
  row.x,row.r = cut_line.get_point(row.percent_hub_shroud)
32
32
  # Radii may have shifted, recompute Ay and rm
33
33
  for i,tr in enumerate(row.percent_hub_shroud):
34
34
  t_streamline, x_streamline, r_streamline = passage.get_streamline(tr)
35
35
  phi, rm, r = passage.streamline_curvature(x_streamline,r_streamline)
36
- row.phi[i] = float(interp1d(t_streamline,phi)(row.axial_location))
37
- row.rm[i] = float(interp1d(t_streamline,rm)(row.axial_location))
38
- row.r[i] = float(interp1d(t_streamline,r)(row.axial_location))
39
- row.x[i] = float(interp1d(t_streamline,x_streamline)(row.axial_location))
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))
@@ -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=Solution('air.yaml'),
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
- gas (ct.Solution, optional): cantera gas solution. Defaults to ct.Solution('air.yaml').
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
  """
@@ -71,7 +71,7 @@ class Spool:
71
71
  '''
72
72
  if (type(self.blade_rows[i]) != Inlet) and (type(self.blade_rows[i]) != Outlet):
73
73
  self.blade_rows[i].rpm = rpm
74
- self.blade_rows[i].axial_chord = self.blade_rows[i].axial_location * self.passage.hub_length
74
+ self.blade_rows[i].axial_chord = self.blade_rows[i].location * self.passage.hub_length
75
75
 
76
76
 
77
77
  @property
@@ -158,10 +158,11 @@ class Spool:
158
158
  row.phi = np.zeros((self.num_streamlines,))
159
159
  row.rm = np.zeros((self.num_streamlines,))
160
160
  row.r = np.zeros((self.num_streamlines,))
161
+ row.m = np.zeros((self.num_streamlines,))
161
162
 
162
163
  t_radial = np.linspace(0,1,self.num_streamlines)
163
164
  self.calculate_streamline_curvature(row,t_radial)
164
-
165
+
165
166
  # Set the loss function if it's not set
166
167
  if (type(row)!= Inlet and type(row) != Outlet):
167
168
  if row.loss_function == None:
@@ -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.axial_location))
181
- row.rm[i] = float(interp1d(t_streamline,rm)(row.axial_location))
182
- row.r[i] = float(interp1d(t_streamline,r)(row.axial_location))
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.axial_location*hub_length +(0.5*row.blade_to_blade_gap*row.axial_chord) - row.axial_chord)/hub_length)
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.axial_location*hub_length)/hub_length)
220
- cut_line2,_,_ = self.passage.get_cutting_line((row.axial_location*hub_length-(0.5*row.blade_to_blade_gap*row.axial_chord))/hub_length)
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))
@@ -93,7 +93,7 @@ def compute_reynolds(rows:List[BladeRow],passage:Passage):
93
93
 
94
94
  for i in range(1,len(rows)):
95
95
  row = rows[i]
96
- xr = passage.get_xr_slice(0.5,[rows[i-1].axial_location,row.axial_location])
96
+ xr = passage.get_xr_slice(0.5,[rows[i-1].location,row.percent_hub])
97
97
  dx = np.diff(xr[:,0])
98
98
  dr = np.diff(xr[:,1])
99
99
  c = np.sum(np.sqrt(dx**2+dr**2))
@@ -111,8 +111,6 @@ def compute_reynolds(rows:List[BladeRow],passage:Passage):
111
111
  row.axial_chord = max(c,1E-12) # Axial chord
112
112
  # row.num_blades = int(2*np.pi*row.r.mean() / row.pitch_to_chord * row.axial_chord)
113
113
 
114
-
115
-
116
114
  def compute_power(row:BladeRow,upstream:BladeRow) -> None:
117
115
  """Calculates the power
118
116
 
@@ -232,8 +230,9 @@ def stator_calc(row:BladeRow,upstream:BladeRow,downstream:BladeRow=None,calculat
232
230
 
233
231
  # Static Pressure is assumed
234
232
  row.P0 = upstream.P0 - row.Yp*(upstream.P0-row.P)
235
- row.P0_P = row.P0/downstream.P
233
+
236
234
  if downstream is not None:
235
+ row.P0_P = row.P0/downstream.P
237
236
  row.rp = (row.P-downstream.P)/(upstream.P0-downstream.P)
238
237
 
239
238
  if calculate_vm:
@@ -261,6 +260,7 @@ def stator_calc(row:BladeRow,upstream:BladeRow,downstream:BladeRow=None,calculat
261
260
  row.beta1 = upstream.beta2
262
261
  row.rho = row.P/(row.R*row.T)
263
262
  row.U = row.omega*row.r
263
+ row.Wt = row.Vt-row.U
264
264
  row.P0_stator_inlet = upstream.P0
265
265
 
266
266
  def rotor_calc(row:BladeRow,upstream:BladeRow,calculate_vm:bool=True):
@@ -296,7 +296,7 @@ def rotor_calc(row:BladeRow,upstream:BladeRow,calculate_vm:bool=True):
296
296
  row.P0R = upstream.P0R - row.Yp*(upstream.P0R-row.P)
297
297
 
298
298
  # Total Relative Temperature stays constant through the rotor. Adjust for change in radius from rotor inlet to exit
299
- row.T0R =upstream.T0R - T0_coolant_weighted_average(row) # (upstream_rothalpy + 0.5*row.U**2)/row.Cp - T0_coolant_weighted_average(row)
299
+ row.T0R = (upstream_rothalpy + 0.5*row.U**2)/row.Cp - T0_coolant_weighted_average(row)
300
300
  P0R_P = row.P0R / row.P
301
301
  T0R_T = P0R_P**((row.gamma-1)/row.gamma)
302
302
  row.T = (row.T0R/T0R_T) # Exit static temperature
@@ -53,15 +53,15 @@ class TurbineSpool(Spool):
53
53
  gamma=self.blade_rows[1].gamma,
54
54
  Cp=self.blade_rows[1].Cp)
55
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)
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)
62
62
 
63
- compute_gas_constants(inlet,self.fluid)
64
- inlet_calc(inlet)
63
+ compute_gas_constants(inlet,self.fluid)
64
+ inlet_calc(inlet)
65
65
 
66
66
  for row in self.blade_rows:
67
67
  interpolate_streamline_radii(row,self.passage,self.num_streamlines)
@@ -122,10 +122,12 @@ class TurbineSpool(Spool):
122
122
 
123
123
  if row.row_type == RowType.Stator:
124
124
  stator_calc(row,upstream,downstream)
125
+ compute_massflow(row)
125
126
  elif row.row_type == RowType.Rotor:
126
127
  rotor_calc(row,upstream)
127
128
  compute_massflow(row)
128
129
  compute_power(row,upstream)
130
+
129
131
 
130
132
  def solve(self):
131
133
  """
@@ -163,7 +165,7 @@ class TurbineSpool(Spool):
163
165
  bounds = [-80,0]
164
166
  if row.row_type != RowType.Inlet:
165
167
  for j in range(1,self.num_streamlines):
166
- res = minimize_scalar(massflow_loss_function, bounds=bounds,args=(j,row,upstream,downstream),tol=1E-2)
168
+ res = minimize_scalar(massflow_loss_function, bounds=bounds,args=(j,row,upstream,downstream),tol=1E-3)
167
169
  if row.row_type == RowType.Rotor:
168
170
  row.beta2[j] = np.radians(res.x)
169
171
  # Initialize the value at the hub to not upset the mean
@@ -178,7 +180,34 @@ class TurbineSpool(Spool):
178
180
  # Step 3: Adjust streamlines to evenly divide massflow
179
181
  adjust_streamlines(self.blade_rows,self.passage)
180
182
  compute_reynolds(self.blade_rows,self.passage)
181
-
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
+
182
211
  def __balance_massflow(self):
183
212
  """ Balances the massflow between rows. Use radial equilibrium.
184
213
 
@@ -195,24 +224,7 @@ class TurbineSpool(Spool):
195
224
  3. Adjust the streamlines for each blade row to balance the massflow
196
225
  """
197
226
 
198
- def calculate_error(blade_rows):
199
- total_massflow = list(); s = 0; massflow_stage = list()
200
- stage_ids = list(set([row.stage_id for row in self.blade_rows if row.stage_id>=0]))
201
- for row in blade_rows[1:]:
202
- total_massflow.append(row.total_massflow_no_coolant)
203
-
204
- sign = 1
205
- for s in stage_ids:
206
- for row in blade_rows:
207
- if row.stage_id == s and row.row_type == RowType.Rotor:
208
- massflow_stage.append(sign*row.total_massflow_no_coolant)
209
- sign*=-1
210
- if len(stage_ids) % 2 == 1:
211
- massflow_stage.append(massflow_stage[-1]*sign)
212
- deviation = np.std(total_massflow)*2
213
- if deviation>1.0:
214
- print("high massflow deviation detected")
215
- return np.std(total_massflow)*2 # + abs(sum(massflow_stage)) # Equation 28
227
+
216
228
 
217
229
  # Balance the massflow between Stages
218
230
  def balance_massflows(x0:List[float],blade_rows:List[List[BladeRow]],P0:npt.NDArray,P:npt.NDArray,balance_mean_pressure:bool=True):
@@ -239,27 +251,24 @@ class TurbineSpool(Spool):
239
251
  if balance_mean_pressure:
240
252
  for j in range(self.num_streamlines):
241
253
  Ps_range = outlet_pressure(x0,P0[j],P[j])
242
- for i in range(1,len(blade_rows)-1):
254
+ for i in range(1,len(blade_rows)-2):
243
255
  blade_rows[i].P[j] = Ps_range[i-1]
244
- blade_rows[-1].P = P
256
+ blade_rows[-2].P = P
245
257
  else:
246
258
  for i in range(1,len(blade_rows)-1):
247
259
  for j in range(self.num_streamlines):
248
260
  blade_rows[i].P[j] = P[j]*x0[(i-1)*self.num_streamlines+j] # x0 size = num_streamlines -1
249
- try:
250
- calculate_massflows(blade_rows,True)
251
- print(x0)
252
- return calculate_error(blade_rows)
253
- except:
254
- blade_rows = blade_rows_backup
255
- return np.inf # Return a high error
261
+ # try:
262
+ calculate_massflows(blade_rows,True,self.fluid)
263
+ print(x0)
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
256
270
 
257
- # except:
258
- # for i in range(1,len(blade_rows)-1):
259
- # for j in range(self.num_streamlines):
260
- # blade_rows[i].P[j] = P[j]
261
- # calculate_massflows(blade_rows,True)
262
- # return 10
271
+
263
272
  # Break apart the rows to stages
264
273
  outlet_P=list(); outlet_P_guess = list() # Outlet P is the bounds, outlet_p_guess is the guessed values
265
274
 
@@ -267,63 +276,40 @@ class TurbineSpool(Spool):
267
276
  outlet_P.append(self.blade_rows[i].inlet_to_outlet_pratio)
268
277
  outlet_P_guess.append(np.mean(self.blade_rows[i].inlet_to_outlet_pratio))
269
278
 
270
- print('Find average P in between stages')
271
- if len(outlet_P) == 1:
272
- # x = balance_massflows(0.658,self.blade_rows[:-1],self.blade_rows[0].P0,self.blade_rows[-1].P)
273
- res = minimize_scalar(fun=balance_massflows,args=(self.blade_rows[:-1],self.blade_rows[0].P0,self.blade_rows[-1].P),bounds=outlet_P[0],tol=0.0001,options={'disp': True},method='bounded')
274
- x = res.x
275
- print(x)
276
- else:
277
- x = fmin_slsqp(func=balance_massflows,args=(self.blade_rows[:-1],self.blade_rows[0].P0,self.blade_rows[-1].P),
278
- bounds=outlet_P, x0=outlet_P_guess,epsilon=0.001,iter=100) # ,tol=0.001,options={'disp': True})
279
- outlet_P_guess = x
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) and loop_iter<10:
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
280
291
 
281
- # Adjust the inlet: Set the massflow
282
- self.blade_rows[0].massflow = np.linspace(0,1,self.num_streamlines)*self.blade_rows[1].total_massflow_no_coolant
283
- self.blade_rows[0].total_massflow_no_coolant = self.blade_rows[1].total_massflow_no_coolant
284
- self.blade_rows[0].total_massflow = np.linspace(0,1,self.num_streamlines)*self.blade_rows[1].total_massflow_no_coolant
285
- inlet_calc(self.blade_rows[0]) # adjust the inlet to match massflow
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
286
298
 
287
- if self.adjust_streamlines:
288
- for _ in range(2):
299
+ if self.adjust_streamlines:
289
300
  adjust_streamlines(self.blade_rows[:-1],self.passage)
290
- self.blade_rows[-1].transfer_quantities(self.blade_rows[-2])
291
- self.blade_rows[-1].P = self.blade_rows[-1].get_static_pressure(self.blade_rows[-1].percent_hub_shroud)
292
- balance_massflows(x,self.blade_rows[:-1],self.blade_rows[0].P0,self.blade_rows[-1].P)
293
- else:
301
+
294
302
  self.blade_rows[-1].transfer_quantities(self.blade_rows[-2])
295
303
  self.blade_rows[-1].P = self.blade_rows[-1].get_static_pressure(self.blade_rows[-1].percent_hub_shroud)
296
- err = calculate_error(self.blade_rows[:-1])
297
- print(f"Massflow convergenced error:{err}")
304
+
305
+ past_err = err
306
+ err = self.__massflow_std__(self.blade_rows)
307
+ loop_iter += 1
308
+ print(f"Loop {loop_iter} massflow convergenced error:{err}")
298
309
 
299
310
  # calculate Reynolds number
300
311
  compute_reynolds(self.blade_rows,self.passage)
301
312
 
302
- # finetune = True
303
- # if finetune:
304
- # print('Finetune static pressure between stages')
305
- # self.blade_rows[-1].transfer_quantities(self.blade_rows[-2])
306
- # self.blade_rows[-1].P = self.blade_rows[-1].get_static_pressure(self.blade_rows[-1].percent_hub_shroud)
307
- # P = [row.P for row in self.blade_rows] # Average static pressures
308
- # bounds = []; guess = []
309
- # for i in range(1,len(self.blade_rows)-1): # set the bounds
310
- # for j in range(self.num_streamlines):
311
- # bounds.append([0.8,1.2]) # vary by +- 20%
312
- # guess.append(1)
313
-
314
- # x = fmin_slsqp(func=balance_massflows,args=(self.blade_rows[:-1],self.blade_rows[0].P0,P,False),
315
- # bounds=bounds, x0=guess,epsilon=0.001,iter=200)
316
- # P = [row.P for row in self.blade_rows] # Average static pressures
317
-
318
- # for _ in range(2):
319
- # adjust_streamlines(self.blade_rows[:-1],self.passage)
320
- # self.blade_rows[-1].transfer_quantities(self.blade_rows[-2])
321
- # self.blade_rows[-1].P = self.blade_rows[-1].get_static_pressure(self.blade_rows[-1].percent_hub_shroud)
322
- # balance_massflows(x,self.blade_rows[:-1],self.blade_rows[0].P0,P,False)
323
-
324
- # err = calculate_error(self.blade_rows[:-1])
325
- # print(f"Massflow convergenced error after finetuning:{err}")
326
-
327
313
 
328
314
  def export_properties(self,filename:str="turbine_spool.json"):
329
315
  """Export the spool object to json
@@ -337,7 +323,7 @@ class TurbineSpool(Spool):
337
323
  total_static_efficiency = list()
338
324
  stage_loading = list()
339
325
  euler_power = list()
340
-
326
+ enthalpy_power = list()
341
327
  x_streamline = np.zeros((self.num_streamlines,len(self.blade_rows)))
342
328
  r_streamline = np.zeros((self.num_streamlines,len(self.blade_rows)))
343
329
  massflow = list()
@@ -352,14 +338,14 @@ class TurbineSpool(Spool):
352
338
 
353
339
  stage_loading.append(row.stage_loading)
354
340
  euler_power.append(row.euler_power)
355
-
341
+ enthalpy_power.append(row.power)
356
342
  if row.row_type!=RowType.Inlet and row.row_type!=RowType.Outlet:
357
343
  massflow.append(row.massflow[-1])
358
344
 
359
345
  for j,p in enumerate(row.percent_hub_shroud):
360
346
  t,x,r = self.passage.get_streamline(p)
361
- x_streamline[j,indx] = float(interp1d(t,x)(row.axial_location))
362
- r_streamline[j,indx] = float(interp1d(t,r)(row.axial_location))
347
+ x_streamline[j,indx] = float(interp1d(t,x)(row.percent_hub))
348
+ r_streamline[j,indx] = float(interp1d(t,r)(row.percent_hub))
363
349
 
364
350
  Pratio_Total_Total = np.mean(self.blade_rows[0].P0 / self.blade_rows[-2].P0)
365
351
  Pratio_Total_Static = np.mean(self.blade_rows[0].P0 / self.blade_rows[-2].P)
@@ -378,6 +364,8 @@ class TurbineSpool(Spool):
378
364
  "xhub":self.passage.xhub_pts.tolist(),
379
365
  "xshroud":self.passage.xshroud_pts.tolist(),
380
366
  "num_streamlines":self.num_streamlines,
367
+ "euler_power": euler_power,
368
+ "enthalpy_power":enthalpy_power,
381
369
  "total-total_efficiency":total_total_efficiency,
382
370
  "total-static_efficiency":total_static_efficiency,
383
371
  "stage_loading":stage_loading,
@@ -408,7 +396,7 @@ def calculate_massflows(blade_rows:List[BladeRow],calculate_vm:bool=False,fluid:
408
396
  calculate_vm (bool, optional): _description_. Defaults to False.
409
397
  """
410
398
  for p in range(3):
411
- for i in range(1,len(blade_rows)):
399
+ for i in range(1,len(blade_rows)-1):
412
400
  row = blade_rows[i]
413
401
  # Upstream Row
414
402
  if i == 0:
@@ -417,8 +405,6 @@ def calculate_massflows(blade_rows:List[BladeRow],calculate_vm:bool=False,fluid:
417
405
  upstream = blade_rows[i-1]
418
406
  if i<len(blade_rows)-1:
419
407
  downstream = blade_rows[i+1]
420
- else:
421
- downstream = None
422
408
 
423
409
  # Pressure loss = shift in entropy which affects the total pressure of the row
424
410
  if row.row_type == RowType.Inlet:
@@ -430,13 +416,13 @@ def calculate_massflows(blade_rows:List[BladeRow],calculate_vm:bool=False,fluid:
430
416
  if row.row_type == RowType.Rotor:
431
417
  rotor_calc(row,upstream,calculate_vm=True)
432
418
  # Finds Equilibrium between Vm, P0, T0
433
- row = radeq(row,upstream)
419
+ row = radeq(row,upstream,downstream)
434
420
  compute_gas_constants(row,fluid)
435
421
  rotor_calc(row,upstream,calculate_vm=False)
436
422
  elif row.row_type == RowType.Stator:
437
423
  stator_calc(row,upstream,downstream,calculate_vm=True)
438
424
  # Finds Equilibrium between Vm, P0, T0
439
- row = radeq(row,upstream)
425
+ row = radeq(row,upstream,downstream)
440
426
  compute_gas_constants(row,fluid)
441
427
  stator_calc(row,upstream,downstream,calculate_vm=False)
442
428
  compute_gas_constants(row,fluid)