From 8e173798594286ae39f462c353b9dd7388c56434 Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Fri, 13 Oct 2023 17:14:57 -0300 Subject: [PATCH 01/30] BUG: cannot print max_acceleration_power_on_time - This happens when we use EmptyMotors - ValueError: attempt to get argmax of an empty sequence --- rocketpy/simulation/flight.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rocketpy/simulation/flight.py b/rocketpy/simulation/flight.py index 1a5533643..ca3c7195f 100644 --- a/rocketpy/simulation/flight.py +++ b/rocketpy/simulation/flight.py @@ -2137,6 +2137,9 @@ def max_acceleration_power_on_time(self): burn_out_time_index = find_closest( self.acceleration.source[:, 0], self.rocket.motor.burn_out_time ) + if burn_out_time_index == 0: + return 0 # the burn out time is before the first time step + max_acceleration_time_index = np.argmax( self.acceleration[:burn_out_time_index, 1] ) From b8194b290bcd30fcc90c73875ca129b0a58f2135 Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Fri, 13 Oct 2023 17:33:19 -0300 Subject: [PATCH 02/30] TST: add a test to cover the last bug behavior --- tests/test_flight.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/test_flight.py b/tests/test_flight.py index 7a0be6544..3ce811169 100644 --- a/tests/test_flight.py +++ b/tests/test_flight.py @@ -141,6 +141,32 @@ def test_initial_solution(mock_show, example_env, calisto_robust): assert test_flight.all_info() == None +@patch("matplotlib.pyplot.show") +def test_empty_motor_flight(mock_show, example_env, calisto_motorless): + flight = Flight( + rocket=calisto_motorless, + environment=example_env, + rail_length=5, + initial_solution=[ # a random flight starting at apogee + 22.945995194368354, + 277.80976806186936, + 353.29457980509113, + 3856.1112773441596, + 12.737953434495966, + 15.524649322067267, + -0.00011874766384947776, + -0.06086838708814366, + 0.019695167217632138, + -0.35099420532705555, + -0.9338841410225396, + 0.25418555574446716, + 0.03632002739509155, + 2.0747266017020563, + ], + ) + assert flight.all_info() == None + + @pytest.mark.parametrize("wind_u, wind_v", [(0, 10), (0, -10), (10, 0), (-10, 0)]) @pytest.mark.parametrize( "static_margin, max_time", @@ -537,6 +563,7 @@ def test_rail_length(calisto_robust, example_env, rail_length, out_of_rail_time) assert abs(test_flight.z(test_flight.out_of_rail_time) - out_of_rail_time) < 1e-6 +@pytest.mark.slow @patch("matplotlib.pyplot.show") def test_time_overshoot(mock_show, calisto_robust, example_env_robust): """Test the time_overshoot parameter of the Flight class. This basically From 743f142ab3012bcb0a53cd4a5b90658d40f248ae Mon Sep 17 00:00:00 2001 From: Lint Action Date: Fri, 13 Oct 2023 20:44:41 +0000 Subject: [PATCH 03/30] Fix code style issues with Black --- rocketpy/simulation/flight.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rocketpy/simulation/flight.py b/rocketpy/simulation/flight.py index ca3c7195f..9b3c6b6f0 100644 --- a/rocketpy/simulation/flight.py +++ b/rocketpy/simulation/flight.py @@ -2138,7 +2138,7 @@ def max_acceleration_power_on_time(self): self.acceleration.source[:, 0], self.rocket.motor.burn_out_time ) if burn_out_time_index == 0: - return 0 # the burn out time is before the first time step + return 0 # the burn out time is before the first time step max_acceleration_time_index = np.argmax( self.acceleration[:burn_out_time_index, 1] From f6ccce30fb1b93b8249b327db837de4a541dedbc Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Sat, 28 Oct 2023 12:00:52 -0300 Subject: [PATCH 04/30] DOC: better documentation of HibridMotor docstring --- rocketpy/motors/hybrid_motor.py | 35 +++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/rocketpy/motors/hybrid_motor.py b/rocketpy/motors/hybrid_motor.py index a4468bc26..d23812ddb 100644 --- a/rocketpy/motors/hybrid_motor.py +++ b/rocketpy/motors/hybrid_motor.py @@ -45,12 +45,15 @@ class HybridMotor(Motor): The total mass of the motor structure, including chambers and tanks, when it is empty and does not contain any propellant. HybridMotor.propellant_initial_mass : float - Total propellant initial mass in kg. + Total propellant initial mass in kg. This is the sum of the initial + mass of fluids in each tank and the initial mass of the solid grains. HybridMotor.total_mass : Function Total motor mass in kg as a function of time, defined as the sum - of propellant and dry mass. + of the dry mass (motor's structure mass) and the propellant mass, which + varies with time. HybridMotor.propellant_mass : Function - Total propellant mass in kg as a function of time. + Total propellant mass in kg as a function of time, this includes the + mass of fluids in each tank and the mass of the solid grains. HybridMotor.total_mass_flow_rate : Function Time derivative of propellant total mass in kg/s as a function of time as obtained by the thrust source. @@ -345,18 +348,19 @@ def exhaust_velocity(self): @funcify_method("Time (s)", "Mass (kg)") def propellant_mass(self): """Evaluates the total propellant mass of the motor as the sum - of each tank mass and the grains mass. + of fluids mass in each tank and the grains mass. Returns ------- Function - Total propellant mass of the motor, in kg. + Total propellant mass of the motor as a function of time, in kg. """ return self.solid.propellant_mass + self.liquid.propellant_mass @cached_property def propellant_initial_mass(self): - """Returns the initial propellant mass of the motor. + """Returns the initial propellant mass of the motor. See the docs of the + HybridMotor.propellant_mass property for more information. Returns ------- @@ -367,8 +371,8 @@ def propellant_initial_mass(self): @funcify_method("Time (s)", "mass flow rate (kg/s)", extrapolation="zero") def mass_flow_rate(self): - """Evaluates the mass flow rate of the motor as the sum of each tank - mass flow rate and the grains mass flow rate. + """Evaluates the mass flow rate of the motor as the sum of mass flow + rates from all tanks and the solid grains mass flow rate. Returns ------- @@ -486,6 +490,21 @@ def propellant_I_33(self): @funcify_method("Time (s)", "Inertia I_12 (kg m²)") def propellant_I_12(self): + """Inertia tensor 12 component of the propellant, the inertia is + relative to the e_1 and e_2 axes, centered at the instantaneous + propellant center of mass. + + Returns + ------- + Function + Propellant inertia tensor 12 component at time t. + + Notes + ----- + This is assumed to be zero due to axial symmetry of the motor. This + could be improved in the future to account for the fact that the + motor is not perfectly symmetric. + """ return 0 @funcify_method("Time (s)", "Inertia I_13 (kg m²)") From 4733c19e8ae96057139ef8dd3c17e1a966be8f23 Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Sat, 28 Oct 2023 12:14:11 -0300 Subject: [PATCH 05/30] DOC: better documentation of LiquidMotor docstring --- rocketpy/motors/liquid_motor.py | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/rocketpy/motors/liquid_motor.py b/rocketpy/motors/liquid_motor.py index d89157e72..ec6b286ca 100644 --- a/rocketpy/motors/liquid_motor.py +++ b/rocketpy/motors/liquid_motor.py @@ -40,11 +40,10 @@ class LiquidMotor(Motor): The total mass of the motor structure, including chambers and tanks, when it is empty and does not contain any propellant. LiquidMotor.propellant_initial_mass : float - Total propellant initial mass in kg, includes - fuel and oxidizer. + Total propellant initial mass in kg, includes fuel and oxidizer. LiquidMotor.total_mass : Function Total motor mass in kg as a function of time, defined as the sum - of propellant and dry mass. + of propellant mass and the motor's dry mass (i.e. structure mass). LiquidMotor.propellant_mass : Function Total propellant mass in kg as a function of time, includes fuel and oxidizer. @@ -260,12 +259,19 @@ def exhaust_velocity(self): ------- self.exhaust_velocity : Function Gas exhaust velocity of the motor. + + Notes + ----- + The exhaust velocity is computed as the ratio of the thrust and the + mass flow rate. Therefore, this will vary with time if the mass flow + rate varies with time. """ return self.thrust / (-1 * self.mass_flow_rate) @funcify_method("Time (s)", "Propellant Mass (kg)") def propellant_mass(self): - """Evaluates the mass of the motor as the sum of each tank mass. + """Evaluates the mass of the motor as the sum of fluids mass in each + tank, which may include fuel and oxidizer and usually vary with time. Returns ------- @@ -281,7 +287,8 @@ def propellant_mass(self): @cached_property def propellant_initial_mass(self): - """Property to store the initial mass of the propellant. + """Property to store the initial mass of the propellant, this includes + fuel and oxidizer. Returns ------- @@ -292,8 +299,9 @@ def propellant_initial_mass(self): @funcify_method("Time (s)", "Mass flow rate (kg/s)", extrapolation="zero") def mass_flow_rate(self): - """Evaluates the mass flow rate of the motor as the sum of each tank - mass flow rate. + """Evaluates the mass flow rate of the motor as the sum of mass flow + rate from each tank, which may include fuel and oxidizer and usually + vary with time. Returns ------- @@ -317,12 +325,12 @@ def mass_flow_rate(self): def center_of_propellant_mass(self): """Evaluates the center of mass of the motor from each tank center of mass and positioning. The center of mass height is measured relative to - the motor nozzle. + the origin of the motor's coordinate system. Returns ------- Function - Center of mass of the motor, in meters. + Position of the propellant center of mass, in meters. """ total_mass = 0 mass_balance = 0 From 5387b49fe1de779984b768e5b8a0835bb2284921 Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Sat, 28 Oct 2023 12:38:50 -0300 Subject: [PATCH 06/30] DOC: better documentation of Motor abstract class --- rocketpy/motors/motor.py | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/rocketpy/motors/motor.py b/rocketpy/motors/motor.py index 2fda0c267..f7bf08e7e 100644 --- a/rocketpy/motors/motor.py +++ b/rocketpy/motors/motor.py @@ -39,12 +39,14 @@ class Motor(ABC): The total mass of the motor structure, including chambers and tanks, when it is empty and does not contain any propellant. Motor.propellant_initial_mass : float - Total propellant initial mass in kg. + Total propellant initial mass in kg, including solid, liquid and gas + phases. Motor.total_mass : Function Total motor mass in kg as a function of time, defined as the sum - of propellant and dry mass. + of propellant mass and the motor's dry mass (i.e. structure mass). Motor.propellant_mass : Function - Total propellant mass in kg as a function of time. + Total propellant mass in kg as a function of time, including solid, + liquid and gas phases. Motor.total_mass_flow_rate : Function Time derivative of propellant total mass in kg/s as a function of time as obtained by the thrust source. @@ -378,7 +380,7 @@ def exhaust_velocity(self): """ pass - @funcify_method("Time (s)", "total mass (kg)") + @funcify_method("Time (s)", "Total mass (kg)") def total_mass(self): """Total mass of the motor as a function of time. It is defined as the propellant mass plus the dry mass. @@ -386,11 +388,11 @@ def total_mass(self): Returns ------- Function - Total mass as a function of time. + Motor total mass as a function of time. """ return self.propellant_mass + self.dry_mass - @funcify_method("Time (s)", "propellant mass (kg)") + @funcify_method("Time (s)", "Propellant mass (kg)") def propellant_mass(self): """Total propellant mass as a Function of time. @@ -403,11 +405,10 @@ def propellant_mass(self): self.total_mass_flow_rate.integral_function() + self.propellant_initial_mass ) - @funcify_method("Time (s)", "mass dot (kg/s)", extrapolation="zero") + @funcify_method("Time (s)", "Mass dot (kg/s)", extrapolation="zero") def total_mass_flow_rate(self): - """Time derivative of propellant mass. Assumes constant exhaust - velocity. The formula used is the opposite of thrust divided by - exhaust velocity. + """Time derivative of the propellant mass as a function of time. The + formula used is the opposite of thrust divided by exhaust velocity. Returns ------- @@ -427,10 +428,8 @@ def total_mass_flow_rate(self): Notes ----- This function computes the total mass flow rate of the motor by - dividing the thrust data by a constant approximation of the exhaust - velocity. - This approximation of the total mass flow rate is used in the - following manner by the child Motor classes: + dividing the thrust data by the exhaust velocity. This is an + approximation, and it is used by the child Motor classes as follows: - The ``SolidMotor`` class uses this approximation to compute the grain's mass flow rate; @@ -449,7 +448,7 @@ def total_mass_flow_rate(self): @property @abstractmethod def propellant_initial_mass(self): - """Propellant initial mass in kg. + """Propellant initial mass in kg, including solid, liquid and gas phases Returns ------- @@ -478,8 +477,8 @@ def center_of_mass(self): @abstractmethod def center_of_propellant_mass(self): """Position of the propellant center of mass as a function of time. - The position is specified as a scalar, relative to the motor's - coordinate system. + The position is specified as a scalar, relative to the origin of the + motor's coordinate system. Returns ------- @@ -501,7 +500,7 @@ def I_11(self): Notes ----- The e_1 direction is assumed to be the direction perpendicular to the - motor body axis. + motor body axis. Also, due to symmetry, I_11 = I_22. References ---------- @@ -540,7 +539,8 @@ def I_22(self): Notes ----- The e_2 direction is assumed to be the direction perpendicular to the - motor body axis, and perpendicular to e_1. + motor body axis, and perpendicular to e_1. Also, due to symmetry, + I_22 = I_11. References ---------- @@ -667,6 +667,7 @@ def I_23(self): ---------- https://en.wikipedia.org/wiki/Moment_of_inertia """ + # wrt = with respect to # Propellant inertia tensor 23 component wrt propellant center of mass propellant_I_23 = self.propellant_I_23 From a26c9c925a8faa337148fe714cbce3e8ace18438 Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Sat, 28 Oct 2023 13:52:48 -0300 Subject: [PATCH 07/30] DOC: better documentation of SolidMotor class --- rocketpy/motors/solid_motor.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/rocketpy/motors/solid_motor.py b/rocketpy/motors/solid_motor.py index c92f95007..761fbd2c0 100644 --- a/rocketpy/motors/solid_motor.py +++ b/rocketpy/motors/solid_motor.py @@ -65,8 +65,10 @@ class SolidMotor(Motor): SolidMotor.grain_initial_mass : float Initial mass of each grain in kg. SolidMotor.dry_mass : float - The total mass of the motor structure, including chambers - and tanks, when it is empty and does not contain any propellant. + The total mass of the motor structure, including chambers, bulkheads, + screws, and others. This should be taken when the motor is empty and + does not contain any propellant. You should not double count a component + that is already accounted for in the rocket class. SolidMotor.propellant_initial_mass : float Total propellant initial mass in kg. SolidMotor.total_mass : Function @@ -78,8 +80,8 @@ class SolidMotor(Motor): Time derivative of propellant total mass in kg/s as a function of time as obtained by the thrust source. SolidMotor.center_of_mass : Function - Position of the motor center of mass in - meters as a function of time. + Position of the motor center of mass in meters as a function of time, + with respect to the motor's coordinate system. See :doc:`Positions and Coordinate Systems ` for more information regarding the motor's coordinate system. @@ -421,7 +423,8 @@ def mass_flow_rate(self): @mass_flow_rate.setter def mass_flow_rate(self, value): - """Sets the mass flow rate of the motor. + """Sets the mass flow rate of the motor. This includes all the grains + burning all at once. Parameters ---------- @@ -432,10 +435,10 @@ def mass_flow_rate(self, value): ------- None """ - self._mass_flow_rate = value.reset("Time (s)", "grain mass flow rate (kg/s)") + self._mass_flow_rate = value.reset("Time (s)", "Grain mass flow rate (kg/s)") self.evaluate_geometry() - @funcify_method("Time (s)", "center of mass (m)", "linear") + @funcify_method("Time (s)", "Center of Propellant Mass (m)", "linear") def center_of_propellant_mass(self): """Position of the propellant center of mass as a function of time. The position is specified as a scalar, relative to the motor's From 8686524c9a7e3a75729497e8e76470216ae9b4b4 Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Sat, 28 Oct 2023 14:00:36 -0300 Subject: [PATCH 08/30] DOC: fix the "tank mass" terminology --- rocketpy/motors/tank.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rocketpy/motors/tank.py b/rocketpy/motors/tank.py index 0b7069141..022afed0e 100644 --- a/rocketpy/motors/tank.py +++ b/rocketpy/motors/tank.py @@ -36,7 +36,7 @@ class Tank(ABC): of time. Tank.net_mass_flow_rate : Function Net mass flow rate of the tank in kg/s as a function of time, also - understood as time derivative of the tank mass. + understood as time derivative of the fluids mass. Tank.liquid_volume : Function Volume of the liquid inside the Tank in m^3 as a function of time. Tank.gas_volume : Function From 575adbe47c7701154727b3efad86693dcd037130 Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Sat, 28 Oct 2023 15:13:11 -0300 Subject: [PATCH 09/30] DOC: improve Rocket class docstrings --- rocketpy/rocket/rocket.py | 207 +++++++++++++++++--------------------- 1 file changed, 94 insertions(+), 113 deletions(-) diff --git a/rocketpy/rocket/rocket.py b/rocketpy/rocket/rocket.py index f491d27c1..2ef9998b2 100644 --- a/rocketpy/rocket/rocket.py +++ b/rocketpy/rocket/rocket.py @@ -19,7 +19,6 @@ class Rocket: - """Keeps rocket information. Attributes @@ -35,6 +34,19 @@ class Rocket: See :doc:`Positions and Coordinate Systems ` for more information regarding the rocket's coordinate system. + Rocket.center_of_mass_without_motor : int, float + Position, in m, of the rocket's center of mass without motor + relative to the rocket's coordinate system. This does not include + the motor or propellant mass. + Rocket.motor_center_of_mass_position : Function + Position, in meters, of the motor's center of mass relative to the user + defined rocket coordinate system. This is a function of time since the + propellant mass decreases with time. For more information, see the + :doc:`Positions and Coordinate Systems `. + Rocket.motor_center_of_dry_mass_position : float + Position, in meters, of the motor's center of dry mass (i.e. center of + mass without propellant) relative to the user defined rocket coordinate + system. This is constant since the motor dry mass is constant. Rocket.coordinate_system_orientation : string String defining the orientation of the rocket's coordinate system. The coordinate system is defined by the rocket's axis of symmetry. @@ -46,7 +58,7 @@ class Rocket: coordinate system is defined with the rocket's axis of symmetry pointing from the rocket's nose cone to the rocket's tail. Rocket.mass : float - Rocket's mass without propellant in kg. + Rocket's mass without motor and propellant, measured in kg. Rocket.center_of_mass : Function Position of the rocket's center of mass, including propellant, relative to the user defined rocket reference system. @@ -116,6 +128,36 @@ class Rocket: :doc:`Positions and Coordinate Systems ` for more information regarding the rocket's coordinate system. Expressed in meters as a function of time. + Rocket.I_11_without_motor : float + Rocket's inertia tensor 11 component without any motors, in kg*m^2. This + is the same value that is passed in the Rocket.__init__() method. + Rocket.I_22_without_motor : float + Rocket's inertia tensor 22 component without any motors, in kg*m^2. This + is the same value that is passed in the Rocket.__init__() method. + Rocket.I_33_without_motor : float + Rocket's inertia tensor 33 component without any motors, in kg*m^2. This + is the same value that is passed in the Rocket.__init__() method. + Rocket.I_12_without_motor : float + Rocket's inertia tensor 12 component without any motors, in kg*m^2. This + is the same value that is passed in the Rocket.__init__() method. + Rocket.I_13_without_motor : float + Rocket's inertia tensor 13 component without any motors, in kg*m^2. This + is the same value that is passed in the Rocket.__init__() method. + Rocket.I_23_without_motor : float + Rocket's inertia tensor 23 component without any motors, in kg*m^2. This + is the same value that is passed in the Rocket.__init__() method. + Rocket.dry_I_11 : float + Rocket's inertia tensor 11 component with unloaded motor,in kg*m^2. + Rocket.dry_I_22 : float + Rocket's inertia tensor 22 component with unloaded motor,in kg*m^2. + Rocket.dry_I_33 : float + Rocket's inertia tensor 33 component with unloaded motor,in kg*m^2. + Rocket.dry_I_12 : float + Rocket's inertia tensor 12 component with unloaded motor,in kg*m^2. + Rocket.dry_I_13 : float + Rocket's inertia tensor 13 component with unloaded motor,in kg*m^2. + Rocket.dry_I_23 : float + Rocket's inertia tensor 23 component with unloaded motor,in kg*m^2. """ def __init__( @@ -138,15 +180,15 @@ def __init__( mass : int, float Rocket total mass without motor in kg. inertia : tuple, list - Tuple or list containing the rocket's dry mass inertia tensor - components, in kg*m^2. + Tuple or list containing the rocket's inertia tensor components, + in kg*m^2. This should be measured without motor and propellant. Assuming e_3 is the rocket's axis of symmetry, e_1 and e_2 are - orthogonal and form a plane perpendicular to e_3, the dry mass - inertia tensor components must be given in the following order: - (I_11, I_22, I_33, I_12, I_13, I_23), where I_ij is the - component of the inertia tensor in the direction of e_i x e_j. - Alternatively, the inertia tensor can be given as - (I_11, I_22, I_33), where I_12 = I_13 = I_23 = 0. + orthogonal and form a plane perpendicular to e_3, the inertia tensor + components must be given in the following order: (I_11, I_22, I_33, + I_12, I_13, I_23), where I_ij is the component of the inertia tensor + in the direction of e_i x e_j. Alternatively, the inertia tensor can + be given as (I_11, I_22, I_33), where I_12 = I_13 = I_23 = 0. This + can also be called as "rocket dry inertia tensor". power_off_drag : int, float, callable, string, array Rocket's drag coefficient when the motor is off. Can be given as an entry to the Function class. See help(Function) for more @@ -217,13 +259,9 @@ def __init__( self.thrust_eccentricity_y = 0 self.thrust_eccentricity_x = 0 - # Parachute data initialization + # Parachute, Aerodynamic and Rail buttons data initialization self.parachutes = [] - - # Aerodynamic data initialization self.aerodynamic_surfaces = Components() - - # Rail buttons data initialization self.rail_buttons = Components() self.cp_position = 0 @@ -249,7 +287,7 @@ def __init__( self.cp_position = 0 # Set by self.evaluate_static_margin() # Create a, possibly, temporary empty motor - # self.motors = Components() # currently unused since only one motor is supported + # self.motors = Components() # currently unused, only 1 motor is supported self.add_motor(motor=EmptyMotor(), position=0) # Important dynamic inertial quantities @@ -273,18 +311,19 @@ def __init__( self.prints = _RocketPrints(self) self.plots = _RocketPlots(self) - return None - @property def nosecones(self): + """A list containing all the noses currently added to the rocket.""" return self.aerodynamic_surfaces.get_by_type(NoseCone) @property def fins(self): + """A list containing all the fins currently added to the rocket.""" return self.aerodynamic_surfaces.get_by_type(Fins) @property def tails(self): + """A list with all the tails currently added to the rocket""" return self.aerodynamic_surfaces.get_by_type(Tail) def evaluate_total_mass(self): @@ -305,11 +344,9 @@ def evaluate_total_mass(self): print("Please associate this rocket with a motor!") return False - # Calculate total mass by summing up propellant and dry mass self.total_mass = self.mass + self.motor.total_mass - self.total_mass.set_outputs("Total Mass (Rocket + Propellant) (kg)") - - # Return total mass + self.total_mass.set_outputs("Total Mass (Rocket + Motor + Propellant) (kg)") + self.total_mass.set_title("Total Mass (Rocket + Motor + Propellant)") return self.total_mass def evaluate_dry_mass(self): @@ -330,10 +367,9 @@ def evaluate_dry_mass(self): print("Please associate this rocket with a motor!") return False - # Calculate total dry mass: motor (without propellant) + rocket + # Calculate total dry mass: motor (without propellant) + rocket mass self.dry_mass = self.mass + self.motor.dry_mass - # Return total mass return self.dry_mass def evaluate_center_of_mass(self): @@ -348,74 +384,63 @@ def evaluate_center_of_mass(self): See :doc:`Positions and Coordinate Systems ` for more information. """ - # Compute center of mass position self.center_of_mass = ( self.center_of_mass_without_motor * self.mass + self.motor_center_of_mass_position * self.motor.total_mass ) / self.total_mass self.center_of_mass.set_inputs("Time (s)") self.center_of_mass.set_outputs("Center of Mass Position (m)") - + self.center_of_mass.set_title( + "Center of Mass Position (includes motor and propellant)" + ) return self.center_of_mass def evaluate_center_of_dry_mass(self): - """Evaluates rocket center dry of mass (i.e. without propellant) - position relative to user defined rocket reference system. + """Evaluates rocket center dry of mass (i.e. rocket + motor without + propellant) position relative to user defined rocket reference system. Returns ------- self.center_of_dry_mass_position : int, float - Rocket's center of dry mass position relative to user defined rocket - reference system. See - :doc:`Positions and Coordinate Systems ` for - more information. + Rocket's center of dry mass position (with unloaded motor) """ - # Compute center of mass position self.center_of_dry_mass_position = ( self.center_of_mass_without_motor * self.mass + self.motor_center_of_dry_mass_position * self.motor.dry_mass ) / self.dry_mass - return self.center_of_dry_mass_position def evaluate_reduced_mass(self): - """Calculates and returns the rocket's total reduced mass. The - reduced mass is defined as the product of the propellant mass - and the mass of the rocket without propellant, divided by the - sum of the propellant mass and the rocket mass. The function - returns an object of the Function class and is defined as a + """Calculates and returns the rocket's total reduced mass. The reduced + mass is defined as the product of the propellant mass and the rocket dry + mass (i.e. with unloaded motor), divided by the loaded rocket mass. + The function returns an object of the Function class and is defined as a function of time. Returns ------- self.reduced_mass : Function - Function of time expressing the reduced mass of the rocket, - defined as the product of the propellant mass and the mass - of the rocket without propellant, divided by the sum of the - propellant mass and the rocket mass. + Function of time expressing the reduced mass of the rocket. """ + # TODO: add tests for reduced_mass values # Make sure there is a motor associated with the rocket if self.motor is None: print("Please associate this rocket with a motor!") return False - # Retrieve propellant mass as a function of time - motor_mass = self.motor.propellant_mass - - # retrieve constant rocket mass without propellant - mass = self.dry_mass - - # calculate reduced mass - self.reduced_mass = motor_mass * mass / (motor_mass + mass) + # Get nicknames + prop_mass = self.motor.propellant_mass + dry_mass = self.dry_mass + # calculate reduced mass and return it + self.reduced_mass = prop_mass * dry_mass / (prop_mass + dry_mass) self.reduced_mass.set_outputs("Reduced Mass (kg)") - - # Return reduced mass + self.reduced_mass.set_title("Reduced Mass") return self.reduced_mass def evaluate_thrust_to_weight(self): - """Evaluates thrust to weight as a Function of time. - - Uses g = 9.80665 m/s² as nominal gravity for weight calculation. + """Evaluates thrust to weight as a Function of time. This is defined as + the motor thrust force divided by rocket weight. The gravitational + acceleration is assumed constants and equal to 9.80665 m/s^2. Returns ------- @@ -424,6 +449,7 @@ def evaluate_thrust_to_weight(self): self.thrust_to_weight = self.motor.thrust / (9.80665 * self.total_mass) self.thrust_to_weight.set_inputs("Time (s)") self.thrust_to_weight.set_outputs("Thrust/Weight") + self.thrust_to_weight.set_title("Thrust to Weight ratio") def evaluate_static_margin(self): """Calculates and returns the rocket's static margin when @@ -455,21 +481,20 @@ def evaluate_static_margin(self): self.static_margin = (self.center_of_mass - self.cp_position) / ( 2 * self.radius ) - self.static_margin *= ( - self._csys - ) # Change sign if coordinate system is upside down + # Change sign if coordinate system is upside down + self.static_margin *= self._csys self.static_margin.set_inputs("Time (s)") self.static_margin.set_outputs("Static Margin (c)") self.static_margin.set_title("Static Margin") self.static_margin.set_discrete( lower=0, upper=self.motor.burn_out_time, samples=200 ) - return None def evaluate_dry_inertias(self): """Calculates and returns the rocket's dry inertias relative to the rocket's center of mass. The inertias are saved and returned - in units of kg*m². + in units of kg*m². This does not consider propellant mass but does take + into account the motor dry mass. Returns ------- @@ -537,7 +562,6 @@ def evaluate_dry_inertias(self): self.dry_I_13 = self.I_13_without_motor + self.motor.dry_I_13 self.dry_I_23 = self.I_23_without_motor + self.motor.dry_I_23 - # Return inertias return ( self.dry_I_11, self.dry_I_22, @@ -582,7 +606,7 @@ def evaluate_inertias(self): """ # Get masses prop_mass = self.motor.propellant_mass # Propellant mass as a function of time - dry_mass = self.dry_mass # Constant rocket dry mass without propellant + dry_mass = self.dry_mass # Constant rocket mass without motor # Compute axes distances CM_to_CDM = self.center_of_mass - self.center_of_dry_mass_position @@ -665,7 +689,6 @@ def add_motor(self, motor, position): self.evaluate_reduced_mass() self.evaluate_thrust_to_weight() self.evaluate_static_margin() - return None def add_surfaces(self, surfaces, positions): """Adds one or more aerodynamic surfaces to the rocket. The aerodynamic @@ -703,7 +726,6 @@ def add_surfaces(self, surfaces, positions): self.aerodynamic_surfaces.add(surfaces, positions) self.evaluate_static_margin() - return None def add_tail( self, top_radius, bottom_radius, length, position, radius=None, name="Tail" @@ -739,17 +761,11 @@ def add_tail( tail : Tail Tail object created. """ - # Modify reference radius if not provided radius = self.radius if radius is None else radius - - # Create new tail as an object of the Tail class + # Create tail, adds it to the rocket and returns it tail = Tail(top_radius, bottom_radius, length, radius, name) - - # Add tail to aerodynamic surfaces self.add_surfaces(tail, position) - - # Return self return tail def add_nose(self, length, kind, position, bluffness=0, name="Nose Cone"): @@ -785,7 +801,6 @@ def add_nose(self, length, kind, position, bluffness=0, name="Nose Cone"): nose : Nose Nose cone object created. """ - # Create a nose as an object of NoseCone class nose = NoseCone( length=length, kind=kind, @@ -794,11 +809,7 @@ def add_nose(self, length, kind, position, bluffness=0, name="Nose Cone"): bluffness=bluffness, name=name, ) - - # Add nose to the list of aerodynamic surfaces self.add_surfaces(nose, position) - - # Return self return nose def add_fins(self, *args, **kwargs): @@ -911,8 +922,6 @@ def add_trapezoidal_fins( # Add fin set to the list of aerodynamic surfaces self.add_surfaces(fin_set, position) - - # Return the created aerodynamic surface return fin_set def add_elliptical_fins( @@ -974,17 +983,9 @@ def add_elliptical_fins( fin_set : EllipticalFins Fin set object created. """ - - # Modify radius if not given, use rocket radius, otherwise use given. radius = radius if radius is not None else self.radius - - # Create a fin set as an object of EllipticalFins class fin_set = EllipticalFins(n, root_chord, span, radius, cant_angle, airfoil, name) - - # Add fin set to the list of aerodynamic surfaces self.add_surfaces(fin_set, position) - - # Return self return fin_set def add_parachute( @@ -1051,13 +1052,8 @@ def add_parachute( noise_signal and noisyPressureSignal which are filled in during Flight simulation. """ - # Create a parachute parachute = Parachute(name, cd_s, trigger, sampling_rate, lag, noise) - - # Add parachute to list of parachutes self.parachutes.append(parachute) - - # Return self return self.parachutes[-1] def set_rail_buttons( @@ -1096,7 +1092,6 @@ def set_rail_buttons( rail_buttons : RailButtons RailButtons object created """ - # Create a rail buttons object buttons_distance = abs(upper_button_position - lower_button_position) rail_buttons = RailButtons( buttons_distance=buttons_distance, angular_position=angular_position @@ -1108,8 +1103,7 @@ def add_cm_eccentricity(self, x, y): """Moves line of action of aerodynamic and thrust forces by equal translation amount to simulate an eccentricity in the position of the center of mass of the rocket relative to its - geometrical center line. Should not be used together with - add_cp_eccentricity and add_thrust_eccentricity. + geometrical center line. Parameters ---------- @@ -1124,16 +1118,16 @@ def add_cm_eccentricity(self, x, y): ------- self : Rocket Object of the Rocket class. + + Notes + ----- + Should not be used together with add_cp_eccentricity and + add_thrust_eccentricity. """ - # Move center of pressure to -x and -y self.cp_eccentricity_x = -x self.cp_eccentricity_y = -y - - # Move thrust center by -x and -y self.thrust_eccentricity_y = -x self.thrust_eccentricity_x = -y - - # Return self return self def add_cp_eccentricity(self, x, y): @@ -1155,11 +1149,8 @@ def add_cp_eccentricity(self, x, y): self : Rocket Object of the Rocket class. """ - # Move center of pressure by x and y self.cp_eccentricity_x = x self.cp_eccentricity_y = y - - # Return self return self def add_thrust_eccentricity(self, x, y): @@ -1182,11 +1173,8 @@ def add_thrust_eccentricity(self, x, y): self : Rocket Object of the Rocket class. """ - # Move thrust line by x and y self.thrust_eccentricity_y = x self.thrust_eccentricity_x = y - - # Return self return self def info(self): @@ -1197,11 +1185,8 @@ def info(self): ------- None """ - # All prints self.prints.all() - return None - def all_info(self): """Prints out all data and graphs available about the Rocket. @@ -1209,9 +1194,5 @@ def all_info(self): ------- None """ - - # All prints and plots self.info() self.plots.all() - - return None From 8eb1a5738cc057519cddcfaf1ea792d15343921b Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Sat, 28 Oct 2023 15:17:04 -0300 Subject: [PATCH 10/30] ENH: use of f-string in rocket prints --- rocketpy/prints/rocket_prints.py | 36 ++++++++------------------------ 1 file changed, 9 insertions(+), 27 deletions(-) diff --git a/rocketpy/prints/rocket_prints.py b/rocketpy/prints/rocket_prints.py index a3150ac02..842252a3c 100644 --- a/rocketpy/prints/rocket_prints.py +++ b/rocketpy/prints/rocket_prints.py @@ -22,8 +22,6 @@ def __init__(self, rocket): """ self.rocket = rocket - pass - def inertia_details(self): """Print inertia details. @@ -32,43 +30,27 @@ def inertia_details(self): None """ print("\nInertia Details\n") - print("Rocket Mass: {:.3f} kg".format(self.rocket.mass)) - print("Rocket Dry Mass: {:.3f} kg (With Motor)".format(self.rocket.dry_mass)) + print("Rocket Mass: {self.rocket.mass:.3f} kg") + print(f"Rocket Dry Mass: {self.rocket.dry_mass:.3f} kg (with unloaded motor)") + print(f"Rocket Mass: {self.rocket.total_mass(0):.3f} kg (With Propellant)") print( - "Rocket Mass: {:.3f} kg (With Propellant)".format(self.rocket.total_mass(0)) + f"Rocket Inertia (with unloaded motor) 11: {self.rocket.dry_I_11:.3f} kg*m2" ) print( - "Rocket Inertia (with motor, but without propellant) 11: {:.3f} kg*m2".format( - self.rocket.dry_I_11 - ) + f"Rocket Inertia (with unloaded motor) 22: {self.rocket.dry_I_22:.3f} kg*m2" ) print( - "Rocket Inertia (with motor, but without propellant) 22: {:.3f} kg*m2".format( - self.rocket.dry_I_22 - ) + f"Rocket Inertia (with unloaded motor) 33: {self.rocket.dry_I_33:.3f} kg*m2" ) print( - "Rocket Inertia (with motor, but without propellant) 33: {:.3f} kg*m2".format( - self.rocket.dry_I_33 - ) + f"Rocket Inertia (with unloaded motor) 12: {self.rocket.dry_I_12:.3f} kg*m2" ) print( - "Rocket Inertia (with motor, but without propellant) 12: {:.3f} kg*m2".format( - self.rocket.dry_I_12 - ) + f"Rocket Inertia (with unloaded motor) 13: {self.rocket.dry_I_13:.3f} kg*m2" ) print( - "Rocket Inertia (with motor, but without propellant) 13: {:.3f} kg*m2".format( - self.rocket.dry_I_13 - ) + f"Rocket Inertia (with unloaded motor) 23: {self.rocket.dry_I_23:.3f} kg*m2" ) - print( - "Rocket Inertia (with motor, but without propellant) 23: {:.3f} kg*m2".format( - self.rocket.dry_I_23 - ) - ) - - return None def rocket_geometrical_parameters(self): """Print rocket geometrical parameters. From c4c8f64e44b52730a705f409bd7cf08ccee476d6 Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Sat, 28 Oct 2023 15:48:40 -0300 Subject: [PATCH 11/30] DOC: fix the rocket mass definition in utilities --- rocketpy/utilities.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/rocketpy/utilities.py b/rocketpy/utilities.py index 78992ffb6..b5e72abcb 100644 --- a/rocketpy/utilities.py +++ b/rocketpy/utilities.py @@ -1,8 +1,8 @@ import traceback import warnings -import numpy as np import matplotlib.pyplot as plt +import numpy as np from scipy.integrate import solve_ivp from .environment.environment import Environment @@ -517,11 +517,11 @@ def create_dispersion_dictionary(filename): def apogee_by_mass(flight, min_mass, max_mass, points=10, plot=True): """Returns a Function object that estimates the apogee of a rocket given - its dry mass. The function will use the rocket's mass as the independent - variable and the estimated apogee as the dependent variable. The function - will use the rocket's environment and inclination to estimate the apogee. - This is useful when you want to adjust the rocket's mass to reach a - specific apogee. + its mass (no motor). The function will use the rocket's mass as the + independent variable and the estimated apogee as the dependent variable. + The function will use the rocket's environment and inclination to estimate + the apogee. This is useful when you want to adjust the rocket's mass to + reach a specific apogee. Parameters ---------- @@ -529,8 +529,8 @@ def apogee_by_mass(flight, min_mass, max_mass, points=10, plot=True): Flight object containing the rocket's flight data min_mass : int The minimum value of mass to calculate the apogee, by default 3. This - value should be the minimum dry mass of the rocket, therefore, a - positive value is expected. + value should be the minimum rocket's mass, therefore, a positive value + is expected. max_mass : int The maximum value of mass to calculate the apogee, by default 30. points : int, optional @@ -544,7 +544,7 @@ def apogee_by_mass(flight, min_mass, max_mass, points=10, plot=True): ------- rocketpy.Function Function object containing the estimated apogee as a function of the - rocket's dry mass. + rocket's mass (without motor nor propellant). """ rocket = flight.rocket @@ -581,8 +581,8 @@ def apogee(mass): def liftoff_speed_by_mass(flight, min_mass, max_mass, points=10, plot=True): """Returns a Function object that estimates the liftoff speed of a rocket - given its dry mass. The function will use the rocket's mass as the - independent variable and the estimated liftoff speed as the dependent + given its mass (without motor). The function will use the rocket's mass as + the independent variable and the estimated liftoff speed as the dependent variable. The function will use the rocket's environment and inclination to estimate the liftoff speed. This is useful when you want to adjust the rocket's mass to reach a specific liftoff speed. @@ -593,8 +593,8 @@ def liftoff_speed_by_mass(flight, min_mass, max_mass, points=10, plot=True): Flight object containing the rocket's flight data min_mass : int The minimum value of mass to calculate the liftoff speed, by default 3. - This value should be the minimum dry mass of the rocket, therefore, a - positive value is expected. + This value should be the minimum mass of the rocket (without a motor), + therefore, a positive value is expected. max_mass : int The maximum value of mass to calculate the liftoff speed, by default 30. points : int, optional @@ -608,7 +608,7 @@ def liftoff_speed_by_mass(flight, min_mass, max_mass, points=10, plot=True): ------- rocketpy.Function Function object containing the estimated liftoff speed as a function of - the rocket's dry mass. + the rocket's mass (without motor nor propellant). """ rocket = flight.rocket From dff8214d26e7968620da1a40036f9a758385d228 Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Sun, 29 Oct 2023 13:48:40 -0300 Subject: [PATCH 12/30] TST: remove travis CI file, no longer used --- .travis.yml | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index b2ec516aa..000000000 --- a/.travis.yml +++ /dev/null @@ -1,18 +0,0 @@ -language: python -python: - - "3.8" -install: - - make install - - pip install -r requirements-tests.txt -before_script: - - make verify-lint -script: - - make test -deploy: - provider: pypi - username: __token__ - password: - secure: ZgTCJPP2JyS9IoJeFZo1x8dwUmFW3ZZ5OzHKJvb/zKZXhpkQsaKWQOu79E00KYq2r36O6gnKiut8XbGuJoCcbIyZiPELizZ7KZskl+pJRhk78YAwb9eIp0yY7Fso6LmdDuYAdxLBHvaFcEpb60IEOXv6iBPbGSKIR18xVdOBzbb/kbscpOPez9Zmdgz6jTQYRwHA8BwW5Eo02FG20GDGiQCzbfhDZbIQcoHLXLM1fEDRLKZl7yBJOO62RGaMaTyn88CrGTIL5cz3od8okLS2c4FJvxGdieMw6LuCXOcJYWKhPCGGBXUOBEfjeEH0rVQz9YRV4Q+l2us/UtRAMXDWGCjWq8cnQU/4CUVWqxkF/7SN/CytkaKGJ0outqYO0nqT1zgRohiIJqL0lW6VrMHDSonA03VEq5DrueIh4/XAPMj4Vl7PIv8R1ztKm+vHEPO6fEjTd2S9B7mIc4VuOLI0NJuaPemlAO4HnIU/WO49T06QTVhjOdzoNnvN/N9W8mBnvMmytvTVvGyF0gAKpvFx48YxsI4ltTLyVjRTrV30mDrylUHr8bGkB2SI/ZEhQFLuO3J1qK7zitgT5h9ZXRtGMs5N08GMhHtAyKdRUmzLtb0+baZnkoLNi8XYbd0Np8fLwH+M080Soy4robPai+ZnZQKLyRObcxhRsfqKuu7L0LI= - on: - tags: true - branch: master From 88f279881afa6c24b045c91f8156549c402dd644 Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Sun, 29 Oct 2023 14:49:52 -0300 Subject: [PATCH 13/30] TST: update workflow to run on new python 3.12 --- .github/workflows/test_pytest.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test_pytest.yaml b/.github/workflows/test_pytest.yaml index 7ca507dde..8b7bf15b9 100644 --- a/.github/workflows/test_pytest.yaml +++ b/.github/workflows/test_pytest.yaml @@ -7,13 +7,13 @@ on: - "**.py" jobs: - fail_if_pull_request_is_draft: + fail_if_pr_is_draft: if: github.event.pull_request.draft == true runs-on: ubuntu-18.04 steps: - name: Fails in order to indicate that pull request needs to be marked as ready to review and unit tests workflow needs to pass. run: exit 1 - run_pytest_and_doctest: + pytest_and_doctest: runs-on: ${{ matrix.os }} strategy: matrix: @@ -23,7 +23,7 @@ jobs: - windows-latest python-version: - 3.8 - - 3.11 + - 3.12 steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} From 4542d9df1ac665bd040d08db95c2a451c6ddce4f Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Sun, 29 Oct 2023 16:37:01 -0300 Subject: [PATCH 14/30] ENH: adding docker files --- .dockerignore | 46 ++++++++++++++++++++++++++++++++++++++++++++++ Dockerfile | 30 ++++++++++++++++++++++++++++++ docker-compose.yml | 24 ++++++++++++++++++++++++ 3 files changed, 100 insertions(+) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 docker-compose.yml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..3ee85f1d4 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,46 @@ +# Python files +__pycache__/ +*.pyc +*.pyo +*.pyd +.Python +db.sqlite3 +/db.sqlite3 +pip-log.txt +pip-delete-this-directory.txt +.pytest_cache/ + +# Documentation and Tests +docs/ +.coverage +readthedocs.yml +.travis.yml +Makefile + +# Binary and Package files +*.egg-info/ +*.egg +dist/ +build/ + +# VCS +.git/ +.gitignore +.gitattributes +.github/ + +# IDEs and Editors +.vscode + +# Virtual environments +.venv +venv +ENV/ +env/ + +# Others +*.log + +# Docker +Dockerfile +.dockerignore diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..34553e2ed --- /dev/null +++ b/Dockerfile @@ -0,0 +1,30 @@ +# Set base image +# python:latest will get the latest version of python, on linux +# Get a full list of python images here: https://hub.docker.com/_/python/tags +FROM python:latest + +# set the working directory in the container +WORKDIR /RocketPy + +# Ensure pip is updated +RUN python3 -m pip install --upgrade pip + +# Copy the dependencies file to the working directory +COPY requirements.txt . +COPY requirements-tests.txt . + +# Install dependencies +# Use a single RUN instruction to minimize the number of layers +RUN pip install \ + -r requirements.txt \ + -r requirements-tests.txt + +# copy the content of the local src directory to the working directory +COPY . . + +# command to run on container start +# print the operational system and the python version +CMD [ "python3", "-c", "import platform;import sys; print('Python ', sys.version, ' running on ', platform.platform())" ] + +# Install the rocketpy package # TODO: check if I can put this in editable mode +RUN pip install . diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..af9afbddd --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,24 @@ +version: '3.8' + +services: + python38-linux: + image: python:3.8 + volumes: + - .:/app + working_dir: /app + command: bash -c "pip install . && pip install -r requirements-tests.txt && pytest && cd rocketpy && pytest --doctest-modules" + logging: + options: + max-size: "10m" + max-file: "3" + + python312-linux: + image: python:3.12 + volumes: + - .:/app + working_dir: /app + command: bash -c "pip install . && pip install -r requirements-tests.txt && pytest && cd rocketpy && pytest --doctest-modules" + logging: + options: + max-size: "10m" + max-file: "3" \ No newline at end of file From 243d266ccc37f464e33f40096bcb8ad0810ec072 Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Sun, 29 Oct 2023 16:37:41 -0300 Subject: [PATCH 15/30] DOC: docker instructions attached to the docs --- docs/development/docker.rst | 174 ++++++++++++++++++++++++++++++++++++ docs/development/index.rst | 1 + 2 files changed, 175 insertions(+) create mode 100644 docs/development/docker.rst diff --git a/docs/development/docker.rst b/docs/development/docker.rst new file mode 100644 index 000000000..cc64d6cfd --- /dev/null +++ b/docs/development/docker.rst @@ -0,0 +1,174 @@ +RocketPy with docker +===================== + +RocketPy does not provide an official docker image, but you can build one +yourself using the provided `Dockerfile` and `docker-compose.yml` files. + +Benefits +-------- + +Docker allows you to run applications in containers. The main benefits of +using docker are: + +1. **Isolation**: run RocketPy in a fresh environment, without + worrying about dependencies. +2. **Portability**: run RocketPy on any operational system that supports + docker, including the 3 main operational systems (Windows, Linux and Mac). +3. **Reproducibility**: ensure that tour code is working regardless of the + operational system. + +Using docker will be specially important when you are not sure if the code +additions will still run on different operational systems. + +Although we have a set of GitHub actions to test the code on different +operational systems every time a pull request is made, it is important to +submit a PR only after you are sure that the code will run flawlessly, +otherwise quota limits may be reached on GitHub. + +Requirements +------------- + +Before you start, you need to install on your machine: + +1. `Docker `__, to build and run the image. +2. `Docker Compose `__, to compose multiple images at once. +3. Also, make sure you have cloned the RocketPy repository in your machine! + +Build the image +---------------- + +To build the image, run the following command on your terminal: + +.. code-block:: console + + docker build -t rocketpy-image -f Dockerfile . + + +This will build the image and tag it as `rocketpy-image` (you can apply another +name of your preference if you want). + +An image is a read-only template with instructions for creating a Docker +container (see `Docker docs `__). + +This process may take a while, since it will create an image that could easily +be 1.5 GB in size. +But don't worry, you just need to build the image once. + +Run the container +----------------- + +Now that you have the image, you can run it as a container: + +.. code-block:: console + + docker run -it --entrypoint /bin/bash rocketpy-image + + +This will run the container and open a bash terminal inside it. +If you are using VSCode, you can even integrate the running container into your +IDE, allowing you to code and test directly within the container environment. +This is particularly useful if you want to maintain your usual development setup +while ensuring consistency in the execution environment. +For more details on how to do this, refer to the +`VSCode docs `__ +on developing inside a container. + +Indeed, vscode offers a full support for docker, read the +`vscode docs `__ +for more details + + +Run the unit tests +-------------------- + +You might have noticed that the container is running in an isolated environment +with no access to your machine's files, but the `Dockerfile` already copied the +RocketPy repository to the container. +This means that you can run tests (and simulations!) as if you were running +RocketPy on your machine. + +As simple as that, you can run the unit tests: + +.. code-block:: console + + pytest + + +To access a list of all available execution options, see the +`pytest docs `__. + +Compose docker images +--------------------- + +We also made available a `docker-compose.yml` file that allows you to compose +multiple docker images at once. +Unfortunately, this file will not allow you to test the code on different +operational systems at once, since docker images inherits from the host +operational system. +However, it is still useful to run the unit tests on different python versions. + +Currently, the `docker-compose.yml` file is configured to run the unit tests +on python 3.8 and 3.12. + +To run the unit tests on both python versions, run the following command +**on your machine**: + +.. code-block:: console + + docker-compose up + +Also, you can check the logs of the containers by running: + +.. code-block:: console + + docker-compose logs + + +This command is especially useful for debugging if any issues occur during the +build process or when running the containers. + +After you're done testing, or if you wish to stop the containers and remove the +services, use the command: + +.. code-block:: console + + docker-compose down + + +This will stop the running containers and remove the networks, volumes, and +images created by up. + + +Changing to other operational systems +------------------------------------- + +The default image in the `Dockerfile` is based on a Linux distribution. +However, you can alter the base image to use different operating systems, though +the process may require additional steps depending on the OS's compatibility +with your project setup. +For instance, certain dependencies or scripts may behave differently or require +different installation procedures, so use it with caution. + +To change the base image, you will need to modify the `FROM` statement in the +`Dockerfile`. +For example, to use a Windows-based image, you might change: + +.. code-block:: Dockerfile + + FROM python:latest + + +to + +.. code-block:: Dockerfile + + FROM mcr.microsoft.com/windows/servercore:ltsc2019 + + +Please note, the above is just an example, and using a different OS may require +further adjustments in the `Dockerfile`. +We recommend you to see the official Python images on the Docker Hub for +different OS options: `Docker Hub Python Tags `__. + +Be aware that switching to a non-Linux image can lead to larger image sizes and +longer pull times. diff --git a/docs/development/index.rst b/docs/development/index.rst index b13769e56..767516c56 100644 --- a/docs/development/index.rst +++ b/docs/development/index.rst @@ -8,5 +8,6 @@ Contributing to RocketPy Running RocketPy as a Developer GitHub Workflow for RocketPy Hackathon 2022 Style Guide + RocketPy with Docker This section is still a work in progress. Here you will find information on how to contribute to our code base. \ No newline at end of file From 9c8971cc0fec866fa42312a1598a61a35676ac13 Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Thu, 2 Nov 2023 20:47:54 -0300 Subject: [PATCH 16/30] DOC: Add documentation on how to build the RocketPy docs locally --- docs/development/build_docs.rst | 97 +++++++++++++++++++++++++++++++++ docs/development/index.rst | 1 + 2 files changed, 98 insertions(+) create mode 100644 docs/development/build_docs.rst diff --git a/docs/development/build_docs.rst b/docs/development/build_docs.rst new file mode 100644 index 000000000..1234ce227 --- /dev/null +++ b/docs/development/build_docs.rst @@ -0,0 +1,97 @@ +RocketPy documentation +====================== + +RocketPy uses `Sphinx `_ to generate the +documentation. +Sphinx makes it easy to create documentation for Python projects and it is +widely used for Python projects, such as +`NumPy `_, +`Pandas `_ and +`SciPy `_. + + +The `ReadTheDocs `_ is used +to host the documentation. It is a free service that automatically builds +documentation from your sphinx source files and hosts them for free. + +RocketPy official documentation is available at +`https://docs.rocketpy.org `_. + + +How to build the documentation in your local machine +---------------------------------------------------- + +When you find yourself modifying the documentation files and trying to see the +results, you can build the documentation in your local machine. +This is important to check if the documentation is being generated correctly +before pushing the changes to the repository. + +To build the documentation in your local machine, you need to install a set of +requirements that are needed to run the sphinx generator. +All these requirements are listed in the `requirements.txt` file inside the +`docs` folder. + +To install the requirements, run the following command in your terminal: + +```bash +cd docs +pip install -r requirements.txt +``` + +After installing the requirements, you can build the documentation by running +the following command in your terminal: + +```bash +make html +``` + +The file named `Makefile` contains the commands to build the documentation. +The `make html` command will generate the documentation in the `docs/_build/html` +folder. + +To see the documentation, open the `docs/_build/html/index.html` file in your +browser. + +.. note:: Watch out for any warnings or errors that may appear in the terminal + when building the documentation. If you find any, fix them before + pushing the changes to the repository or at least warn the team about + them. + +Sometimes you may face problems when building the documentation after several +times of building it. +This may happen because sphinx does not clean the `docs/_build` folder before +building the documentation again. +To clean the `docs/_build` folder, run the following command in your terminal: + +```bash +make clean +``` + +After cleaning the `docs/_build` folder, you can build the documentation again +by running the `make html` command. + +If the error persists, it may be related to other files, such as the `.rst` +files or the `conf.py` file. + +.. danger:: Do not modify the Makefile or the make.bat files. These files are + automatically generated by sphinx and any changes will be lost. + + +How to integrate the documentation with ReadTheDocs +---------------------------------------------------- + +The documentation is automatically built and hosted by ReadTheDocs. +Every time a commit is pushed to the repository, ReadTheDocs will build the +documentation and host it automatically. +This includes other branches besides the master branch. +However, the documentation will only be available for the master branch, and you +need to configure the ReadTheDocs project to build the documentation for other +branches. + +The connection between the GitHub repository and the ReadTheDocs project is +already configured and defined in the `readthedocs.yml` file, available at the +root of the repository. + +.. note:: You need admin permissions to configure the ReadTheDocs project. Ask + the team for help if you don't have admin permissions. + diff --git a/docs/development/index.rst b/docs/development/index.rst index b13769e56..694d8f4af 100644 --- a/docs/development/index.rst +++ b/docs/development/index.rst @@ -8,5 +8,6 @@ Contributing to RocketPy Running RocketPy as a Developer GitHub Workflow for RocketPy Hackathon 2022 Style Guide + Building the Documentation This section is still a work in progress. Here you will find information on how to contribute to our code base. \ No newline at end of file From 3b10a35aa3beb225c67f62fd1cba82890cf12858 Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Thu, 2 Nov 2023 21:05:29 -0300 Subject: [PATCH 17/30] BUG: Update build_docs.rst link in development index.rst --- docs/development/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/development/index.rst b/docs/development/index.rst index 694d8f4af..a4bff85c8 100644 --- a/docs/development/index.rst +++ b/docs/development/index.rst @@ -8,6 +8,6 @@ Contributing to RocketPy Running RocketPy as a Developer GitHub Workflow for RocketPy Hackathon 2022 Style Guide - Building the Documentation + Building the Documentation This section is still a work in progress. Here you will find information on how to contribute to our code base. \ No newline at end of file From 3676578cfdff19830790c8440d11fe4d948319bd Mon Sep 17 00:00:00 2001 From: MateusStano Date: Mon, 6 Nov 2023 22:02:34 +0100 Subject: [PATCH 18/30] DOCS: change italics to inline code --- docs/development/build_docs.rst | 36 ++++++++++++++++----------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/docs/development/build_docs.rst b/docs/development/build_docs.rst index 1234ce227..cf2ad87ac 100644 --- a/docs/development/build_docs.rst +++ b/docs/development/build_docs.rst @@ -28,8 +28,8 @@ before pushing the changes to the repository. To build the documentation in your local machine, you need to install a set of requirements that are needed to run the sphinx generator. -All these requirements are listed in the `requirements.txt` file inside the -`docs` folder. +All these requirements are listed in the ``requirements.txt`` file inside the +``docs`` folder. To install the requirements, run the following command in your terminal: @@ -45,8 +45,8 @@ the following command in your terminal: make html ``` -The file named `Makefile` contains the commands to build the documentation. -The `make html` command will generate the documentation in the `docs/_build/html` +The file named ``Makefile`` contains the commands to build the documentation. +The ``make html`` command will generate the documentation in the ``docs/_build/html`` folder. To see the documentation, open the `docs/_build/html/index.html` file in your @@ -59,39 +59,39 @@ browser. Sometimes you may face problems when building the documentation after several times of building it. -This may happen because sphinx does not clean the `docs/_build` folder before +This may happen because sphinx does not clean the ``docs/_build`` folder before building the documentation again. -To clean the `docs/_build` folder, run the following command in your terminal: +To clean the ``docs/_build`` folder, run the following command in your terminal: ```bash make clean ``` -After cleaning the `docs/_build` folder, you can build the documentation again -by running the `make html` command. +After cleaning the ``docs/_build`` folder, you can build the documentation again +by running the ``make html`` command. -If the error persists, it may be related to other files, such as the `.rst` -files or the `conf.py` file. +If the error persists, it may be related to other files, such as the ``.rst`` +files or the ``conf.py`` file. -.. danger:: Do not modify the Makefile or the make.bat files. These files are +.. danger:: Do not modify the Makefile or the ``make.bat`` files. These files are automatically generated by sphinx and any changes will be lost. How to integrate the documentation with ReadTheDocs ----------------------------------------------------- +--------------------------------------------------- -The documentation is automatically built and hosted by ReadTheDocs. -Every time a commit is pushed to the repository, ReadTheDocs will build the +The documentation is automatically built and hosted by `ReadTheDocs`. +Every time a commit is pushed to the repository, `ReadTheDocs` will build the documentation and host it automatically. This includes other branches besides the master branch. However, the documentation will only be available for the master branch, and you -need to configure the ReadTheDocs project to build the documentation for other +need to configure the `ReadTheDocs` project to build the documentation for other branches. -The connection between the GitHub repository and the ReadTheDocs project is -already configured and defined in the `readthedocs.yml` file, available at the +The connection between the GitHub repository and the `ReadTheDocs` project is +already configured and defined in the ``readthedocs.yml`` file, available at the root of the repository. -.. note:: You need admin permissions to configure the ReadTheDocs project. Ask +.. note:: You need admin permissions to configure the `ReadTheDocs` project. Ask the team for help if you don't have admin permissions. From 99d7789f8d01797252ac492ed2d730f577533353 Mon Sep 17 00:00:00 2001 From: MateusStano Date: Mon, 6 Nov 2023 22:03:03 +0100 Subject: [PATCH 19/30] DOCS: add bash code blocks --- docs/development/build_docs.rst | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/development/build_docs.rst b/docs/development/build_docs.rst index cf2ad87ac..5b60b0c99 100644 --- a/docs/development/build_docs.rst +++ b/docs/development/build_docs.rst @@ -31,25 +31,25 @@ requirements that are needed to run the sphinx generator. All these requirements are listed in the ``requirements.txt`` file inside the ``docs`` folder. -To install the requirements, run the following command in your terminal: +To install the requirements, navigate the terminal to the ``docs`` folder and +run the following command: -```bash -cd docs -pip install -r requirements.txt -``` +.. code-block:: bash + + pip install -r requirements.txt After installing the requirements, you can build the documentation by running the following command in your terminal: -```bash -make html -``` +.. code-block:: bash + + make html The file named ``Makefile`` contains the commands to build the documentation. The ``make html`` command will generate the documentation in the ``docs/_build/html`` folder. -To see the documentation, open the `docs/_build/html/index.html` file in your +To see the documentation, open the ``docs/_build/html/index.html`` file in your browser. .. note:: Watch out for any warnings or errors that may appear in the terminal @@ -63,9 +63,9 @@ This may happen because sphinx does not clean the ``docs/_build`` folder before building the documentation again. To clean the ``docs/_build`` folder, run the following command in your terminal: -```bash -make clean -``` +.. code-block:: bash + + make clean After cleaning the ``docs/_build`` folder, you can build the documentation again by running the ``make html`` command. From 52f66b56e7a871def3d8b3ee189e73e21ec5fcba Mon Sep 17 00:00:00 2001 From: Guilherme Date: Wed, 8 Nov 2023 05:02:47 -0300 Subject: [PATCH 20/30] DOC: accepts commit suggestion Co-authored-by: MateusStano <69485049+MateusStano@users.noreply.github.com> --- rocketpy/rocket/rocket.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rocketpy/rocket/rocket.py b/rocketpy/rocket/rocket.py index 2ef9998b2..ad466e21b 100644 --- a/rocketpy/rocket/rocket.py +++ b/rocketpy/rocket/rocket.py @@ -313,7 +313,7 @@ def __init__( @property def nosecones(self): - """A list containing all the noses currently added to the rocket.""" + """A list containing all the nose cones currently added to the rocket.""" return self.aerodynamic_surfaces.get_by_type(NoseCone) @property From 56ff690f98ab60d292e27b3aade440e01f9c54b1 Mon Sep 17 00:00:00 2001 From: Guilherme Date: Wed, 8 Nov 2023 05:03:22 -0300 Subject: [PATCH 21/30] DOC: accepts commit suggestion Co-authored-by: MateusStano <69485049+MateusStano@users.noreply.github.com> --- rocketpy/rocket/rocket.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rocketpy/rocket/rocket.py b/rocketpy/rocket/rocket.py index ad466e21b..99cdc018b 100644 --- a/rocketpy/rocket/rocket.py +++ b/rocketpy/rocket/rocket.py @@ -391,7 +391,7 @@ def evaluate_center_of_mass(self): self.center_of_mass.set_inputs("Time (s)") self.center_of_mass.set_outputs("Center of Mass Position (m)") self.center_of_mass.set_title( - "Center of Mass Position (includes motor and propellant)" + "Center of Mass Position (Rocket + Motor + Propellant)" ) return self.center_of_mass From ee44405bd51065cbd67b227dc09bff60b0315ec8 Mon Sep 17 00:00:00 2001 From: Guilherme Date: Wed, 8 Nov 2023 05:04:17 -0300 Subject: [PATCH 22/30] DOC: accepts commit suggestion Co-authored-by: MateusStano <69485049+MateusStano@users.noreply.github.com> --- rocketpy/rocket/rocket.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rocketpy/rocket/rocket.py b/rocketpy/rocket/rocket.py index 99cdc018b..dfaa77d08 100644 --- a/rocketpy/rocket/rocket.py +++ b/rocketpy/rocket/rocket.py @@ -606,7 +606,7 @@ def evaluate_inertias(self): """ # Get masses prop_mass = self.motor.propellant_mass # Propellant mass as a function of time - dry_mass = self.dry_mass # Constant rocket mass without motor + dry_mass = self.dry_mass # Constant rocket mass with motor, without propellant # Compute axes distances CM_to_CDM = self.center_of_mass - self.center_of_dry_mass_position From 8d0169ec64c8228196cd6d14ff883f907ace0045 Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Thu, 9 Nov 2023 16:12:22 -0300 Subject: [PATCH 23/30] DOC: Update dry_mass documentation for all motor classes --- rocketpy/motors/hybrid_motor.py | 13 +++++++++---- rocketpy/motors/liquid_motor.py | 13 +++++++++---- rocketpy/motors/motor.py | 20 ++++++++++++++------ rocketpy/motors/solid_motor.py | 6 ++++-- 4 files changed, 36 insertions(+), 16 deletions(-) diff --git a/rocketpy/motors/hybrid_motor.py b/rocketpy/motors/hybrid_motor.py index d23812ddb..7613c0b6b 100644 --- a/rocketpy/motors/hybrid_motor.py +++ b/rocketpy/motors/hybrid_motor.py @@ -42,8 +42,10 @@ class HybridMotor(Motor): HybridMotor.liquid : LiquidMotor Liquid motor object that composes the hybrid motor. HybridMotor.dry_mass : float - The total mass of the motor structure, including chambers - and tanks, when it is empty and does not contain any propellant. + The total mass of the motor structure, including chambers, bulkheads, + screws, tanks, and others. This should be taken when the motor is + empty and does not contain any propellant. You should not double + count a component that is already accounted for in the rocket class. HybridMotor.propellant_initial_mass : float Total propellant initial mass in kg. This is the sum of the initial mass of fluids in each tank and the initial mass of the solid grains. @@ -202,8 +204,11 @@ def __init__( .. seealso:: :doc:`Thrust Source Details ` dry_mass : int, float - The total mass of the motor structure, including chambers - and tanks, when it is empty and does not contain any propellant. + The total mass of the motor structure, including chambers, + bulkheads, screws, tanks, and others. This should be taken when the + motor is empty and does not contain any propellant. You should not + double count a component that is already accounted for in the rocket + class. dry_inertia : tuple, list Tuple or list containing the motor's dry mass inertia tensor components, in kg*m^2. This inertia is defined with respect to the diff --git a/rocketpy/motors/liquid_motor.py b/rocketpy/motors/liquid_motor.py index ec6b286ca..ba0f8940c 100644 --- a/rocketpy/motors/liquid_motor.py +++ b/rocketpy/motors/liquid_motor.py @@ -37,8 +37,10 @@ class LiquidMotor(Motor): List containing the motor's added tanks and their respective positions. LiquidMotor.dry_mass : float - The total mass of the motor structure, including chambers - and tanks, when it is empty and does not contain any propellant. + The total mass of the motor structure, including chambers, bulkheads, + screws, tanks, and others. This should be taken when the motor is empty + and does not contain any propellant. You should not double count a + component that is already accounted for in the rocket class. LiquidMotor.propellant_initial_mass : float Total propellant initial mass in kg, includes fuel and oxidizer. LiquidMotor.total_mass : Function @@ -174,8 +176,11 @@ def __init__( .. seealso:: :doc:`Thrust Source Details ` dry_mass : int, float - The total mass of the motor structure, including chambers - and tanks, when it is empty and does not contain any propellant. + The total mass of the motor structure, including chambers, + bulkheads, screws, tanks, and others. This should be taken when the + motor is empty and does not contain any propellant. You should not + double count a component that is already accounted for in the rocket + class. dry_inertia : tuple, list Tuple or list containing the motor's dry mass inertia tensor components, in kg*m^2. This inertia is defined with respect to the diff --git a/rocketpy/motors/motor.py b/rocketpy/motors/motor.py index f7bf08e7e..c7d07aa21 100644 --- a/rocketpy/motors/motor.py +++ b/rocketpy/motors/motor.py @@ -36,8 +36,10 @@ class Motor(ABC): :doc:`Positions and Coordinate Systems ` for more information. Motor.dry_mass : float - The total mass of the motor structure, including chambers - and tanks, when it is empty and does not contain any propellant. + The total mass of the motor structure, including chambers, bulkheads, + screws, tanks, and others. This should be taken when the motor is empty + and does not contain any propellant. You should not double count a + component that is already accounted for in the rocket class. Motor.propellant_initial_mass : float Total propellant initial mass in kg, including solid, liquid and gas phases. @@ -176,8 +178,11 @@ def __init__( .. seealso:: :doc:`Thrust Source Details ` dry_mass : int, float - The total mass of the motor structure, including chambers - and tanks, when it is empty and does not contain any propellant. + The total mass of the motor structure, including chambers, + bulkheads, screws, tanks, and others. This should be taken when the + motor is empty and does not contain any propellant. You should not + double count a component that is already accounted for in the rocket + class. center_of_dry_mass_position : int, float The position, in meters, of the motor's center of mass with respect to the motor's coordinate system when it is devoid of propellant. @@ -1104,8 +1109,11 @@ def __init__( coordinate system. See :doc:`Positions and Coordinate Systems ` dry_mass : int, float - The total mass of the motor structure, including chambers - and tanks, when it is empty and does not contain any propellant. + The total mass of the motor structure, including chambers, + bulkheads, screws, tanks, and others. This should be taken when the + motor is empty and does not contain any propellant. You should not + double count a component that is already accounted for in the rocket + class. propellant_initial_mass : int, float The initial mass of the propellant in the motor. center_of_dry_mass_position : int, float, optional diff --git a/rocketpy/motors/solid_motor.py b/rocketpy/motors/solid_motor.py index 761fbd2c0..3341ff266 100644 --- a/rocketpy/motors/solid_motor.py +++ b/rocketpy/motors/solid_motor.py @@ -228,8 +228,10 @@ def __init__( nozzle_radius : int, float Motor's nozzle outlet radius in meters. dry_mass : int, float - The total mass of the motor structure, including chambers - and tanks, when it is empty and does not contain any propellant. + The total mass of the motor structure, including chambers, + bulkheads, screws, and others. This should be taken when the motor + is empty and does not contain any propellant. You should not double + count a component that is already accounted for in the rocket class. dry_inertia : tuple, list Tuple or list containing the motor's dry mass inertia tensor components, in kg*m^2. This inertia is defined with respect to the From cf6f9774280c6fce9dca5297c7bf260f0bf34304 Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Thu, 9 Nov 2023 18:08:13 -0300 Subject: [PATCH 24/30] TST: Add .github/** to paths in test_pytest.yaml This is important because the github actions were not being activated after modifying the .yaml files --- .github/workflows/test_pytest.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test_pytest.yaml b/.github/workflows/test_pytest.yaml index 8b7bf15b9..2d611f399 100644 --- a/.github/workflows/test_pytest.yaml +++ b/.github/workflows/test_pytest.yaml @@ -5,6 +5,7 @@ on: types: [opened, synchronize, reopened, ready_for_review] paths: - "**.py" + - ".github/**" jobs: fail_if_pr_is_draft: From 8b0800d0f242e7555b3dc26a9af8e725917e1c5a Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Thu, 9 Nov 2023 22:01:34 -0300 Subject: [PATCH 25/30] MNT: Add Codecov integration to test_pytest.yaml --- .github/workflows/test_pytest.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/test_pytest.yaml b/.github/workflows/test_pytest.yaml index 2d611f399..11e0380ee 100644 --- a/.github/workflows/test_pytest.yaml +++ b/.github/workflows/test_pytest.yaml @@ -48,3 +48,7 @@ jobs: pytest cd rocketpy pytest --doctest-modules + - name: Upload reports to Codecov + uses: codecov/codecov-action@v3 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} From 3a46aede5c77dfa0232532fce31c2d2abdf3b115 Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Thu, 9 Nov 2023 22:17:20 -0300 Subject: [PATCH 26/30] MNT: Add code coverage to pytest workflow --- .github/workflows/test_pytest.yaml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test_pytest.yaml b/.github/workflows/test_pytest.yaml index 11e0380ee..31500e8a9 100644 --- a/.github/workflows/test_pytest.yaml +++ b/.github/workflows/test_pytest.yaml @@ -45,10 +45,16 @@ jobs: pip install -r requirements-tests.txt - name: Test with pytest run: | - pytest + pytest --cov=rocketpy --cov-report=xml cd rocketpy - pytest --doctest-modules - - name: Upload reports to Codecov + pytest --doctest-modules --cov=rocketpy --cov-report=xml --cov-append + - name: Upload coverage report to Codecov uses: codecov/codecov-action@v3 + with: + file: ./coverage.xml + flags: unittests + name: codecov-umbrella + fail_ci_if_error: true + verbose: true env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} From c848621613057174176120d1afa6778b5440e095 Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Thu, 9 Nov 2023 23:03:01 -0300 Subject: [PATCH 27/30] MNT: environment variables with codecov --- .github/workflows/test_pytest.yaml | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test_pytest.yaml b/.github/workflows/test_pytest.yaml index 31500e8a9..855cac949 100644 --- a/.github/workflows/test_pytest.yaml +++ b/.github/workflows/test_pytest.yaml @@ -25,6 +25,9 @@ jobs: python-version: - 3.8 - 3.12 + env: + OS: ${{ matrix.os }} + PYTHON: ${{ matrix.python-version }} steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} @@ -47,14 +50,15 @@ jobs: run: | pytest --cov=rocketpy --cov-report=xml cd rocketpy - pytest --doctest-modules --cov=rocketpy --cov-report=xml --cov-append + pytest --doctest-modules --cov=rocketpy --cov-report=xml - name: Upload coverage report to Codecov uses: codecov/codecov-action@v3 with: - file: ./coverage.xml + token: ${{ secrets.CODECOV_TOKEN }} + directory: ./coverage/reports/ + env_vars: OS,PYTHON + fail_ci_if_error: true + files: ./coverage.xml, ./rocketpy/coverage.xml flags: unittests name: codecov-umbrella - fail_ci_if_error: true verbose: true - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} From 7934beb5c907e7afe854afdf6c8a097f8a152fd0 Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Thu, 9 Nov 2023 23:16:34 -0300 Subject: [PATCH 28/30] DOC: Add codecov badge to README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 3f1a1b989..ee089056b 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/RocketPy-Team/rocketpy/blob/master/docs/notebooks/getting_started_colab.ipynb) [![PyPI](https://img.shields.io/pypi/v/rocketpy?color=g)](https://pypi.org/project/rocketpy/) [![Documentation Status](https://readthedocs.org/projects/rocketpyalpha/badge/?version=latest)](https://docs.rocketpy.org/en/latest/?badge=latest) +[![codecov](https://codecov.io/gh/RocketPy-Team/RocketPy/graph/badge.svg?token=Ecc3bsHFeP)](https://codecov.io/gh/RocketPy-Team/RocketPy) [![Contributors](https://img.shields.io/github/contributors/RocketPy-Team/rocketpy)](https://github.com/RocketPy-Team/RocketPy/graphs/contributors) [![Chat on Discord](https://img.shields.io/discord/765037887016140840?logo=discord)](https://discord.gg/b6xYnNh) [![Sponsor RocketPy](https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub&color=%23fe8e86)](https://github.com/sponsors/RocketPy-Team) From 8eb039d332ed4ef71c5d9ef04b537468ddff80b7 Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Sun, 12 Nov 2023 18:38:55 -0300 Subject: [PATCH 29/30] DOC: improve docs based on latest review. - Refined input parameters for apogee and liftoff speed calculations - Inertia definition in the Rocket class - Change the Motor.dry_mass definition - Avoid repetition of dry_mass definitions - Add a few for docstrings --- rocketpy/motors/hybrid_motor.py | 35 +++++++++++++++++++++++++++---- rocketpy/motors/liquid_motor.py | 10 ++++----- rocketpy/motors/motor.py | 14 ++++++++----- rocketpy/motors/solid_motor.py | 5 +---- rocketpy/rocket/rocket.py | 11 ++++++---- rocketpy/utilities.py | 37 ++++++++++++++++++++------------- 6 files changed, 75 insertions(+), 37 deletions(-) diff --git a/rocketpy/motors/hybrid_motor.py b/rocketpy/motors/hybrid_motor.py index 7613c0b6b..c52d25612 100644 --- a/rocketpy/motors/hybrid_motor.py +++ b/rocketpy/motors/hybrid_motor.py @@ -42,10 +42,7 @@ class HybridMotor(Motor): HybridMotor.liquid : LiquidMotor Liquid motor object that composes the hybrid motor. HybridMotor.dry_mass : float - The total mass of the motor structure, including chambers, bulkheads, - screws, tanks, and others. This should be taken when the motor is - empty and does not contain any propellant. You should not double - count a component that is already accounted for in the rocket class. + Same as in Motor class. See the :class:`Motor ` docs. HybridMotor.propellant_initial_mass : float Total propellant initial mass in kg. This is the sum of the initial mass of fluids in each tank and the initial mass of the solid grains. @@ -514,10 +511,40 @@ def propellant_I_12(self): @funcify_method("Time (s)", "Inertia I_13 (kg m²)") def propellant_I_13(self): + """Inertia tensor 13 component of the propellant, the inertia is + relative to the e_1 and e_3 axes, centered at the instantaneous + propellant center of mass. + + Returns + ------- + Function + Propellant inertia tensor 13 component at time t. + + Notes + ----- + This is assumed to be zero due to axial symmetry of the motor. This + could be improved in the future to account for the fact that the + motor is not perfectly symmetric. + """ return 0 @funcify_method("Time (s)", "Inertia I_23 (kg m²)") def propellant_I_23(self): + """Inertia tensor 23 component of the propellant, the inertia is + relative to the e_2 and e_3 axes, centered at the instantaneous + propellant center of mass. + + Returns + ------- + Function + Propellant inertia tensor 23 component at time t. + + Notes + ----- + This is assumed to be zero due to axial symmetry of the motor. This + could be improved in the future to account for the fact that the + motor is not perfectly symmetric. + """ return 0 def add_tank(self, tank, position): diff --git a/rocketpy/motors/liquid_motor.py b/rocketpy/motors/liquid_motor.py index ba0f8940c..91422132f 100644 --- a/rocketpy/motors/liquid_motor.py +++ b/rocketpy/motors/liquid_motor.py @@ -37,10 +37,7 @@ class LiquidMotor(Motor): List containing the motor's added tanks and their respective positions. LiquidMotor.dry_mass : float - The total mass of the motor structure, including chambers, bulkheads, - screws, tanks, and others. This should be taken when the motor is empty - and does not contain any propellant. You should not double count a - component that is already accounted for in the rocket class. + Same as in Motor class. See the :class:`Motor ` docs. LiquidMotor.propellant_initial_mass : float Total propellant initial mass in kg, includes fuel and oxidizer. LiquidMotor.total_mass : Function @@ -275,8 +272,9 @@ def exhaust_velocity(self): @funcify_method("Time (s)", "Propellant Mass (kg)") def propellant_mass(self): - """Evaluates the mass of the motor as the sum of fluids mass in each - tank, which may include fuel and oxidizer and usually vary with time. + """Evaluates the total propellant mass of the motor as the sum of fluids + mass in each tank, which may include fuel and oxidizer and usually vary + with time. Returns ------- diff --git a/rocketpy/motors/motor.py b/rocketpy/motors/motor.py index c7d07aa21..b951e8724 100644 --- a/rocketpy/motors/motor.py +++ b/rocketpy/motors/motor.py @@ -36,10 +36,14 @@ class Motor(ABC): :doc:`Positions and Coordinate Systems ` for more information. Motor.dry_mass : float - The total mass of the motor structure, including chambers, bulkheads, - screws, tanks, and others. This should be taken when the motor is empty - and does not contain any propellant. You should not double count a - component that is already accounted for in the rocket class. + The mass of the motor when devoid of any propellants, measured in + kilograms (kg). It encompasses the structural weight of the motor, + including the combustion chamber, nozzles, tanks, and fasteners. + Excluded from this measure are the propellants and any other elements + that are dynamically accounted for in the `mass` parameter of the rocket + class. Ensure that mass contributions from components shared with the + rocket structure are not recounted here. This parameter does not vary + with time. Motor.propellant_initial_mass : float Total propellant initial mass in kg, including solid, liquid and gas phases. @@ -410,7 +414,7 @@ def propellant_mass(self): self.total_mass_flow_rate.integral_function() + self.propellant_initial_mass ) - @funcify_method("Time (s)", "Mass dot (kg/s)", extrapolation="zero") + @funcify_method("Time (s)", "Mass flow rate (kg/s)", extrapolation="zero") def total_mass_flow_rate(self): """Time derivative of the propellant mass as a function of time. The formula used is the opposite of thrust divided by exhaust velocity. diff --git a/rocketpy/motors/solid_motor.py b/rocketpy/motors/solid_motor.py index 3341ff266..921dc3d7c 100644 --- a/rocketpy/motors/solid_motor.py +++ b/rocketpy/motors/solid_motor.py @@ -65,10 +65,7 @@ class SolidMotor(Motor): SolidMotor.grain_initial_mass : float Initial mass of each grain in kg. SolidMotor.dry_mass : float - The total mass of the motor structure, including chambers, bulkheads, - screws, and others. This should be taken when the motor is empty and - does not contain any propellant. You should not double count a component - that is already accounted for in the rocket class. + Same as in Motor class. See the :class:`Motor ` docs. SolidMotor.propellant_initial_mass : float Total propellant initial mass in kg. SolidMotor.total_mass : Function diff --git a/rocketpy/rocket/rocket.py b/rocketpy/rocket/rocket.py index dfaa77d08..029f90c24 100644 --- a/rocketpy/rocket/rocket.py +++ b/rocketpy/rocket/rocket.py @@ -181,7 +181,9 @@ def __init__( Rocket total mass without motor in kg. inertia : tuple, list Tuple or list containing the rocket's inertia tensor components, - in kg*m^2. This should be measured without motor and propellant. + in kg*m^2. This should be measured without motor and propellant so + that the inertia reference point is the + `center_of_mass_without_motor`. Assuming e_3 is the rocket's axis of symmetry, e_1 and e_2 are orthogonal and form a plane perpendicular to e_3, the inertia tensor components must be given in the following order: (I_11, I_22, I_33, @@ -396,8 +398,9 @@ def evaluate_center_of_mass(self): return self.center_of_mass def evaluate_center_of_dry_mass(self): - """Evaluates rocket center dry of mass (i.e. rocket + motor without - propellant) position relative to user defined rocket reference system. + """Evaluates the rocket's center of dry mass (i.e. rocket with motor but + without propellant) position relative to user defined rocket reference + system. Returns ------- @@ -440,7 +443,7 @@ def evaluate_reduced_mass(self): def evaluate_thrust_to_weight(self): """Evaluates thrust to weight as a Function of time. This is defined as the motor thrust force divided by rocket weight. The gravitational - acceleration is assumed constants and equal to 9.80665 m/s^2. + acceleration is assumed constant and equals to 9.80665 m/s^2. Returns ------- diff --git a/rocketpy/utilities.py b/rocketpy/utilities.py index b5e72abcb..9f7cf3265 100644 --- a/rocketpy/utilities.py +++ b/rocketpy/utilities.py @@ -527,12 +527,16 @@ def apogee_by_mass(flight, min_mass, max_mass, points=10, plot=True): ---------- flight : rocketpy.Flight Flight object containing the rocket's flight data - min_mass : int - The minimum value of mass to calculate the apogee, by default 3. This - value should be the minimum rocket's mass, therefore, a positive value - is expected. - max_mass : int - The maximum value of mass to calculate the apogee, by default 30. + min_mass : float + The minimum value for the rocket's mass to calculate the apogee, given + in kilograms (kg). This value should be the minimum rocket's mass, + therefore, a positive value is expected. See the Rocket.mass attribute + for more details. + max_mass : float + The maximum value for the rocket's mass to calculate the apogee, given + in kilograms (kg). This value should be the maximum rocket's mass, + therefore, a positive value is expected and it should be higher than the + min_mass attribute. See the Rocket.mass attribute for more details. points : int, optional The number of points to calculate the apogee between the mass boundaries, by default 10. Increasing this value will refine the @@ -591,16 +595,21 @@ def liftoff_speed_by_mass(flight, min_mass, max_mass, points=10, plot=True): ---------- flight : rocketpy.Flight Flight object containing the rocket's flight data - min_mass : int - The minimum value of mass to calculate the liftoff speed, by default 3. - This value should be the minimum mass of the rocket (without a motor), - therefore, a positive value is expected. - max_mass : int - The maximum value of mass to calculate the liftoff speed, by default 30. + min_mass : float + The minimum value for the rocket's mass to calculate the out of rail + speed, given in kilograms (kg). This value should be the minimum + rocket's mass, therefore, a positive value is expected. See the + Rocket.mass attribute for more details. + max_mass : float + The maximum value for the rocket's mass to calculate the out of rail + speed, given in kilograms (kg). This value should be the maximum + rocket's mass, therefore, a positive value is expected and it should be + higher than the min_mass attribute. See the Rocket.mass attribute for + more details. points : int, optional The number of points to calculate the liftoff speed between the mass - boundaries, by default 10. Increasing this value will refine the results, - but will also increase the computational time. + boundaries, by default 10. Increasing this value will refine the + results, but will also increase the computational time. plot : bool, optional If True, the function will plot the results, by default True. From 90d489cd3eb80ed4c96da856ecba4d09fa3a7562 Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Sun, 12 Nov 2023 18:47:51 -0300 Subject: [PATCH 30/30] DOC: Update motor classes to include same dry_mass parameter as in Motor class --- rocketpy/motors/hybrid_motor.py | 6 +----- rocketpy/motors/liquid_motor.py | 6 +----- rocketpy/motors/motor.py | 12 ++---------- rocketpy/motors/solid_motor.py | 5 +---- rocketpy/prints/rocket_prints.py | 6 ++++-- 5 files changed, 9 insertions(+), 26 deletions(-) diff --git a/rocketpy/motors/hybrid_motor.py b/rocketpy/motors/hybrid_motor.py index c52d25612..1ef07859a 100644 --- a/rocketpy/motors/hybrid_motor.py +++ b/rocketpy/motors/hybrid_motor.py @@ -201,11 +201,7 @@ def __init__( .. seealso:: :doc:`Thrust Source Details ` dry_mass : int, float - The total mass of the motor structure, including chambers, - bulkheads, screws, tanks, and others. This should be taken when the - motor is empty and does not contain any propellant. You should not - double count a component that is already accounted for in the rocket - class. + Same as in Motor class. See the :class:`Motor ` docs dry_inertia : tuple, list Tuple or list containing the motor's dry mass inertia tensor components, in kg*m^2. This inertia is defined with respect to the diff --git a/rocketpy/motors/liquid_motor.py b/rocketpy/motors/liquid_motor.py index 91422132f..8fc3c4fce 100644 --- a/rocketpy/motors/liquid_motor.py +++ b/rocketpy/motors/liquid_motor.py @@ -173,11 +173,7 @@ def __init__( .. seealso:: :doc:`Thrust Source Details ` dry_mass : int, float - The total mass of the motor structure, including chambers, - bulkheads, screws, tanks, and others. This should be taken when the - motor is empty and does not contain any propellant. You should not - double count a component that is already accounted for in the rocket - class. + Same as in Motor class. See the :class:`Motor ` docs. dry_inertia : tuple, list Tuple or list containing the motor's dry mass inertia tensor components, in kg*m^2. This inertia is defined with respect to the diff --git a/rocketpy/motors/motor.py b/rocketpy/motors/motor.py index b951e8724..1a8d75f1c 100644 --- a/rocketpy/motors/motor.py +++ b/rocketpy/motors/motor.py @@ -182,11 +182,7 @@ def __init__( .. seealso:: :doc:`Thrust Source Details ` dry_mass : int, float - The total mass of the motor structure, including chambers, - bulkheads, screws, tanks, and others. This should be taken when the - motor is empty and does not contain any propellant. You should not - double count a component that is already accounted for in the rocket - class. + Same as in Motor class. See the :class:`Motor ` docs center_of_dry_mass_position : int, float The position, in meters, of the motor's center of mass with respect to the motor's coordinate system when it is devoid of propellant. @@ -1113,11 +1109,7 @@ def __init__( coordinate system. See :doc:`Positions and Coordinate Systems ` dry_mass : int, float - The total mass of the motor structure, including chambers, - bulkheads, screws, tanks, and others. This should be taken when the - motor is empty and does not contain any propellant. You should not - double count a component that is already accounted for in the rocket - class. + Same as in Motor class. See the :class:`Motor ` docs propellant_initial_mass : int, float The initial mass of the propellant in the motor. center_of_dry_mass_position : int, float, optional diff --git a/rocketpy/motors/solid_motor.py b/rocketpy/motors/solid_motor.py index 921dc3d7c..f809777b3 100644 --- a/rocketpy/motors/solid_motor.py +++ b/rocketpy/motors/solid_motor.py @@ -225,10 +225,7 @@ def __init__( nozzle_radius : int, float Motor's nozzle outlet radius in meters. dry_mass : int, float - The total mass of the motor structure, including chambers, - bulkheads, screws, and others. This should be taken when the motor - is empty and does not contain any propellant. You should not double - count a component that is already accounted for in the rocket class. + Same as in Motor class. See the :class:`Motor ` docs dry_inertia : tuple, list Tuple or list containing the motor's dry mass inertia tensor components, in kg*m^2. This inertia is defined with respect to the diff --git a/rocketpy/prints/rocket_prints.py b/rocketpy/prints/rocket_prints.py index 842252a3c..603e66a9a 100644 --- a/rocketpy/prints/rocket_prints.py +++ b/rocketpy/prints/rocket_prints.py @@ -30,9 +30,11 @@ def inertia_details(self): None """ print("\nInertia Details\n") - print("Rocket Mass: {self.rocket.mass:.3f} kg") + print(f"Rocket Mass: {self.rocket.mass:.3f} kg (without motor)") print(f"Rocket Dry Mass: {self.rocket.dry_mass:.3f} kg (with unloaded motor)") - print(f"Rocket Mass: {self.rocket.total_mass(0):.3f} kg (With Propellant)") + print( + f"Rocket Loaded Mass: {self.rocket.total_mass(0):.3f} kg (with loaded motor)" + ) print( f"Rocket Inertia (with unloaded motor) 11: {self.rocket.dry_I_11:.3f} kg*m2" )