From d4ac946d808aa85ce7b6ad1900f746407fc55506 Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Mon, 12 Feb 2024 12:56:05 -0500 Subject: [PATCH 01/41] REL: Update version to 1.2.0 --- CHANGELOG.md | 2 +- docs/conf.py | 2 +- docs/user/installation.rst | 2 +- setup.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3503a9e9..9005764c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,7 +39,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Fixed -## [v1.2.0] - 2024-02-dd +## [v1.2.0] - 2024-02-12 You can install this version by running `pip install rocketpy==1.2.0` diff --git a/docs/conf.py b/docs/conf.py index 43dbd2d5c..6f116025b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -24,7 +24,7 @@ author = "RocketPy Team" # The full version, including alpha/beta/rc tags -release = "1.1.5" +release = "1.2.0" # -- General configuration --------------------------------------------------- diff --git a/docs/user/installation.rst b/docs/user/installation.rst index 0822bde7d..9742bb8e3 100644 --- a/docs/user/installation.rst +++ b/docs/user/installation.rst @@ -19,7 +19,7 @@ If you want to choose a specific version to guarantee compatibility, you may ins .. code-block:: shell - pip install rocketpy==1.1.5 + pip install rocketpy==1.2.0 Optional Installation Method: ``conda`` diff --git a/setup.py b/setup.py index 326f276ec..4652c4ca9 100644 --- a/setup.py +++ b/setup.py @@ -23,7 +23,7 @@ setuptools.setup( name="rocketpy", - version="1.1.5", + version="1.2.0", install_requires=necessary_require, extras_require={ "env_analysis": env_analysis_require, From 60420bb5c06869c5b36037883c92d3f2216b7398 Mon Sep 17 00:00:00 2001 From: Lucas <69172945+lucasfourier@users.noreply.github.com> Date: Tue, 13 Feb 2024 18:44:50 -0300 Subject: [PATCH 02/41] Splitting conftest.py into several smaller files. --- tests/conftest.py | 1261 +---------------- tests/fixtures/__init__.py | 0 tests/fixtures/environment/__init__.py | 0 .../environment/environment_fixtures.py | 73 + tests/fixtures/flight/__init__.py | 0 tests/fixtures/flight/flight_fixtures.py | 160 +++ tests/fixtures/function/__init__.py | 0 tests/fixtures/function/function_fixtures.py | 140 ++ tests/fixtures/hybrid/__init__.py | 0 tests/fixtures/hybrid/hybrid_fixtures.py | 267 ++++ tests/fixtures/motor/__init__.py | 0 tests/fixtures/motor/motor_fixtures.py | 144 ++ tests/fixtures/parachutes/__init__.py | 0 .../fixtures/parachutes/parachute_fixtures.py | 87 ++ tests/fixtures/rockets/__init__.py | 0 tests/fixtures/rockets/rocket_fixtures.py | 278 ++++ tests/fixtures/surfaces/__init__.py | 0 tests/fixtures/surfaces/surface_fixtures.py | 79 ++ tests/fixtures/units/__init__.py | 0 tests/fixtures/units/numerical_fixtures.py | 28 + 20 files changed, 1269 insertions(+), 1248 deletions(-) create mode 100644 tests/fixtures/__init__.py create mode 100644 tests/fixtures/environment/__init__.py create mode 100644 tests/fixtures/environment/environment_fixtures.py create mode 100644 tests/fixtures/flight/__init__.py create mode 100644 tests/fixtures/flight/flight_fixtures.py create mode 100644 tests/fixtures/function/__init__.py create mode 100644 tests/fixtures/function/function_fixtures.py create mode 100644 tests/fixtures/hybrid/__init__.py create mode 100644 tests/fixtures/hybrid/hybrid_fixtures.py create mode 100644 tests/fixtures/motor/__init__.py create mode 100644 tests/fixtures/motor/motor_fixtures.py create mode 100644 tests/fixtures/parachutes/__init__.py create mode 100644 tests/fixtures/parachutes/parachute_fixtures.py create mode 100644 tests/fixtures/rockets/__init__.py create mode 100644 tests/fixtures/rockets/rocket_fixtures.py create mode 100644 tests/fixtures/surfaces/__init__.py create mode 100644 tests/fixtures/surfaces/surface_fixtures.py create mode 100644 tests/fixtures/units/__init__.py create mode 100644 tests/fixtures/units/numerical_fixtures.py diff --git a/tests/conftest.py b/tests/conftest.py index 9afcbbdd9..597cc9bc6 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,34 +1,21 @@ -import datetime - -import numericalunits -import numpy as np import pytest -from rocketpy import ( - CylindricalTank, - Environment, - EnvironmentAnalysis, - Flight, - Fluid, - Function, - GenericMotor, - HybridMotor, - LevelBasedTank, - LiquidMotor, - MassBasedTank, - NoseCone, - Parachute, - RailButtons, - Rocket, - SolidMotor, - SphericalTank, - Tail, - TrapezoidalFins, - UllageBasedTank, -) # Pytest configuration +pytest_plugins = [ + "tests.fixtures.environment.environment_fixtures", + "tests.fixtures.flight.flight_fixtures", + "tests.fixtures.function.function_fixtures", + "tests.fixtures.hybrid.hybrid_fixtures", + "tests.fixtures.motor.motor_fixtures", + "tests.fixtures.parachutes.parachute_fixtures", + "tests.fixtures.rockets.rocket_fixtures", + "tests.fixtures.surfaces.surface_fixtures", + "tests.fixtures.units.numerical_fixtures" +] + +# def pytest_addoption(parser): """Add option to run slow tests. This is used to skip slow tests by default. @@ -73,1243 +60,21 @@ def pytest_collection_modifyitems(config, items): item.add_marker(skip_slow) -# Fixtures -## Motors and rockets - - -@pytest.fixture -def cesaroni_m1670(): # old name: solid_motor - """Create a simple object of the SolidMotor class to be used in the tests. - This is the same motor that has been used in the getting started guide for - years. - - Returns - ------- - rocketpy.SolidMotor - A simple object of the SolidMotor class - """ - example_motor = 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="nozzle_to_combustion_chamber", - ) - return example_motor - - -@pytest.fixture -def cesaroni_m1670_shifted(): # old name: solid_motor - """Create a simple object of the SolidMotor class to be used in the tests. - This is the same motor that has been used in the getting started guide for - years. The difference relies in the thrust_source, which was shifted for - testing purposes. - - Returns - ------- - rocketpy.SolidMotor - A simple object of the SolidMotor class - """ - example_motor = SolidMotor( - thrust_source="tests/fixtures/motor/Cesaroni_M1670_shifted.eng", - burn_time=3.9, - dry_mass=1.815, - dry_inertia=(0.125, 0.125, 0.002), - center_of_dry_mass_position=0.317, - nozzle_position=0, - grain_number=5, - grain_density=1815, - nozzle_radius=33 / 1000, - throat_radius=11 / 1000, - grain_separation=5 / 1000, - grain_outer_radius=33 / 1000, - grain_initial_height=120 / 1000, - grains_center_of_mass_position=0.397, - grain_initial_inner_radius=15 / 1000, - interpolation_method="linear", - coordinate_system_orientation="nozzle_to_combustion_chamber", - reshape_thrust_curve=(5, 3000), - ) - return example_motor - - -@pytest.fixture -def calisto_motorless(): - """Create a simple object of the Rocket class to be used in the tests. This - is the same rocket that has been used in the getting started guide for years - but without a motor. - - Returns - ------- - rocketpy.Rocket - A simple object of the Rocket class - """ - calisto = 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", - ) - return calisto - - -@pytest.fixture -def calisto(calisto_motorless, cesaroni_m1670): # old name: rocket - """Create a simple object of the Rocket class to be used in the tests. This - is the same rocket that has been used in the getting started guide for - years. The Calisto rocket is the Projeto Jupiter's project launched at the - 2019 Spaceport America Cup. - - Parameters - ---------- - calisto_motorless : rocketpy.Rocket - An object of the Rocket class. This is a pytest fixture too. - cesaroni_m1670 : rocketpy.SolidMotor - An object of the SolidMotor class. This is a pytest fixture too. - - Returns - ------- - rocketpy.Rocket - A simple object of the Rocket class - """ - calisto = calisto_motorless - calisto.add_motor(cesaroni_m1670, position=-1.373) - return calisto - - -@pytest.fixture -def calisto_nose_to_tail(cesaroni_m1670): - """Create a simple object of the Rocket class to be used in the tests. This - is the same as the calisto fixture, but with the coordinate system - orientation set to "nose_to_tail" instead of "tail_to_nose". This allows to - check if the coordinate system orientation is being handled correctly in - the code. - - Parameters - ---------- - cesaroni_m1670 : rocketpy.SolidMotor - An object of the SolidMotor class. This is a pytest fixture too. - - Returns - ------- - rocketpy.Rocket - The Calisto rocket with the coordinate system orientation set to - "nose_to_tail". Rail buttons are already set, as well as the motor. - """ - calisto = 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", - ) - calisto.add_motor(cesaroni_m1670, position=1.373) - calisto.set_rail_buttons( - upper_button_position=-0.082, - lower_button_position=0.618, - angular_position=45, - ) - return calisto - - -@pytest.fixture -def calisto_liquid_modded(calisto_motorless, liquid_motor): - """Create a simple object of the Rocket class to be used in the tests. This - is an example of the Calisto rocket with a liquid motor. - - Parameters - ---------- - calisto_motorless : rocketpy.Rocket - An object of the Rocket class. This is a pytest fixture too. - liquid_motor : rocketpy.LiquidMotor - - Returns - ------- - rocketpy.Rocket - A simple object of the Rocket class - """ - calisto = calisto_motorless - calisto.add_motor(liquid_motor, position=-1.373) - return calisto - - -@pytest.fixture -def calisto_hybrid_modded(calisto_motorless, hybrid_motor): - """Create a simple object of the Rocket class to be used in the tests. This - is an example of the Calisto rocket with a hybrid motor. - - Parameters - ---------- - calisto_motorless : rocketpy.Rocket - An object of the Rocket class. This is a pytest fixture too. - hybrid_motor : rocketpy.HybridMotor - - Returns - ------- - rocketpy.Rocket - A simple object of the Rocket class - """ - calisto = calisto_motorless - calisto.add_motor(hybrid_motor, position=-1.373) - return calisto - - -@pytest.fixture -def calisto_robust( - calisto, - calisto_nose_cone, - calisto_tail, - calisto_trapezoidal_fins, - calisto_rail_buttons, - calisto_main_chute, - calisto_drogue_chute, -): - """Create an object class of the Rocket class to be used in the tests. This - is the same Calisto rocket that was defined in the calisto fixture, but with - all the aerodynamic surfaces and parachutes added. This avoids repeating the - same code in all tests. - - Parameters - ---------- - calisto : rocketpy.Rocket - An object of the Rocket class. This is a pytest fixture too. - calisto_nose_cone : rocketpy.NoseCone - The nose cone of the Calisto rocket. This is a pytest fixture too. - calisto_tail : rocketpy.Tail - The boat tail of the Calisto rocket. This is a pytest fixture too. - calisto_trapezoidal_fins : rocketpy.TrapezoidalFins - The trapezoidal fins of the Calisto rocket. This is a pytest fixture - calisto_rail_buttons : rocketpy.RailButtons - The rail buttons of the Calisto rocket. This is a pytest fixture too. - calisto_main_chute : rocketpy.Parachute - The main parachute of the Calisto rocket. This is a pytest fixture too. - calisto_drogue_chute : rocketpy.Parachute - The drogue parachute of the Calisto rocket. This is a pytest fixture - - Returns - ------- - rocketpy.Rocket - An object of the Rocket class - """ - # we follow this format: calisto.add_surfaces(surface, position) - calisto.add_surfaces(calisto_nose_cone, 1.160) - calisto.add_surfaces(calisto_tail, -1.313) - calisto.add_surfaces(calisto_trapezoidal_fins, -1.168) - # calisto.add_surfaces(calisto_rail_buttons, -1.168) - # TODO: if I use the line above, the calisto won't have rail buttons attribute - # we need to apply a check in the add_surfaces method to set the rail buttons - calisto.set_rail_buttons( - upper_button_position=0.082, - lower_button_position=-0.618, - angular_position=45, - ) - calisto.parachutes.append(calisto_main_chute) - calisto.parachutes.append(calisto_drogue_chute) - return calisto - - -@pytest.fixture -def calisto_air_brakes_clamp_on(calisto_robust, controller_function): - """Create an object class of the Rocket class to be used in the tests. This - is the same Calisto rocket that was defined in the calisto_robust fixture, - but with air brakes added, with clamping. - - Parameters - ---------- - calisto_robust : rocketpy.Rocket - An object of the Rocket class. This is a pytest fixture. - controller_function : function - A function that controls the air brakes. This is a pytest fixture. - - Returns - ------- - rocketpy.Rocket - An object of the Rocket class - """ - calisto = calisto_robust - # remove parachutes - calisto.parachutes = [] - calisto.add_air_brakes( - drag_coefficient_curve="data/calisto/air_brakes_cd.csv", - controller_function=controller_function, - sampling_rate=10, - clamp=True, - ) - return calisto - - -@pytest.fixture -def calisto_air_brakes_clamp_off(calisto_robust, controller_function): - """Create an object class of the Rocket class to be used in the tests. This - is the same Calisto rocket that was defined in the calisto_robust fixture, - but with air brakes added, without clamping. - - Parameters - ---------- - calisto_robust : rocketpy.Rocket - An object of the Rocket class. This is a pytest fixture. - controller_function : function - A function that controls the air brakes. This is a pytest fixture. - - Returns - ------- - rocketpy.Rocket - An object of the Rocket class - """ - calisto = calisto_robust - # remove parachutes - calisto.parachutes = [] - calisto.add_air_brakes( - drag_coefficient_curve="data/calisto/air_brakes_cd.csv", - controller_function=controller_function, - sampling_rate=10, - clamp=False, - ) - return calisto - - -@pytest.fixture -def pressurant_fluid(): - """An example of a pressurant fluid as N2 gas at - 273.15K and 30MPa. - - Returns - ------- - rocketpy.Fluid - An object of the Fluid class. - """ - return Fluid(name="N2", density=300) - - -@pytest.fixture -def fuel_pressurant(): - """An example of a pressurant fluid as N2 gas at - 273.15K and 2MPa. - - Returns - ------- - rocketpy.Fluid - An object of the Fluid class. - """ - return Fluid(name="N2", density=25) - - -@pytest.fixture -def oxidizer_pressurant(): - """An example of a pressurant fluid as N2 gas at - 273.15K and 3MPa. - - Returns - ------- - rocketpy.Fluid - An object of the Fluid class. - """ - return Fluid(name="N2", density=35) - - -@pytest.fixture -def fuel_fluid(): - """An example of propane as fuel fluid at - 273.15K and 2MPa. - - Returns - ------- - rocketpy.Fluid - An object of the Fluid class. - """ - return Fluid(name="Propane", density=500) - - -@pytest.fixture -def oxidizer_fluid(): - """An example of liquid oxygen as oxidizer fluid at - 100K and 3MPa. - - Returns - ------- - rocketpy.Fluid - An object of the Fluid class. - """ - return Fluid(name="O2", density=1000) - - -@pytest.fixture -def pressurant_tank(pressurant_fluid): - """An example of a pressurant cylindrical tank with spherical - caps. - - Parameters - ---------- - pressurant_fluid : rocketpy.Fluid - Pressurizing fluid. This is a pytest fixture. - - Returns - ------- - rocketpy.MassBasedTank - An object of the CylindricalTank class. - """ - geometry = CylindricalTank(0.135 / 2, 0.981, spherical_caps=True) - pressurant_tank = MassBasedTank( - name="Pressure Tank", - geometry=geometry, - liquid_mass=0, - flux_time=(8, 20), - gas_mass="data/SEBLM/pressurantMassFiltered.csv", - gas=pressurant_fluid, - liquid=pressurant_fluid, - ) - - return pressurant_tank - - -@pytest.fixture -def fuel_tank(fuel_fluid, fuel_pressurant): - """An example of a fuel cylindrical tank with spherical - caps. - - Parameters - ---------- - fuel_fluid : rocketpy.Fluid - Fuel fluid of the tank. This is a pytest fixture. - fuel_pressurant : rocketpy.Fluid - Pressurizing fluid of the fuel tank. This is a pytest - fixture. - - Returns - ------- - rocketpy.UllageBasedTank - """ - geometry = CylindricalTank(0.0744, 0.8068, spherical_caps=True) - ullage = ( - -Function("data/SEBLM/test124_Propane_Volume.csv") * 1e-3 - + geometry.total_volume - ) - fuel_tank = UllageBasedTank( - name="Propane Tank", - flux_time=(8, 20), - geometry=geometry, - liquid=fuel_fluid, - gas=fuel_pressurant, - ullage=ullage, - ) - - return fuel_tank - - -@pytest.fixture -def oxidizer_tank(oxidizer_fluid, oxidizer_pressurant): - """An example of a oxidizer cylindrical tank with spherical - caps. - - Parameters - ---------- - oxidizer_fluid : rocketpy.Fluid - Oxidizer fluid of the tank. This is a pytest fixture. - oxidizer_pressurant : rocketpy.Fluid - Pressurizing fluid of the oxidizer tank. This is a pytest - fixture. - - Returns - ------- - rocketpy.UllageBasedTank - """ - geometry = CylindricalTank(0.0744, 0.8068, spherical_caps=True) - ullage = ( - -Function("data/SEBLM/test124_Lox_Volume.csv") * 1e-3 + geometry.total_volume - ) - oxidizer_tank = UllageBasedTank( - name="Lox Tank", - flux_time=(8, 20), - geometry=geometry, - liquid=oxidizer_fluid, - gas=oxidizer_pressurant, - ullage=ullage, - ) - - return oxidizer_tank - - -@pytest.fixture -def liquid_motor(pressurant_tank, fuel_tank, oxidizer_tank): - """An example of a liquid motor with pressurant, fuel and oxidizer tanks. - - Parameters - ---------- - pressurant_tank : rocketpy.MassBasedTank - Tank that contains pressurizing fluid. This is a pytest fixture. - fuel_tank : rocketpy.UllageBasedTank - Tank that contains the motor fuel. This is a pytest fixture. - oxidizer_tank : rocketpy.UllageBasedTank - Tank that contains the motor oxidizer. This is a pytest fixture. - - Returns - ------- - rocketpy.LiquidMotor - """ - liquid_motor = LiquidMotor( - thrust_source="data/SEBLM/test124_Thrust_Curve.csv", - burn_time=(8, 20), - dry_mass=10, - dry_inertia=(5, 5, 0.2), - center_of_dry_mass_position=0, - nozzle_position=-1.364, - nozzle_radius=0.069 / 2, - ) - liquid_motor.add_tank(pressurant_tank, position=2.007) - liquid_motor.add_tank(fuel_tank, position=-1.048) - liquid_motor.add_tank(oxidizer_tank, position=0.711) - - return liquid_motor - - -@pytest.fixture -def spherical_oxidizer_tank(oxidizer_fluid, oxidizer_pressurant): - """An example of a oxidizer spherical tank. - - Parameters - ---------- - oxidizer_fluid : rocketpy.Fluid - Oxidizer fluid of the tank. This is a pytest fixture. - oxidizer_pressurant : rocketpy.Fluid - Pressurizing fluid of the oxidizer tank. This is a pytest - fixture. - - Returns - ------- - rocketpy.UllageBasedTank - """ - geometry = SphericalTank(0.05) - liquid_level = Function(lambda t: 0.1 * np.exp(-t / 2) - 0.05) - oxidizer_tank = LevelBasedTank( - name="Lox Tank", - flux_time=10, - geometry=geometry, - liquid=oxidizer_fluid, - gas=oxidizer_pressurant, - liquid_height=liquid_level, - ) - - return oxidizer_tank - - -@pytest.fixture -def hybrid_motor(spherical_oxidizer_tank): - """An example of a hybrid motor with spherical oxidizer - tank and fuel grains. - - Parameters - ---------- - spherical_oxidizer_tank : rocketpy.LevelBasedTank - Example Tank that contains the motor oxidizer. This is a - pytest fixture. - - Returns - ------- - rocketpy.HybridMotor - """ - motor = HybridMotor( - thrust_source=lambda t: 2000 - 100 * t, - burn_time=10, - center_of_dry_mass_position=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, - ) - - motor.add_tank(spherical_oxidizer_tank, position=0.3) - - return motor - - -@pytest.fixture -def generic_motor(): - """An example of a generic motor for low accuracy simulations. - - Returns - ------- - rocketpy.GenericMotor - """ - motor = GenericMotor( - 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), - ) - - return motor - - ## AeroSurfaces -@pytest.fixture -def calisto_nose_cone(): - """The nose cone of the Calisto rocket. - - Returns - ------- - rocketpy.NoseCone - The nose cone of the Calisto rocket. - """ - return NoseCone( - length=0.55829, - kind="vonkarman", - base_radius=0.0635, - rocket_radius=0.0635, - name="calisto_nose_cone", - ) - - -@pytest.fixture -def calisto_tail(): - """The boat tail of the Calisto rocket. - - Returns - ------- - rocketpy.Tail - The boat tail of the Calisto rocket. - """ - return Tail( - top_radius=0.0635, - bottom_radius=0.0435, - length=0.060, - rocket_radius=0.0635, - name="calisto_tail", - ) - - -@pytest.fixture -def calisto_trapezoidal_fins(): - """The trapezoidal fins of the Calisto rocket. - - Returns - ------- - rocketpy.TrapezoidalFins - The trapezoidal fins of the Calisto rocket. - """ - return TrapezoidalFins( - n=4, - span=0.100, - root_chord=0.120, - tip_chord=0.040, - rocket_radius=0.0635, - name="calisto_trapezoidal_fins", - cant_angle=0, - sweep_length=None, - sweep_angle=None, - airfoil=None, - ) - - -@pytest.fixture -def calisto_rail_buttons(): - """The rail buttons of the Calisto rocket. - - Returns - ------- - rocketpy.RailButtons - The rail buttons of the Calisto rocket. - """ - return RailButtons( - buttons_distance=0.7, - angular_position=45, - name="Rail Buttons", - ) - - ## Parachutes -@pytest.fixture -def calisto_drogue_parachute_trigger(): - """The trigger for the drogue parachute of the Calisto rocket. - - Returns - ------- - function - The trigger for the drogue parachute of the Calisto rocket. - """ - - def drogue_trigger(p, h, y): - # activate drogue when vertical velocity is negative - return True if y[5] < 0 else False - - return drogue_trigger - - -@pytest.fixture -def calisto_main_parachute_trigger(): - """The trigger for the main parachute of the Calisto rocket. - - Returns - ------- - function - The trigger for the main parachute of the Calisto rocket. - """ - - def main_trigger(p, h, y): - # activate main when vertical velocity is <0 and altitude is below 800m - return True if y[5] < 0 and h < 800 else False - - return main_trigger - - -@pytest.fixture -def calisto_main_chute(calisto_main_parachute_trigger): - """The main parachute of the Calisto rocket. - - Parameters - ---------- - calisto_main_parachute_trigger : function - The trigger for the main parachute of the Calisto rocket. This is a - pytest fixture too. - - Returns - ------- - rocketpy.Parachute - The main parachute of the Calisto rocket. - """ - return Parachute( - name="calisto_main_chute", - cd_s=10.0, - trigger=calisto_main_parachute_trigger, - sampling_rate=105, - lag=1.5, - noise=(0, 8.3, 0.5), - ) - - -@pytest.fixture -def calisto_drogue_chute(calisto_drogue_parachute_trigger): - """The drogue parachute of the Calisto rocket. - - Parameters - ---------- - calisto_drogue_parachute_trigger : function - The trigger for the drogue parachute of the Calisto rocket. This is a - pytest fixture too. - - Returns - ------- - rocketpy.Parachute - The drogue parachute of the Calisto rocket. - """ - return Parachute( - name="calisto_drogue_chute", - cd_s=1.0, - trigger=calisto_drogue_parachute_trigger, - sampling_rate=105, - lag=1.5, - noise=(0, 8.3, 0.5), - ) - - ## Flights -@pytest.fixture -def flight_calisto(calisto, example_env): # old name: flight - """A rocketpy.Flight object of the Calisto rocket. This uses the calisto - without the aerodynamic surfaces and parachutes. The environment is the - simplest possible, with no parameters set. - - Parameters - ---------- - calisto : rocketpy.Rocket - An object of the Rocket class. This is a pytest fixture too. - example_env : rocketpy.Environment - An object of the Environment class. This is a pytest fixture too. - - Returns - ------- - rocketpy.Flight - A rocketpy.Flight object of the Calisto rocket in the simplest possible - conditions. - """ - return Flight( - environment=example_env, - rocket=calisto, - rail_length=5.2, - inclination=85, - heading=0, - terminate_on_apogee=False, - ) - - -@pytest.fixture -def flight_calisto_nose_to_tail(calisto_nose_to_tail, example_env): - """A rocketpy.Flight object of the Calisto rocket. This uses the calisto - with "nose_to_tail" coordinate system orientation, just as described in the - calisto_nose_to_tail fixture. - - Parameters - ---------- - calisto_nose_to_tail : rocketpy.Rocket - An object of the Rocket class. This is a pytest fixture too. - example_env : rocketpy.Environment - An object of the Environment class. This is a pytest fixture too. - - Returns - ------- - rocketpy.Flight - The Calisto rocket with the coordinate system orientation set to - "nose_to_tail". - """ - return Flight( - environment=example_env, - rocket=calisto_nose_to_tail, - rail_length=5.2, - inclination=85, - heading=0, - terminate_on_apogee=False, - ) - - -@pytest.fixture -def flight_calisto_robust(calisto_robust, example_env_robust): - """A rocketpy.Flight object of the Calisto rocket. This uses the calisto - with the aerodynamic surfaces and parachutes. The environment is a bit more - complex than the one in the flight_calisto fixture. This time the latitude, - longitude and elevation are set, as well as the datum and the date. The - location refers to the Spaceport America Cup launch site, while the date is - set to tomorrow at noon. - - Parameters - ---------- - calisto_robust : rocketpy.Rocket - An object of the Rocket class. This is a pytest fixture too. - example_env_robust : rocketpy.Environment - An object of the Environment class. This is a pytest fixture too. - - Returns - ------- - rocketpy.Flight - A rocketpy.Flight object of the Calisto rocket in a more complex - condition. - """ - return Flight( - environment=example_env_robust, - rocket=calisto_robust, - rail_length=5.2, - inclination=85, - heading=0, - terminate_on_apogee=False, - ) - - -@pytest.fixture -def flight_calisto_custom_wind(calisto_robust, example_env_robust): - """A rocketpy.Flight object of the Calisto rocket. This uses the calisto - with the aerodynamic surfaces and parachutes. The environment is a bit more - complex than the one in the flight_calisto_robust fixture. Now the wind is - set to 5m/s (x direction) and 2m/s (y direction), constant with altitude. - - Parameters - ---------- - calisto_robust : rocketpy.Rocket - An object of the Rocket class. This is a pytest fixture too. - example_env_robust : rocketpy.Environment - An object of the Environment class. This is a pytest fixture too. - - Returns - ------- - rocketpy.Flight - - """ - env = example_env_robust - env.set_atmospheric_model( - type="custom_atmosphere", - temperature=300, - wind_u=[(0, 5), (4000, 5)], - wind_v=[(0, 2), (4000, 2)], - ) - return Flight( - environment=env, - rocket=calisto_robust, - rail_length=5.2, - inclination=85, - heading=0, - terminate_on_apogee=False, - ) - - -@pytest.fixture -def flight_calisto_air_brakes(calisto_air_brakes_clamp_on, example_env): - """A rocketpy.Flight object of the Calisto rocket. This uses the calisto - with the aerodynamic surfaces and air brakes. The environment is the - simplest possible, with no parameters set. The air brakes are set to clamp - the deployment level. - - Parameters - ---------- - calisto_air_brakes_clamp_on : rocketpy.Rocket - An object of the Rocket class. - example_env : rocketpy.Environment - An object of the Environment class. - - Returns - ------- - rocketpy.Flight - A rocketpy.Flight object of the Calisto rocket in a more complex - condition. - """ - return Flight( - rocket=calisto_air_brakes_clamp_on, - environment=example_env, - rail_length=5.2, - inclination=85, - heading=0, - time_overshoot=False, - terminate_on_apogee=True, - ) - - ## Dimensionless motors and rockets -@pytest.fixture -def m(): - """Create a simple object of the numericalunits.m class to be used in the - tests. This allows to avoid repeating the same code in all tests. - - Returns - ------- - numericalunits.m - A simple object of the numericalunits.m class - """ - return numericalunits.m - - -@pytest.fixture -def kg(): - """Create a simple object of the numericalunits.kg class to be used in the - tests. This allows to avoid repeating the same code in all tests. - - Returns - ------- - numericalunits.kg - A simple object of the numericalunits.kg class - """ - return numericalunits.kg - - -@pytest.fixture -def dimensionless_cesaroni_m1670(kg, m): # old name: dimensionless_motor - """The dimensionless version of the Cesaroni M1670 motor. This is the same - motor as defined in the cesaroni_m1670 fixture, but with all the parameters - converted to dimensionless values. This allows to check if the dimensions - are being handled correctly in the code. - - Parameters - ---------- - kg : numericalunits.kg - An object of the numericalunits.kg class. This is a pytest - m : numericalunits.m - An object of the numericalunits.m class. This is a pytest - - Returns - ------- - rocketpy.SolidMotor - An object of the SolidMotor class - """ - example_motor = SolidMotor( - thrust_source="data/motors/Cesaroni_M1670.eng", - burn_time=3.9, - dry_mass=1.815 * kg, - dry_inertia=( - 0.125 * (kg * m**2), - 0.125 * (kg * m**2), - 0.002 * (kg * m**2), - ), - center_of_dry_mass_position=0.317 * m, - grain_number=5, - grain_separation=5 / 1000 * m, - grain_density=1815 * (kg / m**3), - grain_outer_radius=33 / 1000 * m, - grain_initial_inner_radius=15 / 1000 * m, - grain_initial_height=120 / 1000 * m, - nozzle_radius=33 / 1000 * m, - throat_radius=11 / 1000 * m, - interpolation_method="linear", - grains_center_of_mass_position=0.397 * m, - nozzle_position=0 * m, - coordinate_system_orientation="nozzle_to_combustion_chamber", - ) - return example_motor - - -@pytest.fixture # old name: dimensionless_rocket -def dimensionless_calisto(kg, m, dimensionless_cesaroni_m1670): - """The dimensionless version of the Calisto rocket. This is the same rocket - as defined in the calisto fixture, but with all the parameters converted to - dimensionless values. This allows to check if the dimensions are being - handled correctly in the code. - - Parameters - ---------- - kg : numericalunits.kg - An object of the numericalunits.kg class. This is a pytest fixture too. - m : numericalunits.m - An object of the numericalunits.m class. This is a pytest fixture too. - dimensionless_cesaroni_m1670 : rocketpy.SolidMotor - The dimensionless version of the Cesaroni M1670 motor. This is a pytest - fixture too. - - Returns - ------- - rocketpy.Rocket - An object of the Rocket class - """ - example_rocket = Rocket( - radius=0.0635 * m, - mass=14.426 * kg, - inertia=(6.321 * (kg * m**2), 6.321 * (kg * m**2), 0.034 * (kg * m**2)), - power_off_drag="data/calisto/powerOffDragCurve.csv", - power_on_drag="data/calisto/powerOnDragCurve.csv", - center_of_mass_without_motor=0 * m, - coordinate_system_orientation="tail_to_nose", - ) - example_rocket.add_motor(dimensionless_cesaroni_m1670, position=(-1.373) * m) - return example_rocket - - ## Environment -@pytest.fixture -def example_env(): - """Create a simple object of the Environment class to be used in the tests. - This allows to avoid repeating the same code in all tests. The environment - set here is the simplest possible, with no parameters set. - - Returns - ------- - rocketpy.Environment - The simplest object of the Environment class - """ - return Environment() - - -@pytest.fixture -def example_env_robust(): - """Create an object of the Environment class to be used in the tests. This - allows to avoid repeating the same code in all tests. The environment set - here is a bit more complex than the one in the example_env fixture. This - time the latitude, longitude and elevation are set, as well as the datum and - the date. The location refers to the Spaceport America Cup launch site, - while the date is set to tomorrow at noon. - - Returns - ------- - rocketpy.Environment - An object of the Environment class - """ - env = Environment( - latitude=32.990254, - longitude=-106.974998, - elevation=1400, - datum="WGS84", - ) - tomorrow = datetime.date.today() + datetime.timedelta(days=1) - env.set_date((tomorrow.year, tomorrow.month, tomorrow.day, 12)) - return env - - -@pytest.fixture -def env_analysis(): - """Create a simple object of the Environment Analysis class to be used in - the tests. This allows to avoid repeating the same code in all tests. - - Returns - ------- - EnvironmentAnalysis - A simple object of the Environment Analysis class - """ - env_analysis = EnvironmentAnalysis( - start_date=datetime.datetime(2019, 10, 23), - end_date=datetime.datetime(2021, 10, 23), - latitude=39.3897, - longitude=-8.28896388889, - start_hour=6, - end_hour=18, - surface_data_file="./data/weather/EuroC_single_level_reanalysis_2002_2021.nc", - pressure_level_data_file="./data/weather/EuroC_pressure_levels_reanalysis_2001-2021.nc", - timezone=None, - unit_system="metric", - forecast_date=None, - forecast_args=None, - max_expected_altitude=None, - ) - - return env_analysis - - ## Functions -@pytest.fixture -def linear_func(): - """Create a linear function based on a list of points. The function - represents y = x and may be used on different tests. - - Returns - ------- - Function - A linear function representing y = x. - """ - return Function( - [[0, 0], [1, 1], [2, 2], [3, 3]], - ) - - -@pytest.fixture -def linearly_interpolated_func(): - """Create a linearly interpolated function based on a list of points. - - Returns - ------- - Function - Linearly interpolated Function, with constant extrapolation - """ - return Function( - [[0, 0], [1, 7], [2, -3], [3, -1], [4, 3]], - interpolation="linear", - extrapolation="constant", - ) - - -@pytest.fixture -def spline_interpolated_func(): - """Create a spline interpolated function based on a list of points. - - Returns - ------- - Function - Spline interpolated, with natural extrapolation - """ - return Function( - [[0, 0], [1, 7], [2, -3], [3, -1], [4, 3]], - interpolation="spline", - extrapolation="natural", - ) - - -@pytest.fixture -def func_from_csv(): - """Create a function based on a csv file. The csv file contains the - coordinates of the E473 airfoil at 10e6 degrees, but anything else could be - used here as long as it is a csv file. - - Returns - ------- - rocketpy.Function - A function based on a csv file. - """ - func = Function( - source="tests/fixtures/airfoils/e473-10e6-degrees.csv", - ) - return func - - -@pytest.fixture -def func_2d_from_csv(): - """Create a 2d function based on a csv file. - - Returns - ------- - rocketpy.Function - A function based on a csv file. - """ - # Do not define any of the optional parameters so that the tests can check - # if the defaults are being used correctly. - func = Function( - source="tests/fixtures/function/2d.csv", - ) - return func - - -## Controller -@pytest.fixture -def controller_function(): - """Create a controller function that updates the air brakes deployment level - based on the altitude and vertical velocity of the rocket. This is the same - controller function that is used in the air brakes example in the - documentation. - - Returns - ------- - function - A controller function - """ - - def controller_function( - time, sampling_rate, state, state_history, observed_variables, air_brakes - ): - z = state[2] - vz = state[5] - previous_vz = state_history[-1][5] - if time < 3.9: - return None - if z < 1500: - air_brakes.deployment_level = 0 - else: - new_deployment_level = ( - air_brakes.deployment_level + 0.1 * vz + 0.01 * previous_vz**2 - ) - if new_deployment_level > air_brakes.deployment_level + 0.2 / sampling_rate: - new_deployment_level = air_brakes.deployment_level + 0.2 / sampling_rate - elif ( - new_deployment_level < air_brakes.deployment_level - 0.2 / sampling_rate - ): - new_deployment_level = air_brakes.deployment_level - 0.2 / sampling_rate - else: - new_deployment_level = air_brakes.deployment_level - air_brakes.deployment_level = new_deployment_level - - return controller_function - - -@pytest.fixture -def lambda_quad_func(): - """Create a lambda function based on a string. - - Returns - ------- - Function - A lambda function based on a string. - """ - func = lambda x: x**2 - return Function( - source=func, - ) diff --git a/tests/fixtures/__init__.py b/tests/fixtures/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/fixtures/environment/__init__.py b/tests/fixtures/environment/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/fixtures/environment/environment_fixtures.py b/tests/fixtures/environment/environment_fixtures.py new file mode 100644 index 000000000..2864efdb3 --- /dev/null +++ b/tests/fixtures/environment/environment_fixtures.py @@ -0,0 +1,73 @@ +import datetime + +import pytest + +from rocketpy import Environment, EnvironmentAnalysis + + +@pytest.fixture +def example_env(): + """Create a simple object of the Environment class to be used in the tests. + This allows to avoid repeating the same code in all tests. The environment + set here is the simplest possible, with no parameters set. + + Returns + ------- + rocketpy.Environment + The simplest object of the Environment class + """ + return Environment() + + +@pytest.fixture +def example_env_robust(): + """Create an object of the Environment class to be used in the tests. This + allows to avoid repeating the same code in all tests. The environment set + here is a bit more complex than the one in the example_env fixture. This + time the latitude, longitude and elevation are set, as well as the datum and + the date. The location refers to the Spaceport America Cup launch site, + while the date is set to tomorrow at noon. + + Returns + ------- + rocketpy.Environment + An object of the Environment class + """ + env = Environment( + latitude=32.990254, + longitude=-106.974998, + elevation=1400, + datum="WGS84", + ) + tomorrow = datetime.date.today() + datetime.timedelta(days=1) + env.set_date((tomorrow.year, tomorrow.month, tomorrow.day, 12)) + return env + + +@pytest.fixture +def env_analysis(): + """Create a simple object of the Environment Analysis class to be used in + the tests. This allows to avoid repeating the same code in all tests. + + Returns + ------- + EnvironmentAnalysis + A simple object of the Environment Analysis class + """ + env_analysis = EnvironmentAnalysis( + start_date=datetime.datetime(2019, 10, 23), + end_date=datetime.datetime(2021, 10, 23), + latitude=39.3897, + longitude=-8.28896388889, + start_hour=6, + end_hour=18, + surface_data_file="./data/weather/EuroC_single_level_reanalysis_2002_2021.nc", + pressure_level_data_file="./data/weather/EuroC_pressure_levels_reanalysis_2001-2021.nc", + timezone=None, + unit_system="metric", + forecast_date=None, + forecast_args=None, + max_expected_altitude=None, + ) + + return env_analysis \ No newline at end of file diff --git a/tests/fixtures/flight/__init__.py b/tests/fixtures/flight/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/fixtures/flight/flight_fixtures.py b/tests/fixtures/flight/flight_fixtures.py new file mode 100644 index 000000000..a82ce8ba1 --- /dev/null +++ b/tests/fixtures/flight/flight_fixtures.py @@ -0,0 +1,160 @@ +import pytest + +from rocketpy import Flight + + +@pytest.fixture +def flight_calisto(calisto, example_env): # old name: flight + """A rocketpy.Flight object of the Calisto rocket. This uses the calisto + without the aerodynamic surfaces and parachutes. The environment is the + simplest possible, with no parameters set. + + Parameters + ---------- + calisto : rocketpy.Rocket + An object of the Rocket class. This is a pytest fixture too. + example_env : rocketpy.Environment + An object of the Environment class. This is a pytest fixture too. + + Returns + ------- + rocketpy.Flight + A rocketpy.Flight object of the Calisto rocket in the simplest possible + conditions. + """ + return Flight( + environment=example_env, + rocket=calisto, + rail_length=5.2, + inclination=85, + heading=0, + terminate_on_apogee=False, + ) + + +@pytest.fixture +def flight_calisto_nose_to_tail(calisto_nose_to_tail, example_env): + """A rocketpy.Flight object of the Calisto rocket. This uses the calisto + with "nose_to_tail" coordinate system orientation, just as described in the + calisto_nose_to_tail fixture. + + Parameters + ---------- + calisto_nose_to_tail : rocketpy.Rocket + An object of the Rocket class. This is a pytest fixture too. + example_env : rocketpy.Environment + An object of the Environment class. This is a pytest fixture too. + + Returns + ------- + rocketpy.Flight + The Calisto rocket with the coordinate system orientation set to + "nose_to_tail". + """ + return Flight( + environment=example_env, + rocket=calisto_nose_to_tail, + rail_length=5.2, + inclination=85, + heading=0, + terminate_on_apogee=False, + ) + + +@pytest.fixture +def flight_calisto_robust(calisto_robust, example_env_robust): + """A rocketpy.Flight object of the Calisto rocket. This uses the calisto + with the aerodynamic surfaces and parachutes. The environment is a bit more + complex than the one in the flight_calisto fixture. This time the latitude, + longitude and elevation are set, as well as the datum and the date. The + location refers to the Spaceport America Cup launch site, while the date is + set to tomorrow at noon. + + Parameters + ---------- + calisto_robust : rocketpy.Rocket + An object of the Rocket class. This is a pytest fixture too. + example_env_robust : rocketpy.Environment + An object of the Environment class. This is a pytest fixture too. + + Returns + ------- + rocketpy.Flight + A rocketpy.Flight object of the Calisto rocket in a more complex + condition. + """ + return Flight( + environment=example_env_robust, + rocket=calisto_robust, + rail_length=5.2, + inclination=85, + heading=0, + terminate_on_apogee=False, + ) + + +@pytest.fixture +def flight_calisto_custom_wind(calisto_robust, example_env_robust): + """A rocketpy.Flight object of the Calisto rocket. This uses the calisto + with the aerodynamic surfaces and parachutes. The environment is a bit more + complex than the one in the flight_calisto_robust fixture. Now the wind is + set to 5m/s (x direction) and 2m/s (y direction), constant with altitude. + + Parameters + ---------- + calisto_robust : rocketpy.Rocket + An object of the Rocket class. This is a pytest fixture too. + example_env_robust : rocketpy.Environment + An object of the Environment class. This is a pytest fixture too. + + Returns + ------- + rocketpy.Flight + + """ + env = example_env_robust + env.set_atmospheric_model( + type="custom_atmosphere", + temperature=300, + wind_u=[(0, 5), (4000, 5)], + wind_v=[(0, 2), (4000, 2)], + ) + return Flight( + environment=env, + rocket=calisto_robust, + rail_length=5.2, + inclination=85, + heading=0, + terminate_on_apogee=False, + ) + + +@pytest.fixture +def flight_calisto_air_brakes(calisto_air_brakes_clamp_on, example_env): + """A rocketpy.Flight object of the Calisto rocket. This uses the calisto + with the aerodynamic surfaces and air brakes. The environment is the + simplest possible, with no parameters set. The air brakes are set to clamp + the deployment level. + + Parameters + ---------- + calisto_air_brakes_clamp_on : rocketpy.Rocket + An object of the Rocket class. + example_env : rocketpy.Environment + An object of the Environment class. + + Returns + ------- + rocketpy.Flight + A rocketpy.Flight object of the Calisto rocket in a more complex + condition. + """ + return Flight( + rocket=calisto_air_brakes_clamp_on, + environment=example_env, + rail_length=5.2, + inclination=85, + heading=0, + time_overshoot=False, + terminate_on_apogee=True, + ) diff --git a/tests/fixtures/function/__init__.py b/tests/fixtures/function/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/fixtures/function/function_fixtures.py b/tests/fixtures/function/function_fixtures.py new file mode 100644 index 000000000..c52f4fecf --- /dev/null +++ b/tests/fixtures/function/function_fixtures.py @@ -0,0 +1,140 @@ +import pytest + +from rocketpy import Function + + +@pytest.fixture +def linear_func(): + """Create a linear function based on a list of points. The function + represents y = x and may be used on different tests. + + Returns + ------- + Function + A linear function representing y = x. + """ + return Function( + [[0, 0], [1, 1], [2, 2], [3, 3]], + ) + + +@pytest.fixture +def linearly_interpolated_func(): + """Create a linearly interpolated function based on a list of points. + + Returns + ------- + Function + Linearly interpolated Function, with constant extrapolation + """ + return Function( + [[0, 0], [1, 7], [2, -3], [3, -1], [4, 3]], + interpolation="linear", + extrapolation="constant", + ) + + +@pytest.fixture +def spline_interpolated_func(): + """Create a spline interpolated function based on a list of points. + + Returns + ------- + Function + Spline interpolated, with natural extrapolation + """ + return Function( + [[0, 0], [1, 7], [2, -3], [3, -1], [4, 3]], + interpolation="spline", + extrapolation="natural", + ) + + +@pytest.fixture +def func_from_csv(): + """Create a function based on a csv file. The csv file contains the + coordinates of the E473 airfoil at 10e6 degrees, but anything else could be + used here as long as it is a csv file. + + Returns + ------- + rocketpy.Function + A function based on a csv file. + """ + func = Function( + source="tests/fixtures/airfoils/e473-10e6-degrees.csv", + ) + return func + + +@pytest.fixture +def func_2d_from_csv(): + """Create a 2d function based on a csv file. + + Returns + ------- + rocketpy.Function + A function based on a csv file. + """ + # Do not define any of the optional parameters so that the tests can check + # if the defaults are being used correctly. + func = Function( + source="tests/fixtures/function/2d.csv", + ) + return func + + +## Controller +@pytest.fixture +def controller_function(): + """Create a controller function that updates the air brakes deployment level + based on the altitude and vertical velocity of the rocket. This is the same + controller function that is used in the air brakes example in the + documentation. + + Returns + ------- + function + A controller function + """ + + def controller_function( + time, sampling_rate, state, state_history, observed_variables, air_brakes + ): + z = state[2] + vz = state[5] + previous_vz = state_history[-1][5] + if time < 3.9: + return None + if z < 1500: + air_brakes.deployment_level = 0 + else: + new_deployment_level = ( + air_brakes.deployment_level + 0.1 * vz + 0.01 * previous_vz**2 + ) + if new_deployment_level > air_brakes.deployment_level + 0.2 / sampling_rate: + new_deployment_level = air_brakes.deployment_level + 0.2 / sampling_rate + elif ( + new_deployment_level < air_brakes.deployment_level - 0.2 / sampling_rate + ): + new_deployment_level = air_brakes.deployment_level - 0.2 / sampling_rate + else: + new_deployment_level = air_brakes.deployment_level + air_brakes.deployment_level = new_deployment_level + + return controller_function + + +@pytest.fixture +def lambda_quad_func(): + """Create a lambda function based on a string. + + Returns + ------- + Function + A lambda function based on a string. + """ + func = lambda x: x**2 + return Function( + source=func, + ) \ No newline at end of file diff --git a/tests/fixtures/hybrid/__init__.py b/tests/fixtures/hybrid/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/fixtures/hybrid/hybrid_fixtures.py b/tests/fixtures/hybrid/hybrid_fixtures.py new file mode 100644 index 000000000..471e7a8bf --- /dev/null +++ b/tests/fixtures/hybrid/hybrid_fixtures.py @@ -0,0 +1,267 @@ +import numpy as np +import pytest + +from rocketpy import (CylindricalTank, Fluid, Function, HybridMotor, + LevelBasedTank, LiquidMotor, MassBasedTank, + SphericalTank, UllageBasedTank) + + +@pytest.fixture +def pressurant_fluid(): + """An example of a pressurant fluid as N2 gas at + 273.15K and 30MPa. + + Returns + ------- + rocketpy.Fluid + An object of the Fluid class. + """ + return Fluid(name="N2", density=300) + + +@pytest.fixture +def fuel_pressurant(): + """An example of a pressurant fluid as N2 gas at + 273.15K and 2MPa. + + Returns + ------- + rocketpy.Fluid + An object of the Fluid class. + """ + return Fluid(name="N2", density=25) + + +@pytest.fixture +def oxidizer_pressurant(): + """An example of a pressurant fluid as N2 gas at + 273.15K and 3MPa. + + Returns + ------- + rocketpy.Fluid + An object of the Fluid class. + """ + return Fluid(name="N2", density=35) + + +@pytest.fixture +def fuel_fluid(): + """An example of propane as fuel fluid at + 273.15K and 2MPa. + + Returns + ------- + rocketpy.Fluid + An object of the Fluid class. + """ + return Fluid(name="Propane", density=500) + + +@pytest.fixture +def oxidizer_fluid(): + """An example of liquid oxygen as oxidizer fluid at + 100K and 3MPa. + + Returns + ------- + rocketpy.Fluid + An object of the Fluid class. + """ + return Fluid(name="O2", density=1000) + + +@pytest.fixture +def pressurant_tank(pressurant_fluid): + """An example of a pressurant cylindrical tank with spherical + caps. + + Parameters + ---------- + pressurant_fluid : rocketpy.Fluid + Pressurizing fluid. This is a pytest fixture. + + Returns + ------- + rocketpy.MassBasedTank + An object of the CylindricalTank class. + """ + geometry = CylindricalTank(0.135 / 2, 0.981, spherical_caps=True) + pressurant_tank = MassBasedTank( + name="Pressure Tank", + geometry=geometry, + liquid_mass=0, + flux_time=(8, 20), + gas_mass="data/SEBLM/pressurantMassFiltered.csv", + gas=pressurant_fluid, + liquid=pressurant_fluid, + ) + + return pressurant_tank + + +@pytest.fixture +def fuel_tank(fuel_fluid, fuel_pressurant): + """An example of a fuel cylindrical tank with spherical + caps. + + Parameters + ---------- + fuel_fluid : rocketpy.Fluid + Fuel fluid of the tank. This is a pytest fixture. + fuel_pressurant : rocketpy.Fluid + Pressurizing fluid of the fuel tank. This is a pytest + fixture. + + Returns + ------- + rocketpy.UllageBasedTank + """ + geometry = CylindricalTank(0.0744, 0.8068, spherical_caps=True) + ullage = ( + -Function("data/SEBLM/test124_Propane_Volume.csv") * 1e-3 + + geometry.total_volume + ) + fuel_tank = UllageBasedTank( + name="Propane Tank", + flux_time=(8, 20), + geometry=geometry, + liquid=fuel_fluid, + gas=fuel_pressurant, + ullage=ullage, + ) + + return fuel_tank + + +@pytest.fixture +def oxidizer_tank(oxidizer_fluid, oxidizer_pressurant): + """An example of a oxidizer cylindrical tank with spherical + caps. + + Parameters + ---------- + oxidizer_fluid : rocketpy.Fluid + Oxidizer fluid of the tank. This is a pytest fixture. + oxidizer_pressurant : rocketpy.Fluid + Pressurizing fluid of the oxidizer tank. This is a pytest + fixture. + + Returns + ------- + rocketpy.UllageBasedTank + """ + geometry = CylindricalTank(0.0744, 0.8068, spherical_caps=True) + ullage = ( + -Function("data/SEBLM/test124_Lox_Volume.csv") * 1e-3 + geometry.total_volume + ) + oxidizer_tank = UllageBasedTank( + name="Lox Tank", + flux_time=(8, 20), + geometry=geometry, + liquid=oxidizer_fluid, + gas=oxidizer_pressurant, + ullage=ullage, + ) + + return oxidizer_tank + + +@pytest.fixture +def liquid_motor(pressurant_tank, fuel_tank, oxidizer_tank): + """An example of a liquid motor with pressurant, fuel and oxidizer tanks. + + Parameters + ---------- + pressurant_tank : rocketpy.MassBasedTank + Tank that contains pressurizing fluid. This is a pytest fixture. + fuel_tank : rocketpy.UllageBasedTank + Tank that contains the motor fuel. This is a pytest fixture. + oxidizer_tank : rocketpy.UllageBasedTank + Tank that contains the motor oxidizer. This is a pytest fixture. + + Returns + ------- + rocketpy.LiquidMotor + """ + liquid_motor = LiquidMotor( + thrust_source="data/SEBLM/test124_Thrust_Curve.csv", + burn_time=(8, 20), + dry_mass=10, + dry_inertia=(5, 5, 0.2), + center_of_dry_mass_position=0, + nozzle_position=-1.364, + nozzle_radius=0.069 / 2, + ) + liquid_motor.add_tank(pressurant_tank, position=2.007) + liquid_motor.add_tank(fuel_tank, position=-1.048) + liquid_motor.add_tank(oxidizer_tank, position=0.711) + + return liquid_motor + + +@pytest.fixture +def spherical_oxidizer_tank(oxidizer_fluid, oxidizer_pressurant): + """An example of a oxidizer spherical tank. + + Parameters + ---------- + oxidizer_fluid : rocketpy.Fluid + Oxidizer fluid of the tank. This is a pytest fixture. + oxidizer_pressurant : rocketpy.Fluid + Pressurizing fluid of the oxidizer tank. This is a pytest + fixture. + + Returns + ------- + rocketpy.UllageBasedTank + """ + geometry = SphericalTank(0.05) + liquid_level = Function(lambda t: 0.1 * np.exp(-t / 2) - 0.05) + oxidizer_tank = LevelBasedTank( + name="Lox Tank", + flux_time=10, + geometry=geometry, + liquid=oxidizer_fluid, + gas=oxidizer_pressurant, + liquid_height=liquid_level, + ) + + return oxidizer_tank + + +@pytest.fixture +def hybrid_motor(spherical_oxidizer_tank): + """An example of a hybrid motor with spherical oxidizer + tank and fuel grains. + + Parameters + ---------- + spherical_oxidizer_tank : rocketpy.LevelBasedTank + Example Tank that contains the motor oxidizer. This is a + pytest fixture. + + Returns + ------- + rocketpy.HybridMotor + """ + motor = HybridMotor( + thrust_source=lambda t: 2000 - 100 * t, + burn_time=10, + center_of_dry_mass_position=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, + ) + + motor.add_tank(spherical_oxidizer_tank, position=0.3) + + return motor diff --git a/tests/fixtures/motor/__init__.py b/tests/fixtures/motor/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/fixtures/motor/motor_fixtures.py b/tests/fixtures/motor/motor_fixtures.py new file mode 100644 index 000000000..ca374164a --- /dev/null +++ b/tests/fixtures/motor/motor_fixtures.py @@ -0,0 +1,144 @@ +import pytest + +from rocketpy import GenericMotor, SolidMotor + +# Fixtures +## Motors and rockets + + + +@pytest.fixture +def cesaroni_m1670(): # old name: solid_motor + """Create a simple object of the SolidMotor class to be used in the tests. + This is the same motor that has been used in the getting started guide for + years. + + Returns + ------- + rocketpy.SolidMotor + A simple object of the SolidMotor class + """ + example_motor = 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="nozzle_to_combustion_chamber", + ) + return example_motor + + +@pytest.fixture +def cesaroni_m1670_shifted(): # old name: solid_motor + """Create a simple object of the SolidMotor class to be used in the tests. + This is the same motor that has been used in the getting started guide for + years. The difference relies in the thrust_source, which was shifted for + testing purposes. + + Returns + ------- + rocketpy.SolidMotor + A simple object of the SolidMotor class + """ + example_motor = SolidMotor( + thrust_source="tests/fixtures/motor/Cesaroni_M1670_shifted.eng", + burn_time=3.9, + dry_mass=1.815, + dry_inertia=(0.125, 0.125, 0.002), + center_of_dry_mass_position=0.317, + nozzle_position=0, + grain_number=5, + grain_density=1815, + nozzle_radius=33 / 1000, + throat_radius=11 / 1000, + grain_separation=5 / 1000, + grain_outer_radius=33 / 1000, + grain_initial_height=120 / 1000, + grains_center_of_mass_position=0.397, + grain_initial_inner_radius=15 / 1000, + interpolation_method="linear", + coordinate_system_orientation="nozzle_to_combustion_chamber", + reshape_thrust_curve=(5, 3000), + ) + return example_motor + + +@pytest.fixture +def dimensionless_cesaroni_m1670(kg, m): # old name: dimensionless_motor + """The dimensionless version of the Cesaroni M1670 motor. This is the same + motor as defined in the cesaroni_m1670 fixture, but with all the parameters + converted to dimensionless values. This allows to check if the dimensions + are being handled correctly in the code. + + Parameters + ---------- + kg : numericalunits.kg + An object of the numericalunits.kg class. This is a pytest + m : numericalunits.m + An object of the numericalunits.m class. This is a pytest + + Returns + ------- + rocketpy.SolidMotor + An object of the SolidMotor class + """ + example_motor = SolidMotor( + thrust_source="data/motors/Cesaroni_M1670.eng", + burn_time=3.9, + dry_mass=1.815 * kg, + dry_inertia=( + 0.125 * (kg * m**2), + 0.125 * (kg * m**2), + 0.002 * (kg * m**2), + ), + center_of_dry_mass_position=0.317 * m, + grain_number=5, + grain_separation=5 / 1000 * m, + grain_density=1815 * (kg / m**3), + grain_outer_radius=33 / 1000 * m, + grain_initial_inner_radius=15 / 1000 * m, + grain_initial_height=120 / 1000 * m, + nozzle_radius=33 / 1000 * m, + throat_radius=11 / 1000 * m, + interpolation_method="linear", + grains_center_of_mass_position=0.397 * m, + nozzle_position=0 * m, + coordinate_system_orientation="nozzle_to_combustion_chamber", + ) + return example_motor + + +@pytest.fixture +def generic_motor(): + """An example of a generic motor for low accuracy simulations. + + Returns + ------- + rocketpy.GenericMotor + """ + motor = GenericMotor( + 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), + ) + + return motor \ No newline at end of file diff --git a/tests/fixtures/parachutes/__init__.py b/tests/fixtures/parachutes/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/fixtures/parachutes/parachute_fixtures.py b/tests/fixtures/parachutes/parachute_fixtures.py new file mode 100644 index 000000000..10dbd36d1 --- /dev/null +++ b/tests/fixtures/parachutes/parachute_fixtures.py @@ -0,0 +1,87 @@ +import pytest + +from rocketpy import Parachute + + +@pytest.fixture +def calisto_drogue_parachute_trigger(): + """The trigger for the drogue parachute of the Calisto rocket. + + Returns + ------- + function + The trigger for the drogue parachute of the Calisto rocket. + """ + + def drogue_trigger(p, h, y): + # activate drogue when vertical velocity is negative + return True if y[5] < 0 else False + + return drogue_trigger + + +@pytest.fixture +def calisto_main_parachute_trigger(): + """The trigger for the main parachute of the Calisto rocket. + + Returns + ------- + function + The trigger for the main parachute of the Calisto rocket. + """ + + def main_trigger(p, h, y): + # activate main when vertical velocity is <0 and altitude is below 800m + return True if y[5] < 0 and h < 800 else False + + return main_trigger + + +@pytest.fixture +def calisto_main_chute(calisto_main_parachute_trigger): + """The main parachute of the Calisto rocket. + + Parameters + ---------- + calisto_main_parachute_trigger : function + The trigger for the main parachute of the Calisto rocket. This is a + pytest fixture too. + + Returns + ------- + rocketpy.Parachute + The main parachute of the Calisto rocket. + """ + return Parachute( + name="calisto_main_chute", + cd_s=10.0, + trigger=calisto_main_parachute_trigger, + sampling_rate=105, + lag=1.5, + noise=(0, 8.3, 0.5), + ) + + +@pytest.fixture +def calisto_drogue_chute(calisto_drogue_parachute_trigger): + """The drogue parachute of the Calisto rocket. + + Parameters + ---------- + calisto_drogue_parachute_trigger : function + The trigger for the drogue parachute of the Calisto rocket. This is a + pytest fixture too. + + Returns + ------- + rocketpy.Parachute + The drogue parachute of the Calisto rocket. + """ + return Parachute( + name="calisto_drogue_chute", + cd_s=1.0, + trigger=calisto_drogue_parachute_trigger, + sampling_rate=105, + lag=1.5, + noise=(0, 8.3, 0.5), + ) diff --git a/tests/fixtures/rockets/__init__.py b/tests/fixtures/rockets/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/fixtures/rockets/rocket_fixtures.py b/tests/fixtures/rockets/rocket_fixtures.py new file mode 100644 index 000000000..e471e654b --- /dev/null +++ b/tests/fixtures/rockets/rocket_fixtures.py @@ -0,0 +1,278 @@ +import pytest + +from rocketpy import Rocket + + +@pytest.fixture +def calisto_motorless(): + """Create a simple object of the Rocket class to be used in the tests. This + is the same rocket that has been used in the getting started guide for years + but without a motor. + + Returns + ------- + rocketpy.Rocket + A simple object of the Rocket class + """ + calisto = 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", + ) + return calisto + + +@pytest.fixture +def calisto(calisto_motorless, cesaroni_m1670): # old name: rocket + """Create a simple object of the Rocket class to be used in the tests. This + is the same rocket that has been used in the getting started guide for + years. The Calisto rocket is the Projeto Jupiter's project launched at the + 2019 Spaceport America Cup. + + Parameters + ---------- + calisto_motorless : rocketpy.Rocket + An object of the Rocket class. This is a pytest fixture too. + cesaroni_m1670 : rocketpy.SolidMotor + An object of the SolidMotor class. This is a pytest fixture too. + + Returns + ------- + rocketpy.Rocket + A simple object of the Rocket class + """ + calisto = calisto_motorless + calisto.add_motor(cesaroni_m1670, position=-1.373) + return calisto + + +@pytest.fixture +def calisto_nose_to_tail(cesaroni_m1670): + """Create a simple object of the Rocket class to be used in the tests. This + is the same as the calisto fixture, but with the coordinate system + orientation set to "nose_to_tail" instead of "tail_to_nose". This allows to + check if the coordinate system orientation is being handled correctly in + the code. + + Parameters + ---------- + cesaroni_m1670 : rocketpy.SolidMotor + An object of the SolidMotor class. This is a pytest fixture too. + + Returns + ------- + rocketpy.Rocket + The Calisto rocket with the coordinate system orientation set to + "nose_to_tail". Rail buttons are already set, as well as the motor. + """ + calisto = 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", + ) + calisto.add_motor(cesaroni_m1670, position=1.373) + calisto.set_rail_buttons( + upper_button_position=-0.082, + lower_button_position=0.618, + angular_position=45, + ) + return calisto + + +@pytest.fixture +def calisto_liquid_modded(calisto_motorless, liquid_motor): + """Create a simple object of the Rocket class to be used in the tests. This + is an example of the Calisto rocket with a liquid motor. + + Parameters + ---------- + calisto_motorless : rocketpy.Rocket + An object of the Rocket class. This is a pytest fixture too. + liquid_motor : rocketpy.LiquidMotor + + Returns + ------- + rocketpy.Rocket + A simple object of the Rocket class + """ + calisto = calisto_motorless + calisto.add_motor(liquid_motor, position=-1.373) + return calisto + + +@pytest.fixture +def calisto_hybrid_modded(calisto_motorless, hybrid_motor): + """Create a simple object of the Rocket class to be used in the tests. This + is an example of the Calisto rocket with a hybrid motor. + + Parameters + ---------- + calisto_motorless : rocketpy.Rocket + An object of the Rocket class. This is a pytest fixture too. + hybrid_motor : rocketpy.HybridMotor + + Returns + ------- + rocketpy.Rocket + A simple object of the Rocket class + """ + calisto = calisto_motorless + calisto.add_motor(hybrid_motor, position=-1.373) + return calisto + + +@pytest.fixture +def calisto_robust( + calisto, + calisto_nose_cone, + calisto_tail, + calisto_trapezoidal_fins, + calisto_rail_buttons, + calisto_main_chute, + calisto_drogue_chute, +): + """Create an object class of the Rocket class to be used in the tests. This + is the same Calisto rocket that was defined in the calisto fixture, but with + all the aerodynamic surfaces and parachutes added. This avoids repeating the + same code in all tests. + + Parameters + ---------- + calisto : rocketpy.Rocket + An object of the Rocket class. This is a pytest fixture too. + calisto_nose_cone : rocketpy.NoseCone + The nose cone of the Calisto rocket. This is a pytest fixture too. + calisto_tail : rocketpy.Tail + The boat tail of the Calisto rocket. This is a pytest fixture too. + calisto_trapezoidal_fins : rocketpy.TrapezoidalFins + The trapezoidal fins of the Calisto rocket. This is a pytest fixture + calisto_rail_buttons : rocketpy.RailButtons + The rail buttons of the Calisto rocket. This is a pytest fixture too. + calisto_main_chute : rocketpy.Parachute + The main parachute of the Calisto rocket. This is a pytest fixture too. + calisto_drogue_chute : rocketpy.Parachute + The drogue parachute of the Calisto rocket. This is a pytest fixture + + Returns + ------- + rocketpy.Rocket + An object of the Rocket class + """ + # we follow this format: calisto.add_surfaces(surface, position) + calisto.add_surfaces(calisto_nose_cone, 1.160) + calisto.add_surfaces(calisto_tail, -1.313) + calisto.add_surfaces(calisto_trapezoidal_fins, -1.168) + # calisto.add_surfaces(calisto_rail_buttons, -1.168) + # TODO: if I use the line above, the calisto won't have rail buttons attribute + # we need to apply a check in the add_surfaces method to set the rail buttons + calisto.set_rail_buttons( + upper_button_position=0.082, + lower_button_position=-0.618, + angular_position=45, + ) + calisto.parachutes.append(calisto_main_chute) + calisto.parachutes.append(calisto_drogue_chute) + return calisto + + +@pytest.fixture +def calisto_air_brakes_clamp_on(calisto_robust, controller_function): + """Create an object class of the Rocket class to be used in the tests. This + is the same Calisto rocket that was defined in the calisto_robust fixture, + but with air brakes added, with clamping. + + Parameters + ---------- + calisto_robust : rocketpy.Rocket + An object of the Rocket class. This is a pytest fixture. + controller_function : function + A function that controls the air brakes. This is a pytest fixture. + + Returns + ------- + rocketpy.Rocket + An object of the Rocket class + """ + calisto = calisto_robust + # remove parachutes + calisto.parachutes = [] + calisto.add_air_brakes( + drag_coefficient_curve="data/calisto/air_brakes_cd.csv", + controller_function=controller_function, + sampling_rate=10, + clamp=True, + ) + return calisto + + +@pytest.fixture +def calisto_air_brakes_clamp_off(calisto_robust, controller_function): + """Create an object class of the Rocket class to be used in the tests. This + is the same Calisto rocket that was defined in the calisto_robust fixture, + but with air brakes added, without clamping. + + Parameters + ---------- + calisto_robust : rocketpy.Rocket + An object of the Rocket class. This is a pytest fixture. + controller_function : function + A function that controls the air brakes. This is a pytest fixture. + + Returns + ------- + rocketpy.Rocket + An object of the Rocket class + """ + calisto = calisto_robust + # remove parachutes + calisto.parachutes = [] + calisto.add_air_brakes( + drag_coefficient_curve="data/calisto/air_brakes_cd.csv", + controller_function=controller_function, + sampling_rate=10, + clamp=False, + ) + return calisto + + +@pytest.fixture # old name: dimensionless_rocket +def dimensionless_calisto(kg, m, dimensionless_cesaroni_m1670): + """The dimensionless version of the Calisto rocket. This is the same rocket + as defined in the calisto fixture, but with all the parameters converted to + dimensionless values. This allows to check if the dimensions are being + handled correctly in the code. + + Parameters + ---------- + kg : numericalunits.kg + An object of the numericalunits.kg class. This is a pytest fixture too. + m : numericalunits.m + An object of the numericalunits.m class. This is a pytest fixture too. + dimensionless_cesaroni_m1670 : rocketpy.SolidMotor + The dimensionless version of the Cesaroni M1670 motor. This is a pytest + fixture too. + + Returns + ------- + rocketpy.Rocket + An object of the Rocket class + """ + example_rocket = Rocket( + radius=0.0635 * m, + mass=14.426 * kg, + inertia=(6.321 * (kg * m**2), 6.321 * (kg * m**2), 0.034 * (kg * m**2)), + power_off_drag="data/calisto/powerOffDragCurve.csv", + power_on_drag="data/calisto/powerOnDragCurve.csv", + center_of_mass_without_motor=0 * m, + coordinate_system_orientation="tail_to_nose", + ) + example_rocket.add_motor(dimensionless_cesaroni_m1670, position=(-1.373) * m) + return example_rocket \ No newline at end of file diff --git a/tests/fixtures/surfaces/__init__.py b/tests/fixtures/surfaces/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/fixtures/surfaces/surface_fixtures.py b/tests/fixtures/surfaces/surface_fixtures.py new file mode 100644 index 000000000..795c8e8bd --- /dev/null +++ b/tests/fixtures/surfaces/surface_fixtures.py @@ -0,0 +1,79 @@ +import pytest + +from rocketpy import NoseCone, RailButtons, Tail, TrapezoidalFins + + +@pytest.fixture +def calisto_nose_cone(): + """The nose cone of the Calisto rocket. + + Returns + ------- + rocketpy.NoseCone + The nose cone of the Calisto rocket. + """ + return NoseCone( + length=0.55829, + kind="vonkarman", + base_radius=0.0635, + rocket_radius=0.0635, + name="calisto_nose_cone", + ) + + +@pytest.fixture +def calisto_tail(): + """The boat tail of the Calisto rocket. + + Returns + ------- + rocketpy.Tail + The boat tail of the Calisto rocket. + """ + return Tail( + top_radius=0.0635, + bottom_radius=0.0435, + length=0.060, + rocket_radius=0.0635, + name="calisto_tail", + ) + + +@pytest.fixture +def calisto_trapezoidal_fins(): + """The trapezoidal fins of the Calisto rocket. + + Returns + ------- + rocketpy.TrapezoidalFins + The trapezoidal fins of the Calisto rocket. + """ + return TrapezoidalFins( + n=4, + span=0.100, + root_chord=0.120, + tip_chord=0.040, + rocket_radius=0.0635, + name="calisto_trapezoidal_fins", + cant_angle=0, + sweep_length=None, + sweep_angle=None, + airfoil=None, + ) + + +@pytest.fixture +def calisto_rail_buttons(): + """The rail buttons of the Calisto rocket. + + Returns + ------- + rocketpy.RailButtons + The rail buttons of the Calisto rocket. + """ + return RailButtons( + buttons_distance=0.7, + angular_position=45, + name="Rail Buttons", + ) + diff --git a/tests/fixtures/units/__init__.py b/tests/fixtures/units/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/fixtures/units/numerical_fixtures.py b/tests/fixtures/units/numerical_fixtures.py new file mode 100644 index 000000000..0b0bb0e0d --- /dev/null +++ b/tests/fixtures/units/numerical_fixtures.py @@ -0,0 +1,28 @@ +import numericalunits +import pytest + + +@pytest.fixture +def m(): + """Create a simple object of the numericalunits.m class to be used in the + tests. This allows to avoid repeating the same code in all tests. + + Returns + ------- + numericalunits.m + A simple object of the numericalunits.m class + """ + return numericalunits.m + + +@pytest.fixture +def kg(): + """Create a simple object of the numericalunits.kg class to be used in the + tests. This allows to avoid repeating the same code in all tests. + + Returns + ------- + numericalunits.kg + A simple object of the numericalunits.kg class + """ + return numericalunits.kg \ No newline at end of file From dce20809f94b2d2d9a328a309df8d31fe15405c7 Mon Sep 17 00:00:00 2001 From: Lint Action Date: Tue, 13 Feb 2024 21:53:05 +0000 Subject: [PATCH 03/41] Fix code style issues with Black --- tests/conftest.py | 5 ++--- tests/fixtures/environment/environment_fixtures.py | 2 +- tests/fixtures/function/function_fixtures.py | 2 +- tests/fixtures/hybrid/hybrid_fixtures.py | 14 +++++++++++--- tests/fixtures/motor/motor_fixtures.py | 3 +-- tests/fixtures/rockets/rocket_fixtures.py | 2 +- tests/fixtures/surfaces/surface_fixtures.py | 1 - tests/fixtures/units/numerical_fixtures.py | 2 +- 8 files changed, 18 insertions(+), 13 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 597cc9bc6..88e6fa683 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -12,11 +12,12 @@ "tests.fixtures.parachutes.parachute_fixtures", "tests.fixtures.rockets.rocket_fixtures", "tests.fixtures.surfaces.surface_fixtures", - "tests.fixtures.units.numerical_fixtures" + "tests.fixtures.units.numerical_fixtures", ] # + def pytest_addoption(parser): """Add option to run slow tests. This is used to skip slow tests by default. @@ -76,5 +77,3 @@ def pytest_collection_modifyitems(config, items): ## Functions - - diff --git a/tests/fixtures/environment/environment_fixtures.py b/tests/fixtures/environment/environment_fixtures.py index 2864efdb3..54bc8bcce 100644 --- a/tests/fixtures/environment/environment_fixtures.py +++ b/tests/fixtures/environment/environment_fixtures.py @@ -70,4 +70,4 @@ def env_analysis(): max_expected_altitude=None, ) - return env_analysis \ No newline at end of file + return env_analysis diff --git a/tests/fixtures/function/function_fixtures.py b/tests/fixtures/function/function_fixtures.py index c52f4fecf..566e4d115 100644 --- a/tests/fixtures/function/function_fixtures.py +++ b/tests/fixtures/function/function_fixtures.py @@ -137,4 +137,4 @@ def lambda_quad_func(): func = lambda x: x**2 return Function( source=func, - ) \ No newline at end of file + ) diff --git a/tests/fixtures/hybrid/hybrid_fixtures.py b/tests/fixtures/hybrid/hybrid_fixtures.py index 471e7a8bf..9c3d8c8dc 100644 --- a/tests/fixtures/hybrid/hybrid_fixtures.py +++ b/tests/fixtures/hybrid/hybrid_fixtures.py @@ -1,9 +1,17 @@ import numpy as np import pytest -from rocketpy import (CylindricalTank, Fluid, Function, HybridMotor, - LevelBasedTank, LiquidMotor, MassBasedTank, - SphericalTank, UllageBasedTank) +from rocketpy import ( + CylindricalTank, + Fluid, + Function, + HybridMotor, + LevelBasedTank, + LiquidMotor, + MassBasedTank, + SphericalTank, + UllageBasedTank, +) @pytest.fixture diff --git a/tests/fixtures/motor/motor_fixtures.py b/tests/fixtures/motor/motor_fixtures.py index ca374164a..f53457d49 100644 --- a/tests/fixtures/motor/motor_fixtures.py +++ b/tests/fixtures/motor/motor_fixtures.py @@ -6,7 +6,6 @@ ## Motors and rockets - @pytest.fixture def cesaroni_m1670(): # old name: solid_motor """Create a simple object of the SolidMotor class to be used in the tests. @@ -141,4 +140,4 @@ def generic_motor(): dry_inertia=(0.2, 0.2, 0.08), ) - return motor \ No newline at end of file + return motor diff --git a/tests/fixtures/rockets/rocket_fixtures.py b/tests/fixtures/rockets/rocket_fixtures.py index e471e654b..3a1c82988 100644 --- a/tests/fixtures/rockets/rocket_fixtures.py +++ b/tests/fixtures/rockets/rocket_fixtures.py @@ -275,4 +275,4 @@ def dimensionless_calisto(kg, m, dimensionless_cesaroni_m1670): coordinate_system_orientation="tail_to_nose", ) example_rocket.add_motor(dimensionless_cesaroni_m1670, position=(-1.373) * m) - return example_rocket \ No newline at end of file + return example_rocket diff --git a/tests/fixtures/surfaces/surface_fixtures.py b/tests/fixtures/surfaces/surface_fixtures.py index 795c8e8bd..10595205f 100644 --- a/tests/fixtures/surfaces/surface_fixtures.py +++ b/tests/fixtures/surfaces/surface_fixtures.py @@ -76,4 +76,3 @@ def calisto_rail_buttons(): angular_position=45, name="Rail Buttons", ) - diff --git a/tests/fixtures/units/numerical_fixtures.py b/tests/fixtures/units/numerical_fixtures.py index 0b0bb0e0d..8bc6883cf 100644 --- a/tests/fixtures/units/numerical_fixtures.py +++ b/tests/fixtures/units/numerical_fixtures.py @@ -25,4 +25,4 @@ def kg(): numericalunits.kg A simple object of the numericalunits.kg class """ - return numericalunits.kg \ No newline at end of file + return numericalunits.kg From 560fe321e1242bf8acb887d1e8ee0363f5617b48 Mon Sep 17 00:00:00 2001 From: Lucas <69172945+lucasfourier@users.noreply.github.com> Date: Tue, 13 Feb 2024 21:49:10 -0300 Subject: [PATCH 04/41] Addressing review comments. --- tests/conftest.py | 27 +- .../environment/environment_fixtures.py | 2 +- tests/fixtures/function/function_fixtures.py | 2 +- .../fixtures/motor/generic_motor_fixtures.py | 30 +++ tests/fixtures/motor/hybrid_fixtures.py | 40 +++ tests/fixtures/motor/liquid_fixtures.py | 237 ++++++++++++++++++ tests/fixtures/motor/solid_motor_fixtures.py | 119 +++++++++ tests/fixtures/rockets/rocket_fixtures.py | 2 +- tests/fixtures/surfaces/surface_fixtures.py | 1 - tests/fixtures/units/numerical_fixtures.py | 2 +- 10 files changed, 434 insertions(+), 28 deletions(-) create mode 100644 tests/fixtures/motor/generic_motor_fixtures.py create mode 100644 tests/fixtures/motor/hybrid_fixtures.py create mode 100644 tests/fixtures/motor/liquid_fixtures.py create mode 100644 tests/fixtures/motor/solid_motor_fixtures.py diff --git a/tests/conftest.py b/tests/conftest.py index 597cc9bc6..a8632d356 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,8 +7,9 @@ "tests.fixtures.environment.environment_fixtures", "tests.fixtures.flight.flight_fixtures", "tests.fixtures.function.function_fixtures", - "tests.fixtures.hybrid.hybrid_fixtures", - "tests.fixtures.motor.motor_fixtures", + "tests.fixtures.motor.hybrid_fixtures", + "tests.fixtures.motor.solid_motor_fixtures", + "tests.fixtures.motor.liquid_fixtures", "tests.fixtures.parachutes.parachute_fixtures", "tests.fixtures.rockets.rocket_fixtures", "tests.fixtures.surfaces.surface_fixtures", @@ -57,24 +58,4 @@ def pytest_collection_modifyitems(config, items): skip_slow = pytest.mark.skip(reason="need --runslow option to run") for item in items: if "slow" in item.keywords: - item.add_marker(skip_slow) - - -## AeroSurfaces - - -## Parachutes - - -## Flights - - -## Dimensionless motors and rockets - - -## Environment - - -## Functions - - + item.add_marker(skip_slow) \ No newline at end of file diff --git a/tests/fixtures/environment/environment_fixtures.py b/tests/fixtures/environment/environment_fixtures.py index 2864efdb3..54bc8bcce 100644 --- a/tests/fixtures/environment/environment_fixtures.py +++ b/tests/fixtures/environment/environment_fixtures.py @@ -70,4 +70,4 @@ def env_analysis(): max_expected_altitude=None, ) - return env_analysis \ No newline at end of file + return env_analysis diff --git a/tests/fixtures/function/function_fixtures.py b/tests/fixtures/function/function_fixtures.py index c52f4fecf..566e4d115 100644 --- a/tests/fixtures/function/function_fixtures.py +++ b/tests/fixtures/function/function_fixtures.py @@ -137,4 +137,4 @@ def lambda_quad_func(): func = lambda x: x**2 return Function( source=func, - ) \ No newline at end of file + ) diff --git a/tests/fixtures/motor/generic_motor_fixtures.py b/tests/fixtures/motor/generic_motor_fixtures.py new file mode 100644 index 000000000..925716cb5 --- /dev/null +++ b/tests/fixtures/motor/generic_motor_fixtures.py @@ -0,0 +1,30 @@ +import pytest + +from rocketpy import GenericMotor + +# Fixtures +## Motors and rockets + + +@pytest.fixture +def generic_motor(): + """An example of a generic motor for low accuracy simulations. + + Returns + ------- + rocketpy.GenericMotor + """ + motor = GenericMotor( + 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), + ) + + return motor diff --git a/tests/fixtures/motor/hybrid_fixtures.py b/tests/fixtures/motor/hybrid_fixtures.py new file mode 100644 index 000000000..923a640b1 --- /dev/null +++ b/tests/fixtures/motor/hybrid_fixtures.py @@ -0,0 +1,40 @@ +import pytest + +from rocketpy import HybridMotor + + +@pytest.fixture +def hybrid_motor(spherical_oxidizer_tank): + """An example of a hybrid motor with spherical oxidizer + tank and fuel grains. + + Parameters + ---------- + spherical_oxidizer_tank : rocketpy.LevelBasedTank + Example Tank that contains the motor oxidizer. This is a + pytest fixture. + + Returns + ------- + rocketpy.HybridMotor + """ + motor = HybridMotor( + thrust_source=lambda t: 2000 - 100 * t, + burn_time=10, + center_of_dry_mass_position=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, + ) + + motor.add_tank(spherical_oxidizer_tank, position=0.3) + + return motor diff --git a/tests/fixtures/motor/liquid_fixtures.py b/tests/fixtures/motor/liquid_fixtures.py new file mode 100644 index 000000000..c6fa697ef --- /dev/null +++ b/tests/fixtures/motor/liquid_fixtures.py @@ -0,0 +1,237 @@ +import numpy as np +import pytest + +from rocketpy import ( + CylindricalTank, + Fluid, + Function, + LevelBasedTank, + LiquidMotor, + MassBasedTank, + SphericalTank, + UllageBasedTank, +) + + +@pytest.fixture +def pressurant_fluid(): + """An example of a pressurant fluid as N2 gas at + 273.15K and 30MPa. + + Returns + ------- + rocketpy.Fluid + An object of the Fluid class. + """ + return Fluid(name="N2", density=300) + + +@pytest.fixture +def fuel_pressurant(): + """An example of a pressurant fluid as N2 gas at + 273.15K and 2MPa. + + Returns + ------- + rocketpy.Fluid + An object of the Fluid class. + """ + return Fluid(name="N2", density=25) + + +@pytest.fixture +def oxidizer_pressurant(): + """An example of a pressurant fluid as N2 gas at + 273.15K and 3MPa. + + Returns + ------- + rocketpy.Fluid + An object of the Fluid class. + """ + return Fluid(name="N2", density=35) + + +@pytest.fixture +def fuel_fluid(): + """An example of propane as fuel fluid at + 273.15K and 2MPa. + + Returns + ------- + rocketpy.Fluid + An object of the Fluid class. + """ + return Fluid(name="Propane", density=500) + + +@pytest.fixture +def oxidizer_fluid(): + """An example of liquid oxygen as oxidizer fluid at + 100K and 3MPa. + + Returns + ------- + rocketpy.Fluid + An object of the Fluid class. + """ + return Fluid(name="O2", density=1000) + + +@pytest.fixture +def pressurant_tank(pressurant_fluid): + """An example of a pressurant cylindrical tank with spherical + caps. + + Parameters + ---------- + pressurant_fluid : rocketpy.Fluid + Pressurizing fluid. This is a pytest fixture. + + Returns + ------- + rocketpy.MassBasedTank + An object of the CylindricalTank class. + """ + geometry = CylindricalTank(0.135 / 2, 0.981, spherical_caps=True) + pressurant_tank = MassBasedTank( + name="Pressure Tank", + geometry=geometry, + liquid_mass=0, + flux_time=(8, 20), + gas_mass="data/SEBLM/pressurantMassFiltered.csv", + gas=pressurant_fluid, + liquid=pressurant_fluid, + ) + + return pressurant_tank + + +@pytest.fixture +def fuel_tank(fuel_fluid, fuel_pressurant): + """An example of a fuel cylindrical tank with spherical + caps. + + Parameters + ---------- + fuel_fluid : rocketpy.Fluid + Fuel fluid of the tank. This is a pytest fixture. + fuel_pressurant : rocketpy.Fluid + Pressurizing fluid of the fuel tank. This is a pytest + fixture. + + Returns + ------- + rocketpy.UllageBasedTank + """ + geometry = CylindricalTank(0.0744, 0.8068, spherical_caps=True) + ullage = ( + -Function("data/SEBLM/test124_Propane_Volume.csv") * 1e-3 + + geometry.total_volume + ) + fuel_tank = UllageBasedTank( + name="Propane Tank", + flux_time=(8, 20), + geometry=geometry, + liquid=fuel_fluid, + gas=fuel_pressurant, + ullage=ullage, + ) + + return fuel_tank + + +@pytest.fixture +def oxidizer_tank(oxidizer_fluid, oxidizer_pressurant): + """An example of a oxidizer cylindrical tank with spherical + caps. + + Parameters + ---------- + oxidizer_fluid : rocketpy.Fluid + Oxidizer fluid of the tank. This is a pytest fixture. + oxidizer_pressurant : rocketpy.Fluid + Pressurizing fluid of the oxidizer tank. This is a pytest + fixture. + + Returns + ------- + rocketpy.UllageBasedTank + """ + geometry = CylindricalTank(0.0744, 0.8068, spherical_caps=True) + ullage = ( + -Function("data/SEBLM/test124_Lox_Volume.csv") * 1e-3 + geometry.total_volume + ) + oxidizer_tank = UllageBasedTank( + name="Lox Tank", + flux_time=(8, 20), + geometry=geometry, + liquid=oxidizer_fluid, + gas=oxidizer_pressurant, + ullage=ullage, + ) + + return oxidizer_tank + + +@pytest.fixture +def liquid_motor(pressurant_tank, fuel_tank, oxidizer_tank): + """An example of a liquid motor with pressurant, fuel and oxidizer tanks. + + Parameters + ---------- + pressurant_tank : rocketpy.MassBasedTank + Tank that contains pressurizing fluid. This is a pytest fixture. + fuel_tank : rocketpy.UllageBasedTank + Tank that contains the motor fuel. This is a pytest fixture. + oxidizer_tank : rocketpy.UllageBasedTank + Tank that contains the motor oxidizer. This is a pytest fixture. + + Returns + ------- + rocketpy.LiquidMotor + """ + liquid_motor = LiquidMotor( + thrust_source="data/SEBLM/test124_Thrust_Curve.csv", + burn_time=(8, 20), + dry_mass=10, + dry_inertia=(5, 5, 0.2), + center_of_dry_mass_position=0, + nozzle_position=-1.364, + nozzle_radius=0.069 / 2, + ) + liquid_motor.add_tank(pressurant_tank, position=2.007) + liquid_motor.add_tank(fuel_tank, position=-1.048) + liquid_motor.add_tank(oxidizer_tank, position=0.711) + + return liquid_motor + + +@pytest.fixture +def spherical_oxidizer_tank(oxidizer_fluid, oxidizer_pressurant): + """An example of a oxidizer spherical tank. + + Parameters + ---------- + oxidizer_fluid : rocketpy.Fluid + Oxidizer fluid of the tank. This is a pytest fixture. + oxidizer_pressurant : rocketpy.Fluid + Pressurizing fluid of the oxidizer tank. This is a pytest + fixture. + + Returns + ------- + rocketpy.UllageBasedTank + """ + geometry = SphericalTank(0.05) + liquid_level = Function(lambda t: 0.1 * np.exp(-t / 2) - 0.05) + oxidizer_tank = LevelBasedTank( + name="Lox Tank", + flux_time=10, + geometry=geometry, + liquid=oxidizer_fluid, + gas=oxidizer_pressurant, + liquid_height=liquid_level, + ) + + return oxidizer_tank diff --git a/tests/fixtures/motor/solid_motor_fixtures.py b/tests/fixtures/motor/solid_motor_fixtures.py new file mode 100644 index 000000000..587d5e970 --- /dev/null +++ b/tests/fixtures/motor/solid_motor_fixtures.py @@ -0,0 +1,119 @@ +import pytest + +from rocketpy import SolidMotor + +# Fixtures +## Motors and rockets + + +@pytest.fixture +def cesaroni_m1670(): # old name: solid_motor + """Create a simple object of the SolidMotor class to be used in the tests. + This is the same motor that has been used in the getting started guide for + years. + + Returns + ------- + rocketpy.SolidMotor + A simple object of the SolidMotor class + """ + example_motor = 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="nozzle_to_combustion_chamber", + ) + return example_motor + + +@pytest.fixture +def cesaroni_m1670_shifted(): # old name: solid_motor + """Create a simple object of the SolidMotor class to be used in the tests. + This is the same motor that has been used in the getting started guide for + years. The difference relies in the thrust_source, which was shifted for + testing purposes. + + Returns + ------- + rocketpy.SolidMotor + A simple object of the SolidMotor class + """ + example_motor = SolidMotor( + thrust_source="tests/fixtures/motor/Cesaroni_M1670_shifted.eng", + burn_time=3.9, + dry_mass=1.815, + dry_inertia=(0.125, 0.125, 0.002), + center_of_dry_mass_position=0.317, + nozzle_position=0, + grain_number=5, + grain_density=1815, + nozzle_radius=33 / 1000, + throat_radius=11 / 1000, + grain_separation=5 / 1000, + grain_outer_radius=33 / 1000, + grain_initial_height=120 / 1000, + grains_center_of_mass_position=0.397, + grain_initial_inner_radius=15 / 1000, + interpolation_method="linear", + coordinate_system_orientation="nozzle_to_combustion_chamber", + reshape_thrust_curve=(5, 3000), + ) + return example_motor + + +@pytest.fixture +def dimensionless_cesaroni_m1670(kg, m): # old name: dimensionless_motor + """The dimensionless version of the Cesaroni M1670 motor. This is the same + motor as defined in the cesaroni_m1670 fixture, but with all the parameters + converted to dimensionless values. This allows to check if the dimensions + are being handled correctly in the code. + + Parameters + ---------- + kg : numericalunits.kg + An object of the numericalunits.kg class. This is a pytest + m : numericalunits.m + An object of the numericalunits.m class. This is a pytest + + Returns + ------- + rocketpy.SolidMotor + An object of the SolidMotor class + """ + example_motor = SolidMotor( + thrust_source="data/motors/Cesaroni_M1670.eng", + burn_time=3.9, + dry_mass=1.815 * kg, + dry_inertia=( + 0.125 * (kg * m**2), + 0.125 * (kg * m**2), + 0.002 * (kg * m**2), + ), + center_of_dry_mass_position=0.317 * m, + grain_number=5, + grain_separation=5 / 1000 * m, + grain_density=1815 * (kg / m**3), + grain_outer_radius=33 / 1000 * m, + grain_initial_inner_radius=15 / 1000 * m, + grain_initial_height=120 / 1000 * m, + nozzle_radius=33 / 1000 * m, + throat_radius=11 / 1000 * m, + interpolation_method="linear", + grains_center_of_mass_position=0.397 * m, + nozzle_position=0 * m, + coordinate_system_orientation="nozzle_to_combustion_chamber", + ) + return example_motor diff --git a/tests/fixtures/rockets/rocket_fixtures.py b/tests/fixtures/rockets/rocket_fixtures.py index e471e654b..3a1c82988 100644 --- a/tests/fixtures/rockets/rocket_fixtures.py +++ b/tests/fixtures/rockets/rocket_fixtures.py @@ -275,4 +275,4 @@ def dimensionless_calisto(kg, m, dimensionless_cesaroni_m1670): coordinate_system_orientation="tail_to_nose", ) example_rocket.add_motor(dimensionless_cesaroni_m1670, position=(-1.373) * m) - return example_rocket \ No newline at end of file + return example_rocket diff --git a/tests/fixtures/surfaces/surface_fixtures.py b/tests/fixtures/surfaces/surface_fixtures.py index 795c8e8bd..10595205f 100644 --- a/tests/fixtures/surfaces/surface_fixtures.py +++ b/tests/fixtures/surfaces/surface_fixtures.py @@ -76,4 +76,3 @@ def calisto_rail_buttons(): angular_position=45, name="Rail Buttons", ) - diff --git a/tests/fixtures/units/numerical_fixtures.py b/tests/fixtures/units/numerical_fixtures.py index 0b0bb0e0d..8bc6883cf 100644 --- a/tests/fixtures/units/numerical_fixtures.py +++ b/tests/fixtures/units/numerical_fixtures.py @@ -25,4 +25,4 @@ def kg(): numericalunits.kg A simple object of the numericalunits.kg class """ - return numericalunits.kg \ No newline at end of file + return numericalunits.kg From c215e9fbc5a7c6dc19ea645e22f55589a342865f Mon Sep 17 00:00:00 2001 From: Lint Action Date: Wed, 14 Feb 2024 00:56:06 +0000 Subject: [PATCH 05/41] Fix code style issues with Black --- tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index ba9b4de7b..3494a3bd9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -60,4 +60,4 @@ def pytest_collection_modifyitems(config, items): skip_slow = pytest.mark.skip(reason="need --runslow option to run") for item in items: if "slow" in item.keywords: - item.add_marker(skip_slow) \ No newline at end of file + item.add_marker(skip_slow) From 95b8f637c64f057a172e5a3ba901b4bdf060d049 Mon Sep 17 00:00:00 2001 From: Giovani Hidalgo Ceotto Date: Wed, 14 Feb 2024 00:55:28 -0300 Subject: [PATCH 06/41] FIX: Add reference area factor correction to lift and CP calculations and prints --- rocketpy/prints/rocket_prints.py | 9 ++++++++- rocketpy/rocket/rocket.py | 10 +++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/rocketpy/prints/rocket_prints.py b/rocketpy/prints/rocket_prints.py index 264a67bb0..e6f476434 100644 --- a/rocketpy/prints/rocket_prints.py +++ b/rocketpy/prints/rocket_prints.py @@ -114,9 +114,13 @@ def rocket_aerodynamics_quantities(self): print("\nAerodynamics Lift Coefficient Derivatives\n") for surface, position in self.rocket.aerodynamic_surfaces: name = surface.name + # ref_factor corrects lift for different reference areas + ref_factor = (surface.rocket_radius / self.radius) ** 2 print( name - + " Lift Coefficient Derivative: {:.3f}".format(surface.clalpha(0)) + + " Lift Coefficient Derivative: {:.3f}".format( + ref_factor * surface.clalpha(0) + ) + "/rad" ) @@ -135,6 +139,9 @@ def rocket_aerodynamics_quantities(self): print( f"Center of Mass position (time=0): {self.rocket.center_of_mass(0):.3f} m" ) + print( + f"Center of Pressure position (time=0): {self.rocket.cp_position(0):.3f} m" + ) print( "Initial Static Margin (mach=0, time=0): " + "{:.3f}".format(self.rocket.static_margin(0)) diff --git a/rocketpy/rocket/rocket.py b/rocketpy/rocket/rocket.py index 30f5d389b..18545d9ea 100644 --- a/rocketpy/rocket/rocket.py +++ b/rocketpy/rocket/rocket.py @@ -517,9 +517,13 @@ def evaluate_center_of_pressure(self): # Calculate total lift coefficient derivative and center of pressure if len(self.aerodynamic_surfaces) > 0: for aero_surface, position in self.aerodynamic_surfaces: - self.total_lift_coeff_der += aero_surface.clalpha - self.cp_position += aero_surface.clalpha * ( - position - self._csys * aero_surface.cpz + # ref_factor corrects lift for different reference areas + ref_factor = (aero_surface.rocket_radius / self.radius) ** 2 + self.total_lift_coeff_der += ref_factor * aero_surface.clalpha + self.cp_position += ( + ref_factor + * aero_surface.clalpha + * (position - self._csys * aero_surface.cpz) ) self.cp_position /= self.total_lift_coeff_der From 700c165de4a01700105ee9741cd0126fd373fa78 Mon Sep 17 00:00:00 2001 From: Giovani Hidalgo Ceotto Date: Wed, 14 Feb 2024 01:06:16 -0300 Subject: [PATCH 07/41] ENH: Improve docs and arguments for add_nose and add_*_fins --- rocketpy/rocket/rocket.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/rocketpy/rocket/rocket.py b/rocketpy/rocket/rocket.py index 18545d9ea..5781d22ed 100644 --- a/rocketpy/rocket/rocket.py +++ b/rocketpy/rocket/rocket.py @@ -875,7 +875,9 @@ def add_tail( self.add_surfaces(tail, position) return tail - def add_nose(self, length, kind, position, bluffness=0, name="Nose Cone"): + def add_nose( + self, length, kind, position, bluffness=0, base_radius=None, name="Nose Cone" + ): """Creates a nose cone, storing its parameters as part of the aerodynamic_surfaces list. Its parameters are the axial position along the rocket and its derivative of the coefficient of lift @@ -896,6 +898,8 @@ def add_nose(self, length, kind, position, bluffness=0, name="Nose Cone"): bluffness : float, optional Ratio between the radius of the circle on the tip of the ogive and the radius of the base of the ogive. + base_radius : int, float, optional + Nose cone base radius in meters. If not given, use rocket radius. name : string Nose cone name. Default is "Nose Cone". @@ -911,8 +915,8 @@ def add_nose(self, length, kind, position, bluffness=0, name="Nose Cone"): nose = NoseCone( length=length, kind=kind, - base_radius=self.radius, - rocket_radius=self.radius, + base_radius=base_radius or self.radius, + rocket_radius=base_radius or self.radius, bluffness=bluffness, name=name, ) @@ -987,8 +991,9 @@ def add_trapezoidal_fins( with its base perpendicular to the rocket's axis. Cannot be used in conjunction with sweep_length. radius : int, float, optional - Reference radius to calculate lift coefficient. If None, which is - default, use rocket radius. + Reference fuselage radius where the fins are located. This is used + to calculate lift coefficient and to draw the rocket. If None, + which is default, use rocket radius. airfoil : tuple, optional Default is null, in which case fins will be treated as flat plates. Otherwise, if tuple, fins will be considered as airfoils. The @@ -1068,8 +1073,9 @@ def add_elliptical_fins( Fins cant angle with respect to the rocket centerline. Must be given in degrees. radius : int, float, optional - Reference radius to calculate lift coefficient. If None, which - is default, use rocket radius. + Reference fuselage radius where the fins are located. This is used + to calculate lift coefficient and to draw the rocket. If None, + which is default, use rocket radius. airfoil : tuple, optional Default is null, in which case fins will be treated as flat plates. Otherwise, if tuple, fins will be considered as airfoils. The From 09fa80589873c2f5262bee7e962eeae22ece7a48 Mon Sep 17 00:00:00 2001 From: Giovani Hidalgo Ceotto Date: Wed, 14 Feb 2024 01:08:00 -0300 Subject: [PATCH 08/41] FIX: make sure NoseCone.rocket_radius is always defined --- rocketpy/rocket/aero_surface.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/rocketpy/rocket/aero_surface.py b/rocketpy/rocket/aero_surface.py index eb3c0f36c..728c15ec1 100644 --- a/rocketpy/rocket/aero_surface.py +++ b/rocketpy/rocket/aero_surface.py @@ -349,12 +349,20 @@ def evaluate_geometrical_parameters(self): # If base radius is not given, the ratio between base radius and # rocket radius is assumed as 1, meaning that the nose cone has the # same radius as the rocket - if self.base_radius is None or self.rocket_radius is None: + if self.base_radius is None and self.rocket_radius is not None: self.radius_ratio = 1 + self.base_radius = self.rocket_radius + elif self.base_radius is not None and self.rocket_radius is None: + self.radius_ratio = 1 + self.rocket_radius = self.base_radius # If base radius is given, the ratio between base radius and rocket # radius is calculated - else: + elif self.base_radius is not None or self.rocket_radius is not None: self.radius_ratio = self.base_radius / self.rocket_radius + else: + raise ValueError( + "Either base radius or rocket radius must be given to calculate the nose cone radius ratio." + ) self.fineness_ratio = self.length / (2 * self.base_radius) return None From 7da6c588e0529a4cc92629005e19f2f231d57850 Mon Sep 17 00:00:00 2001 From: Giovani Hidalgo Ceotto Date: Wed, 14 Feb 2024 01:20:16 -0300 Subject: [PATCH 09/41] FIX: rocket radius reference in rocket prints --- rocketpy/prints/rocket_prints.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rocketpy/prints/rocket_prints.py b/rocketpy/prints/rocket_prints.py index e6f476434..615bb55ac 100644 --- a/rocketpy/prints/rocket_prints.py +++ b/rocketpy/prints/rocket_prints.py @@ -115,7 +115,7 @@ def rocket_aerodynamics_quantities(self): for surface, position in self.rocket.aerodynamic_surfaces: name = surface.name # ref_factor corrects lift for different reference areas - ref_factor = (surface.rocket_radius / self.radius) ** 2 + ref_factor = (surface.rocket_radius / self.rocket.radius) ** 2 print( name + " Lift Coefficient Derivative: {:.3f}".format( From 67819457ea0616ff78c2d231502955f5dad4757a Mon Sep 17 00:00:00 2001 From: Giovani Hidalgo Ceotto Date: Wed, 14 Feb 2024 06:19:52 -0300 Subject: [PATCH 10/41] FIX: change conditional when evaluating base and rocket radius values Co-authored-by: Gui-FernandesBR <63590233+Gui-FernandesBR@users.noreply.github.com> --- rocketpy/rocket/aero_surface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rocketpy/rocket/aero_surface.py b/rocketpy/rocket/aero_surface.py index 728c15ec1..c5d154f3e 100644 --- a/rocketpy/rocket/aero_surface.py +++ b/rocketpy/rocket/aero_surface.py @@ -357,7 +357,7 @@ def evaluate_geometrical_parameters(self): self.rocket_radius = self.base_radius # If base radius is given, the ratio between base radius and rocket # radius is calculated - elif self.base_radius is not None or self.rocket_radius is not None: + elif self.base_radius is not None and self.rocket_radius is not None: self.radius_ratio = self.base_radius / self.rocket_radius else: raise ValueError( From c63cb148dbf1e66b162af2a23cabd2896d0bc0e6 Mon Sep 17 00:00:00 2001 From: giovaniceotto Date: Wed, 14 Feb 2024 07:35:08 -0300 Subject: [PATCH 11/41] MAINT: make base_radius last add_nose argument to avoid breaking changes --- rocketpy/rocket/rocket.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rocketpy/rocket/rocket.py b/rocketpy/rocket/rocket.py index 5781d22ed..4b7fb2d91 100644 --- a/rocketpy/rocket/rocket.py +++ b/rocketpy/rocket/rocket.py @@ -876,7 +876,7 @@ def add_tail( return tail def add_nose( - self, length, kind, position, bluffness=0, base_radius=None, name="Nose Cone" + self, length, kind, position, bluffness=0, name="Nose Cone", base_radius=None ): """Creates a nose cone, storing its parameters as part of the aerodynamic_surfaces list. Its parameters are the axial position @@ -898,10 +898,10 @@ def add_nose( bluffness : float, optional Ratio between the radius of the circle on the tip of the ogive and the radius of the base of the ogive. - base_radius : int, float, optional - Nose cone base radius in meters. If not given, use rocket radius. name : string Nose cone name. Default is "Nose Cone". + base_radius : int, float, optional + Nose cone base radius in meters. If not given, use rocket radius. See Also -------- From f5cf265b23cbade3fa99385da584701fd62c0a1b Mon Sep 17 00:00:00 2001 From: Lucas <69172945+lucasfourier@users.noreply.github.com> Date: Sun, 18 Feb 2024 17:49:28 -0300 Subject: [PATCH 12/41] Newly created tanks_fixtures.py and modified modules conftest and liquid_fixtures. --- tests/conftest.py | 10 +- tests/fixtures/motor/liquid_fixtures.py | 138 +----------------------- tests/fixtures/motor/tanks_fixtures.py | 137 +++++++++++++++++++++++ 3 files changed, 141 insertions(+), 144 deletions(-) create mode 100644 tests/fixtures/motor/tanks_fixtures.py diff --git a/tests/conftest.py b/tests/conftest.py index 3494a3bd9..3daed0cc8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,24 +1,20 @@ import pytest - -# Pytest configuration - pytest_plugins = [ "tests.fixtures.environment.environment_fixtures", "tests.fixtures.flight.flight_fixtures", "tests.fixtures.function.function_fixtures", + "tests.fixtures.motor.liquid_fixtures", "tests.fixtures.motor.hybrid_fixtures", "tests.fixtures.motor.solid_motor_fixtures", - "tests.fixtures.motor.liquid_fixtures", + "tests.fixtures.motor.tanks_fixtures", "tests.fixtures.motor.generic_motor_fixtures", "tests.fixtures.parachutes.parachute_fixtures", "tests.fixtures.rockets.rocket_fixtures", "tests.fixtures.surfaces.surface_fixtures", - "tests.fixtures.units.numerical_fixtures", + "tests.fixtures.units.numerical_fixtures" ] -# - def pytest_addoption(parser): """Add option to run slow tests. This is used to skip slow tests by default. diff --git a/tests/fixtures/motor/liquid_fixtures.py b/tests/fixtures/motor/liquid_fixtures.py index c6fa697ef..85a17e3a4 100644 --- a/tests/fixtures/motor/liquid_fixtures.py +++ b/tests/fixtures/motor/liquid_fixtures.py @@ -1,16 +1,6 @@ -import numpy as np import pytest -from rocketpy import ( - CylindricalTank, - Fluid, - Function, - LevelBasedTank, - LiquidMotor, - MassBasedTank, - SphericalTank, - UllageBasedTank, -) +from rocketpy import Fluid, LiquidMotor @pytest.fixture @@ -78,102 +68,6 @@ def oxidizer_fluid(): return Fluid(name="O2", density=1000) -@pytest.fixture -def pressurant_tank(pressurant_fluid): - """An example of a pressurant cylindrical tank with spherical - caps. - - Parameters - ---------- - pressurant_fluid : rocketpy.Fluid - Pressurizing fluid. This is a pytest fixture. - - Returns - ------- - rocketpy.MassBasedTank - An object of the CylindricalTank class. - """ - geometry = CylindricalTank(0.135 / 2, 0.981, spherical_caps=True) - pressurant_tank = MassBasedTank( - name="Pressure Tank", - geometry=geometry, - liquid_mass=0, - flux_time=(8, 20), - gas_mass="data/SEBLM/pressurantMassFiltered.csv", - gas=pressurant_fluid, - liquid=pressurant_fluid, - ) - - return pressurant_tank - - -@pytest.fixture -def fuel_tank(fuel_fluid, fuel_pressurant): - """An example of a fuel cylindrical tank with spherical - caps. - - Parameters - ---------- - fuel_fluid : rocketpy.Fluid - Fuel fluid of the tank. This is a pytest fixture. - fuel_pressurant : rocketpy.Fluid - Pressurizing fluid of the fuel tank. This is a pytest - fixture. - - Returns - ------- - rocketpy.UllageBasedTank - """ - geometry = CylindricalTank(0.0744, 0.8068, spherical_caps=True) - ullage = ( - -Function("data/SEBLM/test124_Propane_Volume.csv") * 1e-3 - + geometry.total_volume - ) - fuel_tank = UllageBasedTank( - name="Propane Tank", - flux_time=(8, 20), - geometry=geometry, - liquid=fuel_fluid, - gas=fuel_pressurant, - ullage=ullage, - ) - - return fuel_tank - - -@pytest.fixture -def oxidizer_tank(oxidizer_fluid, oxidizer_pressurant): - """An example of a oxidizer cylindrical tank with spherical - caps. - - Parameters - ---------- - oxidizer_fluid : rocketpy.Fluid - Oxidizer fluid of the tank. This is a pytest fixture. - oxidizer_pressurant : rocketpy.Fluid - Pressurizing fluid of the oxidizer tank. This is a pytest - fixture. - - Returns - ------- - rocketpy.UllageBasedTank - """ - geometry = CylindricalTank(0.0744, 0.8068, spherical_caps=True) - ullage = ( - -Function("data/SEBLM/test124_Lox_Volume.csv") * 1e-3 + geometry.total_volume - ) - oxidizer_tank = UllageBasedTank( - name="Lox Tank", - flux_time=(8, 20), - geometry=geometry, - liquid=oxidizer_fluid, - gas=oxidizer_pressurant, - ullage=ullage, - ) - - return oxidizer_tank - - @pytest.fixture def liquid_motor(pressurant_tank, fuel_tank, oxidizer_tank): """An example of a liquid motor with pressurant, fuel and oxidizer tanks. @@ -205,33 +99,3 @@ def liquid_motor(pressurant_tank, fuel_tank, oxidizer_tank): liquid_motor.add_tank(oxidizer_tank, position=0.711) return liquid_motor - - -@pytest.fixture -def spherical_oxidizer_tank(oxidizer_fluid, oxidizer_pressurant): - """An example of a oxidizer spherical tank. - - Parameters - ---------- - oxidizer_fluid : rocketpy.Fluid - Oxidizer fluid of the tank. This is a pytest fixture. - oxidizer_pressurant : rocketpy.Fluid - Pressurizing fluid of the oxidizer tank. This is a pytest - fixture. - - Returns - ------- - rocketpy.UllageBasedTank - """ - geometry = SphericalTank(0.05) - liquid_level = Function(lambda t: 0.1 * np.exp(-t / 2) - 0.05) - oxidizer_tank = LevelBasedTank( - name="Lox Tank", - flux_time=10, - geometry=geometry, - liquid=oxidizer_fluid, - gas=oxidizer_pressurant, - liquid_height=liquid_level, - ) - - return oxidizer_tank diff --git a/tests/fixtures/motor/tanks_fixtures.py b/tests/fixtures/motor/tanks_fixtures.py new file mode 100644 index 000000000..4238e1e1e --- /dev/null +++ b/tests/fixtures/motor/tanks_fixtures.py @@ -0,0 +1,137 @@ +import numpy as np +import pytest + +from rocketpy import ( + CylindricalTank, + Function, + LevelBasedTank, + MassBasedTank, + SphericalTank, + UllageBasedTank, +) + + +@pytest.fixture +def pressurant_tank(pressurant_fluid): + """An example of a pressurant cylindrical tank with spherical + caps. + + Parameters + ---------- + pressurant_fluid : rocketpy.Fluid + Pressurizing fluid. This is a pytest fixture. + + Returns + ------- + rocketpy.MassBasedTank + An object of the CylindricalTank class. + """ + geometry = CylindricalTank(0.135 / 2, 0.981, spherical_caps=True) + pressurant_tank = MassBasedTank( + name="Pressure Tank", + geometry=geometry, + liquid_mass=0, + flux_time=(8, 20), + gas_mass="data/SEBLM/pressurantMassFiltered.csv", + gas=pressurant_fluid, + liquid=pressurant_fluid, + ) + + return pressurant_tank + + +@pytest.fixture +def fuel_tank(fuel_fluid, fuel_pressurant): + """An example of a fuel cylindrical tank with spherical + caps. + + Parameters + ---------- + fuel_fluid : rocketpy.Fluid + Fuel fluid of the tank. This is a pytest fixture. + fuel_pressurant : rocketpy.Fluid + Pressurizing fluid of the fuel tank. This is a pytest + fixture. + + Returns + ------- + rocketpy.UllageBasedTank + """ + geometry = CylindricalTank(0.0744, 0.8068, spherical_caps=True) + ullage = ( + -Function("data/SEBLM/test124_Propane_Volume.csv") * 1e-3 + + geometry.total_volume + ) + fuel_tank = UllageBasedTank( + name="Propane Tank", + flux_time=(8, 20), + geometry=geometry, + liquid=fuel_fluid, + gas=fuel_pressurant, + ullage=ullage, + ) + + return fuel_tank + + +@pytest.fixture +def oxidizer_tank(oxidizer_fluid, oxidizer_pressurant): + """An example of a oxidizer cylindrical tank with spherical + caps. + + Parameters + ---------- + oxidizer_fluid : rocketpy.Fluid + Oxidizer fluid of the tank. This is a pytest fixture. + oxidizer_pressurant : rocketpy.Fluid + Pressurizing fluid of the oxidizer tank. This is a pytest + fixture. + + Returns + ------- + rocketpy.UllageBasedTank + """ + geometry = CylindricalTank(0.0744, 0.8068, spherical_caps=True) + ullage = ( + -Function("data/SEBLM/test124_Lox_Volume.csv") * 1e-3 + geometry.total_volume + ) + oxidizer_tank = UllageBasedTank( + name="Lox Tank", + flux_time=(8, 20), + geometry=geometry, + liquid=oxidizer_fluid, + gas=oxidizer_pressurant, + ullage=ullage, + ) + + return oxidizer_tank + + +@pytest.fixture +def spherical_oxidizer_tank(oxidizer_fluid, oxidizer_pressurant): + """An example of a oxidizer spherical tank. + + Parameters + ---------- + oxidizer_fluid : rocketpy.Fluid + Oxidizer fluid of the tank. This is a pytest fixture. + oxidizer_pressurant : rocketpy.Fluid + Pressurizing fluid of the oxidizer tank. This is a pytest + fixture. + + Returns + ------- + rocketpy.UllageBasedTank + """ + geometry = SphericalTank(0.05) + liquid_level = Function(lambda t: 0.1 * np.exp(-t / 2) - 0.05) + oxidizer_tank = LevelBasedTank( + name="Lox Tank", + flux_time=10, + geometry=geometry, + liquid=oxidizer_fluid, + gas=oxidizer_pressurant, + liquid_height=liquid_level, + ) + + return oxidizer_tank From bd1b7c2b5ffa672020f8cf2594c65b5efb993a16 Mon Sep 17 00:00:00 2001 From: Lint Action Date: Sun, 18 Feb 2024 20:50:23 +0000 Subject: [PATCH 13/41] Fix code style issues with Black --- tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index 3daed0cc8..4766b570a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -12,7 +12,7 @@ "tests.fixtures.parachutes.parachute_fixtures", "tests.fixtures.rockets.rocket_fixtures", "tests.fixtures.surfaces.surface_fixtures", - "tests.fixtures.units.numerical_fixtures" + "tests.fixtures.units.numerical_fixtures", ] From 79aa3b6870698244cc0c2cdbcdf7c20cae1fea0d Mon Sep 17 00:00:00 2001 From: Lucas <69172945+lucasfourier@users.noreply.github.com> Date: Sun, 18 Feb 2024 19:59:46 -0300 Subject: [PATCH 14/41] Deleted motor_fixtures.py --- tests/fixtures/motor/motor_fixtures.py | 143 ------------------------- 1 file changed, 143 deletions(-) delete mode 100644 tests/fixtures/motor/motor_fixtures.py diff --git a/tests/fixtures/motor/motor_fixtures.py b/tests/fixtures/motor/motor_fixtures.py deleted file mode 100644 index f53457d49..000000000 --- a/tests/fixtures/motor/motor_fixtures.py +++ /dev/null @@ -1,143 +0,0 @@ -import pytest - -from rocketpy import GenericMotor, SolidMotor - -# Fixtures -## Motors and rockets - - -@pytest.fixture -def cesaroni_m1670(): # old name: solid_motor - """Create a simple object of the SolidMotor class to be used in the tests. - This is the same motor that has been used in the getting started guide for - years. - - Returns - ------- - rocketpy.SolidMotor - A simple object of the SolidMotor class - """ - example_motor = 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="nozzle_to_combustion_chamber", - ) - return example_motor - - -@pytest.fixture -def cesaroni_m1670_shifted(): # old name: solid_motor - """Create a simple object of the SolidMotor class to be used in the tests. - This is the same motor that has been used in the getting started guide for - years. The difference relies in the thrust_source, which was shifted for - testing purposes. - - Returns - ------- - rocketpy.SolidMotor - A simple object of the SolidMotor class - """ - example_motor = SolidMotor( - thrust_source="tests/fixtures/motor/Cesaroni_M1670_shifted.eng", - burn_time=3.9, - dry_mass=1.815, - dry_inertia=(0.125, 0.125, 0.002), - center_of_dry_mass_position=0.317, - nozzle_position=0, - grain_number=5, - grain_density=1815, - nozzle_radius=33 / 1000, - throat_radius=11 / 1000, - grain_separation=5 / 1000, - grain_outer_radius=33 / 1000, - grain_initial_height=120 / 1000, - grains_center_of_mass_position=0.397, - grain_initial_inner_radius=15 / 1000, - interpolation_method="linear", - coordinate_system_orientation="nozzle_to_combustion_chamber", - reshape_thrust_curve=(5, 3000), - ) - return example_motor - - -@pytest.fixture -def dimensionless_cesaroni_m1670(kg, m): # old name: dimensionless_motor - """The dimensionless version of the Cesaroni M1670 motor. This is the same - motor as defined in the cesaroni_m1670 fixture, but with all the parameters - converted to dimensionless values. This allows to check if the dimensions - are being handled correctly in the code. - - Parameters - ---------- - kg : numericalunits.kg - An object of the numericalunits.kg class. This is a pytest - m : numericalunits.m - An object of the numericalunits.m class. This is a pytest - - Returns - ------- - rocketpy.SolidMotor - An object of the SolidMotor class - """ - example_motor = SolidMotor( - thrust_source="data/motors/Cesaroni_M1670.eng", - burn_time=3.9, - dry_mass=1.815 * kg, - dry_inertia=( - 0.125 * (kg * m**2), - 0.125 * (kg * m**2), - 0.002 * (kg * m**2), - ), - center_of_dry_mass_position=0.317 * m, - grain_number=5, - grain_separation=5 / 1000 * m, - grain_density=1815 * (kg / m**3), - grain_outer_radius=33 / 1000 * m, - grain_initial_inner_radius=15 / 1000 * m, - grain_initial_height=120 / 1000 * m, - nozzle_radius=33 / 1000 * m, - throat_radius=11 / 1000 * m, - interpolation_method="linear", - grains_center_of_mass_position=0.397 * m, - nozzle_position=0 * m, - coordinate_system_orientation="nozzle_to_combustion_chamber", - ) - return example_motor - - -@pytest.fixture -def generic_motor(): - """An example of a generic motor for low accuracy simulations. - - Returns - ------- - rocketpy.GenericMotor - """ - motor = GenericMotor( - 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), - ) - - return motor From 51ec868b7a1ea89187f2eaf909f20d40cbe66030 Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Mon, 19 Feb 2024 13:34:17 -0500 Subject: [PATCH 15/41] MNT: update CHANGELOG with #558 --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9005764c2..7372e91e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Fixed +## [Unreleased] - 2024-02-22 + +You can install this version by running `pip install rocketpy==1.2.1` + +### Fixed + +- BUG: Add reference area factor correction to aero surfaces (solves #557) [#558](https://github.com/RocketPy-Team/RocketPy/pull/558) + ## [v1.2.0] - 2024-02-12 You can install this version by running `pip install rocketpy==1.2.0` From 955f61da83d90bda0d5d8443ff70debc99cd0f27 Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Mon, 19 Feb 2024 13:34:42 -0500 Subject: [PATCH 16/41] DOC: change active to passive voice in rocket_radius documentation --- rocketpy/rocket/rocket.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/rocketpy/rocket/rocket.py b/rocketpy/rocket/rocket.py index 4b7fb2d91..ffcf3b1c8 100644 --- a/rocketpy/rocket/rocket.py +++ b/rocketpy/rocket/rocket.py @@ -901,7 +901,8 @@ def add_nose( name : string Nose cone name. Default is "Nose Cone". base_radius : int, float, optional - Nose cone base radius in meters. If not given, use rocket radius. + Nose cone base radius in meters. If not given, the rocket radius + will be used. See Also -------- @@ -993,7 +994,7 @@ def add_trapezoidal_fins( radius : int, float, optional Reference fuselage radius where the fins are located. This is used to calculate lift coefficient and to draw the rocket. If None, - which is default, use rocket radius. + which is default, the rocket radius will be used. airfoil : tuple, optional Default is null, in which case fins will be treated as flat plates. Otherwise, if tuple, fins will be considered as airfoils. The @@ -1075,7 +1076,7 @@ def add_elliptical_fins( radius : int, float, optional Reference fuselage radius where the fins are located. This is used to calculate lift coefficient and to draw the rocket. If None, - which is default, use rocket radius. + which is default, the rocket radius will be used. airfoil : tuple, optional Default is null, in which case fins will be treated as flat plates. Otherwise, if tuple, fins will be considered as airfoils. The From b4e565be9badfbd81ab0c1ae2e18409459c8faa7 Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Mon, 19 Feb 2024 13:35:27 -0500 Subject: [PATCH 17/41] TST: add tests to cover the #557 issue --- tests/test_rocket.py | 75 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/tests/test_rocket.py b/tests/test_rocket.py index 21616a5fc..cd77f41a1 100644 --- a/tests/test_rocket.py +++ b/tests/test_rocket.py @@ -1,8 +1,10 @@ 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") @@ -205,3 +207,76 @@ def test_air_brakes_clamp_off(mock_show, calisto_air_brakes_clamp_off): 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 checks 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) From 643ea6eeb235311297972b3a1599700cc6709e4f Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Mon, 19 Feb 2024 17:39:27 -0500 Subject: [PATCH 18/41] MNT: Fix typos from review --- CHANGELOG.md | 2 +- tests/test_rocket.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7372e91e0..4ab36ca79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,7 +39,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Fixed -## [Unreleased] - 2024-02-22 +## [v1.2.1] - 2024-02-22 You can install this version by running `pip install rocketpy==1.2.1` diff --git a/tests/test_rocket.py b/tests/test_rocket.py index cd77f41a1..70da36e95 100644 --- a/tests/test_rocket.py +++ b/tests/test_rocket.py @@ -211,7 +211,7 @@ def test_air_brakes_clamp_off(mock_show, calisto_air_brakes_clamp_off): def test_add_surfaces_different_noses(calisto): """Test the add_surfaces method with different nose cone configurations. - More specifically, this will checks the static margin of the rocket with + More specifically, this will check the static margin of the rocket with different nose cone configurations. Parameters From d35b4afc59dde46134532f62a90512018f652f86 Mon Sep 17 00:00:00 2001 From: Lucas <69172945+lucasfourier@users.noreply.github.com> Date: Tue, 20 Feb 2024 14:36:07 -0300 Subject: [PATCH 19/41] Motor method 'export_eng' for liquid motors. --- rocketpy/motors/motor.py | 45 ++++++++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/rocketpy/motors/motor.py b/rocketpy/motors/motor.py index 0285a1203..38917dd83 100644 --- a/rocketpy/motors/motor.py +++ b/rocketpy/motors/motor.py @@ -1014,18 +1014,41 @@ def export_eng(self, file_name, motor_name): # Open file file = open(file_name, "w") - # Write first line - file.write( - motor_name - + " {:3.1f} {:3.1f} 0 {:2.3} {:2.3} RocketPy\n".format( - 2000 * self.grain_outer_radius, - 1000 - * self.grain_number - * (self.grain_initial_height + self.grain_separation), - self.propellant_initial_mass, - self.propellant_initial_mass, + if ( + hasattr(self, "grain_outer_radius") + and hasattr(self, "grain_number") + and hasattr(self, "grain_initial_height") + and hasattr(self, "grain_separation") + ): + # Write first line for motors with grains + file.write( + motor_name + + " {:3.1f} {:3.1f} 0 {:2.3} {:2.3} RocketPy\n".format( + 2000 * self.grain_outer_radius, + 1000 + * self.grain_number + * (self.grain_initial_height + self.grain_separation), + self.propellant_initial_mass, + self.propellant_initial_mass, + ) + ) + + else: + # Warn the user that the motor doesn't have at least one grain attribute + warnings.warn( + "The motor object doesn't have some grain-related attributes. Using zeros to write to file." + ) + + # Write first line for motors without grain attributes + # The zeros are here because the first two placeholders + # are grain-related attributes. + file.write( + motor_name + + " 0 0 0 {:2.3} {:2.3} RocketPy\n".format( + self.propellant_initial_mass, + self.propellant_initial_mass, + ) ) - ) # Write thrust curve data points for time, thrust in self.thrust.source[1:-1, :]: From 474eb490b5b69b064143605143774b5c2c61b177 Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR <63590233+Gui-FernandesBR@users.noreply.github.com> Date: Thu, 22 Feb 2024 14:51:42 -0500 Subject: [PATCH 20/41] REL: bump up rocketpy version to v1.2.1 --- docs/conf.py | 2 +- docs/user/installation.rst | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 6f116025b..f2397dfa8 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -24,7 +24,7 @@ author = "RocketPy Team" # The full version, including alpha/beta/rc tags -release = "1.2.0" +release = "1.2.1" # -- General configuration --------------------------------------------------- diff --git a/docs/user/installation.rst b/docs/user/installation.rst index 9742bb8e3..16dec6886 100644 --- a/docs/user/installation.rst +++ b/docs/user/installation.rst @@ -19,7 +19,7 @@ If you want to choose a specific version to guarantee compatibility, you may ins .. code-block:: shell - pip install rocketpy==1.2.0 + pip install rocketpy==1.2.1 Optional Installation Method: ``conda`` diff --git a/setup.py b/setup.py index 4652c4ca9..73d989de4 100644 --- a/setup.py +++ b/setup.py @@ -23,7 +23,7 @@ setuptools.setup( name="rocketpy", - version="1.2.0", + version="1.2.1", install_requires=necessary_require, extras_require={ "env_analysis": env_analysis_require, From ca3e20a3225577ed05a20390712ad9c24be47443 Mon Sep 17 00:00:00 2001 From: Lucas <69172945+lucasfourier@users.noreply.github.com> Date: Sat, 24 Feb 2024 17:24:52 -0300 Subject: [PATCH 21/41] Reviewing the code after comments. --- rocketpy/motors/motor.py | 66 +++++++++++++++------------------------- 1 file changed, 24 insertions(+), 42 deletions(-) diff --git a/rocketpy/motors/motor.py b/rocketpy/motors/motor.py index 38917dd83..6c2242a9f 100644 --- a/rocketpy/motors/motor.py +++ b/rocketpy/motors/motor.py @@ -1012,54 +1012,36 @@ def export_eng(self, file_name, motor_name): None """ # Open file - file = open(file_name, "w") - - if ( - hasattr(self, "grain_outer_radius") - and hasattr(self, "grain_number") - and hasattr(self, "grain_initial_height") - and hasattr(self, "grain_separation") - ): - # Write first line for motors with grains - file.write( - motor_name - + " {:3.1f} {:3.1f} 0 {:2.3} {:2.3} RocketPy\n".format( - 2000 * self.grain_outer_radius, - 1000 - * self.grain_number - * (self.grain_initial_height + self.grain_separation), - self.propellant_initial_mass, - self.propellant_initial_mass, + with open(file_name, "w") as file: + # Write first line + def get_attr_value(obj, attr_name, multiplier=1): + return multiplier * getattr(obj, attr_name, 0) + + grain_outer_radius = get_attr_value(self, "grain_outer_radius", 2000) + grain_number = get_attr_value(self, "grain_number", 1000) + grain_initial_height = get_attr_value(self, "grain_initial_height") + grain_separation = get_attr_value(self, "grain_separation") + + grain_total = grain_number * (grain_initial_height + grain_separation) + + if grain_outer_radius == 0 or grain_total == 0: + warnings.warn( + "The motor object doesn't have some grain-related attributes. " + "Using zeros to write to file." ) - ) - - else: - # Warn the user that the motor doesn't have at least one grain attribute - warnings.warn( - "The motor object doesn't have some grain-related attributes. Using zeros to write to file." - ) - # Write first line for motors without grain attributes - # The zeros are here because the first two placeholders - # are grain-related attributes. file.write( - motor_name - + " 0 0 0 {:2.3} {:2.3} RocketPy\n".format( - self.propellant_initial_mass, - self.propellant_initial_mass, - ) + f"{motor_name} {grain_outer_radius:3.1f} {grain_total:3.1f} 0 " + f"{self.propellant_initial_mass:2.3} " + f"{self.propellant_initial_mass:2.3} RocketPy\n" ) - # Write thrust curve data points - for time, thrust in self.thrust.source[1:-1, :]: - # time, thrust = item - file.write("{:.4f} {:.3f}\n".format(time, thrust)) - - # Write last line - file.write("{:.4f} {:.3f}\n".format(self.thrust.source[-1, 0], 0)) + # Write thrust curve data points + for time, thrust in self.thrust.source[1:-1, :]: + file.write(f"{time:.4f} {thrust:.3f}\n") - # Close file - file.close() + # Write last line + file.write(f"{self.thrust.source[-1, 0]:.4f} {0:.3f}\n") return None From 2f527ffef8beec44861ebc7f0e25e8b27470e4e2 Mon Sep 17 00:00:00 2001 From: Lucas <69172945+lucasfourier@users.noreply.github.com> Date: Sat, 24 Feb 2024 19:48:10 -0300 Subject: [PATCH 22/41] Updated changelog. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3503a9e9..9f9e80366 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -71,6 +71,7 @@ You can install this version by running `pip install rocketpy==1.2.0` ### Fixed +- BUG: export_eng 'Motor' method would not work for liquid motors. [#559](https://github.com/RocketPy-Team/RocketPy/pull/559) - BUG: Update flight trajectory plot axes limits [#552](https://github.com/RocketPy-Team/RocketPy/pull/552) - BUG: fix `get_controller_observed_variables` in the air brakes examples [#551](https://github.com/RocketPy-Team/RocketPy/pull/551) - MNT: small fixes before v1.2 [#550](https://github.com/RocketPy-Team/RocketPy/pull/550) From 998746aee2ee75ee60b33f797d5898a83ca8b6ae Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Mon, 12 Feb 2024 12:56:05 -0500 Subject: [PATCH 23/41] REL: Update version to 1.2.0 --- CHANGELOG.md | 2 +- docs/conf.py | 2 +- docs/user/installation.rst | 2 +- setup.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3503a9e9..9005764c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,7 +39,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Fixed -## [v1.2.0] - 2024-02-dd +## [v1.2.0] - 2024-02-12 You can install this version by running `pip install rocketpy==1.2.0` diff --git a/docs/conf.py b/docs/conf.py index 43dbd2d5c..6f116025b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -24,7 +24,7 @@ author = "RocketPy Team" # The full version, including alpha/beta/rc tags -release = "1.1.5" +release = "1.2.0" # -- General configuration --------------------------------------------------- diff --git a/docs/user/installation.rst b/docs/user/installation.rst index 0822bde7d..9742bb8e3 100644 --- a/docs/user/installation.rst +++ b/docs/user/installation.rst @@ -19,7 +19,7 @@ If you want to choose a specific version to guarantee compatibility, you may ins .. code-block:: shell - pip install rocketpy==1.1.5 + pip install rocketpy==1.2.0 Optional Installation Method: ``conda`` diff --git a/setup.py b/setup.py index 326f276ec..4652c4ca9 100644 --- a/setup.py +++ b/setup.py @@ -23,7 +23,7 @@ setuptools.setup( name="rocketpy", - version="1.1.5", + version="1.2.0", install_requires=necessary_require, extras_require={ "env_analysis": env_analysis_require, From a4ea6807580ab8a19336ca2c5529797fa7c23374 Mon Sep 17 00:00:00 2001 From: Giovani Hidalgo Ceotto Date: Wed, 14 Feb 2024 00:55:28 -0300 Subject: [PATCH 24/41] FIX: Add reference area factor correction to lift and CP calculations and prints --- rocketpy/prints/rocket_prints.py | 9 ++++++++- rocketpy/rocket/rocket.py | 10 +++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/rocketpy/prints/rocket_prints.py b/rocketpy/prints/rocket_prints.py index 264a67bb0..e6f476434 100644 --- a/rocketpy/prints/rocket_prints.py +++ b/rocketpy/prints/rocket_prints.py @@ -114,9 +114,13 @@ def rocket_aerodynamics_quantities(self): print("\nAerodynamics Lift Coefficient Derivatives\n") for surface, position in self.rocket.aerodynamic_surfaces: name = surface.name + # ref_factor corrects lift for different reference areas + ref_factor = (surface.rocket_radius / self.radius) ** 2 print( name - + " Lift Coefficient Derivative: {:.3f}".format(surface.clalpha(0)) + + " Lift Coefficient Derivative: {:.3f}".format( + ref_factor * surface.clalpha(0) + ) + "/rad" ) @@ -135,6 +139,9 @@ def rocket_aerodynamics_quantities(self): print( f"Center of Mass position (time=0): {self.rocket.center_of_mass(0):.3f} m" ) + print( + f"Center of Pressure position (time=0): {self.rocket.cp_position(0):.3f} m" + ) print( "Initial Static Margin (mach=0, time=0): " + "{:.3f}".format(self.rocket.static_margin(0)) diff --git a/rocketpy/rocket/rocket.py b/rocketpy/rocket/rocket.py index 30f5d389b..18545d9ea 100644 --- a/rocketpy/rocket/rocket.py +++ b/rocketpy/rocket/rocket.py @@ -517,9 +517,13 @@ def evaluate_center_of_pressure(self): # Calculate total lift coefficient derivative and center of pressure if len(self.aerodynamic_surfaces) > 0: for aero_surface, position in self.aerodynamic_surfaces: - self.total_lift_coeff_der += aero_surface.clalpha - self.cp_position += aero_surface.clalpha * ( - position - self._csys * aero_surface.cpz + # ref_factor corrects lift for different reference areas + ref_factor = (aero_surface.rocket_radius / self.radius) ** 2 + self.total_lift_coeff_der += ref_factor * aero_surface.clalpha + self.cp_position += ( + ref_factor + * aero_surface.clalpha + * (position - self._csys * aero_surface.cpz) ) self.cp_position /= self.total_lift_coeff_der From 3081fad779488ea43d006f2c9d0d603193d95403 Mon Sep 17 00:00:00 2001 From: Giovani Hidalgo Ceotto Date: Wed, 14 Feb 2024 01:06:16 -0300 Subject: [PATCH 25/41] ENH: Improve docs and arguments for add_nose and add_*_fins --- rocketpy/rocket/rocket.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/rocketpy/rocket/rocket.py b/rocketpy/rocket/rocket.py index 18545d9ea..5781d22ed 100644 --- a/rocketpy/rocket/rocket.py +++ b/rocketpy/rocket/rocket.py @@ -875,7 +875,9 @@ def add_tail( self.add_surfaces(tail, position) return tail - def add_nose(self, length, kind, position, bluffness=0, name="Nose Cone"): + def add_nose( + self, length, kind, position, bluffness=0, base_radius=None, name="Nose Cone" + ): """Creates a nose cone, storing its parameters as part of the aerodynamic_surfaces list. Its parameters are the axial position along the rocket and its derivative of the coefficient of lift @@ -896,6 +898,8 @@ def add_nose(self, length, kind, position, bluffness=0, name="Nose Cone"): bluffness : float, optional Ratio between the radius of the circle on the tip of the ogive and the radius of the base of the ogive. + base_radius : int, float, optional + Nose cone base radius in meters. If not given, use rocket radius. name : string Nose cone name. Default is "Nose Cone". @@ -911,8 +915,8 @@ def add_nose(self, length, kind, position, bluffness=0, name="Nose Cone"): nose = NoseCone( length=length, kind=kind, - base_radius=self.radius, - rocket_radius=self.radius, + base_radius=base_radius or self.radius, + rocket_radius=base_radius or self.radius, bluffness=bluffness, name=name, ) @@ -987,8 +991,9 @@ def add_trapezoidal_fins( with its base perpendicular to the rocket's axis. Cannot be used in conjunction with sweep_length. radius : int, float, optional - Reference radius to calculate lift coefficient. If None, which is - default, use rocket radius. + Reference fuselage radius where the fins are located. This is used + to calculate lift coefficient and to draw the rocket. If None, + which is default, use rocket radius. airfoil : tuple, optional Default is null, in which case fins will be treated as flat plates. Otherwise, if tuple, fins will be considered as airfoils. The @@ -1068,8 +1073,9 @@ def add_elliptical_fins( Fins cant angle with respect to the rocket centerline. Must be given in degrees. radius : int, float, optional - Reference radius to calculate lift coefficient. If None, which - is default, use rocket radius. + Reference fuselage radius where the fins are located. This is used + to calculate lift coefficient and to draw the rocket. If None, + which is default, use rocket radius. airfoil : tuple, optional Default is null, in which case fins will be treated as flat plates. Otherwise, if tuple, fins will be considered as airfoils. The From 72a4c97ea20837c1d0802e1c0ada1de6a93dd406 Mon Sep 17 00:00:00 2001 From: Giovani Hidalgo Ceotto Date: Wed, 14 Feb 2024 01:08:00 -0300 Subject: [PATCH 26/41] FIX: make sure NoseCone.rocket_radius is always defined --- rocketpy/rocket/aero_surface.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/rocketpy/rocket/aero_surface.py b/rocketpy/rocket/aero_surface.py index eb3c0f36c..728c15ec1 100644 --- a/rocketpy/rocket/aero_surface.py +++ b/rocketpy/rocket/aero_surface.py @@ -349,12 +349,20 @@ def evaluate_geometrical_parameters(self): # If base radius is not given, the ratio between base radius and # rocket radius is assumed as 1, meaning that the nose cone has the # same radius as the rocket - if self.base_radius is None or self.rocket_radius is None: + if self.base_radius is None and self.rocket_radius is not None: self.radius_ratio = 1 + self.base_radius = self.rocket_radius + elif self.base_radius is not None and self.rocket_radius is None: + self.radius_ratio = 1 + self.rocket_radius = self.base_radius # If base radius is given, the ratio between base radius and rocket # radius is calculated - else: + elif self.base_radius is not None or self.rocket_radius is not None: self.radius_ratio = self.base_radius / self.rocket_radius + else: + raise ValueError( + "Either base radius or rocket radius must be given to calculate the nose cone radius ratio." + ) self.fineness_ratio = self.length / (2 * self.base_radius) return None From babe58cb9a1aad464fdeb5baeaa4cbf467b23c29 Mon Sep 17 00:00:00 2001 From: Giovani Hidalgo Ceotto Date: Wed, 14 Feb 2024 01:20:16 -0300 Subject: [PATCH 27/41] FIX: rocket radius reference in rocket prints --- rocketpy/prints/rocket_prints.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rocketpy/prints/rocket_prints.py b/rocketpy/prints/rocket_prints.py index e6f476434..615bb55ac 100644 --- a/rocketpy/prints/rocket_prints.py +++ b/rocketpy/prints/rocket_prints.py @@ -115,7 +115,7 @@ def rocket_aerodynamics_quantities(self): for surface, position in self.rocket.aerodynamic_surfaces: name = surface.name # ref_factor corrects lift for different reference areas - ref_factor = (surface.rocket_radius / self.radius) ** 2 + ref_factor = (surface.rocket_radius / self.rocket.radius) ** 2 print( name + " Lift Coefficient Derivative: {:.3f}".format( From 2629bbbee2e99689ff7354486c34496dc3e259e6 Mon Sep 17 00:00:00 2001 From: Giovani Hidalgo Ceotto Date: Wed, 14 Feb 2024 06:19:52 -0300 Subject: [PATCH 28/41] FIX: change conditional when evaluating base and rocket radius values Co-authored-by: Gui-FernandesBR <63590233+Gui-FernandesBR@users.noreply.github.com> --- rocketpy/rocket/aero_surface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rocketpy/rocket/aero_surface.py b/rocketpy/rocket/aero_surface.py index 728c15ec1..c5d154f3e 100644 --- a/rocketpy/rocket/aero_surface.py +++ b/rocketpy/rocket/aero_surface.py @@ -357,7 +357,7 @@ def evaluate_geometrical_parameters(self): self.rocket_radius = self.base_radius # If base radius is given, the ratio between base radius and rocket # radius is calculated - elif self.base_radius is not None or self.rocket_radius is not None: + elif self.base_radius is not None and self.rocket_radius is not None: self.radius_ratio = self.base_radius / self.rocket_radius else: raise ValueError( From d69c7cae8075eff0c344e5af1c433752fc0205e6 Mon Sep 17 00:00:00 2001 From: giovaniceotto Date: Wed, 14 Feb 2024 07:35:08 -0300 Subject: [PATCH 29/41] MAINT: make base_radius last add_nose argument to avoid breaking changes --- rocketpy/rocket/rocket.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rocketpy/rocket/rocket.py b/rocketpy/rocket/rocket.py index 5781d22ed..4b7fb2d91 100644 --- a/rocketpy/rocket/rocket.py +++ b/rocketpy/rocket/rocket.py @@ -876,7 +876,7 @@ def add_tail( return tail def add_nose( - self, length, kind, position, bluffness=0, base_radius=None, name="Nose Cone" + self, length, kind, position, bluffness=0, name="Nose Cone", base_radius=None ): """Creates a nose cone, storing its parameters as part of the aerodynamic_surfaces list. Its parameters are the axial position @@ -898,10 +898,10 @@ def add_nose( bluffness : float, optional Ratio between the radius of the circle on the tip of the ogive and the radius of the base of the ogive. - base_radius : int, float, optional - Nose cone base radius in meters. If not given, use rocket radius. name : string Nose cone name. Default is "Nose Cone". + base_radius : int, float, optional + Nose cone base radius in meters. If not given, use rocket radius. See Also -------- From b298ae2a186b6c22a5db36e917d02d6842e6f26f Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Mon, 19 Feb 2024 13:34:17 -0500 Subject: [PATCH 30/41] MNT: update CHANGELOG with #558 --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9005764c2..7372e91e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Fixed +## [Unreleased] - 2024-02-22 + +You can install this version by running `pip install rocketpy==1.2.1` + +### Fixed + +- BUG: Add reference area factor correction to aero surfaces (solves #557) [#558](https://github.com/RocketPy-Team/RocketPy/pull/558) + ## [v1.2.0] - 2024-02-12 You can install this version by running `pip install rocketpy==1.2.0` From a118cbffe2eb2d7326c24480018652f902af0897 Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Mon, 19 Feb 2024 13:34:42 -0500 Subject: [PATCH 31/41] DOC: change active to passive voice in rocket_radius documentation --- rocketpy/rocket/rocket.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/rocketpy/rocket/rocket.py b/rocketpy/rocket/rocket.py index 4b7fb2d91..ffcf3b1c8 100644 --- a/rocketpy/rocket/rocket.py +++ b/rocketpy/rocket/rocket.py @@ -901,7 +901,8 @@ def add_nose( name : string Nose cone name. Default is "Nose Cone". base_radius : int, float, optional - Nose cone base radius in meters. If not given, use rocket radius. + Nose cone base radius in meters. If not given, the rocket radius + will be used. See Also -------- @@ -993,7 +994,7 @@ def add_trapezoidal_fins( radius : int, float, optional Reference fuselage radius where the fins are located. This is used to calculate lift coefficient and to draw the rocket. If None, - which is default, use rocket radius. + which is default, the rocket radius will be used. airfoil : tuple, optional Default is null, in which case fins will be treated as flat plates. Otherwise, if tuple, fins will be considered as airfoils. The @@ -1075,7 +1076,7 @@ def add_elliptical_fins( radius : int, float, optional Reference fuselage radius where the fins are located. This is used to calculate lift coefficient and to draw the rocket. If None, - which is default, use rocket radius. + which is default, the rocket radius will be used. airfoil : tuple, optional Default is null, in which case fins will be treated as flat plates. Otherwise, if tuple, fins will be considered as airfoils. The From 99f5251512a45f1a63e2674d9a56e8ad6b0bab46 Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Mon, 19 Feb 2024 13:35:27 -0500 Subject: [PATCH 32/41] TST: add tests to cover the #557 issue --- tests/test_rocket.py | 75 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/tests/test_rocket.py b/tests/test_rocket.py index 21616a5fc..cd77f41a1 100644 --- a/tests/test_rocket.py +++ b/tests/test_rocket.py @@ -1,8 +1,10 @@ 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") @@ -205,3 +207,76 @@ def test_air_brakes_clamp_off(mock_show, calisto_air_brakes_clamp_off): 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 checks 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) From c522bc6a33e9403d0633d917fe2622eb944c2b39 Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Mon, 19 Feb 2024 17:39:27 -0500 Subject: [PATCH 33/41] MNT: Fix typos from review --- CHANGELOG.md | 2 +- tests/test_rocket.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7372e91e0..4ab36ca79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,7 +39,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Fixed -## [Unreleased] - 2024-02-22 +## [v1.2.1] - 2024-02-22 You can install this version by running `pip install rocketpy==1.2.1` diff --git a/tests/test_rocket.py b/tests/test_rocket.py index cd77f41a1..70da36e95 100644 --- a/tests/test_rocket.py +++ b/tests/test_rocket.py @@ -211,7 +211,7 @@ def test_air_brakes_clamp_off(mock_show, calisto_air_brakes_clamp_off): def test_add_surfaces_different_noses(calisto): """Test the add_surfaces method with different nose cone configurations. - More specifically, this will checks the static margin of the rocket with + More specifically, this will check the static margin of the rocket with different nose cone configurations. Parameters From 9ac0e4fad7ae5432f9ee90c1dbbe6c09040372e0 Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR <63590233+Gui-FernandesBR@users.noreply.github.com> Date: Thu, 22 Feb 2024 14:51:42 -0500 Subject: [PATCH 34/41] REL: bump up rocketpy version to v1.2.1 --- docs/conf.py | 2 +- docs/user/installation.rst | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 6f116025b..f2397dfa8 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -24,7 +24,7 @@ author = "RocketPy Team" # The full version, including alpha/beta/rc tags -release = "1.2.0" +release = "1.2.1" # -- General configuration --------------------------------------------------- diff --git a/docs/user/installation.rst b/docs/user/installation.rst index 9742bb8e3..16dec6886 100644 --- a/docs/user/installation.rst +++ b/docs/user/installation.rst @@ -19,7 +19,7 @@ If you want to choose a specific version to guarantee compatibility, you may ins .. code-block:: shell - pip install rocketpy==1.2.0 + pip install rocketpy==1.2.1 Optional Installation Method: ``conda`` diff --git a/setup.py b/setup.py index 4652c4ca9..73d989de4 100644 --- a/setup.py +++ b/setup.py @@ -23,7 +23,7 @@ setuptools.setup( name="rocketpy", - version="1.2.0", + version="1.2.1", install_requires=necessary_require, extras_require={ "env_analysis": env_analysis_require, From dc959747c8ab077d1ec1c58157eb4c3a71fcb52f Mon Sep 17 00:00:00 2001 From: Lucas <69172945+lucasfourier@users.noreply.github.com> Date: Sat, 24 Feb 2024 20:12:17 -0300 Subject: [PATCH 35/41] Fixing the changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f9e80366..5a8322e0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). +- BUG: export_eng 'Motor' method would not work for liquid motors. [#559](https://github.com/RocketPy-Team/RocketPy/pull/559) + ### Added @@ -71,7 +73,6 @@ You can install this version by running `pip install rocketpy==1.2.0` ### Fixed -- BUG: export_eng 'Motor' method would not work for liquid motors. [#559](https://github.com/RocketPy-Team/RocketPy/pull/559) - BUG: Update flight trajectory plot axes limits [#552](https://github.com/RocketPy-Team/RocketPy/pull/552) - BUG: fix `get_controller_observed_variables` in the air brakes examples [#551](https://github.com/RocketPy-Team/RocketPy/pull/551) - MNT: small fixes before v1.2 [#550](https://github.com/RocketPy-Team/RocketPy/pull/550) From bb65a337e3e166395039d526b0d8246d515b5e99 Mon Sep 17 00:00:00 2001 From: Lucas <69172945+lucasfourier@users.noreply.github.com> Date: Sun, 25 Feb 2024 21:53:57 -0300 Subject: [PATCH 36/41] Fixing changelog again --- CHANGELOG.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a8322e0f..05a3bfaca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,8 +30,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/). -- BUG: export_eng 'Motor' method would not work for liquid motors. [#559](https://github.com/RocketPy-Team/RocketPy/pull/559) - ### Added @@ -39,7 +37,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Fixed - +- BUG: export_eng 'Motor' method would not work for liquid motors. [#559](https://github.com/RocketPy-Team/RocketPy/pull/559) ## [v1.2.0] - 2024-02-dd From 43be82465341ef0017f07e42c6123115eafca45b Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Mon, 26 Feb 2024 17:32:33 -0500 Subject: [PATCH 37/41] ENH: Optional argument to show the plot in Function.compare_plots --- CHANGELOG.md | 2 +- rocketpy/mathutils/function.py | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 00d4e6708..863077840 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,7 +34,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Changed - +- ENH: Optional argument to show the plot in Function.compare_plots [#563](https://github.com/RocketPy-Team/RocketPy/pull/563) ### Fixed - BUG: export_eng 'Motor' method would not work for liquid motors. [#559](https://github.com/RocketPy-Team/RocketPy/pull/559) diff --git a/rocketpy/mathutils/function.py b/rocketpy/mathutils/function.py index e8b6a9318..d032d40b3 100644 --- a/rocketpy/mathutils/function.py +++ b/rocketpy/mathutils/function.py @@ -1474,6 +1474,7 @@ def compare_plots( force_data=False, force_points=False, return_object=False, + show=True, ): """Plots N 1-Dimensional Functions in the same plot, from a lower limit to an upper limit, by sampling the Functions several times in @@ -1513,6 +1514,8 @@ def compare_plots( Setting force_points to True will plot all points, as a scatter, in which the Function was evaluated to plot it. Default value is False. + show : bool, optional + If True, shows the plot. Default value is True. Returns ------- @@ -1586,7 +1589,8 @@ def compare_plots( plt.xlabel(xlabel) plt.ylabel(ylabel) - plt.show() + if show: + plt.show() if return_object: return fig, ax From 7962ceced9f2ed89bace5cd6b0c0ac827191687a Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Mon, 26 Feb 2024 17:33:19 -0500 Subject: [PATCH 38/41] DOC: update Function.compare_plots docstring --- rocketpy/mathutils/function.py | 35 ++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/rocketpy/mathutils/function.py b/rocketpy/mathutils/function.py index d032d40b3..cd87d7598 100644 --- a/rocketpy/mathutils/function.py +++ b/rocketpy/mathutils/function.py @@ -1482,38 +1482,41 @@ def compare_plots( Parameters ---------- - plot_list : list + plot_list : list[Tuple[Function,str]] List of Functions or list of tuples in the format (Function, label), where label is a string which will be displayed in the legend. - lower : scalar, optional - The lower limit of the interval in which the Functions are to be - plotted. The default value for function type Functions is 0. By - contrast, if the Functions given are defined by a dataset, the - default value is the lowest value of the datasets. - upper : scalar, optional - The upper limit of the interval in which the Functions are to be - plotted. The default value for function type Functions is 10. By - contrast, if the Functions given are defined by a dataset, the - default value is the highest value of the datasets. + lower : float, optional + This represents the lower limit of the interval for plotting the + Functions. If the Functions are defined by a dataset, the smallest + value from the dataset is used. If no value is provided (None), and + the Functions are of Function type, 0 is used as the default. + upper : float, optional + This represents the upper limit of the interval for plotting the + Functions. If the Functions are defined by a dataset, the largest + value from the dataset is used. If no value is provided (None), and + the Functions are of Function type, 10 is used as the default. samples : int, optional The number of samples in which the functions will be evaluated for plotting it, which draws lines between each evaluated point. The default value is 1000. - title : string, optional + title : str, optional Title of the plot. Default value is an empty string. - xlabel : string, optional + xlabel : str, optional X-axis label. Default value is an empty string. - ylabel : string, optional + ylabel : str, optional Y-axis label. Default value is an empty string. - force_data : Boolean, optional + force_data : bool, optional If Function is given by an interpolated dataset, setting force_data to True will plot all points, as a scatter, in the dataset. Default value is False. - force_points : Boolean, optional + force_points : bool, optional Setting force_points to True will plot all points, as a scatter, in which the Function was evaluated to plot it. Default value is False. + return_object : bool, optional + If True, returns the figure and axis objects. Default value is + False. show : bool, optional If True, shows the plot. Default value is True. From 4278de353f1bd659a51fde0d5a538bf711780a5a Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Mon, 12 Feb 2024 12:48:22 -0500 Subject: [PATCH 39/41] ENH: adds `Function.remove_outliers` method --- CHANGELOG.md | 1 + rocketpy/mathutils/function.py | 72 ++++++++++++++++++++++++++++++++++ tests/unit/test_function.py | 30 ++++++++++++++ 3 files changed, 103 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 863077840..7ff32bd8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Added +- ENH: adds `Function.remove_outliers` method [#554](https://github.com/RocketPy-Team/RocketPy/pull/554) ### Changed - ENH: Optional argument to show the plot in Function.compare_plots [#563](https://github.com/RocketPy-Team/RocketPy/pull/563) diff --git a/rocketpy/mathutils/function.py b/rocketpy/mathutils/function.py index cd87d7598..696ccbcb0 100644 --- a/rocketpy/mathutils/function.py +++ b/rocketpy/mathutils/function.py @@ -1144,6 +1144,78 @@ def low_pass_filter(self, alpha, file_path=None): title=self.title, ) + def remove_outliers(self, method="iqr", **kwargs): + """Remove outliers from the Function source using the specified method. + + Parameters + ---------- + method : string, optional + Method to be used to remove outliers. Options are 'iqr'. + Default is 'iqr'. + **kwargs : optional + Keyword arguments to be passed to specific methods according to the + selected method. + If the selected method is the 'iqr', then the following kwargs are + available: + - threshold : float + Threshold for the interquartile range method. Default is 1.5. + + Returns + ------- + Function + The new Function object without outliers. + """ + if callable(self.source): + print("Cannot remove outliers if the source is a callable object.") + return self + + if method.lower() == "iqr": + return self.__remove_outliers_iqr(**kwargs) + else: + print( + f"Method '{method}' not recognized. No outliers removed." + + "Please use one of the following supported methods: 'iqr'." + ) + return self + + def __remove_outliers_iqr(self, threshold=1.5): + """Remove outliers from the Function source using the interquartile + range method. + + Parameters + ---------- + threshold : float, optional + Threshold for the interquartile range method. Default is 1.5. + + Returns + ------- + Function + The Function with the outliers removed. + + References + ---------- + [1] https://en.wikipedia.org/wiki/Outlier#Tukey's_fences + """ + x = self.x_array + y = self.y_array + y_q1 = np.percentile(y, 25) + y_q3 = np.percentile(y, 75) + y_iqr = y_q3 - y_q1 + y_lower = y_q1 - threshold * y_iqr + y_upper = y_q3 + threshold * y_iqr + + y_filtered = y[(y >= y_lower) & (y <= y_upper)] + x_filtered = x[(y >= y_lower) & (y <= y_upper)] + + return Function( + source=np.column_stack((x_filtered, y_filtered)), + inputs=self.__inputs__, + outputs=self.__outputs__, + interpolation=self.__interpolation__, + extrapolation=self.__extrapolation__, + title=self.title, + ) + # Define all presentation methods def __call__(self, *args): """Plot the Function if no argument is given. If an diff --git a/tests/unit/test_function.py b/tests/unit/test_function.py index 1455212d9..4439c9162 100644 --- a/tests/unit/test_function.py +++ b/tests/unit/test_function.py @@ -276,3 +276,33 @@ def test_set_discrete_based_on_model_non_mutator(linear_func): assert isinstance(func, Function) assert discretized_func.source.shape == (4, 2) assert callable(func.source) + + +@pytest.mark.parametrize( + "x, y, expected_x, expected_y", + [ + ( + np.array([1, 2, 3, 4, 5, 6]), + np.array([10, 20, 30, 40, 50000, 60]), + np.array([1, 2, 3, 4, 6]), + np.array([10, 20, 30, 40, 60]), + ), + ], +) +def test_remove_outliers_iqr(x, y, expected_x, expected_y): + """Test the function __remove_outliers_iqr which is expected to remove + outliers from the data based on the Interquartile Range (IQR) method. + """ + func = Function(source=np.column_stack((x, y))) + filtered_func = func.remove_outliers(method="iqr", threshold=1.5) + + # Check if the outliers are removed + assert np.array_equal(filtered_func.x_array, expected_x) + assert np.array_equal(filtered_func.y_array, expected_y) + + # Check if the other attributes are preserved + assert filtered_func.__inputs__ == func.__inputs__ + assert filtered_func.__outputs__ == func.__outputs__ + assert filtered_func.__interpolation__ == func.__interpolation__ + assert filtered_func.__extrapolation__ == func.__extrapolation__ + assert filtered_func.title == func.title From f128c5ee380a7df3a35d8509f657f2e4632c64ce Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR <63590233+Gui-FernandesBR@users.noreply.github.com> Date: Mon, 26 Feb 2024 13:52:26 -0500 Subject: [PATCH 40/41] MNT: refactor the remove outliers method --- rocketpy/mathutils/function.py | 41 +++++----------------------------- tests/unit/test_function.py | 4 ++-- 2 files changed, 8 insertions(+), 37 deletions(-) diff --git a/rocketpy/mathutils/function.py b/rocketpy/mathutils/function.py index 696ccbcb0..1f6d4d3f6 100644 --- a/rocketpy/mathutils/function.py +++ b/rocketpy/mathutils/function.py @@ -1144,41 +1144,7 @@ def low_pass_filter(self, alpha, file_path=None): title=self.title, ) - def remove_outliers(self, method="iqr", **kwargs): - """Remove outliers from the Function source using the specified method. - - Parameters - ---------- - method : string, optional - Method to be used to remove outliers. Options are 'iqr'. - Default is 'iqr'. - **kwargs : optional - Keyword arguments to be passed to specific methods according to the - selected method. - If the selected method is the 'iqr', then the following kwargs are - available: - - threshold : float - Threshold for the interquartile range method. Default is 1.5. - - Returns - ------- - Function - The new Function object without outliers. - """ - if callable(self.source): - print("Cannot remove outliers if the source is a callable object.") - return self - - if method.lower() == "iqr": - return self.__remove_outliers_iqr(**kwargs) - else: - print( - f"Method '{method}' not recognized. No outliers removed." - + "Please use one of the following supported methods: 'iqr'." - ) - return self - - def __remove_outliers_iqr(self, threshold=1.5): + def remove_outliers_iqr(self, threshold=1.5): """Remove outliers from the Function source using the interquartile range method. @@ -1196,6 +1162,11 @@ def __remove_outliers_iqr(self, threshold=1.5): ---------- [1] https://en.wikipedia.org/wiki/Outlier#Tukey's_fences """ + + if callable(self.source): + print("Cannot remove outliers if the source is a callable object.") + return self + x = self.x_array y = self.y_array y_q1 = np.percentile(y, 25) diff --git a/tests/unit/test_function.py b/tests/unit/test_function.py index 4439c9162..8bcefb818 100644 --- a/tests/unit/test_function.py +++ b/tests/unit/test_function.py @@ -290,11 +290,11 @@ def test_set_discrete_based_on_model_non_mutator(linear_func): ], ) def test_remove_outliers_iqr(x, y, expected_x, expected_y): - """Test the function __remove_outliers_iqr which is expected to remove + """Test the function remove_outliers_iqr which is expected to remove outliers from the data based on the Interquartile Range (IQR) method. """ func = Function(source=np.column_stack((x, y))) - filtered_func = func.remove_outliers(method="iqr", threshold=1.5) + filtered_func = func.remove_outliers_iqr(threshold=1.5) # Check if the outliers are removed assert np.array_equal(filtered_func.x_array, expected_x) From fda6b339611aff741eee79b36174fdff98197088 Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR <63590233+Gui-FernandesBR@users.noreply.github.com> Date: Tue, 27 Feb 2024 18:11:09 +0000 Subject: [PATCH 41/41] MNT: update the error handling in the remove_outliers_iqr --- rocketpy/mathutils/function.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/rocketpy/mathutils/function.py b/rocketpy/mathutils/function.py index 1f6d4d3f6..cefed044d 100644 --- a/rocketpy/mathutils/function.py +++ b/rocketpy/mathutils/function.py @@ -1146,7 +1146,7 @@ def low_pass_filter(self, alpha, file_path=None): def remove_outliers_iqr(self, threshold=1.5): """Remove outliers from the Function source using the interquartile - range method. + range method. The Function should have an array-like source. Parameters ---------- @@ -1164,8 +1164,10 @@ def remove_outliers_iqr(self, threshold=1.5): """ if callable(self.source): - print("Cannot remove outliers if the source is a callable object.") - return self + raise TypeError( + "Cannot remove outliers if the source is a callable object." + + " The Function.source should be array-like." + ) x = self.x_array y = self.y_array