Skip to content

Commit 568661d

Browse files
committed
TST: improve fluid tests and solve review comments.
1 parent 1d09834 commit 568661d

File tree

6 files changed

+112
-34
lines changed

6 files changed

+112
-34
lines changed

rocketpy/motors/fluid.py

Lines changed: 46 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import numpy as np
22
from scipy.constants import atm, zero_Celsius
33

4-
from ..mathutils.function import Function
4+
from ..mathutils.function import NUMERICAL_TYPES, Function
55
from ..plots.fluid_plots import _FluidPlots
66
from ..prints.fluid_prints import _FluidPrints
77

88

99
class Fluid:
10-
"""Class that represents a fluid object and its simple quantities,
10+
"""Class that represents a fluid object and its attributes,
1111
such as density.
1212
1313
Attributes
@@ -16,6 +16,7 @@ class Fluid:
1616
Name of the fluid.
1717
density : int, float, callable, string, array, Function
1818
Input density of the fluid in kg/m³.
19+
Used internally to compute the density_function.
1920
density_function : Function
2021
Density of the fluid as a function of temperature and pressure.
2122
"""
@@ -44,6 +45,26 @@ def __init__(self, name, density):
4445
self.prints = _FluidPrints(self)
4546
self.plots = _FluidPlots(self)
4647

48+
@property
49+
def density(self):
50+
"""Density of the fluid from the class parameter input."""
51+
return self._density
52+
53+
@density.setter
54+
def density(self, value):
55+
"""Setter of the density class parameter."""
56+
if isinstance(value, NUMERICAL_TYPES):
57+
# Numerical value kept for retro-compatibility
58+
self._density = value
59+
else:
60+
self._density = Function(
61+
value,
62+
interpolation="linear",
63+
extrapolation="natural",
64+
inputs=["Temperature (K)", "Pressure (Pa)"],
65+
outputs="Density (kg/m³)",
66+
)
67+
4768
@property
4869
def density_function(self):
4970
"""Density of the fluid as a function of temperature and pressure."""
@@ -60,7 +81,7 @@ def density_function(self, value):
6081
Density of the fluid in kg/m³ as a function of temperature
6182
and pressure. The value is used as a ``Function`` source.
6283
"""
63-
if isinstance(value, (int, float, np.number)):
84+
if isinstance(value, NUMERICAL_TYPES):
6485
# pylint: disable=unused-argument
6586
def density_function(temperature, pressure):
6687
return value
@@ -91,33 +112,35 @@ def get_time_variable_density(self, temperature, pressure):
91112
Function
92113
Density of the fluid in kg/m³ as function of time.
93114
"""
94-
if callable(temperature.source) and callable(pressure.source):
115+
is_temperature_callable = callable(temperature.source)
116+
is_pressure_callable = callable(pressure.source)
117+
if is_temperature_callable and is_pressure_callable:
95118
return Function(
96119
lambda time: self.density_function.get_value(
97120
temperature.source(time), pressure.source(time)
98121
),
99122
inputs="Time (s)",
100123
outputs="Density (kg/m³)",
101124
)
125+
126+
if is_temperature_callable or is_pressure_callable:
127+
time_scale = (
128+
temperature.x_array if not is_temperature_callable else pressure.x_array
129+
)
102130
else:
103131
time_scale = np.unique(
104132
np.concatenate((temperature.x_array, pressure.x_array))
105133
)
106-
density_time = self.density_function.get_value(
107-
temperature.get_value(time_scale), pressure.get_value(time_scale)
108-
)
109-
return Function(
110-
np.column_stack(
111-
(
112-
time_scale,
113-
density_time,
114-
)
115-
),
116-
interpolation="linear",
117-
extrapolation="constant",
118-
inputs="Time (s)",
119-
outputs="Density (kg/m³)",
120-
)
134+
density_time = self.density_function.get_value(
135+
temperature.get_value(time_scale), pressure.get_value(time_scale)
136+
)
137+
return Function(
138+
np.column_stack((time_scale, density_time)),
139+
interpolation="linear",
140+
extrapolation="constant",
141+
inputs="Time (s)",
142+
outputs="Density (kg/m³)",
143+
)
121144

122145
def __repr__(self):
123146
"""Representation method.
@@ -143,15 +166,13 @@ def __str__(self):
143166

144167
def to_dict(self, **kwargs): # pylint: disable=unused-argument
145168
discretize = kwargs.get("discretize", False)
146-
if not isinstance(self.density, (int, float, np.number)):
147-
density = self.density_function
148-
else:
149-
density = self.density
150169

170+
density = self.density
151171
if discretize and isinstance(density, Function):
152172
density = density.set_discrete(
153-
((100, atm * 0.9), (zero_Celsius + 30, atm * 100)),
154-
(25, 25),
173+
lower=(100, atm * 0.9),
174+
upper=(zero_Celsius + 30, atm * 50),
175+
samples=(25, 25),
155176
mutate_self=False,
156177
)
157178

rocketpy/prints/fluid_prints.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import numpy as np
1+
from ..mathutils.function import NUMERICAL_TYPES
22

33

44
class _FluidPrints:
@@ -36,7 +36,7 @@ def all(self):
3636
None
3737
"""
3838
print(f"Name: {self.fluid.name}")
39-
if isinstance(self.fluid.density, (int, float, np.number)):
39+
if isinstance(self.fluid.density, NUMERICAL_TYPES):
4040
print(f"Density: {self.fluid.density:.4f} kg/m^3")
4141
else:
4242
print(f"Density: {self.fluid.density_function}")

tests/fixtures/motor/fluid_fixtures.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55

66
@pytest.fixture
7-
def nitrous_oxide_fluid():
7+
def nitrous_oxide_non_constant_fluid():
88
"""A nitrous_oxide fluid whose density varies with temperature
99
and pressure.
1010

