Skip to content
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ Attention: The newest changes should be on top -->

-

### Deprecated

- MNT: Rename `radius` to `radius_function` in `CylindricalTank` and `SphericalTank`; old `radius=` keyword argument now raises `DeprecationWarning` [#957](https://github.com/RocketPy-Team/RocketPy/pull/957)

### Fixed

- BUG: fix NaN in ND linear interpolation outside convex hull [#926](https://github.com/RocketPy-Team/RocketPy/issues/926)
Expand Down
123 changes: 104 additions & 19 deletions rocketpy/motors/tank_geometry.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import warnings
from functools import cached_property

import numpy as np
Expand Down Expand Up @@ -384,14 +385,21 @@ class inherits from the TankGeometry class. See the TankGeometry class
for more information on its attributes and methods.
"""

def __init__(self, radius, height, spherical_caps=False, geometry_dict=None):
def __init__(
self,
radius_function=None,
height=None,
spherical_caps=False,
geometry_dict=None,
**kwargs,
):
"""Initialize CylindricalTank class. The zero reference point of the
cylinder is its center (i.e. half of its height). Therefore the its
height coordinate span is (-height/2, height/2).

Parameters
----------
radius : float
radius_function : int, float
Radius of the cylindrical tank, in meters.
height : float
Height of the cylindrical tank, in meters.
Expand All @@ -401,17 +409,47 @@ def __init__(self, radius, height, spherical_caps=False, geometry_dict=None):
will have flat caps at the top and bottom. Defaults to False.
geometry_dict : Union[dict, None], optional
Dictionary containing the geometry of the tank. See TankGeometry.
"""

Notes
-----
The ``radius`` keyword argument is deprecated. Use ``radius_function``
instead.
"""
if "radius" in kwargs:
if radius_function is not None:
raise TypeError(
"Cannot specify both 'radius_function' and deprecated "
"'radius' arguments. Use 'radius_function' instead."
)
warnings.warn(
"The 'radius' argument in CylindricalTank is deprecated in v1.13.0 "
"and will be removed in v2.0.0. Use 'radius_function' instead.",
DeprecationWarning,
stacklevel=2,
)
radius_function = kwargs.pop("radius")
if radius_function is None:
raise TypeError(
"CylindricalTank.__init__() missing required argument: "
"'radius_function'"
)
if height is None:
raise TypeError(
"CylindricalTank.__init__() missing required argument: 'height'"
)
geometry_dict = geometry_dict or {}
super().__init__(geometry_dict)
self.__input_radius = radius
self.radius_function = radius_function
self.height = height
self.has_caps = False
if spherical_caps:
self.add_geometry((-height / 2 + radius, height / 2 - radius), radius)
self.add_geometry(
(-height / 2 + radius_function, height / 2 - radius_function),
radius_function,
)
self.add_spherical_caps()
else:
self.add_geometry((-height / 2, height / 2), radius)
self.add_geometry((-height / 2, height / 2), radius_function)

def add_spherical_caps(self):
"""
Expand All @@ -424,11 +462,11 @@ def add_spherical_caps(self):
"Warning: Adding spherical caps to the tank will not modify the "
+ f"total height of the tank {self.height} m. "
+ "Its cylindrical portion height will be reduced to "
+ f"{self.height - 2 * self.__input_radius} m."
+ f"{self.height - 2 * self.radius_function} m."
)

if not self.has_caps:
radius = self.__input_radius
radius = self.radius_function
height = self.height
bottom_cap_range = (-height / 2, -height / 2 + radius)
upper_cap_range = (height / 2 - radius, height / 2)
Expand All @@ -447,7 +485,7 @@ def upper_cap_radius(h):

def to_dict(self, **kwargs):
data = {
"radius": self.__input_radius,
"radius_function": self.radius_function,
"height": self.height,
"spherical_caps": self.has_caps,
}
Expand All @@ -459,7 +497,18 @@ def to_dict(self, **kwargs):

@classmethod
def from_dict(cls, data):
return cls(data["radius"], data["height"], data["spherical_caps"])
if "radius_function" in data:
radius_function = data["radius_function"]
else:
warnings.warn(
"The 'radius' key in CylindricalTank serialized data is "
"deprecated in v1.13.0 and will be removed in v2.0.0. "
"Use 'radius_function' instead.",
DeprecationWarning,
stacklevel=2,
)
radius_function = data["radius"]
return cls(radius_function, data["height"], data["spherical_caps"])


class SphericalTank(TankGeometry):
Expand All @@ -468,25 +517,50 @@ class SphericalTank(TankGeometry):
inherits from the TankGeometry class. See the TankGeometry class for
more information on its attributes and methods."""

def __init__(self, radius, geometry_dict=None):
def __init__(self, radius_function=None, geometry_dict=None, **kwargs):
"""Initialize SphericalTank class. The zero reference point of the
sphere is its center (i.e. half of its height). Therefore, its height
coordinate ranges between (-radius, radius).
coordinate ranges between (-radius_function, radius_function).

Parameters
----------
radius : float
Radius of the spherical tank.
radius_function : int, float
Radius of the spherical tank, in meters.
geometry_dict : Union[dict, None], optional
Dictionary containing the geometry of the tank. See TankGeometry.
"""

Notes
-----
The ``radius`` keyword argument is deprecated. Use ``radius_function``
instead.
"""
if "radius" in kwargs:
if radius_function is not None:
raise TypeError(
"Cannot specify both 'radius_function' and deprecated "
"'radius' arguments. Use 'radius_function' instead."
)
warnings.warn(
"The 'radius' argument in SphericalTank is deprecated in v1.13.0 "
"and will be removed in v2.0.0. Use 'radius_function' instead.",
DeprecationWarning,
stacklevel=2,
)
radius_function = kwargs.pop("radius")
if radius_function is None:
raise TypeError(
"SphericalTank.__init__() missing required argument: 'radius_function'"
)
geometry_dict = geometry_dict or {}
super().__init__(geometry_dict)
self.__input_radius = radius
self.add_geometry((-radius, radius), lambda h: (radius**2 - h**2) ** 0.5)
self.radius_function = radius_function
self.add_geometry(
(-radius_function, radius_function),
lambda h: (radius_function**2 - h**2) ** 0.5,
)

def to_dict(self, **kwargs):
data = {"radius": self.__input_radius}
data = {"radius_function": self.radius_function}

if kwargs.get("include_outputs", False):
data.update(super().to_dict(**kwargs))
Expand All @@ -495,4 +569,15 @@ def to_dict(self, **kwargs):

@classmethod
def from_dict(cls, data):
return cls(data["radius"])
if "radius_function" in data:
radius_function = data["radius_function"]
else:
warnings.warn(
"The 'radius' key in SphericalTank serialized data is "
"deprecated in v1.13.0 and will be removed in v2.0.0. "
"Use 'radius_function' instead.",
DeprecationWarning,
stacklevel=2,
)
radius_function = data["radius"]
Comment thread
Gui-FernandesBR marked this conversation as resolved.
return cls(radius_function)
2 changes: 1 addition & 1 deletion tests/fixtures/motor/tanks_fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -499,7 +499,7 @@ def temperature(t):

return MassBasedTank(
name="Variable Density N2O Tank",
geometry=CylindricalTank(height=0.8, radius=0.06, spherical_caps=True),
geometry=CylindricalTank(height=0.8, radius_function=0.06, spherical_caps=True),
flux_time=7,
liquid=nitrous_oxide_non_constant_fluid,
gas=nitrous_oxide_non_constant_fluid,
Expand Down
113 changes: 113 additions & 0 deletions tests/unit/motors/test_tank_geometry.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import warnings
from pathlib import Path
from unittest.mock import patch

import numpy as np
import pytest

from rocketpy import CylindricalTank, SphericalTank
from rocketpy.mathutils.function import Function
from rocketpy.motors import TankGeometry

PRESSURANT_PARAMS = (0.135 / 2, 0.981)
Expand Down Expand Up @@ -132,3 +135,113 @@ def test_tank_inertia(params, request):
@patch("matplotlib.pyplot.show")
def test_tank_geometry_plots_info(mock_show): # pylint: disable=unused-argument
assert TankGeometry({(0, 5): 1}).plots.all() is None


def test_cylindrical_tank_radius_function_attribute():
"""Test that CylindricalTank stores the input radius as 'radius_function'
and that it does not conflict with the 'radius' property (a Function of
height).
"""
r = 0.1
tank = CylindricalTank(r, 2.0)

# radius_function stores the raw input scalar
assert tank.radius_function == r
# radius property is a callable Function, not the scalar
assert callable(tank.radius)
assert isinstance(tank.radius, Function)
# The two must differ in type
assert not isinstance(tank.radius_function, Function)


def test_spherical_tank_radius_function_attribute():
"""Test that SphericalTank stores the input radius as 'radius_function'
and that it does not conflict with the 'radius' property (a Function of
height).
"""
r = 0.05
tank = SphericalTank(r)

# radius_function stores the raw input scalar
assert tank.radius_function == r
# radius property is a callable Function, not the scalar
assert callable(tank.radius)
assert isinstance(tank.radius, Function)
# The two must differ in type
assert not isinstance(tank.radius_function, Function)


def test_cylindrical_tank_deprecated_radius_kwarg():
"""Test that CylindricalTank issues a DeprecationWarning when the old
'radius' keyword argument is used, and still works correctly.
"""
r = 0.1
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
tank = CylindricalTank(radius=r, height=2.0)
assert len(w) == 1
assert issubclass(w[0].category, DeprecationWarning)
assert "radius_function" in str(w[0].message)

assert tank.radius_function == r


def test_spherical_tank_deprecated_radius_kwarg():
"""Test that SphericalTank issues a DeprecationWarning when the old
'radius' keyword argument is used, and still works correctly.
"""
r = 0.05
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
tank = SphericalTank(radius=r)
assert len(w) == 1
assert issubclass(w[0].category, DeprecationWarning)
assert "radius_function" in str(w[0].message)

assert tank.radius_function == r


def test_cylindrical_tank_to_dict_uses_radius_function_key():
"""Test that CylindricalTank.to_dict() uses the 'radius_function' key."""
tank = CylindricalTank(0.1, 2.0)
data = tank.to_dict()
assert "radius_function" in data
assert "radius" not in data
assert data["radius_function"] == 0.1


def test_spherical_tank_to_dict_uses_radius_function_key():
"""Test that SphericalTank.to_dict() uses the 'radius_function' key."""
tank = SphericalTank(0.05)
data = tank.to_dict()
assert "radius_function" in data
assert "radius" not in data
assert data["radius_function"] == 0.05


def test_cylindrical_tank_from_dict_deprecated_radius_key():
"""Test that CylindricalTank.from_dict() issues a DeprecationWarning when
the serialized data contains the old 'radius' key.
"""
old_data = {"radius": 0.1, "height": 2.0, "spherical_caps": False}
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
tank = CylindricalTank.from_dict(old_data)
assert len(w) == 1
assert issubclass(w[0].category, DeprecationWarning)

assert tank.radius_function == 0.1


def test_spherical_tank_from_dict_deprecated_radius_key():
"""Test that SphericalTank.from_dict() issues a DeprecationWarning when
the serialized data contains the old 'radius' key.
"""
old_data = {"radius": 0.05}
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
tank = SphericalTank.from_dict(old_data)
assert len(w) == 1
assert issubclass(w[0].category, DeprecationWarning)

assert tank.radius_function == 0.05
Loading