From 9198ac704f2d5cd1c1448da05c44b9a129a3b83c Mon Sep 17 00:00:00 2001 From: Pedro Bressan Date: Thu, 29 Feb 2024 17:46:23 -0300 Subject: [PATCH 01/18] DOC: phrasing improvements of Environment docstring. --- rocketpy/environment/environment.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/rocketpy/environment/environment.py b/rocketpy/environment/environment.py index 8b8d0498a..4229d3d07 100644 --- a/rocketpy/environment/environment.py +++ b/rocketpy/environment/environment.py @@ -276,15 +276,21 @@ def __init__( timezone="UTC", max_expected_height=80000.0, ): - """Initialize Environment class, saving launch rail length, - launch date, location coordinates and elevation. Note that - by default the standard atmosphere is loaded until another + """Initialize Environment class, saving parameters of the launch location, + such as launch date, coordinates and elevation. This class also computes + relevant quantities for the Flight simulation, such as air pressure, density + and gravitational acceleration. + + Note that the default atmospheric model is the International Standard Atmosphere + as defined by ISO 2533 unless specified otherwise in + :meth:`Environment.set_atmospheric_model`. Parameters ---------- gravity : int, float, callable, string, array, optional Surface gravitational acceleration. Positive values point the - acceleration down. If None, the Somigliana formula is used to + acceleration down. If None, the Somigliana formula is used. + See :meth:`Environment.set_gravity_model` for more information. date : array, optional Array of length 4, stating (year, month, day, hour (UTC)) of rocket launch. Must be given if a Forecast, Reanalysis @@ -468,8 +474,8 @@ def set_gravity_model(self, gravity): ---------- gravity : None or Function source If None, the Somigliana formula is used to compute the gravity - acceleration. Otherwise, the user can provide a Function object - representing the gravity model. + acceleration. Otherwise, the user can provide a Function source + object representing the gravity model. Returns ------- @@ -514,6 +520,10 @@ def somigliana_gravity(self, height): ------- Function Function object representing the gravity model. + + References + ---------- + .. [1] https://en.wikipedia.org/wiki/Theoretical_gravity#Somigliana_equation """ a = 6378137.0 # semi_major_axis f = 1 / 298.257223563 # flattening_factor From f637a9dc80fd7424e9defc68397c96ef73218297 Mon Sep 17 00:00:00 2001 From: Pedro Bressan Date: Thu, 29 Feb 2024 17:49:38 -0300 Subject: [PATCH 02/18] STY: run black for code linting. --- rocketpy/environment/environment.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/rocketpy/environment/environment.py b/rocketpy/environment/environment.py index 4229d3d07..816a455dc 100644 --- a/rocketpy/environment/environment.py +++ b/rocketpy/environment/environment.py @@ -276,20 +276,20 @@ def __init__( timezone="UTC", max_expected_height=80000.0, ): - """Initialize Environment class, saving parameters of the launch location, - such as launch date, coordinates and elevation. This class also computes - relevant quantities for the Flight simulation, such as air pressure, density + """Initialize Environment class, saving parameters of the launch location, + such as launch date, coordinates and elevation. This class also computes + relevant quantities for the Flight simulation, such as air pressure, density and gravitational acceleration. - - Note that the default atmospheric model is the International Standard Atmosphere - as defined by ISO 2533 unless specified otherwise in + + Note that the default atmospheric model is the International Standard Atmosphere + as defined by ISO 2533 unless specified otherwise in :meth:`Environment.set_atmospheric_model`. Parameters ---------- gravity : int, float, callable, string, array, optional Surface gravitational acceleration. Positive values point the - acceleration down. If None, the Somigliana formula is used. + acceleration down. If None, the Somigliana formula is used. See :meth:`Environment.set_gravity_model` for more information. date : array, optional Array of length 4, stating (year, month, day, hour (UTC)) @@ -474,7 +474,7 @@ def set_gravity_model(self, gravity): ---------- gravity : None or Function source If None, the Somigliana formula is used to compute the gravity - acceleration. Otherwise, the user can provide a Function source + acceleration. Otherwise, the user can provide a Function source object representing the gravity model. Returns From 3a3a31141ac47698722d3dd73a926391bbfedf38 Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Thu, 7 Mar 2024 16:12:23 -0500 Subject: [PATCH 03/18] DEV: Add .coveragerc file to exclude exceptions and warnings from test coverage --- .coveragerc | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .coveragerc diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 000000000..6f0db75d0 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,5 @@ +[report] +exclude_also= + ; Don't complain about exceptions or warnings not being covered by tests + raise * + warnings.warn* \ No newline at end of file From 82bc8240ea5ad57f7aed8520eaf7f645489b6d1e Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Fri, 8 Mar 2024 11:03:52 -0500 Subject: [PATCH 04/18] MNT: Refactor inertia calculations using parallel axis theorem --- CHANGELOG.md | 1 + rocketpy/motors/motor.py | 22 ++++++------------- rocketpy/rocket/rocket.py | 45 ++++++++++++++++++--------------------- rocketpy/tools.py | 21 ++++++++++++++++++ 4 files changed, 50 insertions(+), 39 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ff32bd8d..efd71050a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - ENH: adds `Function.remove_outliers` method [#554](https://github.com/RocketPy-Team/RocketPy/pull/554) ### Changed +- MNT: Refactor inertia calculations using parallel axis theorem [#573] (https://github.com/RocketPy-Team/RocketPy/pull/573) - ENH: Optional argument to show the plot in Function.compare_plots [#563](https://github.com/RocketPy-Team/RocketPy/pull/563) ### Fixed diff --git a/rocketpy/motors/motor.py b/rocketpy/motors/motor.py index 6c2242a9f..911860292 100644 --- a/rocketpy/motors/motor.py +++ b/rocketpy/motors/motor.py @@ -7,7 +7,7 @@ from ..mathutils.function import Function, funcify_method from ..plots.motor_plots import _MotorPlots from ..prints.motor_prints import _MotorPrints -from ..tools import tuple_handler +from ..tools import parallel_axis_theorem, tuple_handler try: from functools import cached_property @@ -513,25 +513,17 @@ def I_11(self): ---------- .. [1] https://en.wikipedia.org/wiki/Moment_of_inertia#Inertia_tensor """ - # Propellant inertia tensor 11 component wrt propellant center of mass - propellant_I_11 = self.propellant_I_11 - # Dry inertia tensor 11 component wrt dry center of mass + prop_I_11 = self.propellant_I_11 dry_I_11 = self.dry_I_11 - # Steiner theorem the get inertia wrt motor center of mass - propellant_I_11 += ( - self.propellant_mass - * (self.center_of_propellant_mass - self.center_of_mass) ** 2 - ) + prop_to_cm = self.center_of_propellant_mass - self.center_of_mass + dry_to_cm = self.center_of_dry_mass_position - self.center_of_mass - dry_I_11 += ( - self.dry_mass - * (self.center_of_dry_mass_position - self.center_of_mass) ** 2 - ) + prop_I_11 = parallel_axis_theorem(prop_I_11, self.propellant_mass, prop_to_cm) + dry_I_11 = parallel_axis_theorem(dry_I_11, self.dry_mass, dry_to_cm) - # Sum of inertia components - return propellant_I_11 + dry_I_11 + return prop_I_11 + dry_I_11 @funcify_method("Time (s)", "Inertia I_22 (kg m²)") def I_22(self): diff --git a/rocketpy/rocket/rocket.py b/rocketpy/rocket/rocket.py index ffcf3b1c8..58ae9c703 100644 --- a/rocketpy/rocket/rocket.py +++ b/rocketpy/rocket/rocket.py @@ -18,6 +18,7 @@ ) from rocketpy.rocket.components import Components from rocketpy.rocket.parachute import Parachute +from rocketpy.tools import parallel_axis_theorem class Rocket: @@ -620,6 +621,10 @@ def evaluate_dry_inertias(self): ---------- .. [1] https://en.wikipedia.org/wiki/Moment_of_inertia#Inertia_tensor """ + # Get masses + motor_dry_mass = self.motor.dry_mass + mass = self.mass + # Compute axes distances noMCM_to_CDM = ( self.center_of_mass_without_motor - self.center_of_dry_mass_position @@ -629,18 +634,14 @@ def evaluate_dry_inertias(self): ) # Compute dry inertias - self.dry_I_11 = ( - self.I_11_without_motor - + self.mass * noMCM_to_CDM**2 - + self.motor.dry_I_11 - + self.motor.dry_mass * motorCDM_to_CDM**2 - ) - self.dry_I_22 = ( - self.I_22_without_motor - + self.mass * noMCM_to_CDM**2 - + self.motor.dry_I_22 - + self.motor.dry_mass * motorCDM_to_CDM**2 - ) + self.dry_I_11 = parallel_axis_theorem( + self.I_11_without_motor, mass, noMCM_to_CDM + ) + parallel_axis_theorem(self.motor.dry_I_11, motor_dry_mass, motorCDM_to_CDM) + + self.dry_I_22 = parallel_axis_theorem( + self.I_22_without_motor, mass, noMCM_to_CDM + ) + parallel_axis_theorem(self.motor.dry_I_22, motor_dry_mass, motorCDM_to_CDM) + self.dry_I_33 = self.I_33_without_motor + self.motor.dry_I_33 self.dry_I_12 = self.I_12_without_motor + self.motor.dry_I_12 self.dry_I_13 = self.I_13_without_motor + self.motor.dry_I_13 @@ -697,18 +698,14 @@ def evaluate_inertias(self): CM_to_CPM = self.center_of_mass - self.center_of_propellant_position # Compute inertias - self.I_11 = ( - self.dry_I_11 - + self.motor.I_11 - + dry_mass * CM_to_CDM**2 - + prop_mass * CM_to_CPM**2 - ) - self.I_22 = ( - self.dry_I_22 - + self.motor.I_22 - + dry_mass * CM_to_CDM**2 - + prop_mass * CM_to_CPM**2 - ) + self.I_11 = parallel_axis_theorem( + self.dry_I_11, dry_mass, CM_to_CDM + ) + parallel_axis_theorem(self.motor.I_11, prop_mass, CM_to_CPM) + + self.I_22 = parallel_axis_theorem( + self.dry_I_22, dry_mass, CM_to_CDM + ) + parallel_axis_theorem(self.motor.I_22, prop_mass, CM_to_CPM) + self.I_33 = self.dry_I_33 + self.motor.I_33 self.I_12 = self.dry_I_12 + self.motor.I_12 self.I_13 = self.dry_I_13 + self.motor.I_13 diff --git a/rocketpy/tools.py b/rocketpy/tools.py index bcff91fb8..af4b0b5db 100644 --- a/rocketpy/tools.py +++ b/rocketpy/tools.py @@ -382,6 +382,27 @@ def check_requirement_version(module_name, version): return True +def parallel_axis_theorem(initial_inertia_moment, mass, distance): + """Converts the moment of inertia from one axis to another using the + parallel axis theorem. The axes must be parallel to each other. + + Parameters + ---------- + initial_inertia_moment : float + Initial moment of inertia. + mass : float + Mass of the object. + distance : float + Distance between the two points. + + Returns + ------- + float + New moment of inertia. + """ + return initial_inertia_moment + mass * distance**2 + + # Flight From 481551231e624fc165d94f8b2485824cbce4b79e Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Fri, 8 Mar 2024 11:27:48 -0500 Subject: [PATCH 05/18] MNT: apply parallel_axis_theorem function to hybrid and liquid motors --- rocketpy/motors/hybrid_motor.py | 31 +++++++++++++++---------------- rocketpy/motors/liquid_motor.py | 8 +++----- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/rocketpy/motors/hybrid_motor.py b/rocketpy/motors/hybrid_motor.py index 4b28a96d2..0320c9f3c 100644 --- a/rocketpy/motors/hybrid_motor.py +++ b/rocketpy/motors/hybrid_motor.py @@ -1,3 +1,5 @@ +from rocketpy.tools import parallel_axis_theorem + from ..mathutils.function import Function, funcify_method, reset_funcified_methods from ..plots.hybrid_motor_plots import _HybridMotorPlots from ..prints.hybrid_motor_prints import _HybridMotorPrints @@ -455,23 +457,20 @@ def propellant_I_11(self): ---------- .. [1] https://en.wikipedia.org/wiki/Moment_of_inertia#Inertia_tensor """ - solid_correction = ( - self.solid.propellant_mass - * (self.solid.center_of_propellant_mass - self.center_of_propellant_mass) - ** 2 - ) - liquid_correction = ( - self.liquid.propellant_mass - * (self.liquid.center_of_propellant_mass - self.center_of_propellant_mass) - ** 2 - ) - I_11 = ( - self.solid.propellant_I_11 - + solid_correction - + self.liquid.propellant_I_11 - + liquid_correction - ) + solid_mass = self.solid.propellant_mass + liquid_mass = self.liquid.propellant_mass + + cm = self.center_of_propellant_mass + solid_cm_to_cm = self.solid.center_of_propellant_mass - cm + liquid_cm_to_cm = self.liquid.center_of_propellant_mass - cm + + solid_prop_inertia = self.solid.propellant_I_11 + liquid_prop_inertia = self.liquid.propellant_I_11 + + I_11 = parallel_axis_theorem( + solid_prop_inertia, solid_mass, solid_cm_to_cm + ) + parallel_axis_theorem(liquid_prop_inertia, liquid_mass, liquid_cm_to_cm) return I_11 diff --git a/rocketpy/motors/liquid_motor.py b/rocketpy/motors/liquid_motor.py index ac525b379..62caa44e7 100644 --- a/rocketpy/motors/liquid_motor.py +++ b/rocketpy/motors/liquid_motor.py @@ -7,6 +7,7 @@ funcify_method, reset_funcified_methods, ) +from rocketpy.tools import parallel_axis_theorem from ..plots.liquid_motor_plots import _LiquidMotorPlots from ..prints.liquid_motor_prints import _LiquidMotorPrints @@ -388,11 +389,8 @@ def propellant_I_11(self): for positioned_tank in self.positioned_tanks: tank = positioned_tank.get("tank") tank_position = positioned_tank.get("position") - I_11 += ( - tank.inertia - + tank.fluid_mass - * (tank_position + tank.center_of_mass - center_of_mass) ** 2 - ) + distance = tank_position + tank.center_of_mass - center_of_mass + I_11 += parallel_axis_theorem(tank.inertia, tank.fluid_mass, distance) return I_11 From 191650f6ea3036ad13ec1581690450809e55b293 Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Thu, 7 Mar 2024 16:12:23 -0500 Subject: [PATCH 06/18] DEV: Add .coveragerc file to exclude exceptions and warnings from test coverage --- .coveragerc | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .coveragerc diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 000000000..6f0db75d0 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,5 @@ +[report] +exclude_also= + ; Don't complain about exceptions or warnings not being covered by tests + raise * + warnings.warn* \ No newline at end of file From c5fc05508fda4ac360a546b08eb516b204424fa7 Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Thu, 7 Mar 2024 16:12:23 -0500 Subject: [PATCH 07/18] DEV: Add .coveragerc file to exclude exceptions and warnings from test coverage --- .coveragerc | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .coveragerc diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 000000000..6f0db75d0 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,5 @@ +[report] +exclude_also= + ; Don't complain about exceptions or warnings not being covered by tests + raise * + warnings.warn* \ No newline at end of file From 86f657086380a64edaf04ed76e0c9c24000e9f9e Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Thu, 21 Mar 2024 15:10:31 -0400 Subject: [PATCH 08/18] DEV: re-include raise exceptions into coverage report --- .coveragerc | 1 - 1 file changed, 1 deletion(-) diff --git a/.coveragerc b/.coveragerc index 6f0db75d0..e4405ddbc 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,5 +1,4 @@ [report] exclude_also= ; Don't complain about exceptions or warnings not being covered by tests - raise * warnings.warn* \ No newline at end of file From 4a7ba1836756de0bebc9b0054af0c263188ee7bf Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Thu, 21 Mar 2024 16:21:11 -0400 Subject: [PATCH 09/18] MNT: improve parallel_axis_theorem_from_com documentation --- rocketpy/motors/hybrid_motor.py | 8 +++++--- rocketpy/motors/liquid_motor.py | 6 ++++-- rocketpy/motors/motor.py | 8 +++++--- rocketpy/rocket/rocket.py | 22 +++++++++++++--------- rocketpy/tools.py | 22 ++++++++++++++-------- 5 files changed, 41 insertions(+), 25 deletions(-) diff --git a/rocketpy/motors/hybrid_motor.py b/rocketpy/motors/hybrid_motor.py index 0320c9f3c..6f0849cd0 100644 --- a/rocketpy/motors/hybrid_motor.py +++ b/rocketpy/motors/hybrid_motor.py @@ -1,4 +1,4 @@ -from rocketpy.tools import parallel_axis_theorem +from rocketpy.tools import parallel_axis_theorem_from_com from ..mathutils.function import Function, funcify_method, reset_funcified_methods from ..plots.hybrid_motor_plots import _HybridMotorPlots @@ -468,9 +468,11 @@ def propellant_I_11(self): solid_prop_inertia = self.solid.propellant_I_11 liquid_prop_inertia = self.liquid.propellant_I_11 - I_11 = parallel_axis_theorem( + I_11 = parallel_axis_theorem_from_com( solid_prop_inertia, solid_mass, solid_cm_to_cm - ) + parallel_axis_theorem(liquid_prop_inertia, liquid_mass, liquid_cm_to_cm) + ) + parallel_axis_theorem_from_com( + liquid_prop_inertia, liquid_mass, liquid_cm_to_cm + ) return I_11 diff --git a/rocketpy/motors/liquid_motor.py b/rocketpy/motors/liquid_motor.py index 62caa44e7..01f728473 100644 --- a/rocketpy/motors/liquid_motor.py +++ b/rocketpy/motors/liquid_motor.py @@ -7,7 +7,7 @@ funcify_method, reset_funcified_methods, ) -from rocketpy.tools import parallel_axis_theorem +from rocketpy.tools import parallel_axis_theorem_from_com from ..plots.liquid_motor_plots import _LiquidMotorPlots from ..prints.liquid_motor_prints import _LiquidMotorPrints @@ -390,7 +390,9 @@ def propellant_I_11(self): tank = positioned_tank.get("tank") tank_position = positioned_tank.get("position") distance = tank_position + tank.center_of_mass - center_of_mass - I_11 += parallel_axis_theorem(tank.inertia, tank.fluid_mass, distance) + I_11 += parallel_axis_theorem_from_com( + tank.inertia, tank.fluid_mass, distance + ) return I_11 diff --git a/rocketpy/motors/motor.py b/rocketpy/motors/motor.py index 911860292..3834f4a15 100644 --- a/rocketpy/motors/motor.py +++ b/rocketpy/motors/motor.py @@ -7,7 +7,7 @@ from ..mathutils.function import Function, funcify_method from ..plots.motor_plots import _MotorPlots from ..prints.motor_prints import _MotorPrints -from ..tools import parallel_axis_theorem, tuple_handler +from ..tools import parallel_axis_theorem_from_com, tuple_handler try: from functools import cached_property @@ -520,8 +520,10 @@ def I_11(self): prop_to_cm = self.center_of_propellant_mass - self.center_of_mass dry_to_cm = self.center_of_dry_mass_position - self.center_of_mass - prop_I_11 = parallel_axis_theorem(prop_I_11, self.propellant_mass, prop_to_cm) - dry_I_11 = parallel_axis_theorem(dry_I_11, self.dry_mass, dry_to_cm) + prop_I_11 = parallel_axis_theorem_from_com( + prop_I_11, self.propellant_mass, prop_to_cm + ) + dry_I_11 = parallel_axis_theorem_from_com(dry_I_11, self.dry_mass, dry_to_cm) return prop_I_11 + dry_I_11 diff --git a/rocketpy/rocket/rocket.py b/rocketpy/rocket/rocket.py index 58ae9c703..27195c1df 100644 --- a/rocketpy/rocket/rocket.py +++ b/rocketpy/rocket/rocket.py @@ -18,7 +18,7 @@ ) from rocketpy.rocket.components import Components from rocketpy.rocket.parachute import Parachute -from rocketpy.tools import parallel_axis_theorem +from rocketpy.tools import parallel_axis_theorem_from_com class Rocket: @@ -634,13 +634,17 @@ def evaluate_dry_inertias(self): ) # Compute dry inertias - self.dry_I_11 = parallel_axis_theorem( + self.dry_I_11 = parallel_axis_theorem_from_com( self.I_11_without_motor, mass, noMCM_to_CDM - ) + parallel_axis_theorem(self.motor.dry_I_11, motor_dry_mass, motorCDM_to_CDM) + ) + parallel_axis_theorem_from_com( + self.motor.dry_I_11, motor_dry_mass, motorCDM_to_CDM + ) - self.dry_I_22 = parallel_axis_theorem( + self.dry_I_22 = parallel_axis_theorem_from_com( self.I_22_without_motor, mass, noMCM_to_CDM - ) + parallel_axis_theorem(self.motor.dry_I_22, motor_dry_mass, motorCDM_to_CDM) + ) + parallel_axis_theorem_from_com( + self.motor.dry_I_22, motor_dry_mass, motorCDM_to_CDM + ) self.dry_I_33 = self.I_33_without_motor + self.motor.dry_I_33 self.dry_I_12 = self.I_12_without_motor + self.motor.dry_I_12 @@ -698,13 +702,13 @@ def evaluate_inertias(self): CM_to_CPM = self.center_of_mass - self.center_of_propellant_position # Compute inertias - self.I_11 = parallel_axis_theorem( + self.I_11 = parallel_axis_theorem_from_com( self.dry_I_11, dry_mass, CM_to_CDM - ) + parallel_axis_theorem(self.motor.I_11, prop_mass, CM_to_CPM) + ) + parallel_axis_theorem_from_com(self.motor.I_11, prop_mass, CM_to_CPM) - self.I_22 = parallel_axis_theorem( + self.I_22 = parallel_axis_theorem_from_com( self.dry_I_22, dry_mass, CM_to_CDM - ) + parallel_axis_theorem(self.motor.I_22, prop_mass, CM_to_CPM) + ) + parallel_axis_theorem_from_com(self.motor.I_22, prop_mass, CM_to_CPM) self.I_33 = self.dry_I_33 + self.motor.I_33 self.I_12 = self.dry_I_12 + self.motor.I_12 diff --git a/rocketpy/tools.py b/rocketpy/tools.py index af4b0b5db..1ce588636 100644 --- a/rocketpy/tools.py +++ b/rocketpy/tools.py @@ -382,25 +382,31 @@ def check_requirement_version(module_name, version): return True -def parallel_axis_theorem(initial_inertia_moment, mass, distance): - """Converts the moment of inertia from one axis to another using the - parallel axis theorem. The axes must be parallel to each other. +def parallel_axis_theorem_from_com(com_inertia_moment, mass, distance): + """Calculates the moment of inertia of a object relative to a new axis using + the parallel axis theorem. The new axis is parallel to and at a distance + 'distance' from the original axis, which *must* passes through the object's + center of mass. Parameters ---------- - initial_inertia_moment : float - Initial moment of inertia. + com_inertia_moment : float + Moment of inertia relative to the center of mass of the object. mass : float Mass of the object. distance : float - Distance between the two points. + Perpendicular distance between the original and new axis. Returns ------- float - New moment of inertia. + Moment of inertia relative to the new axis. + + Reference + --------- + https://en.wikipedia.org/wiki/Parallel_axis_theorem """ - return initial_inertia_moment + mass * distance**2 + return com_inertia_moment + mass * distance**2 # Flight From a2aa71467b3be87c8124343cd49768c1ac4d6a9d Mon Sep 17 00:00:00 2001 From: Pedro Bressan Date: Sun, 24 Mar 2024 16:41:39 -0300 Subject: [PATCH 10/18] DOC: add examples and improvements to Environment gravity docstring. --- rocketpy/environment/environment.py | 61 ++++++++++++++++++++++++----- 1 file changed, 52 insertions(+), 9 deletions(-) diff --git a/rocketpy/environment/environment.py b/rocketpy/environment/environment.py index 816a455dc..d0ccc0342 100644 --- a/rocketpy/environment/environment.py +++ b/rocketpy/environment/environment.py @@ -464,23 +464,66 @@ def set_location(self, latitude, longitude): self.atmospheric_model_file, self.atmospheric_model_dict ) - # Return None - - def set_gravity_model(self, gravity): - """Sets the gravity model to be used in the simulation based on the - given user input to the gravity parameter. + def set_gravity_model(self, gravity=None): + """Defines the gravity model based on the given user input to the + gravity parameter. The gravity model is responsible for computing the + gravity acceleration at a given height above sea level in meters. Parameters ---------- - gravity : None or Function source - If None, the Somigliana formula is used to compute the gravity - acceleration. Otherwise, the user can provide a Function source - object representing the gravity model. + gravity : int, float, callable, string, array, optional + The gravitational acceleration in m/s² to be used in the + simulation, this value is positive when pointing downwards. + The input type can be one of the following: + + - ``int`` or ``float``: The gravity acceleration is set as a + constant function with respect to height; + + - ``callable``: This callable should receive the height above + sea level in meters and return the gravity acceleration; + + - ``array``: The datapoints should be structured as + ``[(h_i,g_i), ...]`` where ``h_i`` is the height above sea + level in meters and ``g_i`` is the gravity acceleration in m/s²; + + - ``string``: The string should correspond to a path to a CSV file + containing the gravity acceleration data; + + - ``None``: The Somigliana formula is used to compute the gravity + acceleration. + + This parameter is used as a :class:`Function` object source, check + out the available input types for a more detailed explanation. Returns ------- Function Function object representing the gravity model. + + Notes + ----- + This method **does not** set the gravity acceleration, it only returns + a :class:`Function` object representing the gravity model. + + Examples + -------- + Let's prepare a `Environment` object with a constant gravity + acceleration: + + >>> g_0 = 9.80665 + >>> env_cte_g = Environment(gravity=g_0) + >>> env_cte_g.gravity([0, 100, 1000]) + [9.80665, 9.80665, 9.80665] + + It's also possible to variate the gravity acceleration by defining + its function of height: + + >>> R_t = 6371000 + >>> g_func = lambda h : g_0 * (R_t / (R_t + h))**2 + >>> env_var_g = Environment(gravity=g_func) + >>> g = env_var_g.gravity(1000) + >>> print(f"{g:.6f}") + 9.803572 """ if gravity is None: return self.somigliana_gravity.set_discrete( From 826efcc3125a0cf1d9a24ce1039135a6877e742a Mon Sep 17 00:00:00 2001 From: Pedro Bressan Date: Sun, 24 Mar 2024 16:47:14 -0300 Subject: [PATCH 11/18] DOC: specify datum as optional. --- rocketpy/environment/environment.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/rocketpy/environment/environment.py b/rocketpy/environment/environment.py index d0ccc0342..182736848 100644 --- a/rocketpy/environment/environment.py +++ b/rocketpy/environment/environment.py @@ -57,8 +57,7 @@ class Environment: Environment.datum : string The desired reference ellipsoid model, the following options are available: "SAD69", "WGS84", "NAD83", and "SIRGAS2000". The default - is "SIRGAS2000", then this model will be used if the user make some - typing mistake + is "SIRGAS2000". Environment.initial_east : float Launch site East UTM coordinate Environment.initial_north : float @@ -311,11 +310,10 @@ def __init__( 'Open-Elevation' which uses the Open-Elevation API to find elevation data. For this option, latitude and longitude must also be specified. Default value is 0. - datum : string + datum : string, optional The desired reference ellipsoidal model, the following options are available: "SAD69", "WGS84", "NAD83", and "SIRGAS2000". The default - is "SIRGAS2000", then this model will be used if the user make some - typing mistake. + is "SIRGAS2000". timezone : string, optional Name of the time zone. To see all time zones, import pytz and run print(pytz.all_timezones). Default time zone is "UTC". @@ -324,7 +322,7 @@ def __init__( be above sea level (ASL). Especially useful for visualization. Can be altered as desired by doing `max_expected_height = number`. Depending on the atmospheric model, this value may be automatically - mofified. + modified. Returns ------- From 6b84c03bdaafd96c140067cfbf0a00fe05556d1e Mon Sep 17 00:00:00 2001 From: Pedro Bressan Date: Mon, 25 Mar 2024 19:53:37 -0300 Subject: [PATCH 12/18] DOC: improve wording of Environment init. Co-authored-by: Gui-FernandesBR <63590233+Gui-FernandesBR@users.noreply.github.com> --- rocketpy/environment/environment.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/rocketpy/environment/environment.py b/rocketpy/environment/environment.py index 182736848..1e88d2cb0 100644 --- a/rocketpy/environment/environment.py +++ b/rocketpy/environment/environment.py @@ -275,9 +275,10 @@ def __init__( timezone="UTC", max_expected_height=80000.0, ): - """Initialize Environment class, saving parameters of the launch location, - such as launch date, coordinates and elevation. This class also computes - relevant quantities for the Flight simulation, such as air pressure, density + """Initializes the Environment class, capturing essential parameters of + the launch site, including the launch date, geographical coordinates, + and elevation. This class is designed to calculate crucial variables + for the Flight simulation, such as atmospheric air pressure, density, and gravitational acceleration. Note that the default atmospheric model is the International Standard Atmosphere From 95b61efc8070f59373e81e049eba7dd83c5f7520 Mon Sep 17 00:00:00 2001 From: Pedro Bressan Date: Thu, 28 Mar 2024 16:38:17 -0300 Subject: [PATCH 13/18] DOC: specify latitude and longitude defaults. --- rocketpy/environment/environment.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/rocketpy/environment/environment.py b/rocketpy/environment/environment.py index 1e88d2cb0..3ba22dccb 100644 --- a/rocketpy/environment/environment.py +++ b/rocketpy/environment/environment.py @@ -281,8 +281,8 @@ def __init__( for the Flight simulation, such as atmospheric air pressure, density, and gravitational acceleration. - Note that the default atmospheric model is the International Standard Atmosphere - as defined by ISO 2533 unless specified otherwise in + Note that the default atmospheric model is the International Standard + Atmosphere as defined by ISO 2533 unless specified otherwise in :meth:`Environment.set_atmospheric_model`. Parameters @@ -299,12 +299,16 @@ def __init__( Latitude in degrees (ranging from -90 to 90) of rocket launch location. Must be given if a Forecast, Reanalysis or Ensemble will be used as an atmospheric model or if - Open-Elevation will be used to compute elevation. + Open-Elevation will be used to compute elevation. Positive + values correspond to the North. Default value is 0, which + corresponds to the equator. longitude : float, optional Longitude in degrees (ranging from -180 to 360) of rocket launch location. Must be given if a Forecast, Reanalysis or Ensemble will be used as an atmospheric model or if - Open-Elevation will be used to compute elevation. + Open-Elevation will be used to compute elevation. Positive + values correspond to the East. Default value is 0, which + corresponds to the Greenwich Meridian. elevation : float, optional Elevation of launch site measured as height above sea level in meters. Alternatively, can be set as From 4b42c9ec173188fa0022fe17a198b6982534ffe3 Mon Sep 17 00:00:00 2001 From: Pedro Bressan Date: Thu, 28 Mar 2024 18:20:01 -0300 Subject: [PATCH 14/18] DOC: clarify timezones and dates Environment docs. --- rocketpy/environment/environment.py | 99 ++++++++++++++++++++++------- 1 file changed, 76 insertions(+), 23 deletions(-) diff --git a/rocketpy/environment/environment.py b/rocketpy/environment/environment.py index 3ba22dccb..84bbd8b97 100644 --- a/rocketpy/environment/environment.py +++ b/rocketpy/environment/environment.py @@ -73,7 +73,7 @@ class Environment: Launch site E/W hemisphere Environment.elevation : float Launch site elevation. - Environment.date : datetime + Environment.datetime_date : datetime Date time of launch in UTC. Environment.local_date : datetime Date time of launch in the local time zone, defined by @@ -281,7 +281,7 @@ def __init__( for the Flight simulation, such as atmospheric air pressure, density, and gravitational acceleration. - Note that the default atmospheric model is the International Standard + Note that the default atmospheric model is the International Standard Atmosphere as defined by ISO 2533 unless specified otherwise in :meth:`Environment.set_atmospheric_model`. @@ -292,22 +292,33 @@ def __init__( acceleration down. If None, the Somigliana formula is used. See :meth:`Environment.set_gravity_model` for more information. date : array, optional - Array of length 4, stating (year, month, day, hour (UTC)) - of rocket launch. Must be given if a Forecast, Reanalysis + Array of length 4, stating (year, month, day, hour) in the + time zone of the parameter ``timezone``. + Alternatively, can be a ``Datetime`` object specifying launch + date and time. The dates are stored as follows: + + - :attr:`Environment.local_date`: Local time of launch in + the time zone specified by the parameter ``timezone``. + + - :attr:`Environment.datetime_date`: UTC time of launch. + + Must be given if a Forecast, Reanalysis or Ensemble, will be set as an atmospheric model. + Default is None. + See :meth:`Environment.set_date` for more information. latitude : float, optional Latitude in degrees (ranging from -90 to 90) of rocket launch location. Must be given if a Forecast, Reanalysis or Ensemble will be used as an atmospheric model or if Open-Elevation will be used to compute elevation. Positive - values correspond to the North. Default value is 0, which + values correspond to the North. Default value is 0, which corresponds to the equator. longitude : float, optional Longitude in degrees (ranging from -180 to 360) of rocket launch location. Must be given if a Forecast, Reanalysis or Ensemble will be used as an atmospheric model or if Open-Elevation will be used to compute elevation. Positive - values correspond to the East. Default value is 0, which + values correspond to the East. Default value is 0, which corresponds to the Greenwich Meridian. elevation : float, optional Elevation of launch site measured as height above sea @@ -321,7 +332,7 @@ def __init__( is "SIRGAS2000". timezone : string, optional Name of the time zone. To see all time zones, import pytz and run - print(pytz.all_timezones). Default time zone is "UTC". + ``print(pytz.all_timezones)``. Default time zone is "UTC". max_expected_height : float, optional Maximum altitude in meters to keep weather data. The altitude must be above sea level (ASL). Especially useful for visualization. @@ -405,15 +416,57 @@ def set_date(self, date, timezone="UTC"): Parameters ---------- - date : Datetime - Datetime object specifying launch date and time. + date : array, Datetime + Array of length 4, stating (year, month, day, hour) in the + time zone of the parameter ``timezone``. See Notes for more + information. + Alternatively, can be a Datetime object specifying launch + date and time. timezone : string, optional Name of the time zone. To see all time zones, import pytz and run - print(pytz.all_timezones). Default time zone is "UTC". + ``print(pytz.all_timezones)``. Default time zone is "UTC". Returns ------- None + + Notes + ----- + - If the ``date`` is given as an array, it should be in the same + time zone as specified by the ``timezone`` parameter. This local + time will be available in the attribute :attr:`Environment.local_date` + while the UTC time will be available in the attribute + :attr:`Environment.datetime_date`. + + - If the ``date`` is given as a ``Datetime`` object without a time zone, + it will be assumed to be in the same time zone as specified by the + ``timezone`` parameter. However, if the ``Datetime`` object has a time + zone specified in its ``tzinfo`` attribute, the ``timezone`` + parameter will be ignored. + + Examples + -------- + + Let's set the launch date as an array: + + >>> date = [2000, 1, 1, 13] # January 1st, 2000 at 13:00 UTC+1 + >>> env = Environment() + >>> env.set_date(date, timezone="Europe/Rome") + >>> print(env.datetime_date) # Get UTC time + 2000-01-01 12:00:00+00:00 + >>> print(env.local_date) + 2000-01-01 13:00:00+01:00 + + Now let's set the launch date as a ``Datetime`` object: + + >>> from datetime import datetime + >>> date = datetime(2000, 1, 1, 13, 0, 0) + >>> env = Environment() + >>> env.set_date(date, timezone="Europe/Rome") + >>> print(env.datetime_date) # Get UTC time + 2000-01-01 12:00:00+00:00 + >>> print(env.local_date) + 2000-01-01 13:00:00+01:00 """ # Store date and configure time zone self.timezone = timezone @@ -479,23 +532,23 @@ def set_gravity_model(self, gravity=None): simulation, this value is positive when pointing downwards. The input type can be one of the following: - - ``int`` or ``float``: The gravity acceleration is set as a - constant function with respect to height; + - ``int`` or ``float``: The gravity acceleration is set as a\ + constant function with respect to height; - - ``callable``: This callable should receive the height above - sea level in meters and return the gravity acceleration; + - ``callable``: This callable should receive the height above\ + sea level in meters and return the gravity acceleration; - - ``array``: The datapoints should be structured as - ``[(h_i,g_i), ...]`` where ``h_i`` is the height above sea - level in meters and ``g_i`` is the gravity acceleration in m/s²; + - ``array``: The datapoints should be structured as\ + ``[(h_i,g_i), ...]`` where ``h_i`` is the height above sea\ + level in meters and ``g_i`` is the gravity acceleration in m/s²; - - ``string``: The string should correspond to a path to a CSV file - containing the gravity acceleration data; + - ``string``: The string should correspond to a path to a CSV file\ + containing the gravity acceleration data; - - ``None``: The Somigliana formula is used to compute the gravity - acceleration. + - ``None``: The Somigliana formula is used to compute the gravity\ + acceleration. - This parameter is used as a :class:`Function` object source, check + This parameter is used as a :class:`Function` object source, check\ out the available input types for a more detailed explanation. Returns @@ -552,7 +605,7 @@ def max_expected_height(self, value): @funcify_method("height (m)", "gravity (m/s²)") def somigliana_gravity(self, height): - """Computes the gravity acceleration with the Somigliana formula. + """Computes the gravity acceleration with the Somigliana formula [1]_. An height correction is applied to the normal gravity that is accurate for heights used in aviation. The formula is based on the WGS84 ellipsoid, but is accurate for other reference ellipsoids. From 27dd73d24fa82f3cbf066d6edb5aa3063b102099 Mon Sep 17 00:00:00 2001 From: Pedro Bressan Date: Thu, 28 Mar 2024 18:24:23 -0300 Subject: [PATCH 15/18] DOC: standardize grammar on time zones. --- rocketpy/environment/environment_analysis.py | 2 +- rocketpy/tools.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rocketpy/environment/environment_analysis.py b/rocketpy/environment/environment_analysis.py index c15b32551..0ed638091 100644 --- a/rocketpy/environment/environment_analysis.py +++ b/rocketpy/environment/environment_analysis.py @@ -441,7 +441,7 @@ def __localize_input_dates(self): def __find_preferred_timezone(self): if self.preferred_timezone is None: - # Use local timezone based on lat lon pair + # Use local time zone based on lat lon pair try: timezonefinder = import_optional_dependency("timezonefinder") tf = timezonefinder.TimezoneFinder() diff --git a/rocketpy/tools.py b/rocketpy/tools.py index bcff91fb8..d7d19dfc0 100644 --- a/rocketpy/tools.py +++ b/rocketpy/tools.py @@ -153,7 +153,7 @@ def time_num_to_date_string(time_num, units, timezone, calendar="gregorian"): """Convert time number (usually hours before a certain date) into two strings: one for the date (example: 2022.04.31) and one for the hour (example: 14). See cftime.num2date for details on units and calendar. - Automatically converts time number from UTC to local timezone based on + Automatically converts time number from UTC to local time zone based on lat, lon coordinates. This function was created originally for the EnvironmentAnalysis class. From 477620890952571071623f14bf8cdd7a03838bad Mon Sep 17 00:00:00 2001 From: Pedro Henrique Marinho Bressan <87212571+phmbressan@users.noreply.github.com> Date: Fri, 29 Mar 2024 08:47:18 -0300 Subject: [PATCH 16/18] DOC: Update Longitude value ranges. Co-authored-by: Gui-FernandesBR <63590233+Gui-FernandesBR@users.noreply.github.com> --- rocketpy/environment/environment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rocketpy/environment/environment.py b/rocketpy/environment/environment.py index 38e2ae829..7ea0a2601 100644 --- a/rocketpy/environment/environment.py +++ b/rocketpy/environment/environment.py @@ -314,7 +314,7 @@ def __init__( values correspond to the North. Default value is 0, which corresponds to the equator. longitude : float, optional - Longitude in degrees (ranging from -180 to 360) of rocket + Longitude in degrees (ranging from -180 to 180) of rocket launch location. Must be given if a Forecast, Reanalysis or Ensemble will be used as an atmospheric model or if Open-Elevation will be used to compute elevation. Positive From 9c1a4619ee2beb273d81f63fa78343dfa84ca790 Mon Sep 17 00:00:00 2001 From: Pedro Bressan Date: Mon, 1 Apr 2024 14:47:03 -0300 Subject: [PATCH 17/18] DOC: fix typing issues regarding Environment docs. --- rocketpy/environment/environment.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/rocketpy/environment/environment.py b/rocketpy/environment/environment.py index 7ea0a2601..b72b2cb38 100644 --- a/rocketpy/environment/environment.py +++ b/rocketpy/environment/environment.py @@ -291,10 +291,10 @@ def __init__( Surface gravitational acceleration. Positive values point the acceleration down. If None, the Somigliana formula is used. See :meth:`Environment.set_gravity_model` for more information. - date : array, optional - Array of length 4, stating (year, month, day, hour) in the + date : list or tuple, optional + List or tuple of length 4, stating (year, month, day, hour) in the time zone of the parameter ``timezone``. - Alternatively, can be a ``Datetime`` object specifying launch + Alternatively, can be a ``datetime`` object specifying launch date and time. The dates are stored as follows: - :attr:`Environment.local_date`: Local time of launch in @@ -416,11 +416,11 @@ def set_date(self, date, timezone="UTC"): Parameters ---------- - date : array, Datetime - Array of length 4, stating (year, month, day, hour) in the + date : list, tuple, datetime + List or tuple of length 4, stating (year, month, day, hour) in the time zone of the parameter ``timezone``. See Notes for more information. - Alternatively, can be a Datetime object specifying launch + Alternatively, can be a ``datetime`` object specifying launch date and time. timezone : string, optional Name of the time zone. To see all time zones, import pytz and run @@ -432,22 +432,22 @@ def set_date(self, date, timezone="UTC"): Notes ----- - - If the ``date`` is given as an array, it should be in the same + - If the ``date`` is given as a list or tuple, it should be in the same time zone as specified by the ``timezone`` parameter. This local time will be available in the attribute :attr:`Environment.local_date` while the UTC time will be available in the attribute :attr:`Environment.datetime_date`. - - If the ``date`` is given as a ``Datetime`` object without a time zone, + - If the ``date`` is given as a ``datetime`` object without a time zone, it will be assumed to be in the same time zone as specified by the - ``timezone`` parameter. However, if the ``Datetime`` object has a time + ``timezone`` parameter. However, if the ``datetime`` object has a time zone specified in its ``tzinfo`` attribute, the ``timezone`` parameter will be ignored. Examples -------- - Let's set the launch date as an array: + Let's set the launch date as an list: >>> date = [2000, 1, 1, 13] # January 1st, 2000 at 13:00 UTC+1 >>> env = Environment() @@ -457,7 +457,7 @@ def set_date(self, date, timezone="UTC"): >>> print(env.local_date) 2000-01-01 13:00:00+01:00 - Now let's set the launch date as a ``Datetime`` object: + Now let's set the launch date as a ``datetime`` object: >>> from datetime import datetime >>> date = datetime(2000, 1, 1, 13, 0, 0) @@ -527,7 +527,7 @@ def set_gravity_model(self, gravity=None): Parameters ---------- - gravity : int, float, callable, string, array, optional + gravity : int, float, callable, string, list, optional The gravitational acceleration in m/s² to be used in the simulation, this value is positive when pointing downwards. The input type can be one of the following: @@ -538,7 +538,7 @@ def set_gravity_model(self, gravity=None): - ``callable``: This callable should receive the height above\ sea level in meters and return the gravity acceleration; - - ``array``: The datapoints should be structured as\ + - ``list``: The datapoints should be structured as\ ``[(h_i,g_i), ...]`` where ``h_i`` is the height above sea\ level in meters and ``g_i`` is the gravity acceleration in m/s²; From 61bf4d7aa3961d4e6d3a2911379027a4401345c4 Mon Sep 17 00:00:00 2001 From: Pedro Bressan Date: Mon, 1 Apr 2024 15:00:10 -0300 Subject: [PATCH 18/18] MNT: add docs change to CHANGELOG. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 87e3c50a4..8408aa368 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Changed +- DOC: Improvements of Environment docstring phrasing [#565](https://github.com/RocketPy-Team/RocketPy/pull/565) - MNT: Refactor flight prints module [#579](https://github.com/RocketPy-Team/RocketPy/pull/579) - DOC: Convert CompareFlights example notebooks to .rst files [#576](https://github.com/RocketPy-Team/RocketPy/pull/576) - MNT: Refactor inertia calculations using parallel axis theorem [#573] (https://github.com/RocketPy-Team/RocketPy/pull/573)