tests/fixtures/motor/tanks_fixtures.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -463,14 +463,14 @@ def spherical_oxidizer_tank(oxidizer_fluid, oxidizer_pressurant):
463463

464464

465465
@pytest.fixture
466-
def cylindrical_variable_density_oxidizer_tank(nitrous_oxide_fluid):
466+
def cylindrical_variable_density_oxidizer_tank(nitrous_oxide_non_constant_fluid):
467467
"""Fixture for creating a cylindrical variable density oxidizer
468468
tank. This fixture returns a `MassBasedTank` object representing
469469
a cylindrical oxidizer tank with variable density properties.
470470
471471
Parameters
472472
----------
473-
nitrous_oxide_fluid : rocketpy.Fluid
473+
nitrous_oxide_non_constant_fluid : rocketpy.Fluid
474474
The fluid object representing nitrous oxide.
475475
476476
Returns
@@ -498,11 +498,11 @@ def temperature(t):
498498
return -40 * (t - 4) + zero_Celsius + 2
499499

500500
return MassBasedTank(
501-
name="Variable Density O2 Tank",
501+
name="Variable Density N2O Tank",
502502
geometry=CylindricalTank(height=0.8, radius=0.06, spherical_caps=True),
503503
flux_time=7,
504-
liquid=nitrous_oxide_fluid,
505-
gas=nitrous_oxide_fluid,
504+
liquid=nitrous_oxide_non_constant_fluid,
505+
gas=nitrous_oxide_non_constant_fluid,
506506
discretize=50,
507507
liquid_mass=liquid_mass,
508508
gas_mass=0,

tests/integration/test_encoding.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ def test_rocket_encoder(rocket_name, request):
242242
)
243243

244244

245-
@pytest.mark.parametrize("rocket_name", ["calisto_robust"])
245+
@pytest.mark.parametrize("rocket_name", ["calisto_robust", "calisto_hybrid_modded"])
246246
def test_encoder_discretize(rocket_name, request):
247247
"""Test encoding the total mass of ``rocketpy.Rocket`` with
248248
discretized encoding.

tests/unit/test_fluid.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import numpy as np
2+
from scipy.constants import atm, zero_Celsius
3+
4+
from rocketpy.mathutils.function import Function
5+
from rocketpy.motors.fluid import Fluid
6+
7+
8+
def test_constant_density_fluid_properties():
9+
"""Test Fluid with constant density has correct properties."""
10+
water = Fluid(name="Water", density=1000.0)
11+
assert water.name == "Water"
12+
assert water.density == 1000.0
13+
assert isinstance(water.density_function, Function)
14+
assert np.isclose(water.density_function.get_value(zero_Celsius, atm), 1000.0)
15+
16+
17+
def test_variable_density_fluid_properties():
18+
"""Test Fluid with variable density function."""
19+
test_fluid = Fluid(name="TestFluid", density=lambda t, p: 44 * p / (8.314 * t))
20+
assert test_fluid.name == "TestFluid"
21+
assert callable(test_fluid.density)
22+
assert isinstance(test_fluid.density, Function)
23+
assert isinstance(test_fluid.density_function, Function)
24+
expected_density = 44 * atm / (8.314 * zero_Celsius)
25+
assert np.isclose(test_fluid.density_function(zero_Celsius, atm), expected_density)
26+
27+
28+
def test_get_time_variable_density_with_callable_sources():
29+
"""Test get_time_variable_density with callable temperature and pressure."""
30+
test_fluid = Fluid("TestFluid", lambda t, p: t + p)
31+
temperature = Function(lambda t: 300 + t)
32+
pressure = Function(lambda t: 100_000 + 10 * t)
33+
density_time_func = test_fluid.get_time_variable_density(temperature, pressure)
34+
assert np.isclose(density_time_func.get_value(0), 300 + 100_000)
35+
assert np.isclose(density_time_func.get_value(10), 310 + 100_100)
36+
37+
38+
def test_get_time_variable_density_with_arrays():
39+
"""Test get_time_variable_density with array sources."""
40+
fluid = Fluid("TestFluid", lambda t, p: t + p)
41+
times = np.array([0, 10])
42+
temperature = Function(np.column_stack((times, [300, 310])))
43+
pressure = Function(np.column_stack((times, [100_000, 100_100])))
44+
density_time_func = fluid.get_time_variable_density(temperature, pressure)
45+
expected = [300 + 100_000, 310 + 100_100]
46+
np.testing.assert_allclose(density_time_func(times), expected)
47+
48+
49+
def test_get_time_variable_density_with_callable_and_arrays():
50+
"""Test get_time_variable_density with mixed sources."""
51+
fluid = Fluid("TestFluid", lambda t, p: t + p)
52+
times = np.array([0, 10])
53+
temperature = Function(np.column_stack((times, [300, 310])))
54+
pressure = Function(lambda t: 100_000 + 10 * t)
55+
density_time_func = fluid.get_time_variable_density(temperature, pressure)
56+
expected = [300 + 100_000, 310 + 100_100]
57+
np.testing.assert_allclose(density_time_func(times), expected)

0 commit comments

Comments
 (0)