Source code for rocketpy.rocket.aero_surface.fins._base_fin
import math
from abc import abstractmethod
import numpy as np
from rocketpy.mathutils.function import Function
from ..aero_surface import AeroSurface
[docs]
class _BaseFin(AeroSurface):
"""
Base class for fins, shared by both Fin and Fins classes.
Inherits from AeroSurface.
Handles shared initialization logic and common properties.
"""
[docs]
def __init__(
self, name, rocket_radius, root_chord, span, airfoil=None, cant_angle=0
):
"""
Initialize the base fin class.
Parameters
----------
name : str
Name of the fin or fin set.
rocket_radius : float
Rocket radius in meters.
root_chord : float
Root chord of the fin in meters.
span : float
Span of the fin in meters.
airfoil : tuple, optional
Tuple containing airfoil data and unit ('degrees' or 'radians').
cant_angle : float, optional
Cant angle in degrees.
"""
self.name = name
self._rocket_radius = rocket_radius
self._root_chord = root_chord
self._span = span
self._airfoil = airfoil
self._cant_angle = cant_angle
self._cant_angle_rad = math.radians(cant_angle)
self.geometry = None
self.reference_area = np.pi * rocket_radius**2
super().__init__(name, self.reference_area, self.rocket_diameter)
[docs]
def _update_reference_quantities(self):
"""Update quantities that depend on rocket radius."""
self.reference_area = np.pi * self._rocket_radius**2
self.reference_length = self.rocket_diameter
[docs]
def _update_geometry_chain(self):
"""Update geometry-dependent quantities in dependency order."""
self.evaluate_geometrical_parameters()
self.evaluate_center_of_pressure()
self.evaluate_lift_coefficient()
self.evaluate_roll_parameters()
@property
def rocket_radius(self):
"""Rocket radius in meters.
Returns
-------
float
Rocket radius in meters.
"""
return self._rocket_radius
@rocket_radius.setter
def rocket_radius(self, value):
"""Set rocket radius and update dependent properties.
Parameters
----------
value : float
Rocket radius in meters.
"""
self._rocket_radius = value
self._update_reference_quantities()
self._update_geometry_chain()
@property
def rocket_diameter(self):
"""Reference rocket diameter in meters."""
return 2 * self._rocket_radius
@rocket_diameter.setter
def rocket_diameter(self, value):
"""Set reference rocket diameter in meters."""
self.rocket_radius = value / 2
@property
def diameter(self):
"""Backward-compatible alias for :attr:`rocket_diameter`."""
return self.rocket_diameter
@diameter.setter
def diameter(self, value):
"""Backward-compatible alias setter for :attr:`rocket_diameter`."""
self.rocket_diameter = value
@property
def d(self):
"""Backward-compatible alias for :attr:`rocket_diameter`."""
return self.rocket_diameter
@d.setter
def d(self, value):
"""Backward-compatible alias setter for :attr:`rocket_diameter`."""
self.rocket_diameter = value
@property
def ref_area(self):
"""Backward-compatible alias for :attr:`reference_area`."""
return self.reference_area
@ref_area.setter
def ref_area(self, value):
"""Backward-compatible alias setter for :attr:`reference_area`."""
self.reference_area = value
@property
def root_chord(self):
"""Root chord length in meters.
Returns
-------
float
Root chord length in meters.
"""
return self._root_chord
@root_chord.setter
def root_chord(self, value):
"""Set root chord and update dependent properties.
Parameters
----------
value : float
Root chord length in meters.
"""
self._root_chord = value
self._update_geometry_chain()
self.evaluate_shape()
@property
def span(self):
"""Fin span in meters.
Returns
-------
float
Fin span in meters.
"""
return self._span
@span.setter
def span(self, value):
"""Set fin span and update dependent properties.
Parameters
----------
value : float
Fin span in meters.
"""
self._span = value
self._update_geometry_chain()
self.evaluate_shape()
@property
def cant_angle(self):
"""Cant angle in degrees.
Returns
-------
float
Cant angle in degrees.
"""
return self._cant_angle
@cant_angle.setter
def cant_angle(self, value):
"""Set cant angle and update radian representation.
Parameters
----------
value : float
Cant angle in degrees.
"""
self._cant_angle = value
self.cant_angle_rad = math.radians(value)
@property
def cant_angle_rad(self):
"""Cant angle in radians.
Returns
-------
float
Cant angle in radians.
"""
return self._cant_angle_rad
@cant_angle_rad.setter
def cant_angle_rad(self, value):
"""Set cant angle in radians and update dependent properties.
Parameters
----------
value : float
Cant angle in radians.
"""
self._cant_angle_rad = value
self._update_geometry_chain()
@property
def airfoil(self):
"""Airfoil data for the fin.
Returns
-------
tuple or None
Tuple containing airfoil data and unit ('degrees' or 'radians'),
or None if using planar fin.
"""
return self._airfoil
@airfoil.setter
def airfoil(self, value):
"""Set airfoil data and update dependent properties.
Parameters
----------
value : tuple or None
Tuple containing airfoil data and unit ('degrees' or 'radians'),
or None for planar fin.
"""
self._airfoil = value
self._update_geometry_chain()
[docs]
def info(self):
"""Print fin geometry and lift information."""
self.prints.geometry()
self.prints.lift()
[docs]
def all_info(self):
"""Print all available fin information and show all fin plots."""
self.prints.all()
self.plots.all()
[docs]
def evaluate_single_fin_lift_coefficient(self):
"""Evaluate the lift coefficient derivative for a single fin.
Computes the lift coefficient derivative (clalpha) considering the
fin's geometry, airfoil characteristics (if provided), and Mach number
effects using Prandtl-Glauert compressibility correction and
Diederich's planform correlation.
Sets the `clalpha_single_fin` attribute as a Function of Mach number.
"""
if not self.airfoil:
# Defines clalpha2D as 2*pi for planar fins
clalpha2D_incompressible = 2 * np.pi
else:
# Defines clalpha2D as the derivative of the lift coefficient curve
# for the specific airfoil
self.airfoil_cl = Function(
self.airfoil[0],
title="Airfoil lift coefficient",
interpolation="linear",
)
# Differentiating at alpha = 0 to get cl_alpha
clalpha2D_incompressible = self.airfoil_cl.differentiate(x=1e-3, dx=1e-3)
# Convert to radians if needed
if self.airfoil[1] == "degrees":
clalpha2D_incompressible *= 180 / np.pi
# Correcting for compressible flow (apply Prandtl-Glauert correction)
clalpha2D = Function(lambda mach: clalpha2D_incompressible / self._beta(mach))
# Diederich's Planform Correlation Parameter
planform_correlation_parameter = (
2 * np.pi * self.AR / (clalpha2D * np.cos(self.gamma_c))
)
# Lift coefficient derivative for a single fin
def lift_source(mach):
return (
clalpha2D(mach)
* planform_correlation_parameter(mach)
* (self.Af / self.reference_area)
* np.cos(self.gamma_c)
) / (
2
+ planform_correlation_parameter(mach)
* np.sqrt(1 + (2 / planform_correlation_parameter(mach)) ** 2)
)
self.clalpha_single_fin = Function(
lift_source,
"Mach",
"Lift coefficient derivative for a single fin",
)
[docs]
@abstractmethod
def evaluate_lift_coefficient(self):
"""Evaluate the lift coefficient for the fin."""
[docs]
@abstractmethod
def evaluate_roll_parameters(self):
"""Evaluate roll-related parameters for the fin."""
[docs]
@abstractmethod
def evaluate_center_of_pressure(self):
"""Evaluate the center of pressure for the fin."""
[docs]
def evaluate_geometrical_parameters(self):
"""Evaluate geometric parameters of the fin.
This method delegates to the configured geometry strategy.
"""
geometry_data = self.geometry.evaluate_geometrical_parameters()
for key, value in geometry_data.items():
setattr(self, key, value)
[docs]
def evaluate_shape(self):
"""Evaluate the shape representation of the fin.
This method delegates to the configured geometry strategy.
"""
self.shape_vec = self.geometry.evaluate_shape()
[docs]
@abstractmethod
def draw(self):
"""Draw or render the fin."""