Source code for turbodesign.bladerow

from dataclasses import dataclass, field, Field
from typing import Any, Callable, List, Optional, Sequence, Tuple, Union
from .enums import RowType, PowerType
import numpy as np 
import numpy.typing as npt
from scipy.interpolate import interp1d
from .arrayfuncs import convert_to_ndarray
from cantera import Solution, composite
from .coolant import Coolant
from pyturbo.helper import line2D
from pyturbo.aero.airfoil2D import Airfoil2D
from .loss import LossBaseClass
from .deviation.deviation_base import DeviationBaseClass
from .passage import Passage
from .arrayfuncs import safe_interpolate
    

[docs] @dataclass(eq=False) class BladeRow: """A single blade row (stator or rotor) in a turbomachine stage. Attributes ---------- **Core Configuration** id : int Row identifier. stage_id : int Stage identifier. row_type : RowType Stator or Rotor. loss_function : LossBaseClass, optional Loss model applied to this row. deviation_function : DeviationBaseClass, optional Deviation model applied to this row. cutting_line : line2D, optional Line perpendicular to the streamline. rp : float Degree of reaction. hub_location : float Hub axial location. shroud_location : float Shroud axial location. **Fluid Properties** R : float Ideal gas constant, J/(kg K). Default 287.15. gamma : float Ratio of specific heats Cp/Cv. Default 1.33. Cp : float Specific heat at constant pressure, J/(kg K). Default 1019. Cv : float Specific heat at constant volume, J/(kg K). mu : float Dynamic viscosity, Pa s. **Mass Flow** total_massflow : float Total mass flow including upstream cooling, kg/s. massflow : ndarray Mass flow distribution per radial station. total_massflow_no_coolant : float Inlet mass flow without coolant, kg/s. massflow_target : ndarray, optional Custom mass flow distribution for angle matching, kg/s. **Streamline Geometry** percent_hub : float Where blade row is defined along the hub (0-1). percent_hub_shroud : ndarray Percent streamline length from hub to shroud. x : ndarray Axial coordinates. r : ndarray Radial coordinates. m : ndarray Meridional coordinates. total_area : float Total annular flow area. area : ndarray Flow area per streamline. **Row Efficiency** eta_total : float Total-to-total isentropic efficiency. eta_static : float Total-to-static isentropic efficiency. eta_poly : float Polytropic efficiency. stage_loading : float Stage loading coefficient (work per stage). **Flow Angles** *(radians)* alpha1, alpha2 : ndarray Absolute flow angles at inlet and exit. beta1, beta2 : ndarray Relative flow angles at inlet and exit. deviation : ndarray Flow deviation from metal angle. beta1_fixed, beta2_fixed : bool Whether inlet/exit geometry is already defined. **Velocities** Vm : ndarray Meridional velocity. Vx : ndarray Axial velocity. Vt : ndarray Tangential (swirl) velocity. Vr : ndarray Radial velocity. V : ndarray Absolute velocity magnitude. U : ndarray Blade peripheral velocity. W : ndarray Relative velocity magnitude. Wt : ndarray Relative tangential velocity. M : ndarray Absolute Mach number. M_rel : ndarray Relative Mach number. omega : float Angular velocity, rad/s. **Thermodynamic Quantities** P0 : ndarray Total pressure, Pa. T0 : ndarray Total temperature, K. P : ndarray Static pressure, Pa. T : ndarray Static temperature, K. rho : ndarray Density, kg/m^3. P0R : ndarray Relative total pressure, Pa. T0R : ndarray Relative total temperature, K. entropy_rise : ndarray Entropy rise across row. **Performance** power : float Power, W. P0_P : float Total-to-static pressure ratio. P0_ratio : float Total-to-total pressure ratio. flow_coefficient : float Flow coefficient (Vm/U). Reynolds : float Reynolds number. Yp : ndarray Pressure loss coefficient. **Blade Geometry** *(set via properties)* axial_chord : float Axial chord length. Set via property. aspect_ratio : float Height-to-chord ratio. Set via property. pitch_to_chord : float Pitch-to-chord ratio. Set via property. stagger : float Stagger angle, degrees. Set via property. num_blades : int Blade count. Set via property. tip_clearance : float Clearance as fraction of span. Set via property. te_pitch : float Trailing-edge-to-pitch ratio. Set via property. blade_to_blade_gap : float Inter-row gap as fraction of chord. Set via property. """ id: int = 0 stage_id: int = 0 row_type: RowType = RowType.Stator loss_function: Optional[LossBaseClass] = None deviation_function: Optional[DeviationBaseClass] = None cutting_line: Optional[line2D] = None # Line perpendicular to the streamline rp: float = 0.4 # Degree of Reaction hub_location: float = 0.0 shroud_location: float = 0.0 # Fluid R: float = 287.15 # Ideal Gas constant J/(Kg K) gamma: float = 1.33 # Ratio of Cp/Cv Cp: float = 1019 # Cp J/(Kg*K) Cv: float = 1019/1.14 # Cv J/(Kg*K) _coolant: Optional[Coolant] = None # Coolant Fluid mu: float = 0 total_massflow: float = 0 # Massflow spool + all upstream cooling flow [kg/s] massflow: npt.NDArray = field(default_factory=lambda: np.array([0])) # Massflow per radii total_massflow_no_coolant: float = 0 # Inlet massflow massflow_target: Optional[npt.NDArray] = None # Custom massflow distribution for angle matching [kg/s] # ---------------------------------- # Streamline Properties percent_hub: float = 0 # Where blade row is defined along the hub. percent_hub_shroud: npt.NDArray = field(default_factory=lambda: np.array([0])) # Percent streamline length from hub to shroud. x: npt.NDArray = field(default_factory=lambda: np.array([0])) # x - coordinates (useful for computing axial chord) r: npt.NDArray = field(default_factory=lambda: np.array([0])) # Radius - coordinates m: npt.NDArray = field(default_factory=lambda: np.array([0])) # meridional total_area: float = 0 area: npt.NDArray = field(default_factory=lambda: np.array([0])) # Calculated massflow is the massflow computed after radial eq solver calculated_massflow: float = 0 # Row Efficiency (calculated or specified) eta_total: float = 0 # Total to Total eta_static: float = 0 # Total to static eta_poly: float = 0 # Polytropic efficiency (per row if applicable) stage_loading: float = 0 # stage loading how much work done per stage alpha1: npt.NDArray = field(default_factory=lambda: np.array([0])) # Blade inlet absolute flow angle alpha2: npt.NDArray = field(default_factory=lambda: np.array([0])) # Blade exit absolute flow angle beta1: npt.NDArray = field(default_factory=lambda: np.array([0])) # Blade inlet relative flow angle beta2: npt.NDArray = field(default_factory=lambda: np.array([0])) # Blade exit relative flow angle deviation: npt.NDArray = field(default_factory=lambda: np.array([0])) _beta1_metal: npt.NDArray = field(default_factory=lambda: np.array([0])) # blade inlet metal angle beta1_metal_radii: npt.NDArray = field(default_factory=lambda: np.array([0])) # radii where metal angle is defined _beta2_metal: npt.NDArray = field(default_factory=lambda: np.array([0])) # blade exit metal angle beta2_metal_radii: npt.NDArray = field(default_factory=lambda: np.array([0])) # radii where metal angle is defined beta1_fixed: bool = False # Geometry already defined. This affects the inlet flow angle beta2_fixed: bool = False # Geometry already defined. This affects the exit flow angle # Velocities Vm: npt.NDArray = field(default_factory=lambda: np.array([0])) # Meridional velocity Vx: npt.NDArray = field(default_factory=lambda: np.array([0])) # Axial Velocity Vt: npt.NDArray = field(default_factory=lambda: np.array([0])) # Tangential Velocity Vr: npt.NDArray = field(default_factory=lambda: np.array([0])) # Radial velocity V: npt.NDArray = field(default_factory=lambda: np.array([0])) # Absolute Velocity in 3D coordinate system V2: npt.NDArray = field(default_factory=lambda: np.array([0])) # Absolute Velocity in Theta-Axial plane M: npt.NDArray = field(default_factory=lambda: np.array([0])) # Mach Number M_rel: npt.NDArray = field(default_factory=lambda: np.array([0])) # Relative Mach Number U: npt.NDArray = field(default_factory=lambda: np.array([0])) # Peripheral velocity W: npt.NDArray = field(default_factory=lambda: np.array([0])) # Relative Velocity in Theta-Axial plane Wt: npt.NDArray = field(default_factory=lambda: np.array([0])) # Relative Tangential Velocity _rpm: float = field(default=0, init=False, repr=False) omega: float = 0 # angular velocity rad/s P0_stator_inlet: npt.NDArray = field(default_factory=lambda: np.array([0])) # Every quantity is an exit quantity, This is used for efficiency calcs T0_stator_inlet: npt.NDArray = field(default_factory=lambda: np.array([0])) # Every quantity is an exit quantity, This is used for efficiency calcs P0: npt.NDArray = field(default_factory=lambda: np.array([0])) # Total Quantities P0_is: npt.NDArray = field(default_factory=lambda: np.array([0])) T0: npt.NDArray = field(default_factory=lambda: np.array([0])) T0_is: npt.NDArray = field(default_factory=lambda: np.array([0])) P0R: npt.NDArray = field(default_factory=lambda: np.array([0])) # Relative Total Pressure (Pa) P0R_is: npt.NDArray = field(default_factory=lambda: np.array([0])) T0R: npt.NDArray = field(default_factory=lambda: np.array([0])) # Static Quantities P: npt.NDArray = field(default_factory=lambda: np.array([0])) T: npt.NDArray = field(default_factory=lambda: np.array([0])) T_is: npt.NDArray = field(default_factory=lambda: np.array([0])) rho: npt.NDArray = field(default_factory=lambda: np.array([0])) entropy_rise: npt.NDArray = field(default_factory=lambda: np.array([0])) # Related to streamline curvature phi: npt.NDArray = field(default_factory=lambda: np.array([0])) # Inclination angle x,r plane. AY td2.f rm: npt.NDArray = field(default_factory=lambda: np.array([0])) # Curvature incli_curve_radii: npt.NDArray = field(default_factory=lambda: np.array([0])) # radius at which curvature was evaluated mprime: npt.NDArray = field(default_factory=lambda: np.array([0])) # Mprime distance Yp: npt.NDArray = field(default_factory=lambda: np.array([0])) # Pressure loss blockage: float = 0 flow_coefficient: float = 0 # Vm/U or similar nondimensional flow coefficient power: float = 0 # Watts power_mean: float = 0 power_distribution: npt.NDArray = field(default_factory=lambda: np.array([0])) # How power is divided by radius. P0_P: float = 0 # Total to Static Pressure Ratio P0_ratio: float = 0 # Total-to-total pressure ratio target (design input; may be overwritten in legacy diagnostics) P0_ratio_target: float = 0 # Frozen design target for P0_ratio (never overwritten; used for initial guesses) Power_Type: PowerType = PowerType.P0_P euler_power: float = 0 Reynolds: float = 0 eta_poly: float = 0.0 # Optional per-row polytropic efficiency target num_blades: int = 0 # Used for loss calculations _blade_to_blade_gap: float = 0.025 # Gap between blade in terms of percent chord. _aspect_ratio: float = 0.9 # _pitch_to_chord: npt.NDArray = field(default_factory=lambda: np.array([0.7])) # Pitch to chord ratio, used to determine number of blades and compute loss _axial_chord: float = -1 _chord: npt.NDArray = field(default_factory=lambda: np.array([-1.0])) _stagger: npt.NDArray = field(default_factory=lambda: np.array([42.0])) _te_s: float = 0.08 _tip_clearance: float = 0 # Clearance as a percentage of span or blade height _inlet_to_outlet_pratio: list = field(default_factory=lambda: [0.06,0.95]) def __post_init__(self): if self.shroud_location == 0: self.shroud_location = self.hub_location # Preserve any user-specified target ratio so later diagnostics can safely overwrite P0_ratio. if self.P0_ratio_target == 0 and self.P0_ratio != 0: self.P0_ratio_target = self.P0_ratio @property def inlet_to_outlet_pratio(self) -> Tuple[float,float]: """This is what is varied by the optimization. The range is between [0 and 1] but you should Returns: List[float]: _description_ """ return self._inlet_to_outlet_pratio # type: ignore @inlet_to_outlet_pratio.setter def inlet_to_outlet_pratio(self,val:Tuple[float,float]=(0.06,0.7)): """Sets the inlet_to_outlet pratio of the blade row Args: val (Tuple[float,float], optional): guess value from [0,1], you should not use 0 or 1 though. Defaults to (0.06,0.7). """ self._inlet_to_outlet_pratio = val @property def blade_to_blade_gap(self) -> float: """Returns the blade to blade gap value Returns: float: _description_ """ return self._blade_to_blade_gap @blade_to_blade_gap.setter def blade_to_blade_gap(self,val:float): """Sets the blade to blade gap as a percent. This applies to the next row. So (row1) (row2) if (row1) gap is set to 0.25 then (row2) is offset by 0.25*row1.chord Args: val (float): percentage of chord to space out the blade """ self._blade_to_blade_gap = val @property def aspect_ratio(self): return self._aspect_ratio @aspect_ratio.setter def aspect_ratio(self,val:float): """Sets the aspect ratio Aspect ratio is defined as the height/chord not height/(axial chord) Args: val (float): new aspect ratio """ self._aspect_ratio = val @property def axial_chord(self) -> float: """Returns the mean axial chord defined in the x-direction Returns: float: Axial Chord """ return self._axial_chord @axial_chord.setter def axial_chord(self,val:float): self._axial_chord = val @property def pitch_to_chord(self) -> npt.NDArray: """Gets the pitch to chord ratio Returns: float: pitch to chord ratio """ return self._pitch_to_chord @pitch_to_chord.setter def pitch_to_chord(self,val:float): """Set the pitch to chord ratio Args: val (float): new pitch to chord ratio. Typically stators are 0.8 to 0.95. Rotors 0.7 to 0.8 """ self._pitch_to_chord = convert_to_ndarray(val) @property def solidity(self) -> npt.NDArray: """Inverse of pitch to chord ratio Returns: float: solidity value """ return 1/self._pitch_to_chord @solidity.setter def solidity(self,val:float): """Inverse of pitch to chord ratio Args: val (float): sets the inverse of pitch to chord ratio Returns: float: solidity """ self._pitch_to_chord = 1/convert_to_ndarray(val) @property def metal_inlet_angle(self) -> npt.NDArray: """Blade metal inlet angle (degrees).""" return np.degrees(self._beta1_metal) @property def beta1_metal(self) -> npt.NDArray: """Backward-compatible alias for metal_inlet_angle.""" return self.metal_inlet_angle @property def metal_exit_angle(self) -> npt.NDArray: """Blade metal exit angle (degrees).""" return np.degrees(self._beta2_metal) @property def beta2_metal(self) -> npt.NDArray: """Backward-compatible alias for metal_exit_angle.""" return self.metal_exit_angle @property def stagger(self) -> float: """Average stagger angle Returns: float: stagger angle """ return self._stagger @stagger.setter def stagger(self,val:float): """Set the stagger angle in degrees Args: val (float): stagger angle. Degrees """ self._stagger = val @property def chord(self) -> npt.NDArray: """Chord defined at mean radius Returns: float: axial chord """ return self.axial_chord / np.cos(np.radians(self.stagger)) @property def pitch(self) -> float: """Returns the pitch which is the distance from blade to blade Returns: float: pitch """ return self.pitch_to_chord*self.chord @property def throat(self) -> float: """Throat distance Returns: float: throat """ if self.row_type == RowType.Stator: return self.pitch*np.sin(np.pi/2-self.alpha2.mean()) else: return self.pitch*np.sin(np.pi/2-self.beta2.mean()) _num_blades: float = 0 @property def num_blades(self) -> float: """Configured number of blades (set during design/initialization).""" return self._num_blades @num_blades.setter def num_blades(self, val: float) -> None: self._num_blades = val @property def camber(self) -> float: """Estimates the camber of the blade using a bezier curve. This is not as accurate because thickness is not defined on suction and pressure sides. Returns: float: camber length """ if self.row_type == RowType.Stator: a2d = Airfoil2D(np.degrees(self.alpha1.mean()), np.degrees(self.alpha2.mean()), self.axial_chord, self.stagger) else: a2d = Airfoil2D(np.degrees(self.beta1.mean()), np.degrees(self.beta2.mean()), self.axial_chord, self.stagger) return a2d.camberBezier.get_curve_length() @property def tip_clearance(self): """Tip clearance as a percentage of annulus height """ return self._tip_clearance @tip_clearance.setter def tip_clearance(self,val:float): """Sets the tip clearance Args: val (float): tip clearance as a percentage of annulus height. """ self._tip_clearance = val # Backwards-compatible alias @property def location(self) -> float: return self.hub_location @location.setter def location(self, val: float) -> None: self.hub_location = val @metal_inlet_angle.setter def metal_inlet_angle(self, metal_inlet_angle: List[float], percent: List[float] = []): """Sets the leading edge metal angle for the blade (degrees).""" arr = np.radians(convert_to_ndarray(metal_inlet_angle)) if len(percent) != len(metal_inlet_angle): percent = np.linspace(0, 1, len(arr)).tolist() # type: ignore self._beta1_metal = arr self.beta1_metal_radii = convert_to_ndarray(percent) self.beta1_fixed = True self.beta1 = self.metal_inlet_angle.copy() @beta1_metal.setter def beta1_metal(self, beta1_metal: List[float], percent: List[float] = []): """Backward-compatible alias for metal_inlet_angle setter.""" self.metal_inlet_angle = beta1_metal @metal_exit_angle.setter def metal_exit_angle(self, metal_exit_angle: List[float], percent: List[float] = []): """Sets the trailing edge metal angle for the blade (degrees).""" arr = np.radians(convert_to_ndarray(metal_exit_angle)) if len(percent) != len(metal_exit_angle): percent = np.linspace(0, 1, len(arr)).tolist() # type: ignore self._beta2_metal = arr self.beta2_metal_radii = convert_to_ndarray(percent) self.beta2_fixed = True # Apply deviation if defined; deviation_function returns degrees deviation_func = getattr(self, "deviation_function", None) deviation_rad = 0.0 if callable(deviation_func): try: deviation_val = deviation_func(self, None) deviation_rad = np.radians(deviation_val) except Exception: deviation_rad = 0.0 beta2_effective = self._beta2_metal + deviation_rad if self.row_type == RowType.Stator: self.alpha2 = beta2_effective.copy() self.beta2 = beta2_effective.copy() else: self.beta2 = beta2_effective.copy() self.deviation = np.full_like(self.beta2, deviation_rad) @beta2_metal.setter def beta2_metal(self, beta2_metal: List[float], percent: List[float] = []): """Backward-compatible alias for metal_exit_angle setter.""" self.metal_exit_angle = beta2_metal @property def rpm(self): return self._rpm @rpm.setter def rpm(self,val:float): self._rpm = val self.omega = self._rpm * np.pi/30 # rev/min * 2pi rads/1 rev * 1 min/60 sec @property def coolant(self): return self._coolant @coolant.setter def coolant(self,coolant:Coolant): """Add a coolant to the end of the blade row Args: coolant (Coolant): Coolant """ self._coolant = coolant @property def loss_model(self): return self.loss_function @loss_model.setter def loss_model(self, model:Union[LossBaseClass, Sequence[LossBaseClass]]): """Assign one or more loss models that inherit :class:`LossBaseClass`. Args: model: Either a single loss model or a sequence of models. """ if isinstance(model, LossBaseClass): self.loss_function = model return raise TypeError("Loss models must inherit LossBaseClass.") @property def te_pitch(self): """Trailing edge to pitch ratio Returns: float: trailing edge to pitch ratio """ return self._te_s @te_pitch.setter def te_pitch(self,val:float): """Set the trailing edge to pitch ratio. Typical values from 0.02 to 0.12 Args: val (float): new trailing edge to pitch ratio """ self._te_s = val def __repr__(self): 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}"
[docs] def synchronize_blade_geometry(self) -> None: """Couple num_blades, pitch-to-chord/solidity, chord, and stagger. Uses mean radius from interpolated streamlines to derive pitch, chord, and stagger (axial chord / chord). """ if self.num_blades <= 0 or self.r.size == 0: return # Pitch from blade count and local radius pitch = 2 * np.pi * self.r / self.num_blades # Pitch-to-chord (or 1/solidity) may be scalar or spanwise; broadcast it ptc = convert_to_ndarray(self.pitch_to_chord) if ptc.size == 1: ptc = ptc * np.ones_like(self.r, dtype=float) else: t_src = np.linspace(0, 1, ptc.size) ptc = np.interp(self.percent_hub_shroud, t_src, ptc) chord = pitch / np.maximum(ptc, 1e-9) self._chord = chord self._pitch_to_chord = ptc axial = self.axial_chord if self.axial_chord > 0 else float(np.mean(chord)) if self.axial_chord <= 0: self.axial_chord = axial ratio = np.clip(axial / np.maximum(chord, 1e-9), -1.0, 1.0) stagger_rad = np.arccos(ratio) # Store stagger distribution in degrees self._stagger = np.degrees(stagger_rad)
[docs] def to_dict(self): data = { "StageID":self.stage_id, "RowType":self.row_type.name, "R":self.R, "gamma":self.gamma, "Cp":self.Cp, "Cv":self.Cv, "P0_P":self.P0_P, "rp":self.rp, "total_massflow":self.total_massflow, "massflow":self.massflow.tolist(), "calculated_massflow":self.calculated_massflow, "alpha1":np.degrees(self.alpha1).tolist(), "alpha2":np.degrees(self.alpha2).tolist(), "beta1":np.degrees(self.beta1).tolist(), "beta2":np.degrees(self.beta2).tolist(), "beta1_metal":np.degrees(self._beta1_metal).tolist(), "beta2_metal":np.degrees(self._beta2_metal).tolist(), "Vm":self.Vm.tolist(), "Vx":self.Vx.tolist(), "Vr":self.Vr.tolist(), "Vt":self.Vt.tolist(), "U":self.U.tolist(), "V":self.V.tolist(), "M":self.M.tolist(), "M_rel":self.M_rel.tolist(), "W":self.W.tolist(), "Wt":self.Wt.tolist(), "omega":self.omega, "P0":self.P0.tolist(), "T0":self.T0.tolist(), "P0R":self.P0R.tolist(), "T0R":self.T0R.tolist(), "P":self.P.tolist(), "T":self.T.tolist(), "rho":self.rho.tolist(), "mu":self.mu, "Yp":self.Yp, "flow_coefficient": self.flow_coefficient, "Power":self.power, "P0_P": self.P0_P, "eta_total":self.eta_total, "eta_static":self.eta_static, "eta_poly": self.eta_poly, "euler_power":self.euler_power, "axial_chord":self.axial_chord, "aspect_ratio":self.aspect_ratio, "num_blades":self.num_blades, "total_area": self.total_area, "area": self.area.tolist(), "radius":self.r.tolist(), "x":self.x.tolist(), "dx":self.x[-1]-self.x[0], "dr":self.r[-1]-self.r[0], "mprime":self.mprime[-1], "Reynolds":self.Reynolds, "axial_chord":self.axial_chord } return data
#* Some functions related to blade row
[docs] def interpolate_streamline_quantities(row:BladeRow,passage:Passage,num_streamlines:int=3): """Interpolate all quantities onto the streamline and allocates variables. Run this after setting some initial conditions Args: r_streamline (npt.NDArray): Radii describing the streamline passage (Passage): Passage object describing the geometry of the hub and shroud num_streamlines (int): number of streamlines to consider Returns: (BladeRow): new row object with quantities interpolated """ src_percent = convert_to_ndarray(row.percent_hub_shroud) row.cutting_line,_,_ = passage.get_cutting_line(row.location) t_span = np.array([0.5]) if num_streamlines <= 1 else np.linspace(0, 1, num_streamlines) row.x, row.r = row.cutting_line.get_point(t_span) if num_streamlines <= 1: streamline_percent_length = np.array([0.5]) row.total_area = passage.get_area(row.location) row.area = np.array([row.total_area]) else: streamline_percent_length = np.sqrt((row.r-row.r[0])**2+(row.x-row.x[0])**2)/row.cutting_line.length # Flow angles row._beta1_metal = row._beta1_metal.default_factory() if type(row._beta1_metal) == Field else row._beta1_metal if row.row_type==RowType.Stator: assert type(row._beta2_metal)!=Field,"Stator exit Flow angle must be set" row._beta1_metal = interpolate_quantities(row._beta1_metal,row.beta1_metal_radii,streamline_percent_length) row._beta2_metal = interpolate_quantities(row._beta2_metal,row.beta2_metal_radii,streamline_percent_length) row.beta1_metal_radii = streamline_percent_length row.beta2_metal_radii = streamline_percent_length row.deviation = streamline_percent_length * 0 row.mprime = interpolate_quantities(row.mprime, src_percent, streamline_percent_length) row.alpha1 = safe_interpolate(row.alpha1, src_percent, streamline_percent_length, radians=False) row.alpha2 = safe_interpolate(row.alpha2, src_percent, streamline_percent_length, radians=False) row.beta1 = safe_interpolate(row.beta1, src_percent, streamline_percent_length, radians=False) row.beta2 = safe_interpolate(row.beta2, src_percent, streamline_percent_length, radians=False) # Velocities row.Vm = interpolate_quantities(row.Vm, src_percent, streamline_percent_length) row.Vx = interpolate_quantities(row.Vx, src_percent, streamline_percent_length) row.Vt = interpolate_quantities(row.Vt, src_percent, streamline_percent_length) row.Vr = interpolate_quantities(row.Vr, src_percent, streamline_percent_length) row.V = interpolate_quantities(row.V, src_percent, streamline_percent_length) row.V2 = interpolate_quantities(row.V2, src_percent, streamline_percent_length) row.M = interpolate_quantities(row.M, src_percent, streamline_percent_length) row.M_rel = interpolate_quantities(row.M_rel, src_percent, streamline_percent_length) row.U = interpolate_quantities(row.U, src_percent, streamline_percent_length) row.W = interpolate_quantities(row.W, src_percent, streamline_percent_length) row.Wt = interpolate_quantities(row.Wt, src_percent, streamline_percent_length) # Total Quantities row.T0 = interpolate_quantities(row.T0, src_percent, streamline_percent_length) row.T0_is = interpolate_quantities(row.T0, src_percent, streamline_percent_length) # For Turbines row.P0 = interpolate_quantities(row.P0, src_percent, streamline_percent_length) row.P0_is = interpolate_quantities(row.P0, src_percent, streamline_percent_length) # For Compressors row.P0_stator_inlet = interpolate_quantities(row.P0_stator_inlet, src_percent, streamline_percent_length) # Relative Quantities row.P0R = interpolate_quantities(row.P0R, src_percent, streamline_percent_length) row.P0R_is = interpolate_quantities(row.P0, src_percent, streamline_percent_length) row.T0R = interpolate_quantities(row.T0R, src_percent, streamline_percent_length) # Static Quantities row.P = interpolate_quantities(row.P, src_percent, streamline_percent_length) row.T = interpolate_quantities(row.T, src_percent, streamline_percent_length) row.T_is = interpolate_quantities(row.T_is, src_percent, streamline_percent_length) row.rho = interpolate_quantities(row.rho, src_percent, streamline_percent_length) row.entropy_rise = interpolate_quantities(row.entropy_rise, src_percent, streamline_percent_length) row.percent_hub_shroud = streamline_percent_length return row
[docs] def sutherland(T:Union[float,npt.NDArray]) -> Union[float,npt.NDArray]: """Sutherland viscosity calculation used for reynolds number Args: T (float): Temperature in Kelvin Returns: float: Dynamic Viscosity (mu) in Pa*s """ S = 110.4 C1 = 1.458E-6 return C1*T**1.5 / (T+S)
def interpolate_quantities(q:npt.NDArray,r:npt.NDArray,r2:npt.NDArray): """Interpolates array q Args: q (npt.NDArray): quantities defined at radius r r (npt.NDArray): radius where quantities `q` are defined r2 (npt.NDArray): new radius to interpolate the quantities to e.g. streamline radius Returns: npt.NDArray: quantities interpolated onto r2 """ if (type(q) == Field): q = q.default_factory() if (type(r) == Field): r = r.default_factory() if len(q)==1: q2 = np.zeros(shape=r2.shape) return q[0]+q2 else: if len(r) != len(q): r = np.linspace(0, 1, len(q)) return interp1d(r,q,kind='linear')(r2) def compute_gas_constants(row:BladeRow,fluid:Optional[Solution]=None) -> None: """Updates the Cp, Gamma, and density for a blade row. If fluid is not specified then only density and viscosity is updated. Args: row (BladeRow): _description_ fluid (Solution, optional): _description_. Defaults to None. Returns: (BladeRow): updated row """ if fluid: Tm = row.T.mean() Pm = row.P.mean() fluid.TP = Tm,Pm row.Cp = fluid.cp row.Cv = fluid.cv row.R = row.Cp-row.Cv row.gamma = row.Cp/row.Cv # Use Ideal Gas row.rho = row.P/(row.T*row.R) row.mu = sutherland(row.T) # type: ignore