Skip to content

waterrocketpy.rocket.geometry

Geometric calculations for water rocket components.

GeometricProperties dataclass

Container for geometric properties of rocket components.

Source code in waterrocketpy/rocket/geometry.py
@dataclass
class GeometricProperties:
    """Container for geometric properties of rocket components."""

    volume: float
    surface_area: float
    center_of_mass: Tuple[float, float, float]
    center_of_pressure: Tuple[float, float, float]
    moment_of_inertia: Tuple[float, float, float]  # Ixx, Iyy, Izz

RocketGeometry

Geometric calculations for water rocket components.

Source code in waterrocketpy/rocket/geometry.py
class RocketGeometry:
    """Geometric calculations for water rocket components."""

    @staticmethod
    def cylinder_volume(diameter: float, length: float) -> float:
        """Calculate volume of a cylinder."""
        radius = diameter / 2
        return np.pi * radius**2 * length

    @staticmethod
    def cylinder_surface_area(
        diameter: float, length: float, include_ends: bool = True
    ) -> float:
        """Calculate surface area of a cylinder."""
        radius = diameter / 2
        lateral_area = 2 * np.pi * radius * length
        if include_ends:
            end_area = 2 * np.pi * radius**2
            return lateral_area + end_area
        return lateral_area

    @staticmethod
    def sphere_volume(diameter: float) -> float:
        """Calculate volume of a sphere."""
        radius = diameter / 2
        return (4 / 3) * np.pi * radius**3

    @staticmethod
    def sphere_surface_area(diameter: float) -> float:
        """Calculate surface area of a sphere."""
        radius = diameter / 2
        return 4 * np.pi * radius**2

    @staticmethod
    def cone_volume(diameter: float, height: float) -> float:
        """Calculate volume of a cone."""
        radius = diameter / 2
        return (1 / 3) * np.pi * radius**2 * height

    @staticmethod
    def cone_surface_area(diameter: float, height: float) -> float:
        """Calculate surface area of a cone."""
        radius = diameter / 2
        slant_height = np.sqrt(radius**2 + height**2)
        return np.pi * radius * slant_height + np.pi * radius**2

    @staticmethod
    def ellipsoid_volume(a: float, b: float, c: float) -> float:
        """Calculate volume of an ellipsoid."""
        return (4 / 3) * np.pi * a * b * c

    @staticmethod
    def bottle_volume(
        diameter: float,
        length: float,
        nose_cone_height: float = 0.0,
        bottom_cone_height: float = 0.0,
    ) -> float:
        """
        Calculate total volume of a rocket bottle.

        Args:
            diameter: Bottle diameter
            length: Cylindrical section length
            nose_cone_height: Height of nose cone
            bottom_cone_height: Height of bottom cone

        Returns:
            Total volume
        """
        # Cylindrical section
        cylinder_vol = RocketGeometry.cylinder_volume(diameter, length)

        # Nose cone (if present)
        nose_vol = 0
        if nose_cone_height > 0:
            nose_vol = RocketGeometry.cone_volume(diameter, nose_cone_height)

        # Bottom cone (if present)
        bottom_vol = 0
        if bottom_cone_height > 0:
            bottom_vol = RocketGeometry.cone_volume(
                diameter, bottom_cone_height
            )

        return cylinder_vol + nose_vol + bottom_vol

    @staticmethod
    def calculate_center_of_mass(
        components: List[Dict[str, Any]],
    ) -> Tuple[float, float, float]:
        """
        Calculate center of mass for multiple components.

        Args:
            components: List of component dictionaries with 'mass', 'position' keys

        Returns:
            Center of mass coordinates (x, y, z)
        """
        total_mass = 0
        weighted_position = np.array([0.0, 0.0, 0.0])

        for component in components:
            mass = component["mass"]
            position = np.array(component["position"])

            total_mass += mass
            weighted_position += mass * position

        if total_mass == 0:
            return (0.0, 0.0, 0.0)

        center_of_mass = weighted_position / total_mass
        return tuple(center_of_mass)

    @staticmethod
    def calculate_center_of_pressure(
        fins: List[Dict[str, Any]], body_cp: Tuple[float, float, float]
    ) -> Tuple[float, float, float]:
        """
        Calculate center of pressure for rocket with fins.

        Args:
            fins: List of fin dictionaries with area and position
            body_cp: Center of pressure of the body

        Returns:
            Overall center of pressure
        """
        # Body contribution
        body_area = 1.0  # Normalized
        body_moment = np.array(body_cp) * body_area

        total_area = body_area
        total_moment = body_moment

        # Fin contributions
        for fin in fins:
            fin_area = fin["area"]
            fin_position = np.array(fin["position"])

            total_area += fin_area
            total_moment += fin_area * fin_position

        if total_area == 0:
            return body_cp

        cp = total_moment / total_area
        return tuple(cp)

    @staticmethod
    def calculate_moment_of_inertia_cylinder(
        mass: float, radius: float, length: float
    ) -> Tuple[float, float, float]:
        """
        Calculate moment of inertia for a cylinder.

        Args:
            mass: Mass of cylinder
            radius: Radius of cylinder
            length: Length of cylinder

        Returns:
            Moments of inertia (Ixx, Iyy, Izz)
        """
        # For a cylinder with axis along z:
        # Ixx = Iyy = (1/12) * m * (3*r^2 + h^2)
        # Izz = (1/2) * m * r^2

        Ixx = Iyy = (1 / 12) * mass * (3 * radius**2 + length**2)
        Izz = 0.5 * mass * radius**2

        return (Ixx, Iyy, Izz)

    @staticmethod
    def calculate_stability_margin(
        center_of_mass: Tuple[float, float, float],
        center_of_pressure: Tuple[float, float, float],
        reference_length: float,
    ) -> float:
        """
        Calculate static stability margin.

        Args:
            center_of_mass: Center of mass position
            center_of_pressure: Center of pressure position
            reference_length: Reference length (typically rocket diameter)

        Returns:
            Stability margin (positive = stable)
        """
        # Calculate distance between CP and CG along rocket axis (typically
        # x-axis)
        cp_x = center_of_pressure[0]
        cg_x = center_of_mass[0]

        # Stability margin in calibers (rocket diameters)
        stability_margin = (cp_x - cg_x) / reference_length

        return stability_margin

    @staticmethod
    def calculate_fin_properties(
        span: float,
        root_chord: float,
        tip_chord: float,
        sweep_angle: float = 0.0,
    ) -> Dict[str, float]:
        """
        Calculate properties of a trapezoidal fin.

        Args:
            span: Fin span (height)
            root_chord: Root chord length
            tip_chord: Tip chord length
            sweep_angle: Sweep angle in radians

        Returns:
            Dictionary with fin properties
        """
        # Area
        area = 0.5 * (root_chord + tip_chord) * span

        # Aspect ratio
        aspect_ratio = span**2 / area

        # Taper ratio
        taper_ratio = tip_chord / root_chord if root_chord > 0 else 0

        # Mean aerodynamic chord
        mac = (
            (2 / 3)
            * root_chord
            * (1 + taper_ratio + taper_ratio**2)
            / (1 + taper_ratio)
        )

        # Centroid position (from root leading edge)
        x_centroid = (
            (root_chord + 2 * tip_chord)
            / (3 * (root_chord + tip_chord))
            * root_chord
        )
        y_centroid = (
            span / 3 * (root_chord + 2 * tip_chord) / (root_chord + tip_chord)
        )

        return {
            "area": area,
            "aspect_ratio": aspect_ratio,
            "taper_ratio": taper_ratio,
            "mac": mac,
            "centroid_x": x_centroid,
            "centroid_y": y_centroid,
        }

    @staticmethod
    def calculate_rocket_wetted_area(
        diameter: float,
        length: float,
        nose_cone_height: float = 0.0,
        fin_area: float = 0.0,
    ) -> float:
        """
        Calculate total wetted area of rocket.

        Args:
            diameter: Rocket diameter
            length: Body length
            nose_cone_height: Nose cone height
            fin_area: Total fin area

        Returns:
            Total wetted area
        """
        # Body area
        body_area = RocketGeometry.cylinder_surface_area(
            diameter, length, include_ends=False
        )

        # Nose cone area
        nose_area = 0
        if nose_cone_height > 0:
            nose_area = RocketGeometry.cone_surface_area(
                diameter, nose_cone_height
            )

        # Base area
        base_area = np.pi * (diameter / 2) ** 2

        # Total wetted area (both sides of fins)
        total_area = body_area + nose_area + base_area + 2 * fin_area

        return total_area

