turbo-design 1.0.0__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- turbo_design-1.0.0.dist-info/METADATA +25 -0
- turbo_design-1.0.0.dist-info/RECORD +33 -0
- turbo_design-1.0.0.dist-info/WHEEL +4 -0
- turbodesign/__init__.py +9 -0
- turbodesign/arrayfuncs.py +19 -0
- turbodesign/bladerow.py +614 -0
- turbodesign/cantera_gas/co2.yaml +36 -0
- turbodesign/compressorspool.py +60 -0
- turbodesign/coolant.py +10 -0
- turbodesign/enums.py +36 -0
- turbodesign/inlet.py +146 -0
- turbodesign/isentropic.py +82 -0
- turbodesign/loss/__init__.py +1 -0
- turbodesign/loss/compressor/__init__.py +1 -0
- turbodesign/loss/losstype.py +25 -0
- turbodesign/loss/turbine/TD2.py +142 -0
- turbodesign/loss/turbine/__init__.py +8 -0
- turbodesign/loss/turbine/ainleymathieson.py +118 -0
- turbodesign/loss/turbine/craigcox.py +189 -0
- turbodesign/loss/turbine/fixedefficiency.py +29 -0
- turbodesign/loss/turbine/fixedpressureloss.py +25 -0
- turbodesign/loss/turbine/kackerokapuu.py +124 -0
- turbodesign/loss/turbine/traupel.py +95 -0
- turbodesign/lossinterp.py +178 -0
- turbodesign/outlet.py +56 -0
- turbodesign/passage.py +198 -0
- turbodesign/radeq.py +255 -0
- turbodesign/rotor.py +38 -0
- turbodesign/solve_radeq.py +37 -0
- turbodesign/spool.py +289 -0
- turbodesign/stage.py +7 -0
- turbodesign/td_math.py +388 -0
- turbodesign/turbinespool.py +466 -0
|
@@ -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
|
+
|