turbo-design 1.1.4__tar.gz → 1.3.0__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.1.4 → turbo_design-1.3.0}/PKG-INFO +3 -2
  2. {turbo_design-1.1.4 → turbo_design-1.3.0}/pyproject.toml +2 -2
  3. {turbo_design-1.1.4 → turbo_design-1.3.0}/turbodesign/bladerow.py +28 -24
  4. {turbo_design-1.1.4 → turbo_design-1.3.0}/turbodesign/coolant.py +2 -2
  5. {turbo_design-1.1.4 → turbo_design-1.3.0}/turbodesign/inlet.py +50 -22
  6. {turbo_design-1.1.4 → turbo_design-1.3.0}/turbodesign/outlet.py +2 -1
  7. {turbo_design-1.1.4 → turbo_design-1.3.0}/turbodesign/passage.py +62 -6
  8. {turbo_design-1.1.4 → turbo_design-1.3.0}/turbodesign/radeq.py +36 -7
  9. {turbo_design-1.1.4 → turbo_design-1.3.0}/turbodesign/solve_radeq.py +7 -6
  10. {turbo_design-1.1.4 → turbo_design-1.3.0}/turbodesign/spool.py +17 -15
  11. {turbo_design-1.1.4 → turbo_design-1.3.0}/turbodesign/td_math.py +29 -26
  12. {turbo_design-1.1.4 → turbo_design-1.3.0}/turbodesign/turbinespool.py +122 -128
  13. {turbo_design-1.1.4 → turbo_design-1.3.0}/turbodesign/__init__.py +0 -0
  14. {turbo_design-1.1.4 → turbo_design-1.3.0}/turbodesign/arrayfuncs.py +0 -0
  15. {turbo_design-1.1.4 → turbo_design-1.3.0}/turbodesign/cantera_gas/co2.yaml +0 -0
  16. {turbo_design-1.1.4 → turbo_design-1.3.0}/turbodesign/compressorspool.py +0 -0
  17. {turbo_design-1.1.4 → turbo_design-1.3.0}/turbodesign/enums.py +0 -0
  18. {turbo_design-1.1.4 → turbo_design-1.3.0}/turbodesign/isentropic.py +0 -0
  19. {turbo_design-1.1.4 → turbo_design-1.3.0}/turbodesign/loss/__init__.py +0 -0
  20. {turbo_design-1.1.4 → turbo_design-1.3.0}/turbodesign/loss/compressor/__init__.py +0 -0
  21. {turbo_design-1.1.4 → turbo_design-1.3.0}/turbodesign/loss/losstype.py +0 -0
  22. {turbo_design-1.1.4 → turbo_design-1.3.0}/turbodesign/loss/turbine/TD2.py +0 -0
  23. {turbo_design-1.1.4 → turbo_design-1.3.0}/turbodesign/loss/turbine/__init__.py +0 -0
  24. {turbo_design-1.1.4 → turbo_design-1.3.0}/turbodesign/loss/turbine/ainleymathieson.py +0 -0
  25. {turbo_design-1.1.4 → turbo_design-1.3.0}/turbodesign/loss/turbine/craigcox.py +0 -0
  26. {turbo_design-1.1.4 → turbo_design-1.3.0}/turbodesign/loss/turbine/fixedefficiency.py +0 -0
  27. {turbo_design-1.1.4 → turbo_design-1.3.0}/turbodesign/loss/turbine/fixedpressureloss.py +0 -0
  28. {turbo_design-1.1.4 → turbo_design-1.3.0}/turbodesign/loss/turbine/kackerokapuu.py +0 -0
  29. {turbo_design-1.1.4 → turbo_design-1.3.0}/turbodesign/loss/turbine/traupel.py +0 -0
  30. {turbo_design-1.1.4 → turbo_design-1.3.0}/turbodesign/lossinterp.py +0 -0
  31. {turbo_design-1.1.4 → turbo_design-1.3.0}/turbodesign/rotor.py +0 -0
  32. {turbo_design-1.1.4 → turbo_design-1.3.0}/turbodesign/stage.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: turbo-design
3
- Version: 1.1.4
3
+ Version: 1.3.0
4
4
  Summary: TurboDesign is a library used to design turbines and compressors using radial equilibrium.
5
5
  Author: Paht Juangphanich
6
6
  Author-email: paht.juangphanich@nasa.gov
@@ -10,6 +10,7 @@ Classifier: Programming Language :: Python :: 3.9
10
10
  Classifier: Programming Language :: Python :: 3.10
11
11
  Classifier: Programming Language :: Python :: 3.11
12
12
  Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3.13
13
14
  Requires-Dist: cantera
14
15
  Requires-Dist: findiff
15
16
  Requires-Dist: matplotlib
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "turbo-design"
3
- version = "1.1.4"
3
+ version = "1.3.0"
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 = [
@@ -18,7 +18,7 @@ matplotlib = "*"
18
18
  pandas = "*"
19
19
  python = ">=3.9"
20
20
 
21
- [tool.poetry.dev-dependencies]
21
+ [poetry.group.dev.dependencies]
22
22
  python = ">=3.9"
23
23
 
24
24
  [build-system]
@@ -26,7 +26,6 @@ class BladeRow:
26
26
  Cp: float = 1019 # Cp J/(Kg*K)
27
27
  Cv: float = 1019/1.14 # Cv J/(Kg*K)
28
28
  _coolant:Coolant = None # type: ignore # Coolant Fluid
29
- fluid: composite.Solution = Solution("air.yaml")
30
29
  mu:float = 0
31
30
 
32
31
  total_massflow:float = 0 # Massflow spool + all upstream cooling flow [kg/s]
@@ -35,10 +34,11 @@ class BladeRow:
35
34
  # ----------------------------------
36
35
 
37
36
  # Streamline Properties
38
- 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.
39
38
  percent_hub_shroud: npt.NDArray = field(default_factory=lambda: np.array([0])) # Percent streamline length from hub to shroud.
40
39
  x: npt.NDArray = field(default_factory=lambda: np.array([0])) # x - coordinates (useful for computing axial chord)
41
40
  r: npt.NDArray = field(default_factory=lambda: np.array([0])) # Radius - coordinates
41
+ m: npt.NDArray = field(default_factory=lambda: np.array([0])) # meridional
42
42
  area:float = 0
43
43
  # Calculated massflow is the massflow computed after radial eq solver
44
44
  calculated_massflow: float = 0
@@ -121,6 +121,7 @@ class BladeRow:
121
121
  _tip_clearance:float = 0 # Clearance as a percentage of span or blade height
122
122
 
123
123
  _inlet_to_outlet_pratio = [0.06,0.95]
124
+ location:float = 0 # Percent along hub where bladerow is defined
124
125
 
125
126
  @property
126
127
  def inlet_to_outlet_pratio(self) -> Tuple[float,float]:
@@ -328,18 +329,18 @@ class BladeRow:
328
329
  """
329
330
  self._tip_clearance = val
330
331
 
331
- def __init__(self,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):
332
333
  """Initializes the blade row to be a particular type
333
334
 
334
335
  Args:
335
- 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
336
337
  row_type (RowType): Specifies the Type. Defaults to RowType.Stator
337
338
  power (float, optional): power . Defaults to 0.
338
339
  P0_P (float, optional): Total to Static Pressure Ratio
339
340
  stage_id (int, optional): ID of the stage so if you have 9 stages, the id could be 9. It's used to separate the stages. Each stage will have it's own unique degree of reaction
340
341
  """
341
342
  self.row_type = row_type
342
- self.axial_location = axial_location
343
+ self.location = location
343
344
  self.Yp = 0 # Loss
344
345
  self.stage_id = stage_id
345
346
 
@@ -437,7 +438,7 @@ class BladeRow:
437
438
  self._te_s = val
438
439
 
439
440
  def __repr__(self):
440
- return f"{self.row_type.name:0.2f}' P0:{np.mean(self.P0):0.2f} T0:{np.mean(self.T0):0.2f} P:{np.mean(self.P):0.2f} massflow:{self.total_massflow_no_coolant}:0.3f"
441
+ return f"{self.row_type.name} P0:{np.mean(self.P0):0.2f} T0:{np.mean(self.T0):0.2f} P:{np.mean(self.P):0.2f} massflow:{np.mean(self.total_massflow_no_coolant):0.3f}"
441
442
 
442
443
  def to_dict(self):
443
444
 
@@ -512,7 +513,7 @@ def interpolate_streamline_radii(row:BladeRow,passage:Passage,num_streamlines:in
512
513
  Returns:
513
514
  (BladeRow): new row object with quantities interpolated
514
515
  """
515
- row.cutting_line,_,_ = passage.get_cutting_line(row.axial_location)
516
+ row.cutting_line,_,_ = passage.get_cutting_line(row.location)
516
517
  row.x,row.r = row.cutting_line.get_point(np.linspace(0,1,num_streamlines))
517
518
  streamline_percent_length = np.sqrt((row.r-row.r[0])**2+(row.x-row.x[0])**2)/row.cutting_line.length
518
519
 
@@ -609,21 +610,24 @@ def interpolate_quantities(q:npt.NDArray,r:npt.NDArray,r2:npt.NDArray):
609
610
  else:
610
611
  return interp1d(r,q,kind='linear')(r2)
611
612
 
612
- def compute_gas_constants(row:BladeRow):
613
- """Calculates all the gas constants for a row.
614
- This should be done if T or P change
613
+ def compute_gas_constants(row:BladeRow,fluid:Solution=None) -> None:
614
+ """Updates the Cp, Gamma, and density for a blade row. If fluid is not specified then only density and viscosity is updated.
615
+
616
+ Args:
617
+ row (BladeRow): _description_
618
+ fluid (Solution, optional): _description_. Defaults to None.
619
+
620
+ Returns:
621
+ (BladeRow): updated row
615
622
  """
616
- Tm = row.T.mean()
617
- Pm = row.P.mean()
618
- row.fluid.TP = Tm,Pm
619
- row.Cp = row.fluid.cp
620
- row.Cv = row.fluid.cv
621
- row.R = row.Cp-row.Cv
622
- row.gamma = row.Cp/row.Cv
623
- row.mu = sutherland(Tm) # type: ignore
624
- row.rho[:] = row.fluid.density
625
- # i = 0
626
- # for T,P in zip(row.T,row.P):
627
- # row.rho[i] = P/(T*row.R)
628
- # i+=1
629
- return row
623
+ if fluid:
624
+ Tm = row.T.mean()
625
+ Pm = row.P.mean()
626
+ fluid.TP = Tm,Pm
627
+ row.Cp = fluid.cp
628
+ row.Cv = fluid.cv
629
+ row.R = row.Cp-row.Cv
630
+ row.gamma = row.Cp/row.Cv
631
+ # Use Ideal Gas
632
+ row.rho = row.P/(row.T*row.R)
633
+ row.mu = sutherland(row.T) # type: ignore
@@ -3,8 +3,8 @@ from cantera import Solution
3
3
 
4
4
  @dataclass
5
5
  class Coolant:
6
- fluid:Solution = field(default = Solution('air.yaml')) # cantera solution
7
6
  T0:float = field(default=900) # Kelvin
8
7
  P0:float = field(default=50*101325) # Pascal
9
- massflow_percentage:float = field(default=0.03) # Fraction of total massflow going through compressor
8
+ massflow_percentage:float = field(default=0.03) # Fraction of total massflow going through compressor
9
+ Cp:float = field(default=1000) # J/K
10
10
 
@@ -16,57 +16,85 @@ class Inlet(BladeRow):
16
16
  (BladeRow): Defines the properties of the blade row
17
17
  """
18
18
  fun: interp1d
19
- def __init__(self,M:float,T0:Union[float,List[float]],P0:Union[float,List[float]],percent_radii:Union[float,List[float]],fluid:Solution,axial_location:float=0,beta:Union[float,List[float]]=[0]):
19
+
20
+ def __init__(self,M:float,T0:Union[float,List[float]],
21
+ P0:Union[float,List[float]],
22
+ location:float=0,
23
+ beta:Union[float,List[float]]=[0],
24
+ percent_radii:Union[float,List[float]]=[0.5]):
20
25
  """Initializes the inlet station.
21
26
  Uses the beta and exit mach number to predict a value for Vm
22
27
 
23
28
  Args:
24
29
  M (float): Mach number at the inlet plane
25
- beta (Union[float,List[float]]): exit relative flow angle
26
30
  T0 (Union[float,List[float]]): Total Temperature Array
27
31
  P0 (Union[float,List[float]]): Total Pressure Array
28
32
  percent_radii (Union[float,List[float]]): Radius where total pressure and temperature are defined
29
- fluid (ct.Solution): Cantera mixture
30
- axial_location (float): Axial Location as a percentage of hub length
33
+ location (float): Location as a percentage of hub length
31
34
  beta (Union[float,List[float]], optional): Inlet flow angle in relative direction. Defaults to [].
35
+
32
36
  """
33
- super().__init__(row_type=RowType.Inlet,axial_location=axial_location,stage_id=-1)
34
- self.loss_function = None
37
+ super().__init__(row_type=RowType.Inlet,location=location,stage_id=-1)
35
38
  self.beta1 = convert_to_ndarray(beta)
36
39
  self.M = convert_to_ndarray(M)
37
40
  self.T0 = convert_to_ndarray(T0)
38
41
  self.P0 = convert_to_ndarray(P0)
39
42
  self.percent_hub_shroud = convert_to_ndarray(percent_radii)
43
+
44
+
45
+ def initialize_fluid(self,fluid:Solution=None,R:float=287.15,gamma:float=1.4,Cp:float=1024):
46
+ """Initialize the inlet using the fluid. This function should be called by a class that inherits from spool
47
+
48
+ Args:
49
+ fluid (Solution, optional): Cantera fluid object. Defaults to None.
50
+ R (float, optional): Ideal Gas Constant. Defaults to 287.15 J/(Kg K) for air
51
+ gamma (float, optional): _description_. Defaults to 1.4.
52
+ Cp (float, optional): _description_. Defaults to 1024 J/(Kg K).
53
+
54
+ """
55
+ self.loss_function = None
56
+
40
57
  # if it's inlet alpha and beta are the same, relative flow angle = absolute.
41
- self.beta2 = np.radians(convert_to_ndarray(beta))
42
- self.alpha1 = np.radians(convert_to_ndarray(beta))
43
- fluid.TP = self.T0.mean(),self.P0.mean()
44
- self.gamma = fluid.cp/fluid.cv
58
+ self.beta2 = np.radians(convert_to_ndarray(self.beta1))
59
+ self.alpha1 = np.radians(convert_to_ndarray(self.beta1))
45
60
 
46
- self.T = self.T0 * 1/(1 + (self.gamma-1) * self.M**2)
47
- self.P = self.P0 * 1/(1 + (self.gamma-1) * self.M**2)**(self.gamma/(self.gamma-1))
48
- fluid.TP = self.T.mean(),self.P.mean()
49
- self.rho = convert_to_ndarray([fluid.density])
50
- self.fluid = fluid
61
+ if fluid:
62
+ fluid.TP = self.T0.mean(),self.P0.mean()
63
+ self.gamma = fluid.cp/fluid.cv
64
+ self.T = self.T0 * 1/(1 + (self.gamma-1) * self.M**2)
65
+ self.P = self.P0 * 1/(1 + (self.gamma-1) * self.M**2)**(self.gamma/(self.gamma-1))
66
+ fluid.TP = self.T.mean(),self.P.mean()
67
+ self.rho = convert_to_ndarray([fluid.density])
68
+ else:
69
+ self.Cp = Cp
70
+ self.gamma = gamma
71
+ self.R = R
72
+ self.T = self.T0 * 1/(1 + (self.gamma-1) * self.M**2)
73
+ self.P = self.P0 * 1/(1 + (self.gamma-1) * self.M**2)**(self.gamma/(self.gamma-1))
74
+ self.rho = self.P/(self.R*self.T)
75
+
51
76
  self.rpm = 0
52
-
53
77
  self.beta1_metal = [0]
54
78
  self.beta2_metal = [0]
55
- self.P0_fun = interp1d(self.percent_hub_shroud,P0)
56
- self.T0_fun = interp1d(self.percent_hub_shroud,T0)
79
+ self.P0_fun = interp1d(self.percent_hub_shroud,self.P0)
80
+ self.T0_fun = interp1d(self.percent_hub_shroud,self.T0)
57
81
  self.mprime = [0]
58
-
59
82
 
60
83
  def initialize_velocity(self,passage:Passage,num_streamlines:int):
61
84
  """Initialize velocity calculations. Assumes streamlines and inclination angles have been calculated
62
-
85
+ Call this before performing calculations
86
+
87
+ Args:
88
+ passage (Passage): Passage object
89
+ num_streamlines (int): number of streamlines
90
+
63
91
  """
64
92
  # Perform Calculations on Velocity
65
93
  Vm_prev = 0; Vm_err = 0
66
94
  t,x,radius = passage.get_streamline(self.percent_hub_shroud)
67
95
  radius = radius[0]
68
96
 
69
- cutline,_,_ = passage.get_cutting_line(self.axial_location)
97
+ cutline,_,_ = passage.get_cutting_line(self.location)
70
98
  self.x,self.r = cutline.get_point(np.linspace(0,1,num_streamlines))
71
99
  for _ in range(10):
72
100
  T0_T = (1+(self.gamma-1)/2 * self.M**2)
@@ -84,7 +112,7 @@ class Inlet(BladeRow):
84
112
  self.V = np.sqrt(self.Vm**2 + self.Vt**2)
85
113
  self.Vr = self.Vm * np.sin(self.phi)
86
114
 
87
- self = compute_gas_constants(self)
115
+ compute_gas_constants(self)
88
116
  rho_mean = self.rho.mean()
89
117
  for i in range(len(self.massflow)-1):
90
118
  tube_massflow = self.massflow[i+1]-self.massflow[i]
@@ -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
@@ -142,7 +142,31 @@ class Passage:
142
142
 
143
143
  return phi, rm, r
144
144
 
145
- def get_cutting_line(self, t_hub:float) -> line2D:
145
+ def get_area(self,t_hub:float) -> float:
146
+ """Get Area
147
+
148
+ Args:
149
+ t_hub (float): Percent arc length along the hub
150
+
151
+ Returns:
152
+ float: Area
153
+ """
154
+ n = 100
155
+ line = self.get_cutting_line(t_hub)[0]
156
+ x,r = line.get_point(np.linspace(0,1,n))
157
+ total_area = 0
158
+ for j in range(1,n):
159
+ if np.abs((x[-1]-x[0]))<1E-12: # Axial Machines
160
+ total_area += np.pi*(r[j]**2-r[j-1]**2)
161
+ else: # Radial Machines
162
+ dx = x[j]-x[j-1]
163
+ S = (r[j]-r[j-1])
164
+ C = np.sqrt(1+((r[j]-r[j-1])/dx)**2)
165
+ area = 2*np.pi*C*(S/2*dx**2+r[j-1]*dx)
166
+ total_area += area
167
+ return total_area
168
+
169
+ def get_cutting_line(self, t_hub:float) -> Tuple[line2D,float,float]:
146
170
  """Gets the cutting line perpendicular to hub and shroud
147
171
 
148
172
  Args:
@@ -188,17 +212,18 @@ class Passage:
188
212
  rshroud = self.rshroud(t_shroud)
189
213
  return line2D([xhub,rhub],[xshroud,rshroud]), t_hub, t_shroud
190
214
 
191
- def get_xr_slice(self,t_span:float,axial_location:Tuple[float,float]):
215
+ def get_xr_slice(self,t_span:float,percent_hub:Tuple[float,float],resolution:int=100):
192
216
  """Returns the xr coordinates of a streamline, a line that is parallel to both hub and shroud
193
217
 
194
218
  Args:
195
219
  t_span (float): _description_
196
- axial_location (float): _description_
220
+ meridional_location (float): _description_
221
+ resolution (int): number of points to resolve
197
222
 
198
223
  Returns:
199
224
  np.NDArray: _description_
200
225
  """
201
- t_hub = np.linspace(axial_location[0],axial_location[1],100)
226
+ t_hub = np.linspace(percent_hub[0],percent_hub[1],resolution)
202
227
  t_hub = convert_to_ndarray(t_hub)*self.hub_length
203
228
 
204
229
  shroud_pts_cyl = np.vstack([self.xshroud(t_hub),self.rshroud(t_hub)]).transpose()
@@ -209,8 +234,39 @@ class Passage:
209
234
  for j in range(n):
210
235
  l = line2D(hub_pts_cyl[j,:],shroud_pts_cyl[j,:])
211
236
  xr[j,0],xr[j,1] = l.get_point(t_span)
237
+
212
238
  return xr
213
-
239
+
240
+ def get_m(self,t_span:float,resolution:int=100) -> npt.NDArray:
241
+ """Meridional cooridnates
242
+
243
+ Args:
244
+ t_span (float): _description_
245
+ resolution (int, optional): _description_. Defaults to 100.
246
+
247
+ Returns:
248
+ npt.NDArray: _description_
249
+ """
250
+ xr = self.get_xr_slice(t_span,(0,1),resolution)
251
+ dx = np.diff(xr[:,0])
252
+ dr = np.diff(xr[:,1])
253
+ m = np.concat([[0],np.cumsum(np.sqrt(dx**2 + dr**2))])
254
+ return m
255
+
256
+ def get_dm(self,t_span:float,location:float,resolution:int=1000) -> float:
257
+ """return the derivative in the meridional direction at a particular point
258
+
259
+ Args:
260
+ t_span (float): percent span
261
+ location (float): hub location of the blade
262
+ resolution (int, optional): number of points to represent the hub curve. Defaults to 1000.
263
+
264
+ Returns:
265
+ (float) : returns the derivative
266
+ """
267
+ m = self.get_m(t_span,resolution)
268
+ return PchipInterpolator(np.linspace(0,1,resolution),np.diff(m))(location)
269
+
214
270
  @property
215
271
  def hub_length(self):
216
272
  """returns the computed length of the hub
@@ -1,11 +1,12 @@
1
- from scipy.interpolate import interp1d
1
+ from scipy.interpolate import interp1d,PchipInterpolator
2
2
  from scipy.integrate import odeint
3
- import numpy as np
3
+ import numpy as np
4
+ import numdifftools as nd
4
5
  from .bladerow import BladeRow
5
6
  from .enums import RowType
6
7
 
7
8
 
8
- def radeq(row:BladeRow,upstream:BladeRow) -> BladeRow:
9
+ def radeq(row:BladeRow,upstream:BladeRow,downstream:BladeRow=None) -> BladeRow:
9
10
  """Solves the radial equilibrium equation for axial machines and returns the convergence.
10
11
 
11
12
  Note:
@@ -14,7 +15,8 @@ def radeq(row:BladeRow,upstream:BladeRow) -> BladeRow:
14
15
  Args:
15
16
  row (BladeRow): Current row
16
17
  upstream (BladeRow): Previous row
17
-
18
+ downstream (BladeRow): Next row
19
+
18
20
  Returns:
19
21
  BladeRow: current row with T0, P0, and Vm calculated
20
22
  """
@@ -57,8 +59,30 @@ def radeq(row:BladeRow,upstream:BladeRow) -> BladeRow:
57
59
  # Estimations
58
60
  dVm_dr = float(interp1d(row_radius, np.gradient(row.Vm, row_radius))(r))
59
61
  dVt_dr = dVm_dr*np.tan(alpha)
60
- dVr_dr = dVm_dr*np.sin(phi)
61
-
62
+ # dVr_dr = dVm_dr*np.sin(phi)
63
+
64
+ up_Vm = interp1d(row_radius, upstream.Vm)(r)
65
+ if downstream:
66
+ if downstream.row_type == RowType.Outlet:
67
+ down_Vm = Vm
68
+ else:
69
+ down_Vm = interp1d(row_radius, downstream.Vm)(r)
70
+ else:
71
+ down_Vm = Vm
72
+ up_m = interp1d(row_radius, upstream.m)(r)
73
+
74
+ # Get a rough guess of dVm/dm
75
+ if downstream!=None:
76
+ down_m = interp1d(row_radius, downstream.m)(r)
77
+ row_m = interp1d(row_radius, row.m)(r)
78
+ if down_m != row_m:
79
+ func_Vm_m = PchipInterpolator([up_m, row_m, down_m],[up_Vm, Vm, down_Vm])
80
+ else:
81
+ func_Vm_m = PchipInterpolator([up_m, row_m],[up_Vm, Vm])
82
+ else:
83
+ func_Vm_m = PchipInterpolator([up_m, row_m],[up_Vm, Vm])
84
+ dVm_dm = func_Vm_m.derivative()(row_m)
85
+
62
86
  # Upstream
63
87
  dT0up_dr = float(interp1d(upstream.percent_hub_shroud, np.gradient(upstream.T0,up_radius))((r-row_radius[0])/(row_radius[-1]-row_radius[0]))) # use percentage to get the T0 upstream value
64
88
  dP0up_dr = float(interp1d(upstream.percent_hub_shroud, np.gradient(upstream.P0,up_radius))((r-row_radius[0])/(row_radius[-1]-row_radius[0]))) # use percentage to get the T0 upstream value
@@ -79,7 +103,12 @@ def radeq(row:BladeRow,upstream:BladeRow) -> BladeRow:
79
103
  raise Exception("Invalid value of C {C}, change reduce alpha or Vm")
80
104
  B = (1-C)**(gamma/(gamma-1))
81
105
  A = P0 * gamma/(gamma-1) * (1-C)**(1/(gamma-1))
82
- 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
106
+
107
+ epsilon = 1e-10 # or another small threshold
108
+ if abs(rm) > epsilon:
109
+ dVm_dr = Cp*T0/(Vm*A*(1+np.tan(alpha)**2)) * (rho*(Vt**2/r + Vm**2/rm * np.cos(phi) - Vr*dVm_dm) - B*dP0_dr) + Vm/(2*T0) *dT0_dr # Eqn 6
110
+ else:
111
+ dVm_dr = Cp*T0/(Vm*A*(1+np.tan(alpha)**2)) * (rho*(Vt**2/r - Vr*dVm_dm) - B*dP0_dr) + Vm/(2*T0) *dT0_dr # Eqn 6
83
112
 
84
113
  ydot = np.array([dP0_dr,dT0_dr,dVm_dr])
85
114
 
@@ -18,7 +18,8 @@ def adjust_streamlines(blade_rows:List[BladeRow],passage:Passage):
18
18
  passage (Passage): passage object describing the hub and shroud
19
19
 
20
20
  """
21
- for _,row in enumerate(blade_rows):
21
+ for row_index,row in enumerate(blade_rows):
22
+ print(f"Adjusting Streamlines to balance massflow Row: {row_index}")
22
23
  massflow_fraction = np.linspace(0,1,len(row.percent_hub_shroud))
23
24
  row.total_massflow = row.massflow[-1]
24
25
  ideal_massflow_fraction = row.massflow[-1] * massflow_fraction
@@ -26,13 +27,13 @@ def adjust_streamlines(blade_rows:List[BladeRow],passage:Passage):
26
27
  new_percent_streamline = interp1d(row.massflow,row.percent_hub_shroud)(ideal_massflow_fraction[1:-1])
27
28
  row.percent_hub_shroud[1:-1] = new_percent_streamline
28
29
 
29
- cut_line, thub,_ = passage.get_cutting_line(row.axial_location)
30
+ cut_line, thub,_ = passage.get_cutting_line(row.percent_hub)
30
31
  row.x,row.r = cut_line.get_point(row.percent_hub_shroud)
31
32
  # Radii may have shifted, recompute Ay and rm
32
33
  for i,tr in enumerate(row.percent_hub_shroud):
33
34
  t_streamline, x_streamline, r_streamline = passage.get_streamline(tr)
34
35
  phi, rm, r = passage.streamline_curvature(x_streamline,r_streamline)
35
- row.phi[i] = float(interp1d(t_streamline,phi)(row.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))
38
- 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))
@@ -1,7 +1,7 @@
1
1
  # type: ignore[arg-type, reportUnknownArgumentType]
2
2
  from dataclasses import field
3
3
  import json
4
- from typing import Dict, List, Union
4
+ from typing import Dict, List, Union, Optional
5
5
  import matplotlib.pyplot as plt
6
6
  from .bladerow import BladeRow
7
7
  import numpy as np
@@ -34,7 +34,7 @@ class Spool:
34
34
  def __init__(self,passage:Passage,
35
35
  massflow:float,rows=List[BladeRow],
36
36
  num_streamlines:int=3,
37
- fluid:Solution=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
  """
@@ -70,13 +70,12 @@ class Spool:
70
70
  2. Counter Rotation: We either use the RPM already persecribed for each blade row.
71
71
  '''
72
72
  if (type(self.blade_rows[i]) != Inlet) and (type(self.blade_rows[i]) != Outlet):
73
- self.blade_rows[i].fluid = self.fluid
74
73
  self.blade_rows[i].rpm = rpm
75
- self.blade_rows[i].axial_chord = self.blade_rows[i].axial_location * self.passage.hub_length
74
+ self.blade_rows[i].axial_chord = self.blade_rows[i].location * self.passage.hub_length
76
75
 
77
76
 
78
77
  @property
79
- def fluid(self):
78
+ def fluid(self) -> Optional[Solution]:
80
79
  return self._fluid
81
80
 
82
81
  @fluid.setter
@@ -159,13 +158,15 @@ class Spool:
159
158
  row.phi = np.zeros((self.num_streamlines,))
160
159
  row.rm = np.zeros((self.num_streamlines,))
161
160
  row.r = np.zeros((self.num_streamlines,))
161
+ row.m = np.zeros((self.num_streamlines,))
162
162
 
163
163
  t_radial = np.linspace(0,1,self.num_streamlines)
164
164
  self.calculate_streamline_curvature(row,t_radial)
165
-
165
+
166
166
  # Set the loss function if it's not set
167
- if (row.loss_function == None):
168
- row.loss_function = TD2()
167
+ if (type(row)!= Inlet and type(row) != Outlet):
168
+ if row.loss_function == None:
169
+ row.loss_function = TD2()
169
170
 
170
171
  def calculate_streamline_curvature(self,row:BladeRow,t_radial:Union[List[float],npt.NDArray]):
171
172
  """Called to calculate new streamline curvature
@@ -177,9 +178,10 @@ class Spool:
177
178
  for i,tr in enumerate(t_radial):
178
179
  t_streamline, x_streamline, r_streamline = self.passage.get_streamline(tr)
179
180
  phi, rm, r = self.passage.streamline_curvature(x_streamline,r_streamline)
180
- row.phi[i] = float(interp1d(t_streamline,phi)(row.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))