diff --git a/.github/workflows/test_pytest.yaml b/.github/workflows/test_pytest.yaml index d927a0bb4..3257f7c1a 100644 --- a/.github/workflows/test_pytest.yaml +++ b/.github/workflows/test_pytest.yaml @@ -50,12 +50,12 @@ jobs: - name: Run Unit Tests run: pytest tests/unit --cov=rocketpy - - name: Run Integration Tests - run: pytest $(find tests -maxdepth 1 -name "*.py") --cov=rocketpy --cov-append - - name: Run Documentation Tests run: pytest rocketpy --doctest-modules --cov=rocketpy --cov-append + - name: Run Integration Tests + run: pytest tests/integration --cov=rocketpy --cov-append + - name: Run Acceptance Tests run: pytest tests/acceptance --cov=rocketpy --cov-append --cov-report=xml diff --git a/tests/integration/test_environment_analysis.py b/tests/integration/test_environment_analysis.py new file mode 100644 index 000000000..4ef0146df --- /dev/null +++ b/tests/integration/test_environment_analysis.py @@ -0,0 +1,59 @@ +import copy +import os +from unittest.mock import patch + +import matplotlib as plt +import pytest + +from rocketpy.tools import import_optional_dependency + +plt.rcParams.update({"figure.max_open_warning": 0}) + + +@pytest.mark.slow +@patch("matplotlib.pyplot.show") +def test_all_info(mock_show, env_analysis): + """Test the EnvironmentAnalysis.all_info() method, which already invokes + several other methods. It is a good way to test the whole class in a first view. + However, if it fails, it is hard to know which method is failing. + + Parameters + ---------- + env_analysis : rocketpy.EnvironmentAnalysis + A simple object of the Environment Analysis class + + Returns + ------- + None + """ + assert env_analysis.info() == None + assert env_analysis.all_info() == None + assert env_analysis.plots.info() == None + os.remove("wind_rose.gif") # remove the files created by the method + + +@pytest.mark.slow +@patch("matplotlib.pyplot.show") +def test_exports(mock_show, env_analysis): + """Check the export methods of the EnvironmentAnalysis class. It + only checks if the method runs without errors. It does not check if the + files are correct, as this would require a lot of work and would be + difficult to maintain. + + Parameters + ---------- + env_analysis : EnvironmentAnalysis + A simple object of the EnvironmentAnalysis class. + """ + + assert env_analysis.export_mean_profiles() == None + assert env_analysis.save("env_analysis_dict") == None + + env2 = copy.deepcopy(env_analysis) + env2.load("env_analysis_dict") + assert env2.all_info() == None + + # Delete file created by save method + os.remove("env_analysis_dict") + os.remove("wind_rose.gif") + os.remove("export_env_analysis.json") diff --git a/tests/test_flight_data_importer.py b/tests/integration/test_flight_data_importer.py similarity index 100% rename from tests/test_flight_data_importer.py rename to tests/integration/test_flight_data_importer.py diff --git a/tests/integration/test_function.py b/tests/integration/test_function.py new file mode 100644 index 000000000..15fae4e7e --- /dev/null +++ b/tests/integration/test_function.py @@ -0,0 +1,179 @@ +import os +from unittest.mock import patch + +import matplotlib as plt +import numpy as np +import pytest + +from rocketpy import Function + +plt.rcParams.update({"figure.max_open_warning": 0}) + + +@pytest.mark.parametrize( + "func", + [ + "linearly_interpolated_func", + "spline_interpolated_func", + "func_2d_from_csv", + "lambda_quad_func", + ], +) +def test_savetxt(request, func): + """Test the savetxt method of various Function objects. + + This test function verifies that the `savetxt` method correctly writes the + function's data to a CSV file and that a new function object created from + this file has the same data as the original function object. + + Notes + ----- + The test performs the following steps: + 1. It invokes the `savetxt` method of the given function object. + 2. It then reads this file to create a new function object. + 3. The test asserts that the data of the new function matches the original. + 4. Finally, the test cleans up by removing the created CSV file. + + Raises + ------ + AssertionError + If the `savetxt` method fails to save the file, or if the data of the + newly read function does not match the data of the original function. + """ + func = request.getfixturevalue(func) + assert ( + func.savetxt( + filename="test_func.csv", + lower=0, + upper=9, + samples=10, + fmt="%.6f", + delimiter=",", + newline="\n", + encoding=None, + ) + is None + ), "Couldn't save the file using the Function.savetxt method." + + read_func = Function( + "test_func.csv", + interpolation="linear" if func.get_domain_dim() == 1 else "shepard", + extrapolation="natural", + ) + if callable(func.source): + source = np.column_stack( + (np.linspace(0, 9, 10), func.source(np.linspace(0, 9, 10))) + ) + assert np.allclose(source, read_func.source) + else: + assert np.allclose(func.source, read_func.source) + + # clean up the file + os.remove("test_func.csv") + + +# Test Function creation from .csv file +def test_function_from_csv(func_from_csv, func_2d_from_csv): + """Test the Function class creation from a .csv file. + + Parameters + ---------- + func_from_csv : rocketpy.Function + A Function object created from a .csv file. + func_2d_from_csv : rocketpy.Function + A Function object created from a .csv file with 2 inputs. + """ + # Assert the function is zero at 0 but with a certain tolerance + assert np.isclose(func_from_csv(0), 0.0, atol=1e-6) + assert np.isclose(func_2d_from_csv(0, 0), 0.0, atol=1e-6) + # Check the __str__ method + assert func_from_csv.__str__() == "Function from R1 to R1 : (Scalar) → (Scalar)" + assert ( + func_2d_from_csv.__str__() + == "Function from R2 to R1 : (Input 1, Input 2) → (Scalar)" + ) + # Check the __repr__ method + assert func_from_csv.__repr__() == "'Function from R1 to R1 : (Scalar) → (Scalar)'" + assert ( + func_2d_from_csv.__repr__() + == "'Function from R2 to R1 : (Input 1, Input 2) → (Scalar)'" + ) + + +@pytest.mark.parametrize( + "csv_file", + [ + "tests/fixtures/function/1d_quotes.csv", + "tests/fixtures/function/1d_no_quotes.csv", + ], +) +def test_func_from_csv_with_header(csv_file): + """Tests if a Function can be created from a CSV file with a single header + line. It tests cases where the fields are separated by quotes and without + quotes.""" + f = Function(csv_file) + assert f.__repr__() == "'Function from R1 to R1 : (time) → (value)'" + assert np.isclose(f(0), 100) + assert np.isclose(f(0) + f(1), 300), "Error summing the values of the function" + + +@patch("matplotlib.pyplot.show") +def test_plots(mock_show, func_from_csv, func_2d_from_csv): + """Test different plot methods of the Function class. + + Parameters + ---------- + mock_show : Mock + Mock of the matplotlib.pyplot.show method. + func_from_csv : rocketpy.Function + A Function object created from a .csv file. + """ + # Test plot methods + assert func_from_csv.plot() == None + assert func_2d_from_csv.plot() == None + # Test plot methods with limits + assert func_from_csv.plot(-1, 1) == None + assert func_2d_from_csv.plot(-1, 1) == None + # Test compare_plots + func2 = Function( + source="tests/fixtures/airfoils/e473-10e6-degrees.csv", + inputs=["Scalar"], + outputs=["Scalar"], + interpolation="linear", + extrapolation="natural", + ) + assert ( + func_from_csv.compare_plots([func_from_csv, func2], return_object=False) == None + ) + + +@patch("matplotlib.pyplot.show") +def test_multivariable_dataset_plot(mock_show): + """Test the plot method of the Function class with a multivariable dataset.""" + # Test plane f(x,y) = x - y + source = [ + (-1, -1, -1), + (-1, 0, -1), + (-1, 1, -2), + (0, 1, 1), + (0, 0, 0), + (0, 1, -1), + (1, -1, 2), + (1, 0, 1), + (1, 1, 0), + ] + func = Function(source=source, inputs=["x", "y"], outputs=["z"]) + + # Assert plot + assert func.plot() == None + + +@patch("matplotlib.pyplot.show") +def test_multivariable_function_plot(mock_show): + """Test the plot method of the Function class with a multivariable function.""" + # Test plane f(x,y) = sin(x + y) + source = lambda x, y: np.sin(x * y) + func = Function(source=source, inputs=["x", "y"], outputs=["z"]) + + # Assert plot + assert func.plot() == None diff --git a/tests/integration/test_genericmotor.py b/tests/integration/test_genericmotor.py new file mode 100644 index 000000000..e7591eca1 --- /dev/null +++ b/tests/integration/test_genericmotor.py @@ -0,0 +1,31 @@ +from unittest.mock import patch + +import numpy as np +import pytest +import scipy.integrate + +burn_time = (2, 7) +thrust_source = lambda t: 2000 - 100 * (t - 2) +chamber_height = 0.5 +chamber_radius = 0.075 +chamber_position = -0.25 +propellant_initial_mass = 5.0 +nozzle_position = -0.5 +nozzle_radius = 0.075 +dry_mass = 8.0 +dry_inertia = (0.2, 0.2, 0.08) + + +@patch("matplotlib.pyplot.show") +def test_generic_motor_info(mock_show, generic_motor): + """Tests the GenericMotor.all_info() method. + + Parameters + ---------- + mock_show : mock + Mock of the matplotlib.pyplot.show function. + generic_motor : rocketpy.GenericMotor + The GenericMotor object to be used in the tests. + """ + assert generic_motor.info() == None + assert generic_motor.all_info() == None diff --git a/tests/integration/test_hybridmotor.py b/tests/integration/test_hybridmotor.py new file mode 100644 index 000000000..a595f3c8a --- /dev/null +++ b/tests/integration/test_hybridmotor.py @@ -0,0 +1,34 @@ +from unittest.mock import patch + +import numpy as np + +thrust_function = lambda t: 2000 - 100 * t +burn_time = 10 +center_of_dry_mass = 0 +dry_inertia = (4, 4, 0.1) +dry_mass = 8 +grain_density = 1700 +grain_number = 4 +grain_initial_height = 0.1 +grain_separation = 0 +grain_initial_inner_radius = 0.04 +grain_outer_radius = 0.1 +nozzle_position = -0.4 +nozzle_radius = 0.07 +grains_center_of_mass_position = -0.1 +oxidizer_tank_position = 0.3 + + +@patch("matplotlib.pyplot.show") +def test_hybrid_motor_info(mock_show, hybrid_motor): + """Tests the HybridMotor.all_info() method. + + Parameters + ---------- + mock_show : mock + Mock of the matplotlib.pyplot.show function. + hybrid_motor : rocketpy.HybridMotor + The HybridMotor object to be used in the tests. + """ + assert hybrid_motor.info() == None + assert hybrid_motor.all_info() == None diff --git a/tests/integration/test_liquidmotor.py b/tests/integration/test_liquidmotor.py new file mode 100644 index 000000000..1bc679721 --- /dev/null +++ b/tests/integration/test_liquidmotor.py @@ -0,0 +1,32 @@ +from unittest.mock import patch + +import numpy as np +import pytest +import scipy.integrate + +from rocketpy import Function + +burn_time = (8, 20) +dry_mass = 10 +dry_inertia = (5, 5, 0.2) +center_of_dry_mass = 0 +nozzle_position = -1.364 +nozzle_radius = 0.069 / 2 +pressurant_tank_position = 2.007 +fuel_tank_position = -1.048 +oxidizer_tank_position = 0.711 + + +@patch("matplotlib.pyplot.show") +def test_liquid_motor_info(mock_show, liquid_motor): + """Tests the LiquidMotor.all_info() method. + + Parameters + ---------- + mock_show : mock + Mock of the matplotlib.pyplot.show function. + liquid_motor : rocketpy.LiquidMotor + The LiquidMotor object to be used in the tests. + """ + assert liquid_motor.info() == None + assert liquid_motor.all_info() == None diff --git a/tests/test_monte_carlo.py b/tests/integration/test_monte_carlo.py similarity index 62% rename from tests/test_monte_carlo.py rename to tests/integration/test_monte_carlo.py index 7846a54be..91838c828 100644 --- a/tests/test_monte_carlo.py +++ b/tests/integration/test_monte_carlo.py @@ -8,65 +8,6 @@ plt.rcParams.update({"figure.max_open_warning": 0}) -def test_stochastic_environment_create_object_with_wind_x(stochastic_environment): - """Tests the stochastic environment object by checking if the wind velocity - can be generated properly. The goal is to check if the create_object() - method is being called without any problems. - - Parameters - ---------- - stochastic_environment : StochasticEnvironment - The stochastic environment object, this is a pytest fixture. - """ - wind_x_at_1000m = [] - for _ in range(10): - random_env = stochastic_environment.create_object() - wind_x_at_1000m.append(random_env.wind_velocity_x(1000)) - - assert np.isclose(np.mean(wind_x_at_1000m), 0, atol=0.1) - assert np.isclose(np.std(wind_x_at_1000m), 0, atol=0.1) - # TODO: add a new test for the special case of ensemble member - - -def test_stochastic_solid_motor_create_object_with_impulse(stochastic_solid_motor): - """Tests the stochastic solid motor object by checking if the total impulse - can be generated properly. The goal is to check if the create_object() - method is being called without any problems. - - Parameters - ---------- - stochastic_solid_motor : StochasticSolidMotor - The stochastic solid motor object, this is a pytest fixture. - """ - total_impulse = [] - for _ in range(20): - random_motor = stochastic_solid_motor.create_object() - total_impulse.append(random_motor.total_impulse) - - assert np.isclose(np.mean(total_impulse), 6500, rtol=0.3) - assert np.isclose(np.std(total_impulse), 1000, rtol=0.3) - - -def test_stochastic_calisto_create_object_with_static_margin(stochastic_calisto): - """Tests the stochastic calisto object by checking if the static margin - can be generated properly. The goal is to check if the create_object() - method is being called without any problems. - - Parameters - ---------- - stochastic_calisto : StochasticCalisto - The stochastic calisto object, this is a pytest fixture. - """ - - all_margins = [] - for _ in range(10): - random_rocket = stochastic_calisto.create_object() - all_margins.append(random_rocket.static_margin(0)) - - assert np.isclose(np.mean(all_margins), 2.2625350013000434, rtol=0.15) - assert np.isclose(np.std(all_margins), 0.1, atol=0.2) - - @pytest.mark.slow def test_monte_carlo_simulate(monte_carlo_calisto): """Tests the simulate method of the MonteCarlo class. diff --git a/tests/test_plots.py b/tests/integration/test_plots.py similarity index 69% rename from tests/test_plots.py rename to tests/integration/test_plots.py index 82d50ec45..bef7e6915 100644 --- a/tests/test_plots.py +++ b/tests/integration/test_plots.py @@ -7,41 +7,6 @@ from rocketpy.plots.compare import Compare, CompareFlights -@patch("matplotlib.pyplot.show") -def test_compare(mock_show, flight_calisto): - """Here we want to test the 'x_attributes' argument, which is the only one - that is not tested in the other tests. - - Parameters - ---------- - mock_show : - Mocks the matplotlib.pyplot.show() function to avoid showing the plots. - flight_calisto : rocketpy.Flight - Flight object to be used in the tests. See conftest.py for more details. - """ - flight = flight_calisto - - objects = [flight, flight, flight] - - comparison = Compare(object_list=objects) - - fig, _ = comparison.create_comparison_figure( - y_attributes=["z"], - n_rows=1, - n_cols=1, - figsize=(10, 10), - legend=False, - title="Test", - x_labels=["Time (s)"], - y_labels=["Altitude (m)"], - x_lim=(0, 3), - y_lim=(0, 1000), - x_attributes=["time"], - ) - - assert isinstance(fig, plt.Figure) == True - - @patch("matplotlib.pyplot.show") def test_compare_flights(mock_show, calisto, example_plain_env): """Tests the CompareFlights class. It simply ensures that all the methods diff --git a/tests/integration/test_rocket.py b/tests/integration/test_rocket.py new file mode 100644 index 000000000..69efd7ca5 --- /dev/null +++ b/tests/integration/test_rocket.py @@ -0,0 +1,134 @@ +from unittest.mock import patch + +import numpy as np +import pytest + +from rocketpy import Rocket, SolidMotor +from rocketpy.rocket import NoseCone + + +@patch("matplotlib.pyplot.show") +def test_airfoil( + mock_show, + calisto, + calisto_main_chute, + calisto_drogue_chute, + calisto_nose_cone, + calisto_tail, +): + test_rocket = calisto + test_rocket.set_rail_buttons(0.082, -0.618) + calisto.aerodynamic_surfaces.add(calisto_nose_cone, 1.160) + calisto.aerodynamic_surfaces.add(calisto_tail, -1.313) + + fin_set_NACA = test_rocket.add_trapezoidal_fins( + 2, + span=0.100, + root_chord=0.120, + tip_chord=0.040, + position=-1.168, + airfoil=("tests/fixtures/airfoils/NACA0012-radians.txt", "radians"), + name="NACA0012", + ) + fin_set_E473 = test_rocket.add_trapezoidal_fins( + 2, + span=0.100, + root_chord=0.120, + tip_chord=0.040, + position=-1.168, + airfoil=("tests/fixtures/airfoils/e473-10e6-degrees.csv", "degrees"), + name="E473", + ) + calisto.parachutes.append(calisto_main_chute) + calisto.parachutes.append(calisto_drogue_chute) + + static_margin = test_rocket.static_margin(0) + + assert test_rocket.all_info() == None or not abs(static_margin - 2.03) < 0.01 + + +@patch("matplotlib.pyplot.show") +def test_air_brakes_clamp_on(mock_show, calisto_air_brakes_clamp_on): + """Test the air brakes class with clamp on configuration. This test checks + the basic attributes and the deployment_level setter. It also checks the + all_info method. + + Parameters + ---------- + mock_show : mock + Mock of the matplotlib.pyplot.show method. + calisto_air_brakes_clamp_on : Rocket instance + A predefined instance of the calisto with air brakes in clamp on + configuration. + """ + air_brakes_clamp_on = calisto_air_brakes_clamp_on.air_brakes[0] + + # test basic attributes + assert air_brakes_clamp_on.drag_coefficient.__dom_dim__ == 2 + assert ( + air_brakes_clamp_on.reference_area + == calisto_air_brakes_clamp_on.radius**2 * np.pi + ) + air_brakes_clamp_on.deployment_level = 0.5 + assert air_brakes_clamp_on.deployment_level == 0.5 + air_brakes_clamp_on.deployment_level = 1.5 + assert air_brakes_clamp_on.deployment_level == 1 + air_brakes_clamp_on.deployment_level = -1 + assert air_brakes_clamp_on.deployment_level == 0 + air_brakes_clamp_on.deployment_level = 0 + assert air_brakes_clamp_on.deployment_level == 0 + + assert air_brakes_clamp_on.all_info() == None + + +@patch("matplotlib.pyplot.show") +def test_air_brakes_clamp_off(mock_show, calisto_air_brakes_clamp_off): + """Test the air brakes class with clamp off configuration. This test checks + the basic attributes and the deployment_level setter. It also checks the + all_info method. + + Parameters + ---------- + mock_show : mock + Mock of the matplotlib.pyplot.show method. + calisto_air_brakes_clamp_off : Rocket instance + A predefined instance of the calisto with air brakes in clamp off + configuration. + """ + air_brakes_clamp_off = calisto_air_brakes_clamp_off.air_brakes[0] + + # test basic attributes + assert air_brakes_clamp_off.drag_coefficient.__dom_dim__ == 2 + assert ( + air_brakes_clamp_off.reference_area + == calisto_air_brakes_clamp_off.radius**2 * np.pi + ) + + air_brakes_clamp_off.deployment_level = 0.5 + assert air_brakes_clamp_off.deployment_level == 0.5 + air_brakes_clamp_off.deployment_level = 1.5 + assert air_brakes_clamp_off.deployment_level == 1.5 + air_brakes_clamp_off.deployment_level = -1 + assert air_brakes_clamp_off.deployment_level == -1 + air_brakes_clamp_off.deployment_level = 0 + assert air_brakes_clamp_off.deployment_level == 0 + + assert air_brakes_clamp_off.all_info() == None + + +@patch("matplotlib.pyplot.show") +def test_rocket(mock_show, calisto_robust): + test_rocket = calisto_robust + static_margin = test_rocket.static_margin(0) + # Check if all_info and static_method methods are working properly + assert test_rocket.all_info() == None or not abs(static_margin - 2.05) < 0.01 + + +@patch("matplotlib.pyplot.show") +def test_aero_surfaces_infos( + mock_show, calisto_nose_cone, calisto_tail, calisto_trapezoidal_fins +): + assert calisto_nose_cone.all_info() == None + assert calisto_trapezoidal_fins.all_info() == None + assert calisto_tail.all_info() == None + assert calisto_trapezoidal_fins.draw() == None diff --git a/tests/test_function.py b/tests/test_function.py deleted file mode 100644 index 9a5200c3d..000000000 --- a/tests/test_function.py +++ /dev/null @@ -1,522 +0,0 @@ -from unittest.mock import patch - -import matplotlib as plt -import numpy as np -import pytest - -from rocketpy import Function - -plt.rcParams.update({"figure.max_open_warning": 0}) - - -# Test Function creation from .csv file -def test_function_from_csv(func_from_csv, func_2d_from_csv): - """Test the Function class creation from a .csv file. - - Parameters - ---------- - func_from_csv : rocketpy.Function - A Function object created from a .csv file. - func_2d_from_csv : rocketpy.Function - A Function object created from a .csv file with 2 inputs. - """ - # Assert the function is zero at 0 but with a certain tolerance - assert np.isclose(func_from_csv(0), 0.0, atol=1e-6) - assert np.isclose(func_2d_from_csv(0, 0), 0.0, atol=1e-6) - # Check the __str__ method - assert func_from_csv.__str__() == "Function from R1 to R1 : (Scalar) → (Scalar)" - assert ( - func_2d_from_csv.__str__() - == "Function from R2 to R1 : (Input 1, Input 2) → (Scalar)" - ) - # Check the __repr__ method - assert func_from_csv.__repr__() == "'Function from R1 to R1 : (Scalar) → (Scalar)'" - assert ( - func_2d_from_csv.__repr__() - == "'Function from R2 to R1 : (Input 1, Input 2) → (Scalar)'" - ) - - -@pytest.mark.parametrize( - "csv_file", - [ - "tests/fixtures/function/1d_quotes.csv", - "tests/fixtures/function/1d_no_quotes.csv", - ], -) -def test_func_from_csv_with_header(csv_file): - """Tests if a Function can be created from a CSV file with a single header - line. It tests cases where the fields are separated by quotes and without - quotes.""" - f = Function(csv_file) - assert f.__repr__() == "'Function from R1 to R1 : (time) → (value)'" - assert np.isclose(f(0), 100) - assert np.isclose(f(0) + f(1), 300), "Error summing the values of the function" - - -def test_getters(func_from_csv, func_2d_from_csv): - """Test the different getters of the Function class. - - Parameters - ---------- - func_from_csv : rocketpy.Function - A Function object created from a .csv file. - """ - assert func_from_csv.get_inputs() == ["Scalar"] - assert func_from_csv.get_outputs() == ["Scalar"] - assert func_from_csv.get_interpolation_method() == "spline" - assert func_from_csv.get_extrapolation_method() == "constant" - assert np.isclose(func_from_csv.get_value(0), 0.0, atol=1e-6) - assert np.isclose(func_from_csv.get_value_opt(0), 0.0, atol=1e-6) - - assert func_2d_from_csv.get_inputs() == ["Input 1", "Input 2"] - assert func_2d_from_csv.get_outputs() == ["Scalar"] - assert func_2d_from_csv.get_interpolation_method() == "shepard" - assert func_2d_from_csv.get_extrapolation_method() == "natural" - assert np.isclose(func_2d_from_csv.get_value(0.1, 0.8), 0.058, atol=1e-6) - assert np.isclose(func_2d_from_csv.get_value_opt(0.1, 0.8), 0.058, atol=1e-6) - - -def test_setters(func_from_csv, func_2d_from_csv): - """Test the different setters of the Function class. - - Parameters - ---------- - func_from_csv : rocketpy.Function - A Function object created from a .csv file. - """ - # Test set methods - func_from_csv.set_inputs(["Scalar2"]) - assert func_from_csv.get_inputs() == ["Scalar2"] - func_from_csv.set_outputs(["Scalar2"]) - assert func_from_csv.get_outputs() == ["Scalar2"] - func_from_csv.set_interpolation("linear") - assert func_from_csv.get_interpolation_method() == "linear" - func_from_csv.set_extrapolation("natural") - assert func_from_csv.get_extrapolation_method() == "natural" - - func_2d_from_csv.set_inputs(["Scalar1", "Scalar2"]) - assert func_2d_from_csv.get_inputs() == ["Scalar1", "Scalar2"] - func_2d_from_csv.set_outputs(["Scalar3"]) - assert func_2d_from_csv.get_outputs() == ["Scalar3"] - func_2d_from_csv.set_interpolation("shepard") - assert func_2d_from_csv.get_interpolation_method() == "shepard" - func_2d_from_csv.set_extrapolation("natural") - assert func_2d_from_csv.get_extrapolation_method() == "natural" - - -@patch("matplotlib.pyplot.show") -def test_plots(mock_show, func_from_csv, func_2d_from_csv): - """Test different plot methods of the Function class. - - Parameters - ---------- - mock_show : Mock - Mock of the matplotlib.pyplot.show method. - func_from_csv : rocketpy.Function - A Function object created from a .csv file. - """ - # Test plot methods - assert func_from_csv.plot() == None - assert func_2d_from_csv.plot() == None - # Test plot methods with limits - assert func_from_csv.plot(-1, 1) == None - assert func_2d_from_csv.plot(-1, 1) == None - # Test compare_plots - func2 = Function( - source="tests/fixtures/airfoils/e473-10e6-degrees.csv", - inputs=["Scalar"], - outputs=["Scalar"], - interpolation="linear", - extrapolation="natural", - ) - assert ( - func_from_csv.compare_plots([func_from_csv, func2], return_object=False) == None - ) - - -def test_interpolation_methods(linear_func): - """Tests some of the interpolation methods of the Function class. Methods - not tested here are already being called in other tests. - - Parameters - ---------- - linear_func : rocketpy.Function - A Function object created from a list of values. - """ - # Test Akima - assert isinstance(linear_func.set_interpolation("akima"), Function) - linear_func.set_interpolation("akima") - assert isinstance(linear_func.get_interpolation_method(), str) - assert linear_func.get_interpolation_method() == "akima" - assert np.isclose(linear_func.get_value(0), 0.0, atol=1e-6) - - # Test polynomial - - assert isinstance(linear_func.set_interpolation("polynomial"), Function) - linear_func.set_interpolation("polynomial") - assert isinstance(linear_func.get_interpolation_method(), str) - assert linear_func.get_interpolation_method() == "polynomial" - assert np.isclose(linear_func.get_value(0), 0.0, atol=1e-6) - - -def test_extrapolation_methods(linear_func): - """Test some of the extrapolation methods of the Function class. Methods - not tested here are already being called in other tests. - - Parameters - ---------- - linear_func : rocketpy.Function - A Function object created from a list of values. - """ - # Test zero - linear_func.set_extrapolation("zero") - assert linear_func.get_extrapolation_method() == "zero" - assert np.isclose(linear_func.get_value(-1), 0, atol=1e-6) - - # Test constant - assert isinstance(linear_func.set_extrapolation("constant"), Function) - linear_func.set_extrapolation("constant") - assert isinstance(linear_func.get_extrapolation_method(), str) - assert linear_func.get_extrapolation_method() == "constant" - assert np.isclose(linear_func.get_value(-1), 0, atol=1e-6) - - # Test natural for linear interpolation - linear_func.set_interpolation("linear") - assert isinstance(linear_func.set_extrapolation("natural"), Function) - linear_func.set_extrapolation("natural") - assert isinstance(linear_func.get_extrapolation_method(), str) - assert linear_func.get_extrapolation_method() == "natural" - assert np.isclose(linear_func.get_value(-1), -1, atol=1e-6) - - # Test natural for spline interpolation - linear_func.set_interpolation("spline") - assert isinstance(linear_func.set_extrapolation("natural"), Function) - linear_func.set_extrapolation("natural") - assert isinstance(linear_func.get_extrapolation_method(), str) - assert linear_func.get_extrapolation_method() == "natural" - assert np.isclose(linear_func.get_value(-1), -1, atol=1e-6) - - # Test natural for akima interpolation - linear_func.set_interpolation("akima") - assert isinstance(linear_func.set_extrapolation("natural"), Function) - linear_func.set_extrapolation("natural") - assert isinstance(linear_func.get_extrapolation_method(), str) - assert linear_func.get_extrapolation_method() == "natural" - assert np.isclose(linear_func.get_value(-1), -1, atol=1e-6) - - # Test natural for polynomial interpolation - linear_func.set_interpolation("polynomial") - assert isinstance(linear_func.set_extrapolation("natural"), Function) - linear_func.set_extrapolation("natural") - assert isinstance(linear_func.get_extrapolation_method(), str) - assert linear_func.get_extrapolation_method() == "natural" - assert np.isclose(linear_func.get_value(-1), -1, atol=1e-6) - - -@pytest.mark.parametrize("a", [-1, 0, 1]) -@pytest.mark.parametrize("b", [-1, 0, 1]) -def test_multivariable_dataset(a, b): - """Test the Function class with a multivariable dataset.""" - # Test plane f(x,y) = x + y - source = [ - (-1, -1, -2), - (-1, 0, -1), - (-1, 1, 0), - (0, -1, -1), - (0, 0, 0), - (0, 1, 1), - (1, -1, 0), - (1, 0, 1), - (1, 1, 2), - ] - func = Function(source=source, inputs=["x", "y"], outputs=["z"]) - - # Assert interpolation and extrapolation methods - assert func.get_interpolation_method() == "shepard" - assert func.get_extrapolation_method() == "natural" - - # Assert values - assert np.isclose(func(a, b), a + b, atol=1e-6) - - -@pytest.mark.parametrize( - "x,y,z_expected", - [ - (1, 0, 0), - (0, 1, 0), - (0, 0, 1), - (0.5, 0.5, 1 / 3), - (0.25, 0.25, 25 / (25 + 2 * 5**0.5)), - ([0, 0.5], [0, 0.5], [1, 1 / 3]), - ], -) -def test_2d_shepard_interpolation(x, y, z_expected): - """Test the shepard interpolation method of the Function class.""" - # Test plane x + y + z = 1 - source = [(1, 0, 0), (0, 1, 0), (0, 0, 1)] - func = Function( - source=source, inputs=["x", "y"], outputs=["z"], interpolation="shepard" - ) - z = func(x, y) - z_opt = func.get_value_opt(x, y) - assert np.isclose(z, z_opt, atol=1e-8).all() - assert np.isclose(z_expected, z, atol=1e-8).all() - - -@pytest.mark.parametrize( - "x,y,z,w_expected", - [ - (0, 0, 0, 1), - (1, 0, 0, 0), - (0, 1, 0, 0), - (0, 0, 1, 0), - (0.5, 0.5, 0.5, 1 / 4), - (0.25, 0.25, 0.25, 0.700632626832), - ([0, 0.5], [0, 0.5], [0, 0.5], [1, 1 / 4]), - ], -) -def test_3d_shepard_interpolation(x, y, z, w_expected): - """Test the shepard interpolation method of the Function class.""" - # Test plane x + y + z + w = 1 - source = [(1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1)] - func = Function( - source=source, inputs=["x", "y", "z"], outputs=["w"], interpolation="shepard" - ) - w = func(x, y, z) - w_opt = func.get_value_opt(x, y, z) - assert np.isclose(w, w_opt, atol=1e-8).all() - assert np.isclose(w_expected, w, atol=1e-8).all() - - -@pytest.mark.parametrize("a", [-1, -0.5, 0, 0.5, 1]) -@pytest.mark.parametrize("b", [-1, -0.5, 0, 0.5, 1]) -def test_multivariable_function(a, b): - """Test the Function class with a multivariable function.""" - # Test plane f(x,y) = sin(x + y) - source = lambda x, y: np.sin(x + y) - func = Function(source=source, inputs=["x", "y"], outputs=["z"]) - - # Assert values - assert np.isclose(func(a, b), np.sin(a + b), atol=1e-6) - - -@patch("matplotlib.pyplot.show") -def test_multivariable_dataset_plot(mock_show): - """Test the plot method of the Function class with a multivariable dataset.""" - # Test plane f(x,y) = x - y - source = [ - (-1, -1, -1), - (-1, 0, -1), - (-1, 1, -2), - (0, 1, 1), - (0, 0, 0), - (0, 1, -1), - (1, -1, 2), - (1, 0, 1), - (1, 1, 0), - ] - func = Function(source=source, inputs=["x", "y"], outputs=["z"]) - - # Assert plot - assert func.plot() == None - - -@patch("matplotlib.pyplot.show") -def test_multivariable_function_plot(mock_show): - """Test the plot method of the Function class with a multivariable function.""" - # Test plane f(x,y) = sin(x + y) - source = lambda x, y: np.sin(x * y) - func = Function(source=source, inputs=["x", "y"], outputs=["z"]) - - # Assert plot - assert func.plot() == None - - -def test_set_discrete_2d(): - """Tests the set_discrete method of the Function for - two dimensional domains. - """ - func = Function(lambda x, y: x**2 + y**2) - discretized_func = func.set_discrete([-5, -7], [8, 10], [50, 100]) - - assert isinstance(discretized_func, Function) - assert isinstance(func, Function) - assert discretized_func.source.shape == (50 * 100, 3) - assert np.isclose(discretized_func.source[0, 0], -5) - assert np.isclose(discretized_func.source[0, 1], -7) - assert np.isclose(discretized_func.source[-1, 0], 8) - assert np.isclose(discretized_func.source[-1, 1], 10) - - -def test_set_discrete_2d_simplified(): - """Tests the set_discrete method of the Function for - two dimensional domains with simplified inputs. - """ - source = [(1, 0, 0), (0, 1, 0), (0, 0, 1)] - func = Function(source=source, inputs=["x", "y"], outputs=["z"]) - discretized_func = func.set_discrete(-1, 1, 10) - - assert isinstance(discretized_func, Function) - assert isinstance(func, Function) - assert discretized_func.source.shape == (100, 3) - assert np.isclose(discretized_func.source[0, 0], -1) - assert np.isclose(discretized_func.source[0, 1], -1) - assert np.isclose(discretized_func.source[-1, 0], 1) - assert np.isclose(discretized_func.source[-1, 1], 1) - - -def test_set_discrete_based_on_2d_model(func_2d_from_csv): - """Tests the set_discrete_based_on_model method with a 2d model - Function. - """ - func = Function(lambda x, y: x**2 + y**2) - discretized_func = func.set_discrete_based_on_model(func_2d_from_csv) - - assert isinstance(discretized_func, Function) - assert isinstance(func, Function) - assert np.array_equal( - discretized_func.source[:, :2], func_2d_from_csv.source[:, :2] - ) - assert discretized_func.__interpolation__ == func_2d_from_csv.__interpolation__ - assert discretized_func.__extrapolation__ == func_2d_from_csv.__extrapolation__ - - -@pytest.mark.parametrize( - "x,y,z_expected", - [ - (1, 0, 0), - (0, 1, 0), - (0, 0, 1), - (0.5, 0.5, 1 / 3), - (0.25, 0.25, 25 / (25 + 2 * 5**0.5)), - ([0, 0.5], [0, 0.5], [1, 1 / 3]), - ], -) -def test_shepard_interpolation(x, y, z_expected): - """Test the shepard interpolation method of the Function class.""" - # Test plane x + y + z = 1 - source = [(1, 0, 0), (0, 1, 0), (0, 0, 1)] - func = Function(source=source, inputs=["x", "y"], outputs=["z"]) - z = func(x, y) - assert np.isclose(z, z_expected, atol=1e-8).all() - - -@pytest.mark.parametrize("other", [1, 0.1, np.int_(1), np.float64(0.1), np.array([1])]) -def test_sum_arithmetic_priority(other): - """Test the arithmetic priority of the add operation of the Function class, - specially comparing to the numpy array operations. - """ - func_lambda = Function(lambda x: x**2) - func_array = Function([(0, 0), (1, 1), (2, 4)]) - - assert isinstance(func_lambda + func_array, Function) - assert isinstance(func_array + func_lambda, Function) - assert isinstance(func_lambda + other, Function) - assert isinstance(other + func_lambda, Function) - assert isinstance(func_array + other, Function) - assert isinstance(other + func_array, Function) - - -@pytest.mark.parametrize("other", [1, 0.1, np.int_(1), np.float64(0.1), np.array([1])]) -def test_sub_arithmetic_priority(other): - """Test the arithmetic priority of the sub operation of the Function class, - specially comparing to the numpy array operations. - """ - func_lambda = Function(lambda x: x**2) - func_array = Function([(0, 0), (1, 1), (2, 4)]) - - assert isinstance(func_lambda - func_array, Function) - assert isinstance(func_array - func_lambda, Function) - assert isinstance(func_lambda - other, Function) - assert isinstance(other - func_lambda, Function) - assert isinstance(func_array - other, Function) - assert isinstance(other - func_array, Function) - - -@pytest.mark.parametrize("other", [1, 0.1, np.int_(1), np.float64(0.1), np.array([1])]) -def test_mul_arithmetic_priority(other): - """Test the arithmetic priority of the mul operation of the Function class, - specially comparing to the numpy array operations. - """ - func_lambda = Function(lambda x: x**2) - func_array = Function([(0, 0), (1, 1), (2, 4)]) - - assert isinstance(func_lambda * func_array, Function) - assert isinstance(func_array * func_lambda, Function) - assert isinstance(func_lambda * other, Function) - assert isinstance(other * func_lambda, Function) - assert isinstance(func_array * other, Function) - assert isinstance(other * func_array, Function) - - -@pytest.mark.parametrize("other", [1, 0.1, np.int_(1), np.float64(0.1), np.array([1])]) -def test_truediv_arithmetic_priority(other): - """Test the arithmetic priority of the truediv operation of the Function class, - specially comparing to the numpy array operations. - """ - func_lambda = Function(lambda x: x**2) - func_array = Function([(1, 1), (2, 4)]) - - assert isinstance(func_lambda / func_array, Function) - assert isinstance(func_array / func_lambda, Function) - assert isinstance(func_lambda / other, Function) - assert isinstance(other / func_lambda, Function) - assert isinstance(func_array / other, Function) - assert isinstance(other / func_array, Function) - - -@pytest.mark.parametrize("other", [1, 0.1, np.int_(1), np.float64(0.1), np.array([1])]) -def test_pow_arithmetic_priority(other): - """Test the arithmetic priority of the pow operation of the Function class, - specially comparing to the numpy array operations. - """ - func_lambda = Function(lambda x: x**2) - func_array = Function([(0, 0), (1, 1), (2, 4)]) - - assert isinstance(func_lambda**func_array, Function) - assert isinstance(func_array**func_lambda, Function) - assert isinstance(func_lambda**other, Function) - assert isinstance(other**func_lambda, Function) - assert isinstance(func_array**other, Function) - assert isinstance(other**func_array, Function) - - -@pytest.mark.parametrize("alpha", [0.1, 0.5, 0.9]) -def test_low_pass_filter(alpha): - """Test the low_pass_filter method of the Function class. - - Parameters - ---------- - alpha : float - Attenuation coefficient, 0 < alpha < 1. - """ - # Create a test function, sinus here - source = np.array( - [(1, np.sin(1)), (2, np.sin(2)), (3, np.sin(3)), (4, np.sin(4)), (5, np.sin(5))] - ) - func = Function(source) - - # Apply low pass filter - filtered_func = func.low_pass_filter(alpha) - - # Check that the method works as intended and returns the right object with no issue - assert isinstance(filtered_func, Function), "The returned type is not a Function" - assert np.array_equal( - filtered_func.source[0], source[0] - ), "The initial value is not the expected value" - assert len(filtered_func.source) == len( - source - ), "The filtered Function and the Function have different lengths" - assert ( - filtered_func.__interpolation__ == func.__interpolation__ - ), "The interpolation method was unexpectedly changed" - assert ( - filtered_func.__extrapolation__ == func.__extrapolation__ - ), "The extrapolation method was unexpectedly changed" - for i in range(1, len(source)): - expected = alpha * source[i][1] + (1 - alpha) * filtered_func.source[i - 1][1] - assert np.isclose(filtered_func.source[i][1], expected, atol=1e-6), ( - f"The filtered value at index {i} is not the expected value. " - f"Expected: {expected}, Actual: {filtered_func.source[i][1]}" - ) diff --git a/tests/test_rocket.py b/tests/test_rocket.py deleted file mode 100644 index 70da36e95..000000000 --- a/tests/test_rocket.py +++ /dev/null @@ -1,282 +0,0 @@ -from unittest.mock import patch - -import numpy as np -import pytest - -from rocketpy import Rocket, SolidMotor -from rocketpy.rocket import NoseCone - - -@patch("matplotlib.pyplot.show") -def test_rocket(mock_show, calisto_robust): - test_rocket = calisto_robust - static_margin = test_rocket.static_margin(0) - # Check if all_info and static_method methods are working properly - assert test_rocket.all_info() == None or not abs(static_margin - 2.05) < 0.01 - - -@patch("matplotlib.pyplot.show") -def test_aero_surfaces_infos( - mock_show, calisto_nose_cone, calisto_tail, calisto_trapezoidal_fins -): - assert calisto_nose_cone.all_info() == None - assert calisto_trapezoidal_fins.all_info() == None - assert calisto_tail.all_info() == None - assert calisto_trapezoidal_fins.draw() == None - - -def test_coordinate_system_orientation( - calisto_nose_cone, cesaroni_m1670, calisto_trapezoidal_fins -): - """Test if the coordinate system orientation is working properly. This test - basically checks if the static margin is the same for the same rocket with - different coordinate system orientation. - - Parameters - ---------- - calisto_nose_cone : rocketpy.NoseCone - Nose cone of the rocket - cesaroni_m1670 : rocketpy.SolidMotor - Cesaroni M1670 motor - calisto_trapezoidal_fins : rocketpy.TrapezoidalFins - Trapezoidal fins of the rocket - """ - motor_nozzle_to_combustion_chamber = cesaroni_m1670 - - motor_combustion_chamber_to_nozzle = SolidMotor( - thrust_source="data/motors/Cesaroni_M1670.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="combustion_chamber_to_nozzle", - ) - - rocket_tail_to_nose = Rocket( - radius=0.0635, - mass=14.426, - inertia=(6.321, 6.321, 0.034), - power_off_drag="data/calisto/powerOffDragCurve.csv", - power_on_drag="data/calisto/powerOnDragCurve.csv", - center_of_mass_without_motor=0, - coordinate_system_orientation="tail_to_nose", - ) - - rocket_tail_to_nose.add_motor(motor_nozzle_to_combustion_chamber, position=-1.373) - - rocket_tail_to_nose.aerodynamic_surfaces.add(calisto_nose_cone, 1.160) - rocket_tail_to_nose.aerodynamic_surfaces.add(calisto_trapezoidal_fins, -1.168) - - static_margin_tail_to_nose = rocket_tail_to_nose.static_margin - - rocket_nose_to_tail = Rocket( - radius=0.0635, - mass=14.426, - inertia=(6.321, 6.321, 0.034), - power_off_drag="data/calisto/powerOffDragCurve.csv", - power_on_drag="data/calisto/powerOnDragCurve.csv", - center_of_mass_without_motor=0, - coordinate_system_orientation="nose_to_tail", - ) - - rocket_nose_to_tail.add_motor(motor_combustion_chamber_to_nozzle, position=1.373) - - rocket_nose_to_tail.aerodynamic_surfaces.add(calisto_nose_cone, -1.160) - rocket_nose_to_tail.aerodynamic_surfaces.add(calisto_trapezoidal_fins, 1.168) - - static_margin_nose_to_tail = rocket_nose_to_tail.static_margin - - assert np.array_equal(static_margin_tail_to_nose, static_margin_nose_to_tail) - - -@patch("matplotlib.pyplot.show") -def test_airfoil( - mock_show, - calisto, - calisto_main_chute, - calisto_drogue_chute, - calisto_nose_cone, - calisto_tail, -): - test_rocket = calisto - test_rocket.set_rail_buttons(0.082, -0.618) - calisto.aerodynamic_surfaces.add(calisto_nose_cone, 1.160) - calisto.aerodynamic_surfaces.add(calisto_tail, -1.313) - - fin_set_NACA = test_rocket.add_trapezoidal_fins( - 2, - span=0.100, - root_chord=0.120, - tip_chord=0.040, - position=-1.168, - airfoil=("tests/fixtures/airfoils/NACA0012-radians.txt", "radians"), - name="NACA0012", - ) - fin_set_E473 = test_rocket.add_trapezoidal_fins( - 2, - span=0.100, - root_chord=0.120, - tip_chord=0.040, - position=-1.168, - airfoil=("tests/fixtures/airfoils/e473-10e6-degrees.csv", "degrees"), - name="E473", - ) - calisto.parachutes.append(calisto_main_chute) - calisto.parachutes.append(calisto_drogue_chute) - - static_margin = test_rocket.static_margin(0) - - assert test_rocket.all_info() == None or not abs(static_margin - 2.03) < 0.01 - - -@patch("matplotlib.pyplot.show") -def test_air_brakes_clamp_on(mock_show, calisto_air_brakes_clamp_on): - """Test the air brakes class with clamp on configuration. This test checks - the basic attributes and the deployment_level setter. It also checks the - all_info method. - - Parameters - ---------- - mock_show : mock - Mock of the matplotlib.pyplot.show method. - calisto_air_brakes_clamp_on : Rocket instance - A predefined instance of the calisto with air brakes in clamp on - configuration. - """ - air_brakes_clamp_on = calisto_air_brakes_clamp_on.air_brakes[0] - - # test basic attributes - assert air_brakes_clamp_on.drag_coefficient.__dom_dim__ == 2 - assert ( - air_brakes_clamp_on.reference_area - == calisto_air_brakes_clamp_on.radius**2 * np.pi - ) - air_brakes_clamp_on.deployment_level = 0.5 - assert air_brakes_clamp_on.deployment_level == 0.5 - air_brakes_clamp_on.deployment_level = 1.5 - assert air_brakes_clamp_on.deployment_level == 1 - air_brakes_clamp_on.deployment_level = -1 - assert air_brakes_clamp_on.deployment_level == 0 - air_brakes_clamp_on.deployment_level = 0 - assert air_brakes_clamp_on.deployment_level == 0 - - assert air_brakes_clamp_on.all_info() == None - - -@patch("matplotlib.pyplot.show") -def test_air_brakes_clamp_off(mock_show, calisto_air_brakes_clamp_off): - """Test the air brakes class with clamp off configuration. This test checks - the basic attributes and the deployment_level setter. It also checks the - all_info method. - - Parameters - ---------- - mock_show : mock - Mock of the matplotlib.pyplot.show method. - calisto_air_brakes_clamp_off : Rocket instance - A predefined instance of the calisto with air brakes in clamp off - configuration. - """ - air_brakes_clamp_off = calisto_air_brakes_clamp_off.air_brakes[0] - - # test basic attributes - assert air_brakes_clamp_off.drag_coefficient.__dom_dim__ == 2 - assert ( - air_brakes_clamp_off.reference_area - == calisto_air_brakes_clamp_off.radius**2 * np.pi - ) - - air_brakes_clamp_off.deployment_level = 0.5 - assert air_brakes_clamp_off.deployment_level == 0.5 - air_brakes_clamp_off.deployment_level = 1.5 - assert air_brakes_clamp_off.deployment_level == 1.5 - air_brakes_clamp_off.deployment_level = -1 - assert air_brakes_clamp_off.deployment_level == -1 - air_brakes_clamp_off.deployment_level = 0 - assert air_brakes_clamp_off.deployment_level == 0 - - assert air_brakes_clamp_off.all_info() == None - - -def test_add_surfaces_different_noses(calisto): - """Test the add_surfaces method with different nose cone configurations. - More specifically, this will check the static margin of the rocket with - different nose cone configurations. - - Parameters - ---------- - calisto : Rocket - Pytest fixture for the calisto rocket. - """ - length = 0.55829 - kind = "vonkarman" - position = 1.16 - bluffness = 0 - base_radius = 0.0635 - rocket_radius = 0.0635 - - # Case 1: base_radius == rocket_radius - nose1 = NoseCone( - length, - kind, - base_radius=base_radius, - bluffness=bluffness, - rocket_radius=rocket_radius, - name="Nose Cone 1", - ) - calisto.add_surfaces(nose1, position) - assert nose1.radius_ratio == pytest.approx(1, 1e-8) - assert calisto.static_margin(0) == pytest.approx(-8.9053, 0.01) - - # Case 2: base_radius == rocket_radius / 2 - calisto.aerodynamic_surfaces.remove(nose1) - nose2 = NoseCone( - length, - kind, - base_radius=base_radius / 2, - bluffness=bluffness, - rocket_radius=rocket_radius, - name="Nose Cone 2", - ) - calisto.add_surfaces(nose2, position) - assert nose2.radius_ratio == pytest.approx(0.5, 1e-8) - assert calisto.static_margin(0) == pytest.approx(-8.9053, 0.01) - - # Case 3: base_radius == None - calisto.aerodynamic_surfaces.remove(nose2) - nose3 = NoseCone( - length, - kind, - base_radius=None, - bluffness=bluffness, - rocket_radius=rocket_radius * 2, - name="Nose Cone 3", - ) - calisto.add_surfaces(nose3, position) - assert nose3.radius_ratio == pytest.approx(1, 1e-8) - assert calisto.static_margin(0) == pytest.approx(-8.9053, 0.01) - - # Case 4: rocket_radius == None - calisto.aerodynamic_surfaces.remove(nose3) - nose4 = NoseCone( - length, - kind, - base_radius=base_radius, - bluffness=bluffness, - rocket_radius=None, - name="Nose Cone 4", - ) - calisto.add_surfaces(nose4, position) - assert nose4.radius_ratio == pytest.approx(1, 1e-8) - assert calisto.static_margin(0) == pytest.approx(-8.9053, 0.01) diff --git a/tests/test_solidmotor.py b/tests/test_solidmotor.py deleted file mode 100644 index 024e625a5..000000000 --- a/tests/test_solidmotor.py +++ /dev/null @@ -1,130 +0,0 @@ -import numpy as np -import pytest - -from rocketpy import Function - -burn_time = 3.9 -grain_number = 5 -grain_separation = 5 / 1000 -grain_density = 1815 -grain_outer_radius = 33 / 1000 -grain_initial_inner_radius = 15 / 1000 -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 - - -def test_initialize_motor_asserts_dynamic_values(cesaroni_m1670): - grain_vol = grain_initial_height * ( - np.pi * (grain_outer_radius**2 - grain_initial_inner_radius**2) - ) - grain_mass = grain_vol * grain_density - - assert abs(cesaroni_m1670.max_thrust - 2200.0) < 1e-9 - assert abs(cesaroni_m1670.max_thrust_time - 0.15) < 1e-9 - assert abs(cesaroni_m1670.burn_time[1] - burn_time) < 1e-9 - assert ( - abs(cesaroni_m1670.total_impulse - cesaroni_m1670.thrust.integral(0, burn_time)) - < 1e-9 - ) - assert ( - cesaroni_m1670.average_thrust - - cesaroni_m1670.thrust.integral(0, burn_time) / burn_time - ) < 1e-9 - assert abs(cesaroni_m1670.grain_initial_volume - grain_vol) < 1e-9 - assert abs(cesaroni_m1670.grain_initial_mass - grain_mass) < 1e-9 - assert ( - abs(cesaroni_m1670.propellant_initial_mass - grain_number * grain_mass) < 1e-9 - ) - assert ( - abs( - cesaroni_m1670.exhaust_velocity(0) - - cesaroni_m1670.thrust.integral(0, burn_time) / (grain_number * grain_mass) - ) - < 1e-9 - ) - - -def test_grain_geometry_progression_asserts_extreme_values(cesaroni_m1670): - assert np.allclose( - cesaroni_m1670.grain_inner_radius.get_source()[-1][-1], - cesaroni_m1670.grain_outer_radius, - ) - assert ( - cesaroni_m1670.grain_inner_radius.get_source()[0][-1] - < cesaroni_m1670.grain_inner_radius.get_source()[-1][-1] - ) - assert ( - cesaroni_m1670.grain_height.get_source()[0][-1] - > cesaroni_m1670.grain_height.get_source()[-1][-1] - ) - - -def test_mass_curve_asserts_extreme_values(cesaroni_m1670): - grain_vol = grain_initial_height * ( - np.pi * (grain_outer_radius**2 - grain_initial_inner_radius**2) - ) - grain_mass = grain_vol * grain_density - - assert np.allclose(cesaroni_m1670.propellant_mass.get_source()[-1][-1], 0) - assert np.allclose( - cesaroni_m1670.propellant_mass.get_source()[0][-1], grain_number * grain_mass - ) - - -def test_burn_area_asserts_extreme_values(cesaroni_m1670): - initial_burn_area = ( - 2 - * np.pi - * ( - grain_outer_radius**2 - - grain_initial_inner_radius**2 - + grain_initial_inner_radius * grain_initial_height - ) - * grain_number - ) - final_burn_area = ( - 2 - * np.pi - * ( - cesaroni_m1670.grain_inner_radius.get_source()[-1][-1] - * cesaroni_m1670.grain_height.get_source()[-1][-1] - ) - * grain_number - ) - - assert np.allclose(cesaroni_m1670.burn_area.get_source()[0][-1], initial_burn_area) - assert np.allclose( - cesaroni_m1670.burn_area.get_source()[-1][-1], final_burn_area, atol=1e-6 - ) - - -@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() - - 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 * (tuple_parametric[1] / 7539.1875) - assert thrust_reshaped[7][1] == 2034 * (tuple_parametric[1] / 7539.1875) diff --git a/tests/test_tools_matrix.py b/tests/test_tools_matrix.py deleted file mode 100644 index 6ae36ecd1..000000000 --- a/tests/test_tools_matrix.py +++ /dev/null @@ -1,40 +0,0 @@ -import numpy as np -import pytest - -from rocketpy.mathutils import Matrix - -test_matrix_1 = [[-7, 2, 3], [4, 5, -6], [1, -8, 9]] - -test_matrix_2 = [[np.pi, 2.5, 3.7], [4.2, np.e, -6.7], [1.1, -8.0, 0]] - -test_matrix_3 = [ - [0.1 + 1.0j, 3.1 + 1.2j, 2 + 0.5j], - [2.1 + 0.5j, 1.5 + 0.5j, 1 + 1.8j], - [5.2 + 1.3j, 4.2 + 7.7j, 7 + 5.3j], -] - -test_matrix_4 = [[-7, 0, 0], [0, 5, 0], [0, 0, 9]] - -test_matrix_5 = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] - -test_matrices = [ - test_matrix_1, - test_matrix_2, - test_matrix_3, - test_matrix_4, - test_matrix_5, -] - - -@pytest.mark.parametrize("components", test_matrices) -def test_matrix_x_y_z(components): - matrix = Matrix(components) - assert matrix.xx == components[0][0] - assert matrix.xy == components[0][1] - assert matrix.xz == components[0][2] - assert matrix.yx == components[1][0] - assert matrix.yy == components[1][1] - assert matrix.yz == components[1][2] - assert matrix.zx == components[2][0] - assert matrix.zy == components[2][1] - assert matrix.zz == components[2][2] diff --git a/tests/test_tools_vector.py b/tests/test_tools_vector.py deleted file mode 100644 index 590f8b4aa..000000000 --- a/tests/test_tools_vector.py +++ /dev/null @@ -1,17 +0,0 @@ -import numpy as np -import pytest - -from rocketpy.mathutils import Vector - -test_vector_1 = [1, 2, 3] -test_vector_2 = [-np.pi, 1, np.e] -test_vector_3 = [3 * 1j, -2j, 0j] -test_vectors = [test_vector_1, test_vector_2, test_vector_3] - - -@pytest.mark.parametrize("vector_components", test_vectors) -def test_vector_x_y_z(vector_components): - vector = Vector(vector_components) - assert vector.x == vector_components[0] - assert vector.y == vector_components[1] - assert vector.z == vector_components[2] diff --git a/tests/unit/test_aero_surfaces.py b/tests/unit/test_aero_surfaces.py index a78959897..5258814db 100644 --- a/tests/unit/test_aero_surfaces.py +++ b/tests/unit/test_aero_surfaces.py @@ -1,4 +1,5 @@ import pytest + from rocketpy import NoseCone NOSECONE_LENGTH = 1 diff --git a/tests/test_environment_analysis.py b/tests/unit/test_environment_analysis.py similarity index 79% rename from tests/test_environment_analysis.py rename to tests/unit/test_environment_analysis.py index d14462071..439fa0f04 100644 --- a/tests/test_environment_analysis.py +++ b/tests/unit/test_environment_analysis.py @@ -10,28 +10,6 @@ plt.rcParams.update({"figure.max_open_warning": 0}) -@pytest.mark.slow -@patch("matplotlib.pyplot.show") -def test_all_info(mock_show, env_analysis): - """Test the EnvironmentAnalysis.all_info() method, which already invokes - several other methods. It is a good way to test the whole class in a first view. - However, if it fails, it is hard to know which method is failing. - - Parameters - ---------- - env_analysis : rocketpy.EnvironmentAnalysis - A simple object of the Environment Analysis class - - Returns - ------- - None - """ - assert env_analysis.info() == None - assert env_analysis.all_info() == None - assert env_analysis.plots.info() == None - os.remove("wind_rose.gif") # remove the files created by the method - - @pytest.mark.slow @patch("matplotlib.pyplot.show") def test_distribution_plots(mock_show, env_analysis): @@ -131,6 +109,34 @@ def test_profile_plots(mock_show, env_analysis): ) +@pytest.mark.slow +def test_values(env_analysis): + """Check the numeric properties of the EnvironmentAnalysis class. It computes + a few values and compares them to the expected values. Not all the values are + tested since the most of them were already invoke in the previous tests. + + Parameters + ---------- + env_analysis : EnvironmentAnalysis + A simple object of the EnvironmentAnalysis class. + + Returns + ------- + None + """ + assert pytest.approx(env_analysis.record_min_surface_wind_speed, 1e-6) == 5.190407 + assert ( + pytest.approx(env_analysis.max_average_temperature_at_altitude, 1e-6) + == 24.52549 + ) + assert ( + pytest.approx(env_analysis.min_average_temperature_at_altitude, 1e-6) + == -63.18178 + ) + assert pytest.approx(env_analysis.std_pressure_at_10000ft, 1e-6) == 13.58699 + assert pytest.approx(env_analysis.std_pressure_at_30000ft, 1e-6) == 38.48947 + + @pytest.mark.slow @patch("matplotlib.pyplot.show") def test_animation_plots(mock_show, env_analysis): @@ -160,58 +166,3 @@ def test_animation_plots(mock_show, env_analysis): HTML, ) os.remove("wind_rose.gif") # remove the files created by the method - - -@pytest.mark.slow -@patch("matplotlib.pyplot.show") -def test_exports(mock_show, env_analysis): - """Check the export methods of the EnvironmentAnalysis class. It - only checks if the method runs without errors. It does not check if the - files are correct, as this would require a lot of work and would be - difficult to maintain. - - Parameters - ---------- - env_analysis : EnvironmentAnalysis - A simple object of the EnvironmentAnalysis class. - """ - - assert env_analysis.export_mean_profiles() == None - assert env_analysis.save("env_analysis_dict") == None - - env2 = copy.deepcopy(env_analysis) - env2.load("env_analysis_dict") - assert env2.all_info() == None - - # Delete file created by save method - os.remove("env_analysis_dict") - os.remove("wind_rose.gif") - os.remove("export_env_analysis.json") - - -@pytest.mark.slow -def test_values(env_analysis): - """Check the numeric properties of the EnvironmentAnalysis class. It computes - a few values and compares them to the expected values. Not all the values are - tested since the most of them were already invoke in the previous tests. - - Parameters - ---------- - env_analysis : EnvironmentAnalysis - A simple object of the EnvironmentAnalysis class. - - Returns - ------- - None - """ - assert pytest.approx(env_analysis.record_min_surface_wind_speed, 1e-6) == 5.190407 - assert ( - pytest.approx(env_analysis.max_average_temperature_at_altitude, 1e-6) - == 24.52549 - ) - assert ( - pytest.approx(env_analysis.min_average_temperature_at_altitude, 1e-6) - == -63.18178 - ) - assert pytest.approx(env_analysis.std_pressure_at_10000ft, 1e-6) == 13.58699 - assert pytest.approx(env_analysis.std_pressure_at_30000ft, 1e-6) == 38.48947 diff --git a/tests/unit/test_function.py b/tests/unit/test_function.py index a437f1542..c68fe6587 100644 --- a/tests/unit/test_function.py +++ b/tests/unit/test_function.py @@ -2,13 +2,16 @@ individual method of the Function class. The tests are made on both the expected behaviour and the return instances.""" -import os +from unittest.mock import patch +import matplotlib as plt import numpy as np import pytest from rocketpy import Function +plt.rcParams.update({"figure.max_open_warning": 0}) + @pytest.mark.parametrize("a", [-1, 0, 0.5, 1, 2, 2.5, 3.5, 4, 5]) @pytest.mark.parametrize("b", [-1, 0, 0.5, 1, 2, 2.5, 3.5, 4, 5]) @@ -197,68 +200,6 @@ def test_get_value_opt(x, y, z): assert np.isclose(func.get_value_opt(x, y), z, atol=1e-6) -@pytest.mark.parametrize( - "func", - [ - "linearly_interpolated_func", - "spline_interpolated_func", - "func_2d_from_csv", - "lambda_quad_func", - ], -) -def test_savetxt(request, func): - """Test the savetxt method of various Function objects. - - This test function verifies that the `savetxt` method correctly writes the - function's data to a CSV file and that a new function object created from - this file has the same data as the original function object. - - Notes - ----- - The test performs the following steps: - 1. It invokes the `savetxt` method of the given function object. - 2. It then reads this file to create a new function object. - 3. The test asserts that the data of the new function matches the original. - 4. Finally, the test cleans up by removing the created CSV file. - - Raises - ------ - AssertionError - If the `savetxt` method fails to save the file, or if the data of the - newly read function does not match the data of the original function. - """ - func = request.getfixturevalue(func) - assert ( - func.savetxt( - filename="test_func.csv", - lower=0, - upper=9, - samples=10, - fmt="%.6f", - delimiter=",", - newline="\n", - encoding=None, - ) - is None - ), "Couldn't save the file using the Function.savetxt method." - - read_func = Function( - "test_func.csv", - interpolation="linear" if func.get_domain_dim() == 1 else "shepard", - extrapolation="natural", - ) - if callable(func.source): - source = np.column_stack( - (np.linspace(0, 9, 10), func.source(np.linspace(0, 9, 10))) - ) - assert np.allclose(source, read_func.source) - else: - assert np.allclose(func.source, read_func.source) - - # clean up the file - os.remove("test_func.csv") - - @pytest.mark.parametrize("samples", [2, 50, 1000]) def test_set_discrete_mutator(samples): """Tests the set_discrete method of the Function class.""" @@ -367,3 +308,409 @@ def test_get_domain_dim(linear_func): def test_bool(linear_func): """Test the __bool__ method of the Function class.""" assert bool(linear_func) == True + + +def test_getters(func_from_csv, func_2d_from_csv): + """Test the different getters of the Function class. + + Parameters + ---------- + func_from_csv : rocketpy.Function + A Function object created from a .csv file. + """ + assert func_from_csv.get_inputs() == ["Scalar"] + assert func_from_csv.get_outputs() == ["Scalar"] + assert func_from_csv.get_interpolation_method() == "spline" + assert func_from_csv.get_extrapolation_method() == "constant" + assert np.isclose(func_from_csv.get_value(0), 0.0, atol=1e-6) + assert np.isclose(func_from_csv.get_value_opt(0), 0.0, atol=1e-6) + + assert func_2d_from_csv.get_inputs() == ["Input 1", "Input 2"] + assert func_2d_from_csv.get_outputs() == ["Scalar"] + assert func_2d_from_csv.get_interpolation_method() == "shepard" + assert func_2d_from_csv.get_extrapolation_method() == "natural" + assert np.isclose(func_2d_from_csv.get_value(0.1, 0.8), 0.058, atol=1e-6) + assert np.isclose(func_2d_from_csv.get_value_opt(0.1, 0.8), 0.058, atol=1e-6) + + +def test_setters(func_from_csv, func_2d_from_csv): + """Test the different setters of the Function class. + + Parameters + ---------- + func_from_csv : rocketpy.Function + A Function object created from a .csv file. + """ + # Test set methods + func_from_csv.set_inputs(["Scalar2"]) + assert func_from_csv.get_inputs() == ["Scalar2"] + func_from_csv.set_outputs(["Scalar2"]) + assert func_from_csv.get_outputs() == ["Scalar2"] + func_from_csv.set_interpolation("linear") + assert func_from_csv.get_interpolation_method() == "linear" + func_from_csv.set_extrapolation("natural") + assert func_from_csv.get_extrapolation_method() == "natural" + + func_2d_from_csv.set_inputs(["Scalar1", "Scalar2"]) + assert func_2d_from_csv.get_inputs() == ["Scalar1", "Scalar2"] + func_2d_from_csv.set_outputs(["Scalar3"]) + assert func_2d_from_csv.get_outputs() == ["Scalar3"] + func_2d_from_csv.set_interpolation("shepard") + assert func_2d_from_csv.get_interpolation_method() == "shepard" + func_2d_from_csv.set_extrapolation("natural") + assert func_2d_from_csv.get_extrapolation_method() == "natural" + + +def test_interpolation_methods(linear_func): + """Tests some of the interpolation methods of the Function class. Methods + not tested here are already being called in other tests. + + Parameters + ---------- + linear_func : rocketpy.Function + A Function object created from a list of values. + """ + # Test Akima + assert isinstance(linear_func.set_interpolation("akima"), Function) + linear_func.set_interpolation("akima") + assert isinstance(linear_func.get_interpolation_method(), str) + assert linear_func.get_interpolation_method() == "akima" + assert np.isclose(linear_func.get_value(0), 0.0, atol=1e-6) + + # Test polynomial + + assert isinstance(linear_func.set_interpolation("polynomial"), Function) + linear_func.set_interpolation("polynomial") + assert isinstance(linear_func.get_interpolation_method(), str) + assert linear_func.get_interpolation_method() == "polynomial" + assert np.isclose(linear_func.get_value(0), 0.0, atol=1e-6) + + +def test_extrapolation_methods(linear_func): + """Test some of the extrapolation methods of the Function class. Methods + not tested here are already being called in other tests. + + Parameters + ---------- + linear_func : rocketpy.Function + A Function object created from a list of values. + """ + # Test zero + linear_func.set_extrapolation("zero") + assert linear_func.get_extrapolation_method() == "zero" + assert np.isclose(linear_func.get_value(-1), 0, atol=1e-6) + + # Test constant + assert isinstance(linear_func.set_extrapolation("constant"), Function) + linear_func.set_extrapolation("constant") + assert isinstance(linear_func.get_extrapolation_method(), str) + assert linear_func.get_extrapolation_method() == "constant" + assert np.isclose(linear_func.get_value(-1), 0, atol=1e-6) + + # Test natural for linear interpolation + linear_func.set_interpolation("linear") + assert isinstance(linear_func.set_extrapolation("natural"), Function) + linear_func.set_extrapolation("natural") + assert isinstance(linear_func.get_extrapolation_method(), str) + assert linear_func.get_extrapolation_method() == "natural" + assert np.isclose(linear_func.get_value(-1), -1, atol=1e-6) + + # Test natural for spline interpolation + linear_func.set_interpolation("spline") + assert isinstance(linear_func.set_extrapolation("natural"), Function) + linear_func.set_extrapolation("natural") + assert isinstance(linear_func.get_extrapolation_method(), str) + assert linear_func.get_extrapolation_method() == "natural" + assert np.isclose(linear_func.get_value(-1), -1, atol=1e-6) + + # Test natural for akima interpolation + linear_func.set_interpolation("akima") + assert isinstance(linear_func.set_extrapolation("natural"), Function) + linear_func.set_extrapolation("natural") + assert isinstance(linear_func.get_extrapolation_method(), str) + assert linear_func.get_extrapolation_method() == "natural" + assert np.isclose(linear_func.get_value(-1), -1, atol=1e-6) + + # Test natural for polynomial interpolation + linear_func.set_interpolation("polynomial") + assert isinstance(linear_func.set_extrapolation("natural"), Function) + linear_func.set_extrapolation("natural") + assert isinstance(linear_func.get_extrapolation_method(), str) + assert linear_func.get_extrapolation_method() == "natural" + assert np.isclose(linear_func.get_value(-1), -1, atol=1e-6) + + +@pytest.mark.parametrize("a", [-1, 0, 1]) +@pytest.mark.parametrize("b", [-1, 0, 1]) +def test_multivariable_dataset(a, b): + """Test the Function class with a multivariable dataset.""" + # Test plane f(x,y) = x + y + source = [ + (-1, -1, -2), + (-1, 0, -1), + (-1, 1, 0), + (0, -1, -1), + (0, 0, 0), + (0, 1, 1), + (1, -1, 0), + (1, 0, 1), + (1, 1, 2), + ] + func = Function(source=source, inputs=["x", "y"], outputs=["z"]) + + # Assert interpolation and extrapolation methods + assert func.get_interpolation_method() == "shepard" + assert func.get_extrapolation_method() == "natural" + + # Assert values + assert np.isclose(func(a, b), a + b, atol=1e-6) + + +@pytest.mark.parametrize( + "x,y,z_expected", + [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1), + (0.5, 0.5, 1 / 3), + (0.25, 0.25, 25 / (25 + 2 * 5**0.5)), + ([0, 0.5], [0, 0.5], [1, 1 / 3]), + ], +) +def test_2d_shepard_interpolation(x, y, z_expected): + """Test the shepard interpolation method of the Function class.""" + # Test plane x + y + z = 1 + source = [(1, 0, 0), (0, 1, 0), (0, 0, 1)] + func = Function( + source=source, inputs=["x", "y"], outputs=["z"], interpolation="shepard" + ) + z = func(x, y) + z_opt = func.get_value_opt(x, y) + assert np.isclose(z, z_opt, atol=1e-8).all() + assert np.isclose(z_expected, z, atol=1e-8).all() + + +@pytest.mark.parametrize( + "x,y,z,w_expected", + [ + (0, 0, 0, 1), + (1, 0, 0, 0), + (0, 1, 0, 0), + (0, 0, 1, 0), + (0.5, 0.5, 0.5, 1 / 4), + (0.25, 0.25, 0.25, 0.700632626832), + ([0, 0.5], [0, 0.5], [0, 0.5], [1, 1 / 4]), + ], +) +def test_3d_shepard_interpolation(x, y, z, w_expected): + """Test the shepard interpolation method of the Function class.""" + # Test plane x + y + z + w = 1 + source = [(1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1)] + func = Function( + source=source, inputs=["x", "y", "z"], outputs=["w"], interpolation="shepard" + ) + w = func(x, y, z) + w_opt = func.get_value_opt(x, y, z) + assert np.isclose(w, w_opt, atol=1e-8).all() + assert np.isclose(w_expected, w, atol=1e-8).all() + + +@pytest.mark.parametrize("a", [-1, -0.5, 0, 0.5, 1]) +@pytest.mark.parametrize("b", [-1, -0.5, 0, 0.5, 1]) +def test_multivariable_function(a, b): + """Test the Function class with a multivariable function.""" + # Test plane f(x,y) = sin(x + y) + source = lambda x, y: np.sin(x + y) + func = Function(source=source, inputs=["x", "y"], outputs=["z"]) + + # Assert values + assert np.isclose(func(a, b), np.sin(a + b), atol=1e-6) + + +def test_set_discrete_2d(): + """Tests the set_discrete method of the Function for + two dimensional domains. + """ + func = Function(lambda x, y: x**2 + y**2) + discretized_func = func.set_discrete([-5, -7], [8, 10], [50, 100]) + + assert isinstance(discretized_func, Function) + assert isinstance(func, Function) + assert discretized_func.source.shape == (50 * 100, 3) + assert np.isclose(discretized_func.source[0, 0], -5) + assert np.isclose(discretized_func.source[0, 1], -7) + assert np.isclose(discretized_func.source[-1, 0], 8) + assert np.isclose(discretized_func.source[-1, 1], 10) + + +def test_set_discrete_2d_simplified(): + """Tests the set_discrete method of the Function for + two dimensional domains with simplified inputs. + """ + source = [(1, 0, 0), (0, 1, 0), (0, 0, 1)] + func = Function(source=source, inputs=["x", "y"], outputs=["z"]) + discretized_func = func.set_discrete(-1, 1, 10) + + assert isinstance(discretized_func, Function) + assert isinstance(func, Function) + assert discretized_func.source.shape == (100, 3) + assert np.isclose(discretized_func.source[0, 0], -1) + assert np.isclose(discretized_func.source[0, 1], -1) + assert np.isclose(discretized_func.source[-1, 0], 1) + assert np.isclose(discretized_func.source[-1, 1], 1) + + +def test_set_discrete_based_on_2d_model(func_2d_from_csv): + """Tests the set_discrete_based_on_model method with a 2d model + Function. + """ + func = Function(lambda x, y: x**2 + y**2) + discretized_func = func.set_discrete_based_on_model(func_2d_from_csv) + + assert isinstance(discretized_func, Function) + assert isinstance(func, Function) + assert np.array_equal( + discretized_func.source[:, :2], func_2d_from_csv.source[:, :2] + ) + assert discretized_func.__interpolation__ == func_2d_from_csv.__interpolation__ + assert discretized_func.__extrapolation__ == func_2d_from_csv.__extrapolation__ + + +@pytest.mark.parametrize( + "x,y,z_expected", + [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1), + (0.5, 0.5, 1 / 3), + (0.25, 0.25, 25 / (25 + 2 * 5**0.5)), + ([0, 0.5], [0, 0.5], [1, 1 / 3]), + ], +) +def test_shepard_interpolation(x, y, z_expected): + """Test the shepard interpolation method of the Function class.""" + # Test plane x + y + z = 1 + source = [(1, 0, 0), (0, 1, 0), (0, 0, 1)] + func = Function(source=source, inputs=["x", "y"], outputs=["z"]) + z = func(x, y) + assert np.isclose(z, z_expected, atol=1e-8).all() + + +@pytest.mark.parametrize("other", [1, 0.1, np.int_(1), np.float64(0.1), np.array([1])]) +def test_sum_arithmetic_priority(other): + """Test the arithmetic priority of the add operation of the Function class, + specially comparing to the numpy array operations. + """ + func_lambda = Function(lambda x: x**2) + func_array = Function([(0, 0), (1, 1), (2, 4)]) + + assert isinstance(func_lambda + func_array, Function) + assert isinstance(func_array + func_lambda, Function) + assert isinstance(func_lambda + other, Function) + assert isinstance(other + func_lambda, Function) + assert isinstance(func_array + other, Function) + assert isinstance(other + func_array, Function) + + +@pytest.mark.parametrize("other", [1, 0.1, np.int_(1), np.float64(0.1), np.array([1])]) +def test_sub_arithmetic_priority(other): + """Test the arithmetic priority of the sub operation of the Function class, + specially comparing to the numpy array operations. + """ + func_lambda = Function(lambda x: x**2) + func_array = Function([(0, 0), (1, 1), (2, 4)]) + + assert isinstance(func_lambda - func_array, Function) + assert isinstance(func_array - func_lambda, Function) + assert isinstance(func_lambda - other, Function) + assert isinstance(other - func_lambda, Function) + assert isinstance(func_array - other, Function) + assert isinstance(other - func_array, Function) + + +@pytest.mark.parametrize("other", [1, 0.1, np.int_(1), np.float64(0.1), np.array([1])]) +def test_mul_arithmetic_priority(other): + """Test the arithmetic priority of the mul operation of the Function class, + specially comparing to the numpy array operations. + """ + func_lambda = Function(lambda x: x**2) + func_array = Function([(0, 0), (1, 1), (2, 4)]) + + assert isinstance(func_lambda * func_array, Function) + assert isinstance(func_array * func_lambda, Function) + assert isinstance(func_lambda * other, Function) + assert isinstance(other * func_lambda, Function) + assert isinstance(func_array * other, Function) + assert isinstance(other * func_array, Function) + + +@pytest.mark.parametrize("other", [1, 0.1, np.int_(1), np.float64(0.1), np.array([1])]) +def test_truediv_arithmetic_priority(other): + """Test the arithmetic priority of the truediv operation of the Function class, + specially comparing to the numpy array operations. + """ + func_lambda = Function(lambda x: x**2) + func_array = Function([(1, 1), (2, 4)]) + + assert isinstance(func_lambda / func_array, Function) + assert isinstance(func_array / func_lambda, Function) + assert isinstance(func_lambda / other, Function) + assert isinstance(other / func_lambda, Function) + assert isinstance(func_array / other, Function) + assert isinstance(other / func_array, Function) + + +@pytest.mark.parametrize("other", [1, 0.1, np.int_(1), np.float64(0.1), np.array([1])]) +def test_pow_arithmetic_priority(other): + """Test the arithmetic priority of the pow operation of the Function class, + specially comparing to the numpy array operations. + """ + func_lambda = Function(lambda x: x**2) + func_array = Function([(0, 0), (1, 1), (2, 4)]) + + assert isinstance(func_lambda**func_array, Function) + assert isinstance(func_array**func_lambda, Function) + assert isinstance(func_lambda**other, Function) + assert isinstance(other**func_lambda, Function) + assert isinstance(func_array**other, Function) + assert isinstance(other**func_array, Function) + + +@pytest.mark.parametrize("alpha", [0.1, 0.5, 0.9]) +def test_low_pass_filter(alpha): + """Test the low_pass_filter method of the Function class. + + Parameters + ---------- + alpha : float + Attenuation coefficient, 0 < alpha < 1. + """ + # Create a test function, sinus here + source = np.array( + [(1, np.sin(1)), (2, np.sin(2)), (3, np.sin(3)), (4, np.sin(4)), (5, np.sin(5))] + ) + func = Function(source) + + # Apply low pass filter + filtered_func = func.low_pass_filter(alpha) + + # Check that the method works as intended and returns the right object with no issue + assert isinstance(filtered_func, Function), "The returned type is not a Function" + assert np.array_equal( + filtered_func.source[0], source[0] + ), "The initial value is not the expected value" + assert len(filtered_func.source) == len( + source + ), "The filtered Function and the Function have different lengths" + assert ( + filtered_func.__interpolation__ == func.__interpolation__ + ), "The interpolation method was unexpectedly changed" + assert ( + filtered_func.__extrapolation__ == func.__extrapolation__ + ), "The extrapolation method was unexpectedly changed" + for i in range(1, len(source)): + expected = alpha * source[i][1] + (1 - alpha) * filtered_func.source[i - 1][1] + assert np.isclose(filtered_func.source[i][1], expected, atol=1e-6), ( + f"The filtered value at index {i} is not the expected value. " + f"Expected: {expected}, Actual: {filtered_func.source[i][1]}" + ) diff --git a/tests/test_genericmotor.py b/tests/unit/test_genericmotor.py similarity index 91% rename from tests/test_genericmotor.py rename to tests/unit/test_genericmotor.py index 513fca40d..b1bd5fd8e 100644 --- a/tests/test_genericmotor.py +++ b/tests/unit/test_genericmotor.py @@ -16,21 +16,6 @@ dry_inertia = (0.2, 0.2, 0.08) -@patch("matplotlib.pyplot.show") -def test_generic_motor_info(mock_show, generic_motor): - """Tests the GenericMotor.all_info() method. - - Parameters - ---------- - mock_show : mock - Mock of the matplotlib.pyplot.show function. - generic_motor : rocketpy.GenericMotor - The GenericMotor object to be used in the tests. - """ - assert generic_motor.info() == None - assert generic_motor.all_info() == None - - def test_generic_motor_basic_parameters(generic_motor): """Tests the GenericMotor class construction parameters. diff --git a/tests/test_hybridmotor.py b/tests/unit/test_hybridmotor.py similarity index 93% rename from tests/test_hybridmotor.py rename to tests/unit/test_hybridmotor.py index 0a1d4dcef..acf4b3e54 100644 --- a/tests/test_hybridmotor.py +++ b/tests/unit/test_hybridmotor.py @@ -23,21 +23,6 @@ oxidizer_tank_position = 0.3 -@patch("matplotlib.pyplot.show") -def test_hybrid_motor_info(mock_show, hybrid_motor): - """Tests the HybridMotor.all_info() method. - - Parameters - ---------- - mock_show : mock - Mock of the matplotlib.pyplot.show function. - hybrid_motor : rocketpy.HybridMotor - The HybridMotor object to be used in the tests. - """ - assert hybrid_motor.info() == None - assert hybrid_motor.all_info() == None - - def test_hybrid_motor_basic_parameters(hybrid_motor): """Tests the HybridMotor class construction parameters. diff --git a/tests/test_liquidmotor.py b/tests/unit/test_liquidmotor.py similarity index 95% rename from tests/test_liquidmotor.py rename to tests/unit/test_liquidmotor.py index ba11d7133..ed4fe0ab3 100644 --- a/tests/test_liquidmotor.py +++ b/tests/unit/test_liquidmotor.py @@ -17,21 +17,6 @@ oxidizer_tank_position = 0.711 -@patch("matplotlib.pyplot.show") -def test_liquid_motor_info(mock_show, liquid_motor): - """Tests the LiquidMotor.all_info() method. - - Parameters - ---------- - mock_show : mock - Mock of the matplotlib.pyplot.show function. - liquid_motor : rocketpy.LiquidMotor - The LiquidMotor object to be used in the tests. - """ - assert liquid_motor.info() == None - assert liquid_motor.all_info() == None - - def test_liquid_motor_basic_parameters(liquid_motor): """Tests the LiquidMotor class construction parameters. diff --git a/tests/unit/test_monte_carlo.py b/tests/unit/test_monte_carlo.py new file mode 100644 index 000000000..7af6a5db5 --- /dev/null +++ b/tests/unit/test_monte_carlo.py @@ -0,0 +1,66 @@ +from unittest.mock import patch + +import matplotlib as plt +import numpy as np +import pytest + +plt.rcParams.update({"figure.max_open_warning": 0}) + + +def test_stochastic_environment_create_object_with_wind_x(stochastic_environment): + """Tests the stochastic environment object by checking if the wind velocity + can be generated properly. The goal is to check if the create_object() + method is being called without any problems. + + Parameters + ---------- + stochastic_environment : StochasticEnvironment + The stochastic environment object, this is a pytest fixture. + """ + wind_x_at_1000m = [] + for _ in range(10): + random_env = stochastic_environment.create_object() + wind_x_at_1000m.append(random_env.wind_velocity_x(1000)) + + assert np.isclose(np.mean(wind_x_at_1000m), 0, atol=0.1) + assert np.isclose(np.std(wind_x_at_1000m), 0, atol=0.1) + # TODO: add a new test for the special case of ensemble member + + +def test_stochastic_solid_motor_create_object_with_impulse(stochastic_solid_motor): + """Tests the stochastic solid motor object by checking if the total impulse + can be generated properly. The goal is to check if the create_object() + method is being called without any problems. + + Parameters + ---------- + stochastic_solid_motor : StochasticSolidMotor + The stochastic solid motor object, this is a pytest fixture. + """ + total_impulse = [] + for _ in range(20): + random_motor = stochastic_solid_motor.create_object() + total_impulse.append(random_motor.total_impulse) + + assert np.isclose(np.mean(total_impulse), 6500, rtol=0.3) + assert np.isclose(np.std(total_impulse), 1000, rtol=0.3) + + +def test_stochastic_calisto_create_object_with_static_margin(stochastic_calisto): + """Tests the stochastic calisto object by checking if the static margin + can be generated properly. The goal is to check if the create_object() + method is being called without any problems. + + Parameters + ---------- + stochastic_calisto : StochasticCalisto + The stochastic calisto object, this is a pytest fixture. + """ + + all_margins = [] + for _ in range(10): + random_rocket = stochastic_calisto.create_object() + all_margins.append(random_rocket.static_margin(0)) + + assert np.isclose(np.mean(all_margins), 2.2625350013000434, rtol=0.15) + assert np.isclose(np.std(all_margins), 0.1, atol=0.2) diff --git a/tests/unit/test_plots.py b/tests/unit/test_plots.py index e69de29bb..cafd7cf8b 100644 --- a/tests/unit/test_plots.py +++ b/tests/unit/test_plots.py @@ -0,0 +1,42 @@ +import os +from unittest.mock import patch + +import matplotlib.pyplot as plt + +from rocketpy import Flight +from rocketpy.plots.compare import Compare, CompareFlights + + +@patch("matplotlib.pyplot.show") +def test_compare(mock_show, flight_calisto): + """Here we want to test the 'x_attributes' argument, which is the only one + that is not tested in the other tests. + + Parameters + ---------- + mock_show : + Mocks the matplotlib.pyplot.show() function to avoid showing the plots. + flight_calisto : rocketpy.Flight + Flight object to be used in the tests. See conftest.py for more details. + """ + flight = flight_calisto + + objects = [flight, flight, flight] + + comparison = Compare(object_list=objects) + + fig, _ = comparison.create_comparison_figure( + y_attributes=["z"], + n_rows=1, + n_cols=1, + figsize=(10, 10), + legend=False, + title="Test", + x_labels=["Time (s)"], + y_labels=["Altitude (m)"], + x_lim=(0, 3), + y_lim=(0, 1000), + x_attributes=["time"], + ) + + assert isinstance(fig, plt.Figure) == True diff --git a/tests/unit/test_rocket.py b/tests/unit/test_rocket.py index fdaecf60f..ea9b5972f 100644 --- a/tests/unit/test_rocket.py +++ b/tests/unit/test_rocket.py @@ -512,3 +512,151 @@ def test_add_cm_eccentricity(calisto): assert calisto.cp_eccentricity_y == 0.1 assert calisto.thrust_eccentricity_x == 0.1 assert calisto.thrust_eccentricity_y == 0.1 + + +def test_add_surfaces_different_noses(calisto): + """Test the add_surfaces method with different nose cone configurations. + More specifically, this will check the static margin of the rocket with + different nose cone configurations. + + Parameters + ---------- + calisto : Rocket + Pytest fixture for the calisto rocket. + """ + length = 0.55829 + kind = "vonkarman" + position = 1.16 + bluffness = 0 + base_radius = 0.0635 + rocket_radius = 0.0635 + + # Case 1: base_radius == rocket_radius + nose1 = NoseCone( + length, + kind, + base_radius=base_radius, + bluffness=bluffness, + rocket_radius=rocket_radius, + name="Nose Cone 1", + ) + calisto.add_surfaces(nose1, position) + assert nose1.radius_ratio == pytest.approx(1, 1e-8) + assert calisto.static_margin(0) == pytest.approx(-8.9053, 0.01) + + # Case 2: base_radius == rocket_radius / 2 + calisto.aerodynamic_surfaces.remove(nose1) + nose2 = NoseCone( + length, + kind, + base_radius=base_radius / 2, + bluffness=bluffness, + rocket_radius=rocket_radius, + name="Nose Cone 2", + ) + calisto.add_surfaces(nose2, position) + assert nose2.radius_ratio == pytest.approx(0.5, 1e-8) + assert calisto.static_margin(0) == pytest.approx(-8.9053, 0.01) + + # Case 3: base_radius == None + calisto.aerodynamic_surfaces.remove(nose2) + nose3 = NoseCone( + length, + kind, + base_radius=None, + bluffness=bluffness, + rocket_radius=rocket_radius * 2, + name="Nose Cone 3", + ) + calisto.add_surfaces(nose3, position) + assert nose3.radius_ratio == pytest.approx(1, 1e-8) + assert calisto.static_margin(0) == pytest.approx(-8.9053, 0.01) + + # Case 4: rocket_radius == None + calisto.aerodynamic_surfaces.remove(nose3) + nose4 = NoseCone( + length, + kind, + base_radius=base_radius, + bluffness=bluffness, + rocket_radius=None, + name="Nose Cone 4", + ) + calisto.add_surfaces(nose4, position) + assert nose4.radius_ratio == pytest.approx(1, 1e-8) + assert calisto.static_margin(0) == pytest.approx(-8.9053, 0.01) + + +def test_coordinate_system_orientation( + calisto_nose_cone, cesaroni_m1670, calisto_trapezoidal_fins +): + """Test if the coordinate system orientation is working properly. This test + basically checks if the static margin is the same for the same rocket with + different coordinate system orientation. + + Parameters + ---------- + calisto_nose_cone : rocketpy.NoseCone + Nose cone of the rocket + cesaroni_m1670 : rocketpy.SolidMotor + Cesaroni M1670 motor + calisto_trapezoidal_fins : rocketpy.TrapezoidalFins + Trapezoidal fins of the rocket + """ + motor_nozzle_to_combustion_chamber = cesaroni_m1670 + + motor_combustion_chamber_to_nozzle = SolidMotor( + thrust_source="data/motors/Cesaroni_M1670.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="combustion_chamber_to_nozzle", + ) + + rocket_tail_to_nose = Rocket( + radius=0.0635, + mass=14.426, + inertia=(6.321, 6.321, 0.034), + power_off_drag="data/calisto/powerOffDragCurve.csv", + power_on_drag="data/calisto/powerOnDragCurve.csv", + center_of_mass_without_motor=0, + coordinate_system_orientation="tail_to_nose", + ) + + rocket_tail_to_nose.add_motor(motor_nozzle_to_combustion_chamber, position=-1.373) + + rocket_tail_to_nose.aerodynamic_surfaces.add(calisto_nose_cone, 1.160) + rocket_tail_to_nose.aerodynamic_surfaces.add(calisto_trapezoidal_fins, -1.168) + + static_margin_tail_to_nose = rocket_tail_to_nose.static_margin + + rocket_nose_to_tail = Rocket( + radius=0.0635, + mass=14.426, + inertia=(6.321, 6.321, 0.034), + power_off_drag="data/calisto/powerOffDragCurve.csv", + power_on_drag="data/calisto/powerOnDragCurve.csv", + center_of_mass_without_motor=0, + coordinate_system_orientation="nose_to_tail", + ) + + rocket_nose_to_tail.add_motor(motor_combustion_chamber_to_nozzle, position=1.373) + + rocket_nose_to_tail.aerodynamic_surfaces.add(calisto_nose_cone, -1.160) + rocket_nose_to_tail.aerodynamic_surfaces.add(calisto_trapezoidal_fins, 1.168) + + static_margin_nose_to_tail = rocket_nose_to_tail.static_margin + + assert np.array_equal(static_margin_tail_to_nose, static_margin_nose_to_tail) diff --git a/tests/unit/test_solidmotor.py b/tests/unit/test_solidmotor.py index 831ab503d..dd3e54faa 100644 --- a/tests/unit/test_solidmotor.py +++ b/tests/unit/test_solidmotor.py @@ -2,6 +2,9 @@ from unittest.mock import patch import numpy as np +import pytest + +from rocketpy import Function BURN_TIME = 3.9 GRAIN_NUMBER = 5 @@ -15,6 +18,18 @@ GRAIN_VOL = 0.12 * (np.pi * (0.033**2 - 0.015**2)) GRAIN_MASS = GRAIN_VOL * 1815 * 5 +burn_time = 3.9 +grain_number = 5 +grain_separation = 5 / 1000 +grain_density = 1815 +grain_outer_radius = 33 / 1000 +grain_initial_inner_radius = 15 / 1000 +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") def test_motor(mock_show, cesaroni_m1670): @@ -163,3 +178,117 @@ def tests_export_eng_asserts_exported_values_correct(cesaroni_m1670): [3.4, 350.0], [3.9, 0.0], ] + + +def test_initialize_motor_asserts_dynamic_values(cesaroni_m1670): + grain_vol = grain_initial_height * ( + np.pi * (grain_outer_radius**2 - grain_initial_inner_radius**2) + ) + grain_mass = grain_vol * grain_density + + assert abs(cesaroni_m1670.max_thrust - 2200.0) < 1e-9 + assert abs(cesaroni_m1670.max_thrust_time - 0.15) < 1e-9 + assert abs(cesaroni_m1670.burn_time[1] - burn_time) < 1e-9 + assert ( + abs(cesaroni_m1670.total_impulse - cesaroni_m1670.thrust.integral(0, burn_time)) + < 1e-9 + ) + assert ( + cesaroni_m1670.average_thrust + - cesaroni_m1670.thrust.integral(0, burn_time) / burn_time + ) < 1e-9 + assert abs(cesaroni_m1670.grain_initial_volume - grain_vol) < 1e-9 + assert abs(cesaroni_m1670.grain_initial_mass - grain_mass) < 1e-9 + assert ( + abs(cesaroni_m1670.propellant_initial_mass - grain_number * grain_mass) < 1e-9 + ) + assert ( + abs( + cesaroni_m1670.exhaust_velocity(0) + - cesaroni_m1670.thrust.integral(0, burn_time) / (grain_number * grain_mass) + ) + < 1e-9 + ) + + +def test_grain_geometry_progression_asserts_extreme_values(cesaroni_m1670): + assert np.allclose( + cesaroni_m1670.grain_inner_radius.get_source()[-1][-1], + cesaroni_m1670.grain_outer_radius, + ) + assert ( + cesaroni_m1670.grain_inner_radius.get_source()[0][-1] + < cesaroni_m1670.grain_inner_radius.get_source()[-1][-1] + ) + assert ( + cesaroni_m1670.grain_height.get_source()[0][-1] + > cesaroni_m1670.grain_height.get_source()[-1][-1] + ) + + +def test_mass_curve_asserts_extreme_values(cesaroni_m1670): + grain_vol = grain_initial_height * ( + np.pi * (grain_outer_radius**2 - grain_initial_inner_radius**2) + ) + grain_mass = grain_vol * grain_density + + assert np.allclose(cesaroni_m1670.propellant_mass.get_source()[-1][-1], 0) + assert np.allclose( + cesaroni_m1670.propellant_mass.get_source()[0][-1], grain_number * grain_mass + ) + + +def test_burn_area_asserts_extreme_values(cesaroni_m1670): + initial_burn_area = ( + 2 + * np.pi + * ( + grain_outer_radius**2 + - grain_initial_inner_radius**2 + + grain_initial_inner_radius * grain_initial_height + ) + * grain_number + ) + final_burn_area = ( + 2 + * np.pi + * ( + cesaroni_m1670.grain_inner_radius.get_source()[-1][-1] + * cesaroni_m1670.grain_height.get_source()[-1][-1] + ) + * grain_number + ) + + assert np.allclose(cesaroni_m1670.burn_area.get_source()[0][-1], initial_burn_area) + assert np.allclose( + cesaroni_m1670.burn_area.get_source()[-1][-1], final_burn_area, atol=1e-6 + ) + + +@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() + + 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 * (tuple_parametric[1] / 7539.1875) + assert thrust_reshaped[7][1] == 2034 * (tuple_parametric[1] / 7539.1875) diff --git a/tests/test_tank.py b/tests/unit/test_tank.py similarity index 99% rename from tests/test_tank.py rename to tests/unit/test_tank.py index 6b2f03bc9..14bc733c4 100644 --- a/tests/test_tank.py +++ b/tests/unit/test_tank.py @@ -17,6 +17,7 @@ fuel_params = (0.0744, 0.8068) oxidizer_params = (0.0744, 0.8068) + parametrize_fixtures = pytest.mark.parametrize( "params", [ diff --git a/tests/unit/test_tools_matrix.py b/tests/unit/test_tools_matrix.py index b43818450..959e56f19 100644 --- a/tests/unit/test_tools_matrix.py +++ b/tests/unit/test_tools_matrix.py @@ -242,3 +242,17 @@ def test_matrix_transformation(): q1, q2, q3 = np.sin(phi / 2) * n matrix = Matrix.transformation((q0, q1, q2, q3)) assert matrix @ Vector([0, 0, 1]) == Vector([0, -1, 0]) + + +@pytest.mark.parametrize("components", test_matrices) +def test_matrix_x_y_z(components): + matrix = Matrix(components) + assert matrix.xx == components[0][0] + assert matrix.xy == components[0][1] + assert matrix.xz == components[0][2] + assert matrix.yx == components[1][0] + assert matrix.yy == components[1][1] + assert matrix.yz == components[1][2] + assert matrix.zx == components[2][0] + assert matrix.zy == components[2][1] + assert matrix.zz == components[2][2] diff --git a/tests/unit/test_tools_vector.py b/tests/unit/test_tools_vector.py index ccedadd84..476bb01c0 100644 --- a/tests/unit/test_tools_vector.py +++ b/tests/unit/test_tools_vector.py @@ -222,3 +222,17 @@ def test_vector_j(): def test_vector_k(): assert Vector.k() == [0, 0, 1] + + +test_vector_1 = [1, 2, 3] +test_vector_2 = [-np.pi, 1, np.e] +test_vector_3 = [3 * 1j, -2j, 0j] +test_vectors = [test_vector_1, test_vector_2, test_vector_3] + + +@pytest.mark.parametrize("vector_components", test_vectors) +def test_vector_x_y_z(vector_components): + vector = Vector(vector_components) + assert vector.x == vector_components[0] + assert vector.y == vector_components[1] + assert vector.z == vector_components[2]