bottle_volume(diameter, length, nose_cone_height=0.0, bottom_cone_height=0.0) staticmethod

Calculate total volume of a rocket bottle.

Parameters:

Name Type Description Default
diameter float

Bottle diameter

required
length float

Cylindrical section length

required
nose_cone_height float

Height of nose cone

0.0
bottom_cone_height float

Height of bottom cone

0.0

Returns:

Type Description
float

Total volume

Source code in waterrocketpy/rocket/geometry.py
@staticmethod
def bottle_volume(
    diameter: float,
    length: float,
    nose_cone_height: float = 0.0,
    bottom_cone_height: float = 0.0,
) -> float:
    """
    Calculate total volume of a rocket bottle.

    Args:
        diameter: Bottle diameter
        length: Cylindrical section length
        nose_cone_height: Height of nose cone
        bottom_cone_height: Height of bottom cone

    Returns:
        Total volume
    """
    # Cylindrical section
    cylinder_vol = RocketGeometry.cylinder_volume(diameter, length)

    # Nose cone (if present)
    nose_vol = 0
    if nose_cone_height > 0:
        nose_vol = RocketGeometry.cone_volume(diameter, nose_cone_height)

    # Bottom cone (if present)
    bottom_vol = 0
    if bottom_cone_height > 0:
        bottom_vol = RocketGeometry.cone_volume(
            diameter, bottom_cone_height
        )

    return cylinder_vol + nose_vol + bottom_vol

