diff --git a/rocketpy/environment/environment.py b/rocketpy/environment/environment.py index f5305aef2..63d24fa9e 100644 --- a/rocketpy/environment/environment.py +++ b/rocketpy/environment/environment.py @@ -2365,7 +2365,7 @@ def process_forecast_reanalysis(self, file, dictionary): self.wind_vs = wind_vs self.levels = levels self.temperatures = temperatures - self.time_array = time_array + self.time_array = time_array[:].tolist() self.height = height # Close weather data @@ -2735,7 +2735,7 @@ def process_ensemble(self, file, dictionary): self.wind_vs = wind_vs self.levels = levels self.temperatures = temperatures - self.time_array = time_array + self.time_array = time_array[:].tolist() self.height = height # Close weather data @@ -3126,7 +3126,19 @@ def all_plot_info_returned(self): ------ plot_info : Dict Dict of data relevant to plot externally + + Warning + ------- + Deprecated in favor of `utilities.get_instance_attributes`. + """ + warnings.warn( + "The method 'all_plot_info_returned' is deprecated as of version " + + "1.2 and will be removed in version 1.4 " + + "Use 'utilities.get_instance_attributes' instead.", + DeprecationWarning, + ) + grid = np.linspace(self.elevation, self.max_expected_height) plot_info = dict( grid=[i for i in grid], @@ -3187,7 +3199,18 @@ def all_info_returned(self): ------ info : Dict Information relevant about the Environment class. + + Warning + ------- + Deprecated in favor of `utilities.get_instance_attributes`. + """ + warnings.warn( + "The method 'all_info_returned' is deprecated as of version " + + "1.2 and will be removed in version 1.4 " + + "Use 'utilities.get_instance_attributes' instead.", + DeprecationWarning, + ) # Dictionary creation, if not commented follows the SI info = dict( diff --git a/rocketpy/utilities.py b/rocketpy/utilities.py index ac649eeac..a1dfadaf5 100644 --- a/rocketpy/utilities.py +++ b/rocketpy/utilities.py @@ -1,3 +1,4 @@ +import inspect import traceback import warnings @@ -654,3 +655,25 @@ def liftoff_speed(mass): if plot: retfunc.plot(min_mass, max_mass, points) return retfunc + + +def get_instance_attributes(instance): + """Returns a dictionary with all attributes of a given instance. + + Parameters + ---------- + instance : object + Instance of a class. + + Returns + ------- + dictionary + Dictionary with all attributes of the given instance. + """ + attributes_dict = dict() + members = inspect.getmembers(instance) + for member in members: + # Filter out methods and protected attributes + if not inspect.ismethod(member[1]) and not member[0].startswith("__"): + attributes_dict[member[0]] = member[1] + return attributes_dict diff --git a/tests/conftest.py b/tests/conftest.py index 03e307274..fb817a4ee 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -122,6 +122,41 @@ def cesaroni_m1670(): # old name: solid_motor return example_motor +@pytest.fixture +def cesaroni_m1670_shifted(): # old name: solid_motor + """Create a simple object of the SolidMotor class to be used in the tests. + This is the same motor that has been used in the getting started guide for + years. The difference relies in the thrust_source, which was shifted for + testing purposes. + + Returns + ------- + rocketpy.SolidMotor + A simple object of the SolidMotor class + """ + example_motor = SolidMotor( + thrust_source="tests/fixtures/motor/Cesaroni_M1670_shifted.eng", + burn_time=3.9, + dry_mass=1.815, + dry_inertia=(0.125, 0.125, 0.002), + center_of_dry_mass_position=0.317, + nozzle_position=0, + grain_number=5, + grain_density=1815, + nozzle_radius=33 / 1000, + throat_radius=11 / 1000, + grain_separation=5 / 1000, + grain_outer_radius=33 / 1000, + grain_initial_height=120 / 1000, + grains_center_of_mass_position=0.397, + grain_initial_inner_radius=15 / 1000, + interpolation_method="linear", + coordinate_system_orientation="nozzle_to_combustion_chamber", + reshape_thrust_curve=(5, 3000), + ) + return example_motor + + @pytest.fixture def calisto_motorless(): """Create a simple object of the Rocket class to be used in the tests. This diff --git a/tests/test_function.py b/tests/test_function.py index 5362b0486..3eb3db8e3 100644 --- a/tests/test_function.py +++ b/tests/test_function.py @@ -233,21 +233,29 @@ def test_integral_spline_interpolation(request, func, a, b): ) -def test_differentiate(): - """Tests the differentiation method of the Function class. - Both with respect to return instances and expected behaviour. - """ - func = Function(1) - assert isinstance(func.differentiate(0), float) - assert np.isclose(func.differentiate(5), 0) - - func_x = Function(lambda x: x) - assert isinstance(func_x.differentiate(0), float) - assert np.isclose(func_x.differentiate(0), 1) +@pytest.mark.parametrize( + "func_input, derivative_input, expected_derivative", + [ + (1, 0, 0), # Test case 1: Function(1) + (lambda x: x, 0, 1), # Test case 2: Function(lambda x: x) + (lambda x: x**2, 1, 2), # Test case 3: Function(lambda x: x**2) + ], +) +def test_differentiate(func_input, derivative_input, expected_derivative): + """Test the differentiate method of the Function class. - f_square = Function(lambda x: x**2) - assert isinstance(f_square.differentiate(1), float) - assert np.isclose(f_square.differentiate(1), 2) + Parameters + ---------- + func_input : function + A function object created from a list of values. + derivative_input : int + Point at which to differentiate. + expected_derivative : float + Expected value of the derivative. + """ + func = Function(func_input) + assert isinstance(func.differentiate(derivative_input), float) + assert np.isclose(func.differentiate(derivative_input), expected_derivative) def test_get_value(): diff --git a/tests/test_solidmotor.py b/tests/test_solidmotor.py index ef9dc2c4a..98ab95a59 100644 --- a/tests/test_solidmotor.py +++ b/tests/test_solidmotor.py @@ -4,7 +4,7 @@ import numpy as np import pytest -from rocketpy import SolidMotor +from rocketpy import Function, SolidMotor burn_time = 3.9 grain_number = 5 @@ -15,6 +15,8 @@ grain_initial_height = 120 / 1000 nozzle_radius = 33 / 1000 throat_radius = 11 / 1000 +grain_vol = 0.12 * (np.pi * (0.033**2 - 0.015**2)) +grain_mass = grain_vol * 1815 * 5 @patch("matplotlib.pyplot.show") @@ -170,6 +172,14 @@ def test_evaluate_inertia_33_asserts_extreme_values(cesaroni_m1670): def tests_import_eng_asserts_read_values_correctly(cesaroni_m1670): + """Tests the import_eng method. It checks whether the import operation + extracts the values correctly. + + Parameters + ---------- + cesaroni_m1670_shifted : rocketpy.SolidMotor + The SolidMotor object to be used in the tests. + """ comments, description, data_points = cesaroni_m1670.import_eng( "tests/fixtures/motor/Cesaroni_M1670.eng" ) @@ -197,8 +207,14 @@ def tests_import_eng_asserts_read_values_correctly(cesaroni_m1670): def tests_export_eng_asserts_exported_values_correct(cesaroni_m1670): - grain_vol = 0.12 * (np.pi * (0.033**2 - 0.015**2)) - grain_mass = grain_vol * 1815 * 5 + """Tests the export_eng method. It checks whether the exported values + of the thrust curve still match data_points. + + Parameters + ---------- + cesaroni_m1670_shifted : rocketpy.SolidMotor + The SolidMotor object to be used in the tests. + """ cesaroni_m1670.export_eng( file_name="tests/cesaroni_m1670.eng", motor_name="test_motor" @@ -239,31 +255,30 @@ def tests_export_eng_asserts_exported_values_correct(cesaroni_m1670): ] -def test_reshape_thrust_curve_asserts_resultant_thrust_curve_correct(): - example_motor = SolidMotor( - thrust_source="tests/fixtures/motor/Cesaroni_M1670_shifted.eng", - burn_time=burn_time, - dry_mass=1.815, - dry_inertia=(0.125, 0.125, 0.002), - center_of_dry_mass_position=0.317, - nozzle_position=0, - grain_number=grain_number, - grain_density=grain_density, - nozzle_radius=nozzle_radius, - throat_radius=throat_radius, - grain_separation=grain_separation, - grain_outer_radius=grain_outer_radius, - grain_initial_height=grain_initial_height, - grains_center_of_mass_position=0.397, - grain_initial_inner_radius=grain_initial_inner_radius, - interpolation_method="linear", - coordinate_system_orientation="nozzle_to_combustion_chamber", - reshape_thrust_curve=(5, 3000), +@pytest.mark.parametrize("tuple_parametric", [(5, 3000)]) +def test_reshape_thrust_curve_asserts_resultant_thrust_curve_correct( + cesaroni_m1670_shifted, tuple_parametric, linear_func +): + """Tests the reshape_thrust_curve. It checks whether the resultant + thrust curve is correct when the user passes a certain tuple to the + reshape_thrust_curve attribute. Also checking for the correct return + data type. + + Parameters + ---------- + cesaroni_m1670_shifted : rocketpy.SolidMotor + The SolidMotor object to be used in the tests. + tuple_parametric : tuple + Tuple passed to the reshape_thrust_curve method. + """ + + assert isinstance( + cesaroni_m1670_shifted.reshape_thrust_curve(linear_func, 1, 3000), Function ) + thrust_reshaped = cesaroni_m1670_shifted.thrust.get_source() - thrust_reshaped = example_motor.thrust.get_source() - assert thrust_reshaped[1][0] == 0.155 * (5 / 4) - assert thrust_reshaped[-1][0] == 5 + assert thrust_reshaped[1][0] == 0.155 * (tuple_parametric[0] / 4) + assert thrust_reshaped[-1][0] == tuple_parametric[0] - assert thrust_reshaped[1][1] == 100 * (3000 / 7539.1875) - assert thrust_reshaped[7][1] == 2034 * (3000 / 7539.1875) + assert thrust_reshaped[1][1] == 100 * (tuple_parametric[1] / 7539.1875) + assert thrust_reshaped[7][1] == 2034 * (tuple_parametric[1] / 7539.1875) diff --git a/tests/test_utilities.py b/tests/test_utilities.py index da4fae64f..7ac281138 100644 --- a/tests/test_utilities.py +++ b/tests/test_utilities.py @@ -192,3 +192,16 @@ def test_fin_flutter_analysis(mock_show, flight): ) == None ) + + +def test_get_instance_attributes(flight_calisto_robust): + """Tests if get_instance_attributes returns the expected results for a + robust flight object.""" + + attributes = utilities.get_instance_attributes(flight_calisto_robust) + for key, value in attributes.items(): + attr = getattr(flight_calisto_robust, key) + if isinstance(attr, np.ndarray): + assert np.allclose(attr, value) + else: + assert attr == value