from dataclasses import dataclass, field
from optparse import Option
from typing import List, Optional, Union
from .enums import RowType
from .bladerow import BladeRow, compute_gas_constants
from .arrayfuncs import convert_to_ndarray, safe_interpolate
import numpy as np
from cantera import Solution
from .passage import Passage
from scipy.interpolate import interp1d
import numpy.typing as npt
[docs]
class Inlet(BladeRow):
"""Station defined at Inlet
Inherits:
(BladeRow): Defines the properties of the blade row
"""
fun: interp1d
static_defined: bool
def __init__(self,
hub_location:float=0,
shroud_location:Optional[float]=None,
alpha:Union[float,List[float]]=[0]):
"""Initializes the inlet station.
Uses the beta and exit mach number to predict a value for Vm
Args:
location (float): Location as a percentage of hub length
beta (Union[float,List[float]], optional): Inlet flow angle in relative direction. Defaults to [].
"""
super().__init__(row_type=RowType.Inlet,hub_location=hub_location,shroud_location=shroud_location,stage_id=-1)
self.beta1 = convert_to_ndarray([0.0])
# Default absolute angles to zero to avoid attribute errors during interpolation
self.alpha1 = convert_to_ndarray([0.0])
self.alpha2 = convert_to_ndarray(alpha)
self.beta2 = convert_to_ndarray([0.0])
[docs]
def init_static(self,P:Union[float,List[float]],T:Union[float,List[float]],M:Union[float,List[float]],percent_radii:Union[float,List[float]]=[0.5]):
"""Initializes the inlet with static quantities at the inlet
Args:
P (Union[float,List[float]]): Static Pressure either as a float or array
T (Union[float,List[float]]): Static Temperature either as a float or array
M (Union[float,List[float]]): Mach Number either as a float or array
percent_radii (Union[float,List[float]], optional): Percent radii where P,T, and M are defined. Defaults to [0.5].
"""
self.P = convert_to_ndarray(P)
self.M = convert_to_ndarray(M)
self.T = convert_to_ndarray(T)
self.static_defined = True
self.percent_hub_shroud = convert_to_ndarray(percent_radii)
[docs]
def init_total(
self,
P0:Union[float,List[float]],
T0:Union[float,List[float]],
M:Union[float,List[float]],
percent_radii:Optional[Union[float,List[float]]]=None
):
"""Initializes the inlet with total quantities at the inlet
Args:
P0 (Union[float,List[float]]): Total Pressure either as a float or array
T0 (Union[float,List[float]]): Total Temperature either as a float or array
M (Union[float,List[float]]): Mach Number either as a float or array
percent_radii (Optional[Union[float,List[float]]], optional): Percent radii where P0, T0, and M are defined. Defaults to `None`, which uses evenly spaced radii when multiple values exist or `[0.5]` otherwise.
"""
self.P0 = convert_to_ndarray(P0)
self.T0 = convert_to_ndarray(T0)
self.M = convert_to_ndarray(M)
if percent_radii is None:
percent_radii = convert_to_ndarray([0.5]) # type: ignore
if len(self.M)>1:
percent_radii = np.linspace(0,1,len(self.M)) # type: ignore
self.static_defined = False
self.percent_hub_shroud = percent_radii
[docs]
def __interpolate_quantities__(self,num_streamlines:int=5):
"""Initializes the inputs
Args:
num_streamlines (int, optional): _description_. Defaults to 5.
IsCompressor (bool, optional): This is if static pressure is defined at the inlet and total pressure at the outlet. Defaults to False.
"""
dst = np.array([0.5]) if num_streamlines <= 1 else np.linspace(0,1,num_streamlines)
self.M = safe_interpolate(self.M, self.percent_hub_shroud, dst)
if self.static_defined: # This comes from the initialization
self.P = safe_interpolate(self.P, self.percent_hub_shroud, dst)
else:
self.P0 = safe_interpolate(self.P0, self.percent_hub_shroud, dst)
self.T0 = safe_interpolate(self.T0, self.percent_hub_shroud, dst)
# Angles: default to 0 if unspecified
self.beta1 = safe_interpolate(self.beta1, self.percent_hub_shroud, dst, radians=True)
self.beta2 = safe_interpolate(self.beta2, self.percent_hub_shroud, dst, radians=True)
self.alpha1 = safe_interpolate(self.alpha1, self.percent_hub_shroud, dst, radians=True)
self.alpha2 = safe_interpolate(self.alpha2, self.percent_hub_shroud, dst, radians=True)
def __initialize_fluid__(self,fluid:Optional[Solution]=None,R:float=287.15,gamma:float=1.4,Cp:float=1024):
"""Initialize the inlet using the fluid. This function should be called by a class that inherits from spool
Args:
fluid (Solution, optional): Cantera fluid object. Defaults to None.
R (float, optional): Ideal Gas Constant. Defaults to 287.15 J/(Kg K) for air
gamma (float, optional): _description_. Defaults to 1.4.
Cp (float, optional): _description_. Defaults to 1024 J/(Kg K).
"""
self.loss_function = None
if fluid:
fluid.TP = self.T0.mean(),self.P0.mean()
self.gamma = fluid.cp/fluid.cv
if self.static_defined:
self.P0 = self.P * (1+(self.gamma-1)/2 * self.M**2) ** (self.gamma/(self.gamma-1))
self.T0 = self.T * (1+(self.gamma-1)/2 * self.M**2)
else:
self.P = self.P0 * 1/(1 + (self.gamma-1) * self.M**2)**(self.gamma/(self.gamma-1))
self.T = self.T0 * 1/(1 + (self.gamma-1) * self.M**2)
fluid.TP = self.T.mean(),self.P.mean()
self.rho = convert_to_ndarray([fluid.density])
else:
self.Cp = Cp
self.gamma = gamma
self.R = R
self.T = self.T0 * 1/(1 + (self.gamma-1) * self.M**2)
if self.static_defined:
self.P0 = self.P * (1+(self.gamma-1)/2 * self.M**2) ** (self.gamma/(self.gamma-1))
self.T0 = self.T * (1+(self.gamma-1)/2 * self.M**2)
else:
self.P = self.P0 * 1/(1 + (self.gamma-1) * self.M**2)**(self.gamma/(self.gamma-1))
self.rho = self.P/(self.R*self.T)
self.beta1_metal = [0]
self.beta2_metal = [0]
if len(self.percent_hub_shroud) == 1:
self.percent_hub_shroud = np.linspace(0,1,2)
self.P0 = self.percent_hub_shroud*0+self.P0[0]
self.T0 = self.percent_hub_shroud*0+self.T0[0]
self.P0_fun = interp1d(self.percent_hub_shroud,self.P0)
self.T0_fun = interp1d(self.percent_hub_shroud,self.T0)
self.mprime = [0] # type: ignore
[docs]
def __initialize_velocity__(self,passage:Passage,num_streamlines:int):
"""Initialize velocity calculations. Assumes streamlines and inclination angles have been calculated
Call this before performing calculations
Args:
passage (Passage): Passage object
num_streamlines (int): number of streamlines
"""
# Perform Calculations on Velocity
Vm_prev = 0; Vm_err = 0
cutline,_,_ = passage.get_cutting_line(t_hub=self.location,t_shroud=self.shroud_location)
t_span = np.array([0.5]) if num_streamlines <= 1 else np.linspace(0,1,num_streamlines)
self.x,self.r = cutline.get_point(t_span)
for _ in range(2):
T0_T = (1+(self.gamma-1)/2 * self.M**2)
self.Vm = self.M**2 * self.gamma*self.R*self.T0/T0_T \
/ (1+np.cos(self.phi)**2 * np.tan(self.alpha2)**2)
self.Vm = np.sqrt(self.Vm)
self.T = self.T0/T0_T
self.P = self.P0/(T0_T)**(self.gamma/(self.gamma-1))
self.rho = self.P/(self.R*self.T)
self.Vx = self.Vm * np.cos(self.phi)
self.Vt = self.Vm * np.cos(self.phi) * np.tan(self.alpha2)
self.V = np.sqrt(self.Vm**2 + self.Vt**2)
self.Vr = self.Vm * np.sin(self.phi)
compute_gas_constants(self)
rho_mean = self.rho.mean()
Vm_tube = np.zeros(max(len(self.massflow)-1, 1))
if len(self.massflow) <= 1:
Vm_tube[0] = float(self.Vm.mean())
# Compute tube-averaged Vm from massflow differences
for i in range(1, len(self.massflow)):
tube_massflow = self.massflow[i]-self.massflow[i-1]
rho_bar = rho_mean if len(self.rho) == 1 else 0.5 * (self.rho[i] + self.rho[i-1])
if np.abs((self.x[-1]-self.x[0]))<1E-5: # Axial Machines
area = np.pi*(self.r[i]**2-self.r[i-1]**2)
else: # Radial Machines
dx = self.x[i]-self.x[i-1]
S = (self.r[i]-self.r[i-1])
C = np.sqrt(1+((self.r[i]-self.r[i-1])/dx)**2)
area = 2*np.pi*C*(S/2*dx**2+self.r[i-1]*dx)
Vm_tube[i-1] = tube_massflow/(rho_bar*area + 1e-12)
# Recover per-streamline Vm; handle single-streamline as meanline
if len(self.Vm) <= 1:
self.Vm = np.array([Vm_tube[0] if len(Vm_tube) else rho_mean*0])
else:
self.Vm[0] = Vm_tube[0]
for i in range(1, len(self.Vm)):
self.Vm[i] = 2 * Vm_tube[i-1] - self.Vm[i-1]
self.M = self.V /np.sqrt(self.gamma*self.R*self.T)
Vm_err = np.max(abs(self.Vm-Vm_prev)/self.Vm)
Vm_prev = self.Vm
if Vm_err < 1E-4:
break
if num_streamlines <= 1:
Area = passage.get_area(self.location)
else:
Area = 0
for j in range(1,num_streamlines):
if np.abs((self.x[j]-self.x[j-1]))<1E-12: # Axial Machines
Area += np.pi*(self.r[j]**2-self.r[j-1]**2)
else: # Radial Machines
dx = self.x[j]-self.x[j-1]
S = (self.r[j]-self.r[j-1])
C = np.sqrt(1+((self.r[j]-self.r[j-1])/dx)**2)
Area += 2*np.pi*C*(S/2*dx**2+self.r[j-1]*dx)
self.calculated_massflow = self.rho.mean()*self.Vm.mean() * Area
[docs]
def get_total_pressure(self,percent_hub_shroud:Union[float,npt.NDArray]):
"""Returns the static pressure at a certain percent hub_shroud
Args:
percent_hub_shroud (Union[float,npt.NDArray]): _description_
Returns:
_type_: _description_
"""
if type(percent_hub_shroud) == float:
return float(self.P0_fun(percent_hub_shroud))
else:
return self.P0_fun(percent_hub_shroud)
[docs]
def get_total_temperature(self,percent_hub_shroud:Union[float,npt.NDArray]):
"""Returns the static pressure at a certain percent hub_shroud
Args:
percent_hub_shroud (Union[float,npt.NDArray]): _description_
Returns:
_type_: _description_
"""
if type(percent_hub_shroud) == float:
return float(self.T0_fun(percent_hub_shroud))
else:
return self.T0_fun(percent_hub_shroud)