calculate_center_of_mass(components) staticmethod

Calculate center of mass for multiple components.

Parameters:

Name Type Description Default
components List[Dict[str, Any]]

List of component dictionaries with 'mass', 'position' keys

required

Returns:

Type Description
Tuple[float, float, float]

Center of mass coordinates (x, y, z)

Source code in waterrocketpy/rocket/geometry.py
@staticmethod
def calculate_center_of_mass(
    components: List[Dict[str, Any]],
) -> Tuple[float, float, float]:
    """
    Calculate center of mass for multiple components.

    Args:
        components: List of component dictionaries with 'mass', 'position' keys

    Returns:
        Center of mass coordinates (x, y, z)
    """
    total_mass = 0
    weighted_position = np.array([0.0, 0.0, 0.0])

    for component in components:
        mass = component["mass"]
        position = np.array(component["position"])

        total_mass += mass
        weighted_position += mass * position

    if total_mass == 0:
        return (0.0, 0.0, 0.0)

    center_of_mass = weighted_position / total_mass
    return tuple(center_of_mass)

calculate_center_of_pressure(fins, body_cp) staticmethod

Calculate center of pressure for rocket with fins.

Parameters:

Name Type Description Default
fins List[Dict[str, Any]]

List of fin dictionaries with area and position

required
body_cp Tuple[float, float, float]

Center of pressure of the body

required

Returns:

Type Description
Tuple[float, float, float]

Overall center of pressure

Source code in waterrocketpy/rocket/geometry.py
@staticmethod
def calculate_center_of_pressure(
    fins: List[Dict[str, Any]], body_cp: Tuple[float, float, float]
) -> Tuple[float, float, float]:
    """
    Calculate center of pressure for rocket with fins.

    Args:
        fins: List of fin dictionaries with area and position
        body_cp: Center of pressure of the body

    Returns:
        Overall center of pressure
    """
    # Body contribution
    body_area = 1.0  # Normalized
    body_moment = np.array(body_cp) * body_area

    total_area = body_area
    total_moment = body_moment

    # Fin contributions
    for fin in fins:
        fin_area = fin["area"]
        fin_position = np.array(fin["position"])

        total_area += fin_area
        total_moment += fin_area * fin_position

    if total_area == 0:
        return body_cp

    cp = total_moment / total_area
    return tuple(cp)

