rocketpy 1.6.0__tar.gz → 1.6.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. {rocketpy-1.6.0/rocketpy.egg-info → rocketpy-1.6.2}/PKG-INFO +11 -8
  2. {rocketpy-1.6.0 → rocketpy-1.6.2}/README.md +10 -7
  3. {rocketpy-1.6.0 → rocketpy-1.6.2}/pyproject.toml +10 -10
  4. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/control/controller.py +2 -0
  5. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/environment/environment.py +15 -125
  6. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/environment/fetchers.py +7 -4
  7. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/mathutils/function.py +7 -7
  8. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/mathutils/vector_matrix.py +8 -0
  9. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/motors/hybrid_motor.py +2 -0
  10. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/motors/liquid_motor.py +2 -0
  11. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/motors/motor.py +22 -1
  12. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/motors/solid_motor.py +2 -0
  13. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/plots/monte_carlo_plots.py +22 -7
  14. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/prints/hybrid_motor_prints.py +1 -0
  15. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/prints/liquid_motor_prints.py +1 -0
  16. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/prints/motor_prints.py +1 -0
  17. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/prints/rocket_prints.py +1 -0
  18. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/prints/solid_motor_prints.py +1 -0
  19. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/rocket/rocket.py +48 -17
  20. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/sensors/accelerometer.py +4 -1
  21. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/sensors/barometer.py +1 -0
  22. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/sensors/gnss_receiver.py +1 -0
  23. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/sensors/gyroscope.py +3 -0
  24. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/sensors/sensor.py +4 -3
  25. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/simulation/monte_carlo.py +66 -26
  26. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/tools.py +152 -147
  27. {rocketpy-1.6.0 → rocketpy-1.6.2/rocketpy.egg-info}/PKG-INFO +11 -8
  28. {rocketpy-1.6.0 → rocketpy-1.6.2}/LICENSE +0 -0
  29. {rocketpy-1.6.0 → rocketpy-1.6.2}/requirements.txt +0 -0
  30. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/__init__.py +0 -0
  31. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/_encoders.py +0 -0
  32. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/control/__init__.py +0 -0
  33. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/environment/__init__.py +0 -0
  34. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/environment/environment_analysis.py +0 -0
  35. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/environment/tools.py +0 -0
  36. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/environment/weather_model_mapping.py +0 -0
  37. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/mathutils/__init__.py +0 -0
  38. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/motors/__init__.py +0 -0
  39. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/motors/fluid.py +0 -0
  40. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/motors/tank.py +0 -0
  41. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/motors/tank_geometry.py +0 -0
  42. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/plots/__init__.py +0 -0
  43. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/plots/aero_surface_plots.py +0 -0
  44. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/plots/compare/__init__.py +0 -0
  45. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/plots/compare/compare.py +0 -0
  46. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/plots/compare/compare_flights.py +0 -0
  47. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/plots/environment_analysis_plots.py +0 -0
  48. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/plots/environment_plots.py +0 -0
  49. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/plots/flight_plots.py +0 -0
  50. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/plots/fluid_plots.py +0 -0
  51. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/plots/hybrid_motor_plots.py +0 -0
  52. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/plots/liquid_motor_plots.py +0 -0
  53. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/plots/motor_plots.py +0 -0
  54. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/plots/rocket_plots.py +0 -0
  55. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/plots/sensitivity_plots.py +0 -0
  56. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/plots/solid_motor_plots.py +0 -0
  57. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/plots/tank_geometry_plots.py +0 -0
  58. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/plots/tank_plots.py +0 -0
  59. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/prints/__init__.py +0 -0
  60. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/prints/aero_surface_prints.py +0 -0
  61. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/prints/compare_prints.py +0 -0
  62. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/prints/controller_prints.py +0 -0
  63. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/prints/environment_analysis_prints.py +0 -0
  64. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/prints/environment_prints.py +0 -0
  65. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/prints/flight_prints.py +0 -0
  66. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/prints/fluid_prints.py +0 -0
  67. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/prints/monte_carlo_prints.py +0 -0
  68. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/prints/parachute_prints.py +0 -0
  69. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/prints/sensitivity_prints.py +0 -0
  70. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/prints/sensors_prints.py +0 -0
  71. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/prints/tank_geometry_prints.py +0 -0
  72. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/prints/tank_prints.py +0 -0
  73. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/rocket/__init__.py +0 -0
  74. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/rocket/aero_surface/__init__.py +0 -0
  75. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/rocket/aero_surface/aero_surface.py +0 -0
  76. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/rocket/aero_surface/air_brakes.py +0 -0
  77. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/rocket/aero_surface/fins/__init__.py +0 -0
  78. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/rocket/aero_surface/fins/elliptical_fins.py +0 -0
  79. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/rocket/aero_surface/fins/fins.py +0 -0
  80. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/rocket/aero_surface/fins/free_form_fins.py +0 -0
  81. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/rocket/aero_surface/fins/trapezoidal_fins.py +0 -0
  82. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/rocket/aero_surface/generic_surface.py +0 -0
  83. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/rocket/aero_surface/linear_generic_surface.py +0 -0
  84. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/rocket/aero_surface/nose_cone.py +0 -0
  85. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/rocket/aero_surface/rail_buttons.py +0 -0
  86. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/rocket/aero_surface/tail.py +0 -0
  87. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/rocket/components.py +0 -0
  88. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/rocket/parachute.py +0 -0
  89. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/sensitivity/__init__.py +0 -0
  90. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/sensitivity/sensitivity_model.py +0 -0
  91. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/sensors/__init__.py +0 -0
  92. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/simulation/__init__.py +0 -0
  93. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/simulation/flight.py +0 -0
  94. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/simulation/flight_data_importer.py +0 -0
  95. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/stochastic/__init__.py +0 -0
  96. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/stochastic/stochastic_aero_surfaces.py +0 -0
  97. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/stochastic/stochastic_environment.py +0 -0
  98. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/stochastic/stochastic_flight.py +0 -0
  99. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/stochastic/stochastic_generic_motor.py +0 -0
  100. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/stochastic/stochastic_model.py +0 -0
  101. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/stochastic/stochastic_motor_model.py +0 -0
  102. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/stochastic/stochastic_parachute.py +0 -0
  103. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/stochastic/stochastic_rocket.py +0 -0
  104. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/stochastic/stochastic_solid_motor.py +0 -0
  105. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/units.py +0 -0
  106. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy/utilities.py +0 -0
  107. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy.egg-info/SOURCES.txt +0 -0
  108. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy.egg-info/dependency_links.txt +0 -0
  109. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy.egg-info/requires.txt +0 -0
  110. {rocketpy-1.6.0 → rocketpy-1.6.2}/rocketpy.egg-info/top_level.txt +0 -0
  111. {rocketpy-1.6.0 → rocketpy-1.6.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: rocketpy
3
- Version: 1.6.0
3
+ Version: 1.6.2
4
4
  Summary: Advanced 6-DOF trajectory simulation for High-Power Rocketry.
5
5
  Author-email: Giovani Hidalgo Ceotto <ghceotto@gmail.com>
6
6
  Project-URL: homepage, https://rocketpy.org/
@@ -52,18 +52,18 @@ Requires-Dist: rocketpy[monte-carlo]; extra == "all"
52
52
  <br>
53
53
 
54
54
  [![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)
55
- [![PyPI](https://img.shields.io/pypi/v/rocketpy?color=g)](https://pypi.org/project/rocketpy/)
56
55
  [![Documentation Status](https://readthedocs.org/projects/rocketpyalpha/badge/?version=latest)](https://docs.rocketpy.org/en/latest/?badge=latest)
56
+ [![PyPI](https://img.shields.io/pypi/v/rocketpy?color=g)](https://pypi.org/project/rocketpy/)
57
+ ![Conda Version](https://img.shields.io/conda/v/conda-forge/rocketpy?color=g)
57
58
  [![codecov](https://codecov.io/gh/RocketPy-Team/RocketPy/graph/badge.svg?token=Ecc3bsHFeP)](https://codecov.io/gh/RocketPy-Team/RocketPy)
59
+ [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
58
60
  [![Contributors](https://img.shields.io/github/contributors/RocketPy-Team/rocketpy)](https://github.com/RocketPy-Team/RocketPy/graphs/contributors)
59
- [![Chat on Discord](https://img.shields.io/discord/765037887016140840?logo=discord)](https://discord.gg/b6xYnNh)
60
61
  [![Sponsor RocketPy](https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub&color=%23fe8e86)](https://github.com/sponsors/RocketPy-Team)
62
+ [![Chat on Discord](https://img.shields.io/discord/765037887016140840?logo=discord)](https://discord.gg/b6xYnNh)
61
63
  [![Instagram](https://img.shields.io/badge/Instagram-E4405F?style=flat&logo=instagram&logoColor=white)](https://www.instagram.com/rocketpyteam)
62
64
  [![LinkedIn](https://img.shields.io/badge/LinkedIn-0077B5?style=flat&logo=linkedin&logoColor=white)](https://www.linkedin.com/company/rocketpy)
63
65
  [![DOI](https://img.shields.io/badge/DOI-10.1061%2F%28ASCE%29AS.1943--5525.0001331-blue.svg)](http://dx.doi.org/10.1061/%28ASCE%29AS.1943-5525.0001331)
64
66
 
65
- <br>
66
-
67
67
  # RocketPy
68
68
 
69
69
  RocketPy is the next-generation trajectory simulation solution for High-Power Rocketry. The code is written as a [Python](http://www.python.org) library and allows for a complete 6 degrees of freedom simulation of a rocket's flight trajectory, including high-fidelity variable mass effects as well as descent under parachutes. Weather conditions, such as wind profiles, can be imported from sophisticated datasets, allowing for realistic scenarios. Furthermore, the implementation facilitates complex simulations, such as multi-stage rockets, design and trajectory optimization and dispersion analysis.
@@ -78,7 +78,7 @@ RocketPy is the next-generation trajectory simulation solution for High-Power Ro
78
78
 
79
79
  2. **Accurate Weather Modeling**
80
80
  - Supports International Standard Atmosphere (1976)
81
- - Custom atmospheric profiles and Soundings (Wyoming, NOAARuc)
81
+ - Custom atmospheric profiles and Soundings (Wyoming)
82
82
  - Weather forecasts, reanalysis, and ensembles for realistic scenarios
83
83
 
84
84
  3. **Aerodynamic Models**
@@ -125,6 +125,9 @@ Flight data and rocket parameters used in this comparison were kindly provided b
125
125
  | NDRT launch vehicle | Apogee time (s) | 16.77 | 17.10 | **-1.90 %** |
126
126
  | NDRT launch vehicle | Maximum velocity (m/s) | 172.86 | 168.95 | **2.31 %** |
127
127
 
128
+ Over years of development and testing, RocketPy has been validated across an expanding range of flight scenarios.
129
+ For more information on these validated flights, visit our [Flight Examples](https://docs.rocketpy.org/en/latest/examples/index.html) page in the documentation.
130
+
128
131
  # Documentation
129
132
 
130
133
  Check out documentation details using the links below:
@@ -206,7 +209,7 @@ env = Environment(
206
209
  latitude=32.990254,
207
210
  longitude=-106.974998,
208
211
  elevation=1400,
209
- )
212
+ )
210
213
 
211
214
  tomorrow = datetime.date.today() + datetime.timedelta(days=1)
212
215
 
@@ -397,7 +400,7 @@ You can also become a [sponsor](https://github.com/sponsors/RocketPy-Team) and h
397
400
 
398
401
  If you are actively using RocketPy in one of your projects, reaching out to our core team via [Discord](https://discord.gg/b6xYnNh) and providing feedback can help improve RocketPy a lot!
399
402
 
400
- And if you are interested in going one step further, please read [CONTRIBUTING.md](https://github.com/RocketPy-Team/RocketPy/blob/master/CONTRIBUTING.md) for details on our code of conduct and learn more about how you can contribute to the development of this next-gen trajectory simulation solution for rocketry.
403
+ And if you are interested in going one step further, please read the [development documentation](https://docs.rocketpy.org/en/latest/development/index.html) to learn more about how you can contribute to the development of this next-gen trajectory simulation solution for rocketry.
401
404
 
402
405
  ## License
403
406
 
@@ -7,18 +7,18 @@
7
7
  <br>
8
8
 
9
9
  [![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)
10
- [![PyPI](https://img.shields.io/pypi/v/rocketpy?color=g)](https://pypi.org/project/rocketpy/)
11
10
  [![Documentation Status](https://readthedocs.org/projects/rocketpyalpha/badge/?version=latest)](https://docs.rocketpy.org/en/latest/?badge=latest)
11
+ [![PyPI](https://img.shields.io/pypi/v/rocketpy?color=g)](https://pypi.org/project/rocketpy/)
12
+ ![Conda Version](https://img.shields.io/conda/v/conda-forge/rocketpy?color=g)
12
13
  [![codecov](https://codecov.io/gh/RocketPy-Team/RocketPy/graph/badge.svg?token=Ecc3bsHFeP)](https://codecov.io/gh/RocketPy-Team/RocketPy)
14
+ [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
13
15
  [![Contributors](https://img.shields.io/github/contributors/RocketPy-Team/rocketpy)](https://github.com/RocketPy-Team/RocketPy/graphs/contributors)
14
- [![Chat on Discord](https://img.shields.io/discord/765037887016140840?logo=discord)](https://discord.gg/b6xYnNh)
15
16
  [![Sponsor RocketPy](https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub&color=%23fe8e86)](https://github.com/sponsors/RocketPy-Team)
17
+ [![Chat on Discord](https://img.shields.io/discord/765037887016140840?logo=discord)](https://discord.gg/b6xYnNh)
16
18
  [![Instagram](https://img.shields.io/badge/Instagram-E4405F?style=flat&logo=instagram&logoColor=white)](https://www.instagram.com/rocketpyteam)
17
19
  [![LinkedIn](https://img.shields.io/badge/LinkedIn-0077B5?style=flat&logo=linkedin&logoColor=white)](https://www.linkedin.com/company/rocketpy)
18
20
  [![DOI](https://img.shields.io/badge/DOI-10.1061%2F%28ASCE%29AS.1943--5525.0001331-blue.svg)](http://dx.doi.org/10.1061/%28ASCE%29AS.1943-5525.0001331)
19
21
 
20
- <br>
21
-
22
22
  # RocketPy
23
23
 
24
24
  RocketPy is the next-generation trajectory simulation solution for High-Power Rocketry. The code is written as a [Python](http://www.python.org) library and allows for a complete 6 degrees of freedom simulation of a rocket's flight trajectory, including high-fidelity variable mass effects as well as descent under parachutes. Weather conditions, such as wind profiles, can be imported from sophisticated datasets, allowing for realistic scenarios. Furthermore, the implementation facilitates complex simulations, such as multi-stage rockets, design and trajectory optimization and dispersion analysis.
@@ -33,7 +33,7 @@ RocketPy is the next-generation trajectory simulation solution for High-Power Ro
33
33
 
34
34
  2. **Accurate Weather Modeling**
35
35
  - Supports International Standard Atmosphere (1976)
36
- - Custom atmospheric profiles and Soundings (Wyoming, NOAARuc)
36
+ - Custom atmospheric profiles and Soundings (Wyoming)
37
37
  - Weather forecasts, reanalysis, and ensembles for realistic scenarios
38
38
 
39
39
  3. **Aerodynamic Models**
@@ -80,6 +80,9 @@ Flight data and rocket parameters used in this comparison were kindly provided b
80
80
  | NDRT launch vehicle | Apogee time (s) | 16.77 | 17.10 | **-1.90 %** |
81
81
  | NDRT launch vehicle | Maximum velocity (m/s) | 172.86 | 168.95 | **2.31 %** |
82
82
 
83
+ Over years of development and testing, RocketPy has been validated across an expanding range of flight scenarios.
84
+ For more information on these validated flights, visit our [Flight Examples](https://docs.rocketpy.org/en/latest/examples/index.html) page in the documentation.
85
+
83
86
  # Documentation
84
87
 
85
88
  Check out documentation details using the links below:
@@ -161,7 +164,7 @@ env = Environment(
161
164
  latitude=32.990254,
162
165
  longitude=-106.974998,
163
166
  elevation=1400,
164
- )
167
+ )
165
168
 
166
169
  tomorrow = datetime.date.today() + datetime.timedelta(days=1)
167
170
 
@@ -352,7 +355,7 @@ You can also become a [sponsor](https://github.com/sponsors/RocketPy-Team) and h
352
355
 
353
356
  If you are actively using RocketPy in one of your projects, reaching out to our core team via [Discord](https://discord.gg/b6xYnNh) and providing feedback can help improve RocketPy a lot!
354
357
 
355
- And if you are interested in going one step further, please read [CONTRIBUTING.md](https://github.com/RocketPy-Team/RocketPy/blob/master/CONTRIBUTING.md) for details on our code of conduct and learn more about how you can contribute to the development of this next-gen trajectory simulation solution for rocketry.
358
+ And if you are interested in going one step further, please read the [development documentation](https://docs.rocketpy.org/en/latest/development/index.html) to learn more about how you can contribute to the development of this next-gen trajectory simulation solution for rocketry.
356
359
 
357
360
  ## License
358
361
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "rocketpy"
3
- version = "1.6.0"
3
+ version = "1.6.2"
4
4
  description="Advanced 6-DOF trajectory simulation for High-Power Rocketry."
5
5
  dynamic = ["dependencies"]
6
6
  readme = "README.md"
@@ -30,8 +30,8 @@ packages = { find = { where = ["."], include = ["rocketpy*"] } }
30
30
  dependencies = { file = ["requirements.txt"] }
31
31
 
32
32
  [project.optional-dependencies]
33
- tests = [
34
- "pytest",
33
+ tests = [
34
+ "pytest",
35
35
  "pytest-coverage",
36
36
  "black[jupyter]",
37
37
  "flake8-black",
@@ -42,12 +42,12 @@ tests = [
42
42
  "isort"
43
43
  ]
44
44
 
45
- env-analysis = [
46
- "windrose>=1.6.8",
47
- "timezonefinder",
48
- "jsonpickle",
49
- "ipython",
50
- "ipywidgets>=7.6.3"
45
+ env-analysis = [
46
+ "windrose>=1.6.8",
47
+ "timezonefinder",
48
+ "jsonpickle",
49
+ "ipython",
50
+ "ipywidgets>=7.6.3"
51
51
  ]
52
52
 
53
53
  monte-carlo = [
@@ -59,7 +59,7 @@ monte-carlo = [
59
59
  all = ["rocketpy[env-analysis]", "rocketpy[monte-carlo]"]
60
60
 
61
61
  [tool.black]
62
- line-length = 88
62
+ line-length = 88
63
63
  include = '\.py$|\.ipynb$'
64
64
  skip-string-normalization = true
65
65
 
@@ -84,8 +84,10 @@ class _Controller:
84
84
  None
85
85
  """
86
86
  self.interactive_objects = interactive_objects
87
+ self.base_controller_function = controller_function
87
88
  self.controller_function = self.__init_controller_function(controller_function)
88
89
  self.sampling_rate = sampling_rate
90
+ self.initial_observed_variables = initial_observed_variables
89
91
  self.name = name
90
92
  self.prints = _ControllerPrints(self)
91
93
 
@@ -16,7 +16,6 @@ from rocketpy.environment.fetchers import (
16
16
  fetch_gfs_file_return_dataset,
17
17
  fetch_hiresw_file_return_dataset,
18
18
  fetch_nam_file_return_dataset,
19
- fetch_noaaruc_sounding,
20
19
  fetch_open_elevation,
21
20
  fetch_rap_file_return_dataset,
22
21
  fetch_wyoming_sounding,
@@ -142,11 +141,11 @@ class Environment:
142
141
  Environment.atmospheric_model_type : string
143
142
  Describes the atmospheric model which is being used. Can only assume the
144
143
  following values: ``standard_atmosphere``, ``custom_atmosphere``,
145
- ``wyoming_sounding``, ``NOAARucSounding``, ``Forecast``, ``Reanalysis``,
144
+ ``wyoming_sounding``, ``Forecast``, ``Reanalysis``,
146
145
  ``Ensemble``.
147
146
  Environment.atmospheric_model_file : string
148
147
  Address of the file used for the atmospheric model being used. Only
149
- defined for ``wyoming_sounding``, ``NOAARucSounding``, ``Forecast``,
148
+ defined for ``wyoming_sounding``, ``Forecast``,
150
149
  ``Reanalysis``, ``Ensemble``
151
150
  Environment.atmospheric_model_dict : dictionary
152
151
  Dictionary used to properly interpret ``netCDF`` and ``OPeNDAP`` files.
@@ -1053,24 +1052,6 @@ class Environment:
1053
1052
 
1054
1053
  .. _weather.uwyo: http://weather.uwyo.edu/upperair/sounding.html
1055
1054
 
1056
- - ``NOAARucSounding``: sets pressure, temperature, wind-u
1057
- and wind-v profiles and surface elevation obtained from
1058
- an upper air sounding given by the file parameter through
1059
- an URL. This URL should point to a data webpage obtained
1060
- through NOAA's Ruc Sounding servers, which can be accessed
1061
- in `rucsoundings`_. Selecting ROABs as the
1062
- initial data source, specifying the station through it's
1063
- WMO-ID and opting for the ASCII (GSD format) button, the
1064
- following example URL opens up:
1065
-
1066
- https://rucsoundings.noaa.gov/get_raobs.cgi?data_source=RAOB&latest=latest&start_year=2019&start_month_name=Feb&start_mday=5&start_hour=12&start_min=0&n_hrs=1.0&fcst_len=shortest&airport=83779&text=Ascii%20text%20%28GSD%20format%29&hydrometeors=false&start=latest
1067
-
1068
- Any ASCII GSD format page from this server can be read,
1069
- so information from virtual soundings such as GFS and NAM
1070
- can also be imported.
1071
-
1072
- .. _rucsoundings: https://rucsoundings.noaa.gov/
1073
-
1074
1055
  - ``windy_atmosphere``: sets pressure, temperature, wind-u and
1075
1056
  wind-v profiles and surface elevation obtained from the Windy API.
1076
1057
  See file argument to specify the model as either ``ECMWF``,
@@ -1279,8 +1260,6 @@ class Environment:
1279
1260
  self.process_standard_atmosphere()
1280
1261
  elif type == "wyoming_sounding":
1281
1262
  self.process_wyoming_sounding(file)
1282
- elif type == "noaarucsounding":
1283
- self.process_noaaruc_sounding(file)
1284
1263
  elif type == "custom_atmosphere":
1285
1264
  self.process_custom_atmosphere(pressure, temperature, wind_u, wind_v)
1286
1265
  elif type == "windy":
@@ -1334,7 +1313,7 @@ class Environment:
1334
1313
  self.__set_wind_speed_function(0)
1335
1314
 
1336
1315
  # 80k meters is the limit of the standard atmosphere
1337
- self.max_expected_height = 80000
1316
+ self._max_expected_height = 80000
1338
1317
 
1339
1318
  def process_custom_atmosphere(
1340
1319
  self, pressure=None, temperature=None, wind_u=0, wind_v=0
@@ -1411,7 +1390,7 @@ class Environment:
1411
1390
  None
1412
1391
  """
1413
1392
  # Initialize an estimate of the maximum expected atmospheric model height
1414
- max_expected_height = 1000
1393
+ max_expected_height = self.max_expected_height or 1000
1415
1394
 
1416
1395
  # Save pressure profile
1417
1396
  if pressure is None:
@@ -1455,7 +1434,7 @@ class Environment:
1455
1434
  self.__reset_wind_direction_function()
1456
1435
  self.__reset_wind_speed_function()
1457
1436
 
1458
- self.max_expected_height = max_expected_height
1437
+ self._max_expected_height = max_expected_height
1459
1438
 
1460
1439
  def process_windy_atmosphere(
1461
1440
  self, model="ECMWF"
@@ -1530,7 +1509,7 @@ class Environment:
1530
1509
  self.__set_wind_speed_function(data_array[:, (1, 7)])
1531
1510
 
1532
1511
  # Save maximum expected height
1533
- self.max_expected_height = max(altitude_array[0], altitude_array[-1])
1512
+ self._max_expected_height = max(altitude_array[0], altitude_array[-1])
1534
1513
 
1535
1514
  # Get elevation data from file
1536
1515
  self.elevation = float(response["header"]["elevation"])
@@ -1668,7 +1647,7 @@ class Environment:
1668
1647
  )
1669
1648
 
1670
1649
  # Save maximum expected height
1671
- self.max_expected_height = data_array[-1, 1]
1650
+ self._max_expected_height = data_array[-1, 1]
1672
1651
 
1673
1652
  def process_noaaruc_sounding(self, file): # pylint: disable=too-many-statements
1674
1653
  """Import and process the upper air sounding data from `NOAA
@@ -1689,107 +1668,18 @@ class Environment:
1689
1668
 
1690
1669
  See also
1691
1670
  --------
1692
- More details can be found at: https://rucsoundings.noaa.gov/.
1671
+ This method is deprecated and will be fully deleted in version 1.8.0.
1693
1672
 
1694
1673
  Returns
1695
1674
  -------
1696
1675
  None
1697
1676
  """
1698
- # Request NOAA Ruc Sounding from file url
1699
- response = fetch_noaaruc_sounding(file)
1700
-
1701
- # Split response into lines
1702
- lines = response.text.split("\n")
1703
-
1704
- # Process GSD format (https://rucsoundings.noaa.gov/raob_format.html)
1705
-
1706
- # Extract elevation data
1707
- for line in lines:
1708
- # Split line into columns
1709
- columns = re.split(" +", line)[1:]
1710
- if len(columns) > 0:
1711
- if columns[0] == "1" and columns[5] != "99999":
1712
- # Save elevation
1713
- self.elevation = float(columns[5])
1714
- else:
1715
- # No elevation data available
1716
- pass
1717
-
1718
- pressure_array = []
1719
- barometric_height_array = []
1720
- temperature_array = []
1721
- wind_speed_array = []
1722
- wind_direction_array = []
1723
-
1724
- for line in lines:
1725
- # Split line into columns
1726
- columns = re.split(" +", line)[1:]
1727
- if len(columns) < 6:
1728
- # skip lines with less than 6 columns
1729
- continue
1730
- if columns[0] in ["4", "5", "6", "7", "8", "9"]:
1731
- # Convert columns to floats
1732
- columns = np.array(columns, dtype=float)
1733
- # Select relevant columns
1734
- altitude, pressure, temperature, wind_direction, wind_speed = columns[
1735
- [2, 1, 3, 5, 6]
1736
- ]
1737
- # Check for missing values
1738
- if altitude == 99999:
1739
- continue
1740
- # Save values only if they are not missing
1741
- if pressure != 99999:
1742
- pressure_array.append([altitude, pressure])
1743
- barometric_height_array.append([pressure, altitude])
1744
- if temperature != 99999:
1745
- temperature_array.append([altitude, temperature])
1746
- if wind_direction != 99999:
1747
- wind_direction_array.append([altitude, wind_direction])
1748
- if wind_speed != 99999:
1749
- wind_speed_array.append([altitude, wind_speed])
1750
-
1751
- # Convert lists to arrays
1752
- pressure_array = np.array(pressure_array)
1753
- barometric_height_array = np.array(barometric_height_array)
1754
- temperature_array = np.array(temperature_array)
1755
- wind_speed_array = np.array(wind_speed_array)
1756
- wind_direction_array = np.array(wind_direction_array)
1757
-
1758
- # Converts 10*hPa to Pa and save values
1759
- pressure_array[:, 1] = 10 * pressure_array[:, 1]
1760
- self.__set_pressure_function(pressure_array)
1761
- # Converts 10*hPa to Pa and save values
1762
- barometric_height_array[:, 0] = 10 * barometric_height_array[:, 0]
1763
- self.__set_barometric_height_function(barometric_height_array)
1764
-
1765
- # Convert C to K and save values
1766
- temperature_array[:, 1] = temperature_array[:, 1] / 10 + 273.15
1767
- self.__set_temperature_function(temperature_array)
1768
-
1769
- # Process wind-u and wind-v
1770
- # Converts Knots to m/s
1771
- wind_speed_array[:, 1] = wind_speed_array[:, 1] * 1.852 / 3.6
1772
- wind_heading_array = wind_direction_array[:, :] * 1
1773
- # Convert wind direction to wind heading
1774
- wind_heading_array[:, 1] = (wind_direction_array[:, 1] + 180) % 360
1775
- wind_u = wind_speed_array[:, :] * 1
1776
- wind_v = wind_speed_array[:, :] * 1
1777
- wind_u[:, 1] = wind_speed_array[:, 1] * np.sin(
1778
- np.deg2rad(wind_heading_array[:, 1])
1779
- )
1780
- wind_v[:, 1] = wind_speed_array[:, 1] * np.cos(
1781
- np.deg2rad(wind_heading_array[:, 1])
1677
+ warnings.warn(
1678
+ "NOAA RUC models are no longer available. "
1679
+ "This method is deprecated and will be fully deleted in version 1.8.0.",
1680
+ DeprecationWarning,
1782
1681
  )
1783
-
1784
- # Save wind data
1785
- self.__set_wind_direction_function(wind_direction_array)
1786
- self.__set_wind_heading_function(wind_heading_array)
1787
- self.__set_wind_speed_function(wind_speed_array)
1788
- self.__set_wind_velocity_x_function(wind_u)
1789
- self.__set_wind_velocity_y_function(wind_v)
1790
-
1791
- # Save maximum expected height
1792
- self.max_expected_height = pressure_array[-1, 0]
1682
+ return file
1793
1683
 
1794
1684
  def process_forecast_reanalysis(
1795
1685
  self, file, dictionary
@@ -2009,7 +1899,7 @@ class Environment:
2009
1899
  self.__set_wind_speed_function(data_array[:, (1, 7)])
2010
1900
 
2011
1901
  # Save maximum expected height
2012
- self.max_expected_height = max(height[0], height[-1])
1902
+ self._max_expected_height = max(height[0], height[-1])
2013
1903
 
2014
1904
  # Get elevation data from file
2015
1905
  if dictionary["surface_geopotential_height"] is not None:
@@ -2354,7 +2244,7 @@ class Environment:
2354
2244
  self.__set_wind_speed_function(data_array[:, (1, 7)])
2355
2245
 
2356
2246
  # Save other attributes
2357
- self.max_expected_height = max(height[0], height[-1])
2247
+ self._max_expected_height = max(height[0], height[-1])
2358
2248
  self.ensemble_member = member
2359
2249
 
2360
2250
  # Update air density, speed of sound and dynamic viscosity
@@ -5,6 +5,7 @@ functions may be changed without notice in future feature releases.
5
5
 
6
6
  import re
7
7
  import time
8
+ import warnings
8
9
  from datetime import datetime, timedelta, timezone
9
10
 
10
11
  import netCDF4
@@ -347,10 +348,12 @@ def fetch_noaaruc_sounding(file):
347
348
  ImportError
348
349
  If unable to load the specified file or the file content is too short.
349
350
  """
350
- response = requests.get(file)
351
- if response.status_code != 200 or len(response.text) < 10:
352
- raise ImportError("Unable to load " + file + ".")
353
- return response
351
+ warnings.warn(
352
+ "The NOAA RUC soundings are deprecated since September 30th, 2024. "
353
+ "This method will be removed in version 1.8.0.",
354
+ DeprecationWarning,
355
+ )
356
+ return file
354
357
 
355
358
 
356
359
  @exponential_backoff(max_attempts=5, base_delay=2, max_delay=60)
@@ -3171,7 +3171,7 @@ class Function: # pylint: disable=too-many-public-methods
3171
3171
  )
3172
3172
  # Generate the data points using the callable
3173
3173
  x = np.linspace(lower, upper, samples)
3174
- data_points = np.column_stack((x, self.source(x)))
3174
+ data_points = np.column_stack((x, self(x)))
3175
3175
  else:
3176
3176
  # If the source is already an array, use it as is
3177
3177
  data_points = self.source
@@ -3247,12 +3247,12 @@ class Function: # pylint: disable=too-many-public-methods
3247
3247
  )
3248
3248
 
3249
3249
  source_len, source_dim = source.shape
3250
-
3251
- if source_len < source_dim:
3252
- raise ValueError(
3253
- "Too few data points to define a domain. The number of rows "
3254
- "must be greater than or equal to the number of columns."
3255
- )
3250
+ if not source_len == 1: # do not check for one point Functions
3251
+ if source_len < source_dim:
3252
+ raise ValueError(
3253
+ "Too few data points to define a domain. The number of rows "
3254
+ "must be greater than or equal to the number of columns."
3255
+ )
3256
3256
 
3257
3257
  return source
3258
3258
 
@@ -418,6 +418,10 @@ class Vector:
418
418
  """Returns the k vector, [0, 0, 1]."""
419
419
  return Vector([0, 0, 1])
420
420
 
421
+ def to_dict(self):
422
+ """Returns the vector as a JSON compatible element."""
423
+ return list(self.components)
424
+
421
425
 
422
426
  class Matrix:
423
427
  """Pure Python 3x3 Matrix class for simple matrix-matrix and matrix-vector
@@ -998,6 +1002,10 @@ class Matrix:
998
1002
  + f" [{self.zx}, {self.zy}, {self.zz}])"
999
1003
  )
1000
1004
 
1005
+ def to_dict(self):
1006
+ """Returns the matrix as a JSON compatible element."""
1007
+ return [list(row) for row in self.components]
1008
+
1001
1009
  @staticmethod
1002
1010
  def identity():
1003
1011
  """Returns the 3x3 identity matrix."""
@@ -72,6 +72,8 @@ class HybridMotor(Motor):
72
72
  HybridMotor.propellant_mass : Function
73
73
  Total propellant mass in kg as a function of time, this includes the
74
74
  mass of fluids in each tank and the mass of the solid grains.
75
+ HybridMotor.structural_mass_ratio: float
76
+ Initial ratio between the dry mass and the total mass.
75
77
  HybridMotor.total_mass_flow_rate : Function
76
78
  Time derivative of propellant total mass in kg/s as a function
77
79
  of time as obtained by the thrust source.
@@ -47,6 +47,8 @@ class LiquidMotor(Motor):
47
47
  LiquidMotor.propellant_mass : Function
48
48
  Total propellant mass in kg as a function of time, includes fuel
49
49
  and oxidizer.
50
+ LiquidMotor.structural_mass_ratio: float
51
+ Initial ratio between the dry mass and the total mass.
50
52
  LiquidMotor.total_mass_flow_rate : Function
51
53
  Time derivative of propellant total mass in kg/s as a function
52
54
  of time as obtained by the tanks mass flow.
@@ -49,6 +49,8 @@ class Motor(ABC):
49
49
  Motor.propellant_mass : Function
50
50
  Total propellant mass in kg as a function of time, including solid,
51
51
  liquid and gas phases.
52
+ Motor.structural_mass_ratio: float
53
+ Initial ratio between the dry mass and the total mass.
52
54
  Motor.total_mass_flow_rate : Function
53
55
  Time derivative of propellant total mass in kg/s as a function
54
56
  of time as obtained by the thrust source.
@@ -370,7 +372,7 @@ class Motor(ABC):
370
372
  dry_mass : float
371
373
  Motor dry mass in kg.
372
374
  """
373
- if dry_mass:
375
+ if dry_mass is not None:
374
376
  if isinstance(dry_mass, (int, float)):
375
377
  self._dry_mass = dry_mass
376
378
  else:
@@ -497,6 +499,24 @@ class Motor(ABC):
497
499
  Propellant initial mass in kg.
498
500
  """
499
501
 
502
+ @property
503
+ def structural_mass_ratio(self):
504
+ """Calculates the structural mass ratio. The ratio is defined as
505
+ the dry mass divided by the initial total mass.
506
+
507
+ Returns
508
+ -------
509
+ float
510
+ Initial structural mass ratio.
511
+ """
512
+ initial_total_mass = self.dry_mass + self.propellant_initial_mass
513
+ try:
514
+ return self.dry_mass / initial_total_mass
515
+ except ZeroDivisionError as e:
516
+ raise ValueError(
517
+ "Total motor mass (dry + propellant) cannot be zero"
518
+ ) from e
519
+
500
520
  @funcify_method("Time (s)", "Motor center of mass (m)")
501
521
  def center_of_mass(self):
502
522
  """Position of the center of mass as a function of time. The position
@@ -1502,6 +1522,7 @@ class EmptyMotor:
1502
1522
  self.nozzle_radius = 0
1503
1523
  self.thrust = Function(0, "Time (s)", "Thrust (N)")
1504
1524
  self.propellant_mass = Function(0, "Time (s)", "Propellant Mass (kg)")
1525
+ self.propellant_initial_mass = 0
1505
1526
  self.total_mass = Function(0, "Time (s)", "Total Mass (kg)")
1506
1527
  self.total_mass_flow_rate = Function(
1507
1528
  0, "Time (s)", "Mass Depletion Rate (kg/s)"
@@ -70,6 +70,8 @@ class SolidMotor(Motor):
70
70
  of propellant and dry mass.
71
71
  SolidMotor.propellant_mass : Function
72
72
  Total propellant mass in kg as a function of time.
73
+ SolidMotor.structural_mass_ratio: float
74
+ Initial ratio between the dry mass and the total mass.
73
75
  SolidMotor.total_mass_flow_rate : Function
74
76
  Time derivative of propellant total mass in kg/s as a function
75
77
  of time as obtained by the thrust source.
@@ -1,4 +1,5 @@
1
1
  import matplotlib.pyplot as plt
2
+ import numpy as np
2
3
 
3
4
  from ..tools import generate_monte_carlo_ellipses, import_optional_dependency
4
5
 
@@ -54,14 +55,30 @@ class _MonteCarloPlots:
54
55
  "The image file was not found. Please check the path."
55
56
  ) from e
56
57
 
57
- (
58
- impact_ellipses,
59
- apogee_ellipses,
58
+ try:
59
+ apogee_x = np.array(self.monte_carlo.results["apogee_x"])
60
+ apogee_y = np.array(self.monte_carlo.results["apogee_y"])
61
+ except KeyError:
62
+ print("No apogee data found. Skipping apogee ellipses.")
63
+ apogee_x = np.array([])
64
+ apogee_y = np.array([])
65
+ try:
66
+ impact_x = np.array(self.monte_carlo.results["x_impact"])
67
+ impact_y = np.array(self.monte_carlo.results["y_impact"])
68
+ except KeyError:
69
+ print("No impact data found. Skipping impact ellipses.")
70
+ impact_x = np.array([])
71
+ impact_y = np.array([])
72
+
73
+ if len(apogee_x) == 0 and len(impact_x) == 0:
74
+ raise ValueError("No apogee or impact data found. Cannot plot ellipses.")
75
+
76
+ impact_ellipses, apogee_ellipses = generate_monte_carlo_ellipses(
60
77
  apogee_x,
61
78
  apogee_y,
62
79
  impact_x,
63
80
  impact_y,
64
- ) = generate_monte_carlo_ellipses(self.monte_carlo.results)
81
+ )
65
82
 
66
83
  # Create plot figure
67
84
  plt.figure(figsize=(8, 6), dpi=150)
@@ -97,9 +114,7 @@ class _MonteCarloPlots:
97
114
  )
98
115
 
99
116
  plt.legend()
100
- ax.set_title(
101
- "1$\\sigma$, 2$\\sigma$ and 3$\\sigma$ Monte Carlo Ellipses: Apogee and Landing Points"
102
- )
117
+ ax.set_title("1$\\sigma$, 2$\\sigma$ and 3$\\sigma$ Monte Carlo Ellipses")
103
118
  ax.set_ylabel("North (m)")
104
119
  ax.set_xlabel("East (m)")
105
120
 
@@ -77,6 +77,7 @@ class _HybridMotorPrints:
77
77
  print(
78
78
  f"Total Propellant Mass: {self.hybrid_motor.propellant_initial_mass:.3f} kg"
79
79
  )
80
+ print(f"Structural Mass Ratio: {self.hybrid_motor.structural_mass_ratio:.3f}")
80
81
  avg = self.hybrid_motor.exhaust_velocity.average(*self.hybrid_motor.burn_time)
81
82
  print(f"Average Propellant Exhaust Velocity: {avg:.3f} m/s")
82
83
  print(f"Average Thrust: {self.hybrid_motor.average_thrust:.3f} N")
@@ -47,6 +47,7 @@ class _LiquidMotorPrints:
47
47
  print(
48
48
  f"Total Propellant Mass: {self.liquid_motor.propellant_initial_mass:.3f} kg"
49
49
  )
50
+ print(f"Structural Mass Ratio: {self.liquid_motor.structural_mass_ratio:.3f}")
50
51
  avg = self.liquid_motor.exhaust_velocity.average(*self.liquid_motor.burn_time)
51
52
  print(f"Average Propellant Exhaust Velocity: {avg:.3f} m/s")
52
53
  print(f"Average Thrust: {self.liquid_motor.average_thrust:.3f} N")
@@ -35,6 +35,7 @@ class _MotorPrints:
35
35
  print("Motor Details")
36
36
  print("Total Burning Time: " + str(self.motor.burn_out_time) + " s")
37
37
  print(f"Total Propellant Mass: {self.motor.propellant_initial_mass:.3f} kg")
38
+ print(f"Structural Mass Ratio: {self.motor.structural_mass_ratio:.3f}")
38
39
  print(
39
40
  "Average Propellant Exhaust Velocity: "
40
41
  f"{self.motor.exhaust_velocity.average(*self.motor.burn_time):.3f} m/s"