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.
@@ -0,0 +1,60 @@
1
+ from typing import List
2
+ from cantera import composite
3
+ from cantera.composite import Solution
4
+ from .bladerow import BladeRow,RowType
5
+ from .spool import Spool
6
+ import json
7
+
8
+ class CompressorSpool(Spool):
9
+ def __init__(self,rhub:List[float],xhub:List[float],
10
+ rshroud:List[float],xshroud:List[float],
11
+ massflow:float,rows=List[BladeRow],
12
+ num_streamlines:int=3,
13
+ fluid:Solution=Solution('air.yaml'),rpm:float=-1):
14
+ """Initializes a Compressor Spool
15
+
16
+ Args:
17
+ rhub (List[float]): hub radius. Units meters
18
+ xhub (List[float]): x location of each hub radius station. Units meters
19
+ rshroud (List[float]): shroud radius. Units meters
20
+ xshroud (List[float]): x location of each shroud radius. Units meters
21
+ massflow (float): massflow at spool inlet
22
+ rows (List[BladeRow], optional): List of blade rows. Defaults to List[BladeRow].
23
+ num_streamlines (int, optional): number of streamlines. Defaults to 3.
24
+ gas (ct.Solution, optional): cantera gas solution. Defaults to ct.Solution('air.yaml').
25
+ rpm (float, optional): RPM for the entire spool Optional, you can also set rpm of the blade rows individually. Defaults to -1.
26
+ power_target (float, optional): Sets a target power in kW
27
+ """
28
+ super().__init__(rhub, xhub, rshroud, xshroud, massflow, rows,num_streamlines, fluid, rpm)
29
+
30
+ pass
31
+
32
+ def export_properties(self,filename:str="compressor_spool.json"):
33
+ """Export the spool object to json
34
+
35
+ Args:
36
+ filename (str, optional): name of export file. Defaults to "spool.json".
37
+ """
38
+ blade_rows = list()
39
+ total_total_efficiency = list()
40
+
41
+ for indx,row in enumerate(self.blade_rows):
42
+ blade_rows.append(row.to_dict()) # Appending data
43
+ if row.row_type == RowType.Stator:
44
+ pass
45
+
46
+ data = {
47
+ "blade_rows": blade_rows,
48
+ "massflow":self.massflow,
49
+ "rpm":self.rpm,
50
+ "r_streamline":self.r_streamline.tolist(),
51
+ "x_streamline":self.x_streamline.tolist(),
52
+ "rhub":self.rhub.tolist(),
53
+ "rshroud":self.rshroud.tolist(),
54
+ "xhub":self.xhub.tolist(),
55
+ "xshroud":self.xshroud.tolist(),
56
+ "num_streamlines":self.num_streamlines,
57
+ }
58
+ # Dump all the Python objects into a single JSON file.
59
+ with open(filename, "w") as f:
60
+ json.dump(data, f, indent=4)
turbodesign/coolant.py ADDED
@@ -0,0 +1,10 @@
1
+ from dataclasses import dataclass, field
2
+ from cantera import Solution
3
+
4
+ @dataclass
5
+ class Coolant:
6
+ fluid:Solution = field(default = Solution('air.yaml')) # cantera solution
7
+ T0:float = field(default=900) # Kelvin
8
+ P0:float = field(default=50*101325) # Pascal
9
+ massflow_percentage:float = field(default=0.03) # Fraction of total massflow going through compressor
10
+
turbodesign/enums.py ADDED
@@ -0,0 +1,36 @@
1
+ from enum import Enum
2
+
3
+ class LossType(Enum):
4
+ Pressure = 1
5
+ Enthalpy = 2
6
+ Entropy = 3
7
+
8
+ class RowType(Enum):
9
+ Stator = 1
10
+ Rotor = 2
11
+ CounterRotating = 2
12
+ Inlet = 3
13
+ Outlet = 4
14
+
15
+ class MassflowConstraint(Enum):
16
+ MatchMassFlow:int = 1 # Changes the exit angles to match the massflow
17
+ BalanceMassFlow:int = 2 # Keeps the exit angle but balances the massflow between the stages as best it can. This will affect the static pressure at the stage exit
18
+
19
+ class PowerType(Enum):
20
+ """The code for BladeRow will assume a PowerType automatically depending on what you specify. If you specify the blade row to have P0_P which is the stator inlet total pressure to rotor exit static pressure then that will be used to calculate all the quantities.
21
+
22
+ Or if you specify the power for the blade row, the Total Temperature at Rotor Exit will change.
23
+
24
+ P0_P: Assumes a Total-Static Pressure Ratio. Using this method will not allow you to match a particular massflow
25
+
26
+ T0: Total Temperature at exit is specified based on power required
27
+
28
+ """
29
+ #
30
+ P0_P:int = 1
31
+ T0:int = 2
32
+
33
+
34
+ class PassageType(Enum):
35
+ Centrifugal:int=0
36
+ Axial:int=1
turbodesign/inlet.py ADDED
@@ -0,0 +1,146 @@
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
+ from cantera import Solution
8
+ from .passage import Passage
9
+ from scipy.interpolate import interp1d
10
+ import numpy.typing as npt
11
+
12
+ class Inlet(BladeRow):
13
+ """Station defined at Inlet
14
+
15
+ Inherits:
16
+ (BladeRow): Defines the properties of the blade row
17
+ """
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]):
20
+ """Initializes the inlet station.
21
+ Uses the beta and exit mach number to predict a value for Vm
22
+
23
+ Args:
24
+ M (float): Mach number at the inlet plane
25
+ beta (Union[float,List[float]]): exit relative flow angle
26
+ T0 (Union[float,List[float]]): Total Temperature Array
27
+ P0 (Union[float,List[float]]): Total Pressure Array
28
+ 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
31
+ beta (Union[float,List[float]], optional): Inlet flow angle in relative direction. Defaults to [].
32
+ """
33
+ super().__init__(row_type=RowType.Inlet,axial_location=axial_location,stage_id=-1)
34
+ self.loss_function = None
35
+ self.beta1 = convert_to_ndarray(beta)
36
+ self.M = convert_to_ndarray(M)
37
+ self.T0 = convert_to_ndarray(T0)
38
+ self.P0 = convert_to_ndarray(P0)
39
+ self.percent_hub_shroud = convert_to_ndarray(percent_radii)
40
+ # 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
45
+
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
51
+ self.rpm = 0
52
+
53
+ self.beta1_metal = [0]
54
+ self.beta2_metal = [0]
55
+ self.P0_fun = interp1d(self.percent_hub_shroud,P0)
56
+ self.T0_fun = interp1d(self.percent_hub_shroud,T0)
57
+
58
+
59
+ def initialize_velocity(self,passage:Passage,num_streamlines:int):
60
+ """Initialize velocity calculations. Assumes streamlines and inclination angles have been calculated
61
+
62
+ """
63
+ # Perform Calculations on Velocity
64
+ Vm_prev = 0; Vm_err = 0
65
+ t,x,radius = passage.get_streamline(self.percent_hub_shroud)
66
+ radius = radius[0]
67
+
68
+ cutline,_,_ = passage.get_cutting_line(self.axial_location)
69
+ self.x,self.r = cutline.get_point(np.linspace(0,1,num_streamlines))
70
+
71
+ for _ in range(10):
72
+ T0_T = (1+(self.gamma-1)/2 * self.M**2)
73
+
74
+ self.Vm = self.M**2 * self.gamma*self.R*self.T0/T0_T \
75
+ / (1+np.cos(self.phi)**2 * np.tan(self.alpha1)**2)
76
+
77
+ self.Vm = np.sqrt(self.Vm)
78
+ self.T = self.T0/T0_T
79
+ self.P = self.P0/(T0_T)**(self.gamma/(self.gamma-1))
80
+ self.rho = self.P/(self.R*self.T)
81
+
82
+ self.Vx = self.Vm * np.cos(self.phi)
83
+ self.Vt = self.Vm * np.cos(self.phi) * np.tan(self.beta1)
84
+ self.V = np.sqrt(self.Vm**2 + self.Vt**2)
85
+ self.Vr = self.Vm * np.sin(self.phi)
86
+
87
+ self = compute_gas_constants(self)
88
+ rho_mean = self.rho.mean()
89
+ for i in range(len(self.massflow)-1):
90
+ tube_massflow = self.massflow[i+1]-self.massflow[i]
91
+ if np.abs((self.x[i]-self.x[i-1]))<1E-12: # Axial Machines
92
+ self.Vm[i+1] = tube_massflow/(rho_mean*np.pi*(self.r[i+1]**2-self.r[i]**2))
93
+ else: # Radial Machines
94
+ dx = self.x[i]-self.x[i-1]
95
+ S = (self.r[i]-self.r[i-1])
96
+ C = np.sqrt(1+((self.r[i]-self.r[i-1])/dx)**2)
97
+ area = 2*np.pi*C*(S/2*dx**2+self.r[i-1]*dx)
98
+ self.Vm[i+1] = tube_massflow/(rho_mean*area)
99
+ self.Vm[0] = 1/(len(self.Vm)-1)*self.Vm[1:].sum()
100
+
101
+ self.M = self.Vm /np.sqrt(self.gamma*self.R*self.T)
102
+ Vm_err = np.max(abs(self.Vm-Vm_prev)/self.Vm)
103
+ Vm_prev = self.Vm
104
+ if Vm_err < 1E-4:
105
+ break
106
+
107
+ Area = 0
108
+ for j in range(1,num_streamlines):
109
+ if np.abs((self.x[j]-self.x[j-1]))<1E-12: # Axial Machines
110
+ Area += np.pi*(self.r[j]**2-self.r[j-1]**2)
111
+ else: # Radial Machines
112
+ dx = self.x[j]-self.x[j-1]
113
+ S = (self.r[j]-self.r[j-1])
114
+ C = np.sqrt(1+((self.r[j]-self.r[j-1])/dx)**2)
115
+ Area += 2*np.pi*C*(S/2*dx**2+self.r[j-1]*dx)
116
+
117
+ self.calculated_massflow = self.rho.mean()*self.Vm.mean() * Area
118
+
119
+
120
+ def get_total_pressure(self,percent_hub_shroud:Union[float,npt.NDArray]):
121
+ """Returns the static pressure at a certain percent hub_shroud
122
+
123
+ Args:
124
+ percent_hub_shroud (Union[float,npt.NDArray]): _description_
125
+
126
+ Returns:
127
+ _type_: _description_
128
+ """
129
+ if type(percent_hub_shroud) == float:
130
+ return float(self.P0_fun(percent_hub_shroud))
131
+ else:
132
+ return self.P0_fun(percent_hub_shroud)
133
+
134
+ def get_total_temperature(self,percent_hub_shroud:Union[float,npt.NDArray]):
135
+ """Returns the static pressure at a certain percent hub_shroud
136
+
137
+ Args:
138
+ percent_hub_shroud (Union[float,npt.NDArray]): _description_
139
+
140
+ Returns:
141
+ _type_: _description_
142
+ """
143
+ if type(percent_hub_shroud) == float:
144
+ return float(self.T0_fun(percent_hub_shroud))
145
+ else:
146
+ return self.T0_fun(percent_hub_shroud)
@@ -0,0 +1,82 @@
1
+ import numpy as np
2
+ import math
3
+
4
+
5
+ def IsenP(M:np.ndarray,gamma:float) -> float:
6
+ """Computes the ratio P0/Ps
7
+
8
+ Args:
9
+ M (np.ndarray): Mach Number
10
+ gamma (float): specific heat ratio
11
+
12
+ Returns:
13
+ float: P0/P ratio
14
+ """
15
+ return np.power((1+(gamma-1)/2.0 * M*M),gamma/(gamma-1))
16
+
17
+
18
+ def FindMachP0P(P0_P:np.ndarray,gamma:float) -> float:
19
+ """Finds the mach number given a P0/P ratio
20
+
21
+ Args:
22
+ P0_P (np.ndarray): ratio of total to static pressure
23
+ gamma (float): specific heat ratio
24
+
25
+ Returns:
26
+ float: [description]
27
+ """
28
+ n = (gamma-1)/gamma
29
+ c = 2.0/(gamma-1) * (np.power(P0_P,n) - 1.0)
30
+
31
+ M = np.sqrt(c)
32
+ return M # Subsonic and supersonic solution
33
+
34
+
35
+
36
+ def IsenT(M:np.ndarray,gamma:float) -> float:
37
+ """Computes T0/Ts
38
+
39
+ Args:
40
+ M (np.ndarray): _description_
41
+ gamma (float): _description_
42
+
43
+ Returns:
44
+ float: Ratio of T0/Ts
45
+ """
46
+ return (1.0+(gamma-1.0)/2.0 *M*M)
47
+
48
+
49
+ def A_As(M:np.ndarray,gamma:float) -> float:
50
+ """Computes the ratio of Area to Throat Area give a given mach number and gamma
51
+
52
+ Args:
53
+ M (np.ndarray): Mach Number
54
+ gamma (float): Specific Heat Ratio
55
+
56
+ Returns:
57
+ float: Area to throat area ratio
58
+ """
59
+ a = (gamma+1.0)/(2.0*(gamma-1.0))
60
+ temp1 = np.power((gamma+1.0)/2.0,a)
61
+ temp2 = np.power((1+(gamma-1)/2*M*M),-a)/M
62
+ return temp1*temp2
63
+
64
+
65
+ def Massflow(P0:float,T0:float,A:float,M:float,gamma:float,R:float=287):
66
+ """Massflow rate calculation
67
+
68
+ Args:
69
+ P0 (float): Inlet Total Pressure (Pa)
70
+ T0 (float): Inlet Total Temperature (K)
71
+ A (float): Area (m^2)
72
+ M (float): Mach Number
73
+ gamma (float): Ratio of specific heats
74
+ R (float): Ideal Gas Constant. Defaults to 287 J/(KgK).
75
+
76
+ Returns:
77
+ float: Nusselt Number
78
+ """
79
+ mdot = A * P0/np.sqrt(T0) * np.sqrt(gamma/R) * M \
80
+ *np.power(1.0+(gamma-1.0)/2.0 * M*M, -(gamma+1.0)/(2.0*(gamma-1.0)))
81
+
82
+ return mdot
@@ -0,0 +1 @@
1
+ from .losstype import LossType, LossBaseClass
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,25 @@
1
+ from typing import Any, Dict
2
+ from ..lossinterp import LossInterp
3
+ import os
4
+ from ..enums import LossType
5
+
6
+ class LossBaseClass:
7
+ data: Dict[str,LossInterp]
8
+ _loss_type:LossType
9
+
10
+ def __init__(self,lossType:LossType):
11
+ # Make the environment directory
12
+ default_home = os.path.join(os.path.expanduser("~"), ".cache")
13
+ os.environ['TD3_HOME'] = os.path.join(default_home,'TD3_LossModels')
14
+ os.makedirs(os.environ['TD3_HOME'],exist_ok=True)
15
+
16
+ self._loss_type = lossType
17
+
18
+ def __call__(self, row:Any, upstream:Any):
19
+ raise Exception("Method needs to be overridden")
20
+
21
+
22
+
23
+ @property
24
+ def loss_type(self):
25
+ return self._loss_type
@@ -0,0 +1,142 @@
1
+ import math
2
+ from ...lossinterp import LossInterp
3
+ from ...enums import RowType, LossType
4
+ from typing import Any, Callable, Dict, List, Union
5
+ from ...bladerow import BladeRow
6
+ from scipy.stats import linregress
7
+ import numpy as np
8
+ from ..losstype import LossBaseClass
9
+
10
+
11
+ class TD2(LossBaseClass):
12
+
13
+
14
+ def __init__(self):
15
+ super().__init__(LossType.Pressure)
16
+
17
+
18
+ @property
19
+ def LossType(self):
20
+ return self._loss_type
21
+
22
+ def __call__(self,row:BladeRow, upstream:BladeRow) -> float:
23
+ """TD2-2_Manual equations 12a and 12b. This is the loss equation used inside original TD2.
24
+ #! Wrong very wrong from the book definition
25
+ Total Pressure Loss (Y) = (P_in - P_ex) / (P_ex - P_ex,static)
26
+ Where P_in and P_ex are total quantities
27
+
28
+ Authors Comments:
29
+ Given the assumptions, the use of this loss correlation should be limited to starting a solution.
30
+
31
+ Assumptions:
32
+ 1. Rotor and stator loss coefficients were equal when their relative design requirements are identical
33
+ 2. Stage reaction at meanline is 50%
34
+ 3. Axial velocity was constant through the stage
35
+ 4. Stator exit Mach number is 0.8
36
+
37
+ Args:
38
+ beta_in (float): Inlet flow angle in degrees.
39
+ beta_ex (float): Exit flow angle in degrees.
40
+ V_ratio (float): Ratio if inlet velocity magnitude with relative exit velocity magnitude
41
+
42
+ Returns:
43
+ float: Total Pressure loss coefficient
44
+ """
45
+ beta_in = row.beta1.mean() # absolute flow angle entering the blade row
46
+ beta_ex = row.beta2.mean() # flow angle leaving the blade row
47
+ if row.row_type == RowType.Stator:
48
+ V_ratio = row.V.mean() / upstream.V.mean() # Vin/Vex Equation 12a and 12b. Relative to Stator
49
+ elif row.row_type == RowType.Rotor:
50
+ V_ratio = row.W.mean() /upstream.W.mean() # Vin/Vex Equation 12a and 12b. Relative to Rotor
51
+
52
+ a = [0.055, 0.15, 0.6, 0.6, 0.8, 0.03, 0.157255, 3.6] # Coefficients from pw3hp1.json
53
+ # mu should be in
54
+ if V_ratio < a[2]:
55
+ Y = abs(math.tan(beta_in) - math.tan(beta_ex)) / (a[3] + a[4]*math.cos(beta_ex)) * (a[5] + a[6]*V_ratio**a[7])
56
+ else:
57
+ Y = abs(math.tan(beta_in) - math.tan(beta_ex)) / (a[3] + a[4]*math.cos(beta_ex)) * (a[0] + a[1]*(V_ratio - a[2]))
58
+
59
+ return Y
60
+
61
+ class TD2_Reynolds_Correction(LossBaseClass):
62
+
63
+ def __init__(self):
64
+ super().__init__(LossType.Pressure)
65
+
66
+ self.TD2 = TD2()
67
+
68
+ @property
69
+ def LossType(self):
70
+ return self._loss_type
71
+
72
+ def __call__(self,upstream:BladeRow, row:BladeRow) -> float:
73
+ """TD2_Pressure_Loss with Reynolds Correction Factor. This is from NASA SP-290 (Vol.1, p.62)
74
+
75
+ The correlations come from td2-2.f Line 2771
76
+ WYECOR(I)=WYECOR(I)*(0.35+0.65*18.21)/(0.35+0.65*(FLWP/VISC/RST(MEAN))**(0.2))
77
+
78
+ I have assumed that 18.21 is some reference reynolds number. There is very little documentation on what these numbers are. The entire fortran code is frustrating and probably should never have happened in the first place.
79
+
80
+ Args:
81
+ row (BladeRow): Current Blade Row
82
+
83
+ Returns:
84
+ float: Total Pressure loss coefficient
85
+ """
86
+ Y = self.TD2(upstream,row)
87
+ A = 0.35
88
+ B = 0.65
89
+ Y = Y * (A+B*18.21)/(0.35+0.65*row.massflow/(row.mu* row.r.mean()))
90
+ row.Yp = Y
91
+ return Y
92
+
93
+ # def Soderberg(upstream:BladeRow,row:BladeRow) -> float:
94
+ # """Soderberg Loss for axial machines. Takes into account the aspect ratio
95
+
96
+ # Args:
97
+ # upstream (BladeRow): _description_
98
+ # row (BladeRow): _description_
99
+
100
+ # Returns:
101
+ # float: Enthalpy Loss Coefficients
102
+ # """
103
+ # H = row.r.max()-row.r.min()
104
+ # l = row.x[-1]-row.x[0]
105
+ # xi = 0.04+0.06*((row.beta2-row.beta1)/100)**2
106
+ # if row.row_type == RowType.Stator:
107
+ # mu = sutherland(row.T.mean())
108
+ # Re = row.rho*row.V*(l)/mu
109
+ # enthalpy_loss = (10E5/Re)**0.25 * ((1+xi)*(0.993+0.075*l/H)-1)
110
+ # else:
111
+ # mu = sutherland(row.T.mean())
112
+ # Re = row.rho*row.W*(row.x[-1]-row.x[0])/mu
113
+ # enthalpy_loss = (10E5/Re)**0.25 * ((1+xi)*(0.975+0.075*l/H)-1)
114
+ # return enthalpy_loss
115
+
116
+ # def AinleyMathieson(upstream:BladeRow,row:BladeRow) -> float:
117
+ # """Ainley Mathieson equation for computing total pressure loss (Yp)
118
+
119
+ # Args:
120
+ # upstream (BladeRow): _description_
121
+ # row (BladeRow): _description_
122
+ # """
123
+ # #! Need to extract data from plots
124
+ # pass
125
+
126
+ # def AinleyMathiesonUpdated(upstream:BladeRow,row:BladeRow) -> float:
127
+ # """Updated version of Ainley Mathieson
128
+ # https://www.mdpi.com/2504-186X/7/2/14
129
+ # These derivations are valid for steam turbines.
130
+
131
+ # Note:
132
+ # According to the authors, Pressure Loss divided in to 3 components: Profile Loss, secondary loss, and tip/shroud clearance loss
133
+
134
+ # Args:
135
+ # upstream (BladeRow): _description_
136
+ # row (BladeRow): _description_
137
+
138
+ # Returns:
139
+ # float: _description_
140
+ # """
141
+
142
+ # pass
@@ -0,0 +1,8 @@
1
+
2
+ from .craigcox import CraigCox
3
+ from .TD2 import TD2, TD2_Reynolds_Correction
4
+ from .ainleymathieson import AinleyMathieson
5
+ from .fixedefficiency import FixedEfficiency
6
+ from .fixedpressureloss import FixedPressureLoss
7
+ from .kackerokapuu import KrackerOkapuu
8
+ from .traupel import Traupel
@@ -0,0 +1,118 @@
1
+ import pickle, os
2
+ from typing import Dict
3
+ from ...bladerow import BladeRow, sutherland
4
+ from ...lossinterp import LossInterp
5
+ from ...enums import RowType, LossType
6
+ import numpy as np
7
+ import pathlib
8
+ from ..losstype import LossBaseClass
9
+
10
+ class AinleyMathieson(LossBaseClass):
11
+
12
+ def __init__(self):
13
+ """Ainley Mathieson loss correlation used for loss estimation for Gas and Steam Turbines. If you are designing an impulse turbine, this could be a good model to start with.
14
+ Use only with axial turbines. Loss correlations used with Reynolds numbers 1E5 to 3E5.
15
+
16
+ Limitations:
17
+ - Doesn't factor incidence Loss.
18
+ - Steamturbines
19
+ - Impulse Turbines beta1=alpha2
20
+ - Reynolds Range
21
+
22
+ Reference:
23
+ Ainley, D. G., and Mathieson, G. C. R., "A Method of Performance Estimation for Axial-Flow Turbines," British ARC, R & M 2974, 1951.
24
+
25
+ """
26
+ super().__init__(LossType.Pressure)
27
+ path = pathlib.Path(os.path.join(os.environ['TD3_HOME'],"ainleymathieson"+".pkl"))
28
+
29
+ if not path.exists():
30
+ print('Download file if doesn\'t exist')
31
+
32
+ with open(path.absolute(),'rb') as f:
33
+ self.data = pickle.load(f) # type: ignore
34
+
35
+ def __call__(self,row:BladeRow, upstream:BladeRow) -> float:
36
+ """Ainley Mathieson predicts the pressure loss of a turbine nozzle or rotor. Since these correlations are from Cascade experiments, the user should be familar with the reynolds and mach number requirements for each equation and figure. Using something outside the bounds can give inaccuare approximations of loss. Additionally these correlations were done on unoptimized blades so efficiencies maybe lower than what's attainable. Massflow can also be affected because exit P0 is affected.
37
+
38
+ This code will attempt use the correct equations and warn the user if mach number is out of range.
39
+
40
+ Note:
41
+ alpha: gas flow angle relative to axial direction
42
+ beta: blade angle relative to axial direction
43
+
44
+ Args:
45
+ upstream (BladeRow): Upstream blade row
46
+ row (BladeRow): downstream blade row
47
+
48
+ Returns:
49
+ float: Pressure Loss at zero incidence
50
+ """
51
+ Kp = 12300 # Ft / (pdl C); 1 pdl = 0.138254954376 N :(
52
+ At = row.throat
53
+ s = row.pitch
54
+ Area_upstream = np.pi*(upstream.r[-1]**2-upstream.r[0]**2)
55
+ Area_downstream = np.pi*(row.r[-1]**2-row.r[0]**2)
56
+ e = row.r.mean() # mean radius
57
+ h = (row.r[-1]-row.r[0])
58
+
59
+ s_c = row.pitch/row.chord
60
+
61
+ if row.row_type == RowType.Stator:
62
+ k = 0
63
+ alpha1 = -np.abs(row.alpha1)
64
+ beta1 = -np.abs(np.radians(row.beta1_metal))
65
+ alpha2 = np.abs(row.alpha2)
66
+ if row.M<0.5:
67
+ alpha2 = self.data['Fig05'](np.degrees(np.cos(row.throat/row.pitch)))
68
+ elif row.M<0.95:
69
+ X = 0.7
70
+ alpha2 = np.arctan(
71
+ ((1-X*k/h)*(np.cos(beta1)/np.cos(alpha2)))*np.tan(alpha2)
72
+ + X*k/h*np.cos(beta1)/np.cos(alpha2)*np.tan(beta1)
73
+ ) # Eqn 4
74
+ elif row.M<=1 and row.M>0.5:
75
+ if Area_upstream/Area_downstream <1.02 and Area_upstream/Area_downstream>0.98: # Not flared
76
+ alpha2 = -np.arccos(At/Area_downstream) # Eqn 2
77
+ else: # Flared so use equation 3
78
+ At = At/s * (5*Area_downstream + Area_upstream)/6 # Eqn 3
79
+ B = 0.25 # Eqn 6
80
+ else: # Rotor
81
+ k = row.tip_clearance
82
+ alpha1 = np.abs(row.beta1)
83
+ beta1 = np.abs(np.radians(row.beta1_metal))
84
+ alpha2 = -np.abs(row.beta2)
85
+ if row.M_rel<0.5:
86
+ alpha2 = self.data['Fig05'](np.degrees(np.cos(row.throat/row.pitch)))
87
+ elif row.M_rel<0.95:
88
+ X = 1.35 # Shrouded Blade
89
+ alpha2 = np.arctan(
90
+ ((1-X*k/h)*(np.cos(beta1)/np.cos(alpha2)))*np.tan(alpha2)
91
+ + X*k/h*np.cos(beta1)/np.cos(alpha2)*np.tan(beta1)
92
+ ) # Eqn 4
93
+ elif row.M<=1.05 and row.M>=0.95:
94
+ alpha2 = -np.arccos(At/Area_downstream) # Eqn 2
95
+
96
+ B = 0.5 # Eqn 6
97
+
98
+
99
+ t_c = 0.2; # Impulse turbine 0.15 < t/c < 0.25
100
+
101
+ ID = (upstream.r[-1]+row.r[-1])/2; OD = (upstream.r[-1]+row.r[-1])/2
102
+ A1 = np.pi*(upstream.r[-1]**2 - upstream.r[0]**2)*np.cos(beta1)
103
+ A2 = np.pi*(row.r[-1]**2 - row.r[0]**2)*np.cos(alpha2)
104
+ lam = self.data['Fig08']((A2/A1)**2/(1+ID/OD)) # Eqn but using Figure 8
105
+ alpha_m = np.arctan((np.tan(alpha1) - np.tan(alpha2))/2)
106
+ Cl_s_c = 2*(np.tan(alpha1)-np.tan(alpha2))*np.cos(alpha_m)
107
+ # calculated but not used
108
+ Y_secondary_clearance = (lam + B * k/h) * (Cl_s_c)**2 * (np.cos(alpha2)/np.cos(alpha_m)) # Eqn 6
109
+
110
+
111
+ Yp_beta0 = self.data['Fig04a'](s_c, np.degrees(alpha2))
112
+ Yp_beta1_eq_alpha2 = self.data['Fig04b'](s_c, np.degrees(alpha2))
113
+
114
+ Yp_i0 = (Yp_beta0 + (beta1/alpha2)**2 *(Yp_beta1_eq_alpha2 - Yp_beta0)) *(t_c/0.2)**(-beta1/alpha2) # Fig 4 and Eqn 5
115
+
116
+ Yt = Yp_i0 + Y_secondary_clearance
117
+ return Yt # Profile loss at zero incidence
118
+