calculate_fin_properties(span, root_chord, tip_chord, sweep_angle=0.0) staticmethod

Calculate properties of a trapezoidal fin.

Parameters:

Name Type Description Default
span float

Fin span (height)

required
root_chord float

Root chord length

required
tip_chord float

Tip chord length

required
sweep_angle float

Sweep angle in radians

0.0

Returns:

Type Description
Dict[str, float]

Dictionary with fin properties

Source code in waterrocketpy/rocket/geometry.py
@staticmethod
def calculate_fin_properties(
    span: float,
    root_chord: float,
    tip_chord: float,
    sweep_angle: float = 0.0,
) -> Dict[str, float]:
    """
    Calculate properties of a trapezoidal fin.

    Args:
        span: Fin span (height)
        root_chord: Root chord length
        tip_chord: Tip chord length
        sweep_angle: Sweep angle in radians

    Returns:
        Dictionary with fin properties
    """
    # Area
    area = 0.5 * (root_chord + tip_chord) * span

    # Aspect ratio
    aspect_ratio = span**2 / area

    # Taper ratio
    taper_ratio = tip_chord / root_chord if root_chord > 0 else 0

    # Mean aerodynamic chord
    mac = (
        (2 / 3)
        * root_chord
        * (1 + taper_ratio + taper_ratio**2)
        / (1 + taper_ratio)
    )

    # Centroid position (from root leading edge)
    x_centroid = (
        (root_chord + 2 * tip_chord)
        / (3 * (root_chord + tip_chord))
        * root_chord
    )
    y_centroid = (
        span / 3 * (root_chord + 2 * tip_chord) / (root_chord + tip_chord)
    )

    return {
        "area": area,
        "aspect_ratio": aspect_ratio,
        "taper_ratio": taper_ratio,
        "mac": mac,
        "centroid_x": x_centroid,
        "centroid_y": y_centroid,
    }

calculate_moment_of_inertia_cylinder(mass, radius, length) staticmethod

Calculate moment of inertia for a cylinder.

Parameters:

Name Type Description Default
mass float

Mass of cylinder

required
radius float

Radius of cylinder

required
length float

Length of cylinder

required

Returns:

Type Description
Tuple[float, float, float]

Moments of inertia (Ixx, Iyy, Izz)

Source code in waterrocketpy/rocket/geometry.py
@staticmethod
def calculate_moment_of_inertia_cylinder(
    mass: float, radius: float, length: float
) -> Tuple[float, float, float]:
    """
    Calculate moment of inertia for a cylinder.

    Args:
        mass: Mass of cylinder
        radius: Radius of cylinder
        length: Length of cylinder

    Returns:
        Moments of inertia (Ixx, Iyy, Izz)
    """
    # For a cylinder with axis along z:
    # Ixx = Iyy = (1/12) * m * (3*r^2 + h^2)
    # Izz = (1/2) * m * r^2

    Ixx = Iyy = (1 / 12) * mass * (3 * radius**2 + length**2)
    Izz = 0.5 * mass * radius**2

    return (Ixx, Iyy, Izz)

calculate_rocket_wetted_area(diameter, length, nose_cone_height=0.0, fin_area=0.0) staticmethod

Calculate total wetted area of rocket.

Parameters:

Name Type Description Default
diameter float

Rocket diameter

required
length float

Body length

required
nose_cone_height float

Nose cone height

0.0
fin_area float

Total fin area

0.0

Returns:

Type Description
float

Total wetted area

Source code in waterrocketpy/rocket/geometry.py
@staticmethod
def calculate_rocket_wetted_area(
    diameter: float,
    length: float,
    nose_cone_height: float = 0.0,
    fin_area: float = 0.0,
) -> float:
    """
    Calculate total wetted area of rocket.

    Args:
        diameter: Rocket diameter
        length: Body length
        nose_cone_height: Nose cone height
        fin_area: Total fin area

    Returns:
        Total wetted area
    """
    # Body area
    body_area = RocketGeometry.cylinder_surface_area(
        diameter, length, include_ends=False
    )

    # Nose cone area
    nose_area = 0
    if nose_cone_height > 0:
        nose_area = RocketGeometry.cone_surface_area(
            diameter, nose_cone_height
        )

    # Base area
    base_area = np.pi * (diameter / 2) ** 2

    # Total wetted area (both sides of fins)
    total_area = body_area + nose_area + base_area + 2 * fin_area

    return total_area

