Skip to content

Commit 822a89e

Browse files
authored
Merge branch 'develop' into mnt/modularize-rocket-draw
2 parents e3e1a59 + 624cc15 commit 822a89e

File tree

9 files changed

+226
-88
lines changed

9 files changed

+226
-88
lines changed

.coveragerc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[report]
2+
exclude_also=
3+
; Don't complain about exceptions or warnings not being covered by tests
4+
warnings.warn*

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
3838
### Changed
3939

4040
- MNT: Modularize Rocket Draw [#580](https://github.com/RocketPy-Team/RocketPy/pull/580)
41+
- DOC: Improvements of Environment docstring phrasing [#565](https://github.com/RocketPy-Team/RocketPy/pull/565)
4142
- MNT: Refactor flight prints module [#579](https://github.com/RocketPy-Team/RocketPy/pull/579)
4243
- DOC: Convert CompareFlights example notebooks to .rst files [#576](https://github.com/RocketPy-Team/RocketPy/pull/576)
44+
- MNT: Refactor inertia calculations using parallel axis theorem [#573] (https://github.com/RocketPy-Team/RocketPy/pull/573)
4345
- ENH: Optional argument to show the plot in Function.compare_plots [#563](https://github.com/RocketPy-Team/RocketPy/pull/563)
4446

4547
### Fixed

rocketpy/environment/environment.py

Lines changed: 140 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,7 @@ class Environment:
5757
Environment.datum : string
5858
The desired reference ellipsoid model, the following options are
5959
available: "SAD69", "WGS84", "NAD83", and "SIRGAS2000". The default
60-
is "SIRGAS2000", then this model will be used if the user make some
61-
typing mistake
60+
is "SIRGAS2000".
6261
Environment.initial_east : float
6362
Launch site East UTM coordinate
6463
Environment.initial_north : float
@@ -74,7 +73,7 @@ class Environment:
7473
Launch site E/W hemisphere
7574
Environment.elevation : float
7675
Launch site elevation.
77-
Environment.date : datetime
76+
Environment.datetime_date : datetime
7877
Date time of launch in UTC.
7978
Environment.local_date : datetime
8079
Date time of launch in the local time zone, defined by
@@ -276,49 +275,70 @@ def __init__(
276275
timezone="UTC",
277276
max_expected_height=80000.0,
278277
):
279-
"""Initialize Environment class, saving launch rail length,
280-
launch date, location coordinates and elevation. Note that
281-
by default the standard atmosphere is loaded until another
278+
"""Initializes the Environment class, capturing essential parameters of
279+
the launch site, including the launch date, geographical coordinates,
280+
and elevation. This class is designed to calculate crucial variables
281+
for the Flight simulation, such as atmospheric air pressure, density,
282+
and gravitational acceleration.
283+
284+
Note that the default atmospheric model is the International Standard
285+
Atmosphere as defined by ISO 2533 unless specified otherwise in
286+
:meth:`Environment.set_atmospheric_model`.
282287
283288
Parameters
284289
----------
285290
gravity : int, float, callable, string, array, optional
286291
Surface gravitational acceleration. Positive values point the
287-
acceleration down. If None, the Somigliana formula is used to
288-
date : array, optional
289-
Array of length 4, stating (year, month, day, hour (UTC))
290-
of rocket launch. Must be given if a Forecast, Reanalysis
292+
acceleration down. If None, the Somigliana formula is used.
293+
See :meth:`Environment.set_gravity_model` for more information.
294+
date : list or tuple, optional
295+
List or tuple of length 4, stating (year, month, day, hour) in the
296+
time zone of the parameter ``timezone``.
297+
Alternatively, can be a ``datetime`` object specifying launch
298+
date and time. The dates are stored as follows:
299+
300+
- :attr:`Environment.local_date`: Local time of launch in
301+
the time zone specified by the parameter ``timezone``.
302+
303+
- :attr:`Environment.datetime_date`: UTC time of launch.
304+
305+
Must be given if a Forecast, Reanalysis
291306
or Ensemble, will be set as an atmospheric model.
307+
Default is None.
308+
See :meth:`Environment.set_date` for more information.
292309
latitude : float, optional
293310
Latitude in degrees (ranging from -90 to 90) of rocket
294311
launch location. Must be given if a Forecast, Reanalysis
295312
or Ensemble will be used as an atmospheric model or if
296-
Open-Elevation will be used to compute elevation.
313+
Open-Elevation will be used to compute elevation. Positive
314+
values correspond to the North. Default value is 0, which
315+
corresponds to the equator.
297316
longitude : float, optional
298-
Longitude in degrees (ranging from -180 to 360) of rocket
317+
Longitude in degrees (ranging from -180 to 180) of rocket
299318
launch location. Must be given if a Forecast, Reanalysis
300319
or Ensemble will be used as an atmospheric model or if
301-
Open-Elevation will be used to compute elevation.
320+
Open-Elevation will be used to compute elevation. Positive
321+
values correspond to the East. Default value is 0, which
322+
corresponds to the Greenwich Meridian.
302323
elevation : float, optional
303324
Elevation of launch site measured as height above sea
304325
level in meters. Alternatively, can be set as
305326
'Open-Elevation' which uses the Open-Elevation API to
306327
find elevation data. For this option, latitude and
307328
longitude must also be specified. Default value is 0.
308-
datum : string
329+
datum : string, optional
309330
The desired reference ellipsoidal model, the following options are
310331
available: "SAD69", "WGS84", "NAD83", and "SIRGAS2000". The default
311-
is "SIRGAS2000", then this model will be used if the user make some
312-
typing mistake.
332+
is "SIRGAS2000".
313333
timezone : string, optional
314334
Name of the time zone. To see all time zones, import pytz and run
315-
print(pytz.all_timezones). Default time zone is "UTC".
335+
``print(pytz.all_timezones)``. Default time zone is "UTC".
316336
max_expected_height : float, optional
317337
Maximum altitude in meters to keep weather data. The altitude must
318338
be above sea level (ASL). Especially useful for visualization.
319339
Can be altered as desired by doing `max_expected_height = number`.
320340
Depending on the atmospheric model, this value may be automatically
321-
mofified.
341+
modified.
322342
323343
Returns
324344
-------
@@ -396,15 +416,57 @@ def set_date(self, date, timezone="UTC"):
396416
397417
Parameters
398418
----------
399-
date : Datetime
400-
Datetime object specifying launch date and time.
419+
date : list, tuple, datetime
420+
List or tuple of length 4, stating (year, month, day, hour) in the
421+
time zone of the parameter ``timezone``. See Notes for more
422+
information.
423+
Alternatively, can be a ``datetime`` object specifying launch
424+
date and time.
401425
timezone : string, optional
402426
Name of the time zone. To see all time zones, import pytz and run
403-
print(pytz.all_timezones). Default time zone is "UTC".
427+
``print(pytz.all_timezones)``. Default time zone is "UTC".
404428
405429
Returns
406430
-------
407431
None
432+
433+
Notes
434+
-----
435+
- If the ``date`` is given as a list or tuple, it should be in the same
436+
time zone as specified by the ``timezone`` parameter. This local
437+
time will be available in the attribute :attr:`Environment.local_date`
438+
while the UTC time will be available in the attribute
439+
:attr:`Environment.datetime_date`.
440+
441+
- If the ``date`` is given as a ``datetime`` object without a time zone,
442+
it will be assumed to be in the same time zone as specified by the
443+
``timezone`` parameter. However, if the ``datetime`` object has a time
444+
zone specified in its ``tzinfo`` attribute, the ``timezone``
445+
parameter will be ignored.
446+
447+
Examples
448+
--------
449+
450+
Let's set the launch date as an list:
451+
452+
>>> date = [2000, 1, 1, 13] # January 1st, 2000 at 13:00 UTC+1
453+
>>> env = Environment()
454+
>>> env.set_date(date, timezone="Europe/Rome")
455+
>>> print(env.datetime_date) # Get UTC time
456+
2000-01-01 12:00:00+00:00
457+
>>> print(env.local_date)
458+
2000-01-01 13:00:00+01:00
459+
460+
Now let's set the launch date as a ``datetime`` object:
461+
462+
>>> from datetime import datetime
463+
>>> date = datetime(2000, 1, 1, 13, 0, 0)
464+
>>> env = Environment()
465+
>>> env.set_date(date, timezone="Europe/Rome")
466+
>>> print(env.datetime_date) # Get UTC time
467+
2000-01-01 12:00:00+00:00
468+
>>> print(env.local_date)
469+
2000-01-01 13:00:00+01:00
408470
"""
409471
# Store date and configure time zone
410472
self.timezone = timezone
@@ -458,23 +520,66 @@ def set_location(self, latitude, longitude):
458520
self.atmospheric_model_file, self.atmospheric_model_dict
459521
)
460522

461-
# Return None
462-
463-
def set_gravity_model(self, gravity):
464-
"""Sets the gravity model to be used in the simulation based on the
465-
given user input to the gravity parameter.
523+
def set_gravity_model(self, gravity=None):
524+
"""Defines the gravity model based on the given user input to the
525+
gravity parameter. The gravity model is responsible for computing the
526+
gravity acceleration at a given height above sea level in meters.
466527
467528
Parameters
468529
----------
469-
gravity : None or Function source
470-
If None, the Somigliana formula is used to compute the gravity
471-
acceleration. Otherwise, the user can provide a Function object
472-
representing the gravity model.
530+
gravity : int, float, callable, string, list, optional
531+
The gravitational acceleration in m/s² to be used in the
532+
simulation, this value is positive when pointing downwards.
533+
The input type can be one of the following:
534+
535+
- ``int`` or ``float``: The gravity acceleration is set as a\
536+
constant function with respect to height;
537+
538+
- ``callable``: This callable should receive the height above\
539+
sea level in meters and return the gravity acceleration;
540+
541+
- ``list``: The datapoints should be structured as\
542+
``[(h_i,g_i), ...]`` where ``h_i`` is the height above sea\
543+
level in meters and ``g_i`` is the gravity acceleration in m/s²;
544+
545+
- ``string``: The string should correspond to a path to a CSV file\
546+
containing the gravity acceleration data;
547+
548+
- ``None``: The Somigliana formula is used to compute the gravity\
549+
acceleration.
550+
551+
This parameter is used as a :class:`Function` object source, check\
552+
out the available input types for a more detailed explanation.
473553
474554
Returns
475555
-------
476556
Function
477557
Function object representing the gravity model.
558+
559+
Notes
560+
-----
561+
This method **does not** set the gravity acceleration, it only returns
562+
a :class:`Function` object representing the gravity model.
563+
564+
Examples
565+
--------
566+
Let's prepare a `Environment` object with a constant gravity
567+
acceleration:
568+
569+
>>> g_0 = 9.80665
570+
>>> env_cte_g = Environment(gravity=g_0)
571+
>>> env_cte_g.gravity([0, 100, 1000])
572+
[9.80665, 9.80665, 9.80665]
573+
574+
It's also possible to variate the gravity acceleration by defining
575+
its function of height:
576+
577+
>>> R_t = 6371000
578+
>>> g_func = lambda h : g_0 * (R_t / (R_t + h))**2
579+
>>> env_var_g = Environment(gravity=g_func)
580+
>>> g = env_var_g.gravity(1000)
581+
>>> print(f"{g:.6f}")
582+
9.803572
478583
"""
479584
if gravity is None:
480585
return self.somigliana_gravity.set_discrete(
@@ -500,7 +605,7 @@ def max_expected_height(self, value):
500605

501606
@funcify_method("height (m)", "gravity (m/s²)")
502607
def somigliana_gravity(self, height):
503-
"""Computes the gravity acceleration with the Somigliana formula.
608+
"""Computes the gravity acceleration with the Somigliana formula [1]_.
504609
An height correction is applied to the normal gravity that is
505610
accurate for heights used in aviation. The formula is based on the
506611
WGS84 ellipsoid, but is accurate for other reference ellipsoids.
@@ -514,6 +619,10 @@ def somigliana_gravity(self, height):
514619
-------
515620
Function
516621
Function object representing the gravity model.
622+
623+
References
624+
----------
625+
.. [1] https://en.wikipedia.org/wiki/Theoretical_gravity#Somigliana_equation
517626
"""
518627
a = 6378137.0 # semi_major_axis
519628
f = 1 / 298.257223563 # flattening_factor

rocketpy/environment/environment_analysis.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -441,7 +441,7 @@ def __localize_input_dates(self):
441441

442442
def __find_preferred_timezone(self):
443443
if self.preferred_timezone is None:
444-
# Use local timezone based on lat lon pair
444+
# Use local time zone based on lat lon pair
445445
try:
446446
timezonefinder = import_optional_dependency("timezonefinder")
447447
tf = timezonefinder.TimezoneFinder()

rocketpy/motors/hybrid_motor.py

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from rocketpy.tools import parallel_axis_theorem_from_com
2+
13
from ..mathutils.function import Function, funcify_method, reset_funcified_methods
24
from ..plots.hybrid_motor_plots import _HybridMotorPlots
35
from ..prints.hybrid_motor_prints import _HybridMotorPrints
@@ -455,22 +457,21 @@ def propellant_I_11(self):
455457
----------
456458
.. [1] https://en.wikipedia.org/wiki/Moment_of_inertia#Inertia_tensor
457459
"""
458-
solid_correction = (
459-
self.solid.propellant_mass
460-
* (self.solid.center_of_propellant_mass - self.center_of_propellant_mass)
461-
** 2
462-
)
463-
liquid_correction = (
464-
self.liquid.propellant_mass
465-
* (self.liquid.center_of_propellant_mass - self.center_of_propellant_mass)
466-
** 2
467-
)
468460

469-
I_11 = (
470-
self.solid.propellant_I_11
471-
+ solid_correction
472-
+ self.liquid.propellant_I_11
473-
+ liquid_correction
461+
solid_mass = self.solid.propellant_mass
462+
liquid_mass = self.liquid.propellant_mass
463+
464+
cm = self.center_of_propellant_mass
465+
solid_cm_to_cm = self.solid.center_of_propellant_mass - cm
466+
liquid_cm_to_cm = self.liquid.center_of_propellant_mass - cm
467+
468+
solid_prop_inertia = self.solid.propellant_I_11
469+
liquid_prop_inertia = self.liquid.propellant_I_11
470+
471+
I_11 = parallel_axis_theorem_from_com(
472+
solid_prop_inertia, solid_mass, solid_cm_to_cm
473+
) + parallel_axis_theorem_from_com(
474+
liquid_prop_inertia, liquid_mass, liquid_cm_to_cm
474475
)
475476

476477
return I_11

rocketpy/motors/liquid_motor.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
funcify_method,
88
reset_funcified_methods,
99
)
10+
from rocketpy.tools import parallel_axis_theorem_from_com
1011

1112
from ..plots.liquid_motor_plots import _LiquidMotorPlots
1213
from ..prints.liquid_motor_prints import _LiquidMotorPrints
@@ -388,10 +389,9 @@ def propellant_I_11(self):
388389
for positioned_tank in self.positioned_tanks:
389390
tank = positioned_tank.get("tank")
390391
tank_position = positioned_tank.get("position")
391-
I_11 += (
392-
tank.inertia
393-
+ tank.fluid_mass
394-
* (tank_position + tank.center_of_mass - center_of_mass) ** 2
392+
distance = tank_position + tank.center_of_mass - center_of_mass
393+
I_11 += parallel_axis_theorem_from_com(
394+
tank.inertia, tank.fluid_mass, distance
395395
)
396396

397397
return I_11

rocketpy/motors/motor.py

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from ..mathutils.function import Function, funcify_method
88
from ..plots.motor_plots import _MotorPlots
99
from ..prints.motor_prints import _MotorPrints
10-
from ..tools import tuple_handler
10+
from ..tools import parallel_axis_theorem_from_com, tuple_handler
1111

1212
try:
1313
from functools import cached_property
@@ -513,25 +513,19 @@ def I_11(self):
513513
----------
514514
.. [1] https://en.wikipedia.org/wiki/Moment_of_inertia#Inertia_tensor
515515
"""
516-
# Propellant inertia tensor 11 component wrt propellant center of mass
517-
propellant_I_11 = self.propellant_I_11
518516

519-
# Dry inertia tensor 11 component wrt dry center of mass
517+
prop_I_11 = self.propellant_I_11
520518
dry_I_11 = self.dry_I_11
521519

522-
# Steiner theorem the get inertia wrt motor center of mass
523-
propellant_I_11 += (
524-
self.propellant_mass
525-
* (self.center_of_propellant_mass - self.center_of_mass) ** 2
526-
)
520+
prop_to_cm = self.center_of_propellant_mass - self.center_of_mass
521+
dry_to_cm = self.center_of_dry_mass_position - self.center_of_mass
527522

528-
dry_I_11 += (
529-
self.dry_mass
530-
* (self.center_of_dry_mass_position - self.center_of_mass) ** 2
523+
prop_I_11 = parallel_axis_theorem_from_com(
524+
prop_I_11, self.propellant_mass, prop_to_cm
531525
)
526+
dry_I_11 = parallel_axis_theorem_from_com(dry_I_11, self.dry_mass, dry_to_cm)
532527

533-
# Sum of inertia components
534-
return propellant_I_11 + dry_I_11
528+
return prop_I_11 + dry_I_11
535529

536530
@funcify_method("Time (s)", "Inertia I_22 (kg m²)")
537531
def I_22(self):

0 commit comments

Comments
 (0)