calculate_stability_margin(center_of_mass, center_of_pressure, reference_length) staticmethod

Calculate static stability margin.

Parameters:

Name Type Description Default
center_of_mass Tuple[float, float, float]

Center of mass position

required
center_of_pressure Tuple[float, float, float]

Center of pressure position

required
reference_length float

Reference length (typically rocket diameter)

required

Returns:

Type Description
float

Stability margin (positive = stable)

Source code in waterrocketpy/rocket/geometry.py
@staticmethod
def calculate_stability_margin(
    center_of_mass: Tuple[float, float, float],
    center_of_pressure: Tuple[float, float, float],
    reference_length: float,
) -> float:
    """
    Calculate static stability margin.

    Args:
        center_of_mass: Center of mass position
        center_of_pressure: Center of pressure position
        reference_length: Reference length (typically rocket diameter)

    Returns:
        Stability margin (positive = stable)
    """
    # Calculate distance between CP and CG along rocket axis (typically
    # x-axis)
    cp_x = center_of_pressure[0]
    cg_x = center_of_mass[0]

    # Stability margin in calibers (rocket diameters)
    stability_margin = (cp_x - cg_x) / reference_length

    return stability_margin

cone_surface_area(diameter, height) staticmethod

Calculate surface area of a cone.

Source code in waterrocketpy/rocket/geometry.py
@staticmethod
def cone_surface_area(diameter: float, height: float) -> float:
    """Calculate surface area of a cone."""
    radius = diameter / 2
    slant_height = np.sqrt(radius**2 + height**2)
    return np.pi * radius * slant_height + np.pi * radius**2

cone_volume(diameter, height) staticmethod

Calculate volume of a cone.

Source code in waterrocketpy/rocket/geometry.py
@staticmethod
def cone_volume(diameter: float, height: float) -> float:
    """Calculate volume of a cone."""
    radius = diameter / 2
    return (1 / 3) * np.pi * radius**2 * height

cylinder_surface_area(diameter, length, include_ends=True) staticmethod

Calculate surface area of a cylinder.

Source code in waterrocketpy/rocket/geometry.py
@staticmethod
def cylinder_surface_area(
    diameter: float, length: float, include_ends: bool = True
) -> float:
    """Calculate surface area of a cylinder."""
    radius = diameter / 2
    lateral_area = 2 * np.pi * radius * length
    if include_ends:
        end_area = 2 * np.pi * radius**2
        return lateral_area + end_area
    return lateral_area

cylinder_volume(diameter, length) staticmethod

Calculate volume of a cylinder.

Source code in waterrocketpy/rocket/geometry.py
@staticmethod
def cylinder_volume(diameter: float, length: float) -> float:
    """Calculate volume of a cylinder."""
    radius = diameter / 2
    return np.pi * radius**2 * length

ellipsoid_volume(a, b, c) staticmethod

Calculate volume of an ellipsoid.

Source code in waterrocketpy/rocket/geometry.py
@staticmethod
def ellipsoid_volume(a: float, b: float, c: float) -> float:
    """Calculate volume of an ellipsoid."""
    return (4 / 3) * np.pi * a * b * c

sphere_surface_area(diameter) staticmethod

Calculate surface area of a sphere.

Source code in waterrocketpy/rocket/geometry.py
@staticmethod
def sphere_surface_area(diameter: float) -> float:
    """Calculate surface area of a sphere."""
    radius = diameter / 2
    return 4 * np.pi * radius**2

sphere_volume(diameter) staticmethod

Calculate volume of a sphere.

Source code in waterrocketpy/rocket/geometry.py
@staticmethod
def sphere_volume(diameter: float) -> float:
    """Calculate volume of a sphere."""
    radius = diameter / 2
    return (4 / 3) * np.pi * radius**3