Skip to content

Commit 25947fa

Browse files
ayoubdspGui-FernandesBR
authored andcommitted
Implement ClusterMotor class for motor aggregation
- The solver now calls `motor.get_total_thrust_vector(t)` and `motor.get_total_moment(t, rocket_cm)` to get the full 3D forces and torques from the propulsion system. - These vectors are used to solve the full rigid body equations of motion (Euler's equations) for angular acceleration. - This enables correct 6-DOF simulation of motor clusters, vectored thrust, and thrust misalignments. Update Rocket class for 3D inertia and ClusterMotor Refactors the `Rocket` class to integrate the new `ClusterMotor` and handle 3D centers of mass and dynamic inertia. - `add_motor()` is updated to detect `ClusterMotor` objects. - `add_cluster_motor()` helper method added. - `evaluate_center_of_mass` and `evaluate_center_of_dry_mass` now perform weighted 3D vector averaging for CoM. - `evaluate_dry_inertias` and `evaluate_inertias` are updated to use the new dynamic `parallel_axis_theorem` functions. - Internal attributes like `center_of_dry_mass_position` are now 3D `Vector`s instead of Z-axis scalars. Update HybridMotor to new dynamic inertia contract Refactors `HybridMotor.__init__` to comply with the new base class inertia contract. - The class now calculates the individual inertias of the liquid (tanks) and solid (grain) components relative to their own CoMs. - It then uses the new dynamic PAT functions to aggregate these inertias relative to the *total propellant center of mass*. - The result is stored in `self.propellant_I_xx_from_propellant_CM` for the `Motor` base class to consume. Update LiquidMotor to new dynamic inertia contract Refactors `LiquidMotor` to comply with the new base class inertia contract. - The class now correctly calculates the aggregated inertia of all tanks relative to the total propellant center of mass (logic handled by base class and PAT tools). - Corrects the `propellant_I_11`, `_I_22`, etc. methods to return the pre-calculated `propellant_I_xx_from_propellant_CM` attribute, preventing an incorrect double application of the Parallel Axis Theorem. SolidMotor inertia logic for dynamic contract 1. **`.eng`/`.rse` File Pre-parsing:** * The logic to read `.eng` and `.rse` files (using `Motor.import_eng` and `Motor.import_rse`) has been moved from the base class into the `SolidMotor.__init__` method. * This ensures that the `thrust_source` data array is loaded and available *before* `super().__init__` is called. 2. **Inertia Re-calculation:** * The original `SolidMotor` relied on the `motor.py` base class to handle all inertia calculations within `super().__init__`. * This was problematic because `evaluate_geometry()` (which defines the grain properties needed for inertia) was called *after* `super().__init__`. * This commit fixes this by adding a new block of code at the **end** of `SolidMotor.__init__` (after `evaluate_geometry()` has run). * This new block explicitly: 1. Assigns the now-valid propellant inertia methods (e.g., `self.propellant_I_11`) to the new "contract" attributes (e.g., `self.propellant_I_11_from_propellant_CM`). 2. Imports the dynamic `parallel_axis_theorem` tools. 3. **Re-calculates** the propellant inertia relative to the motor origin (using the PAT logic from `motor.py`). 4. **Re-calculates** the final total motor inertia (`self.I_11`, `self.I_33`, etc.) by summing the dry and (now correct) propellant inertias. This overwrites the incorrect values that were set during the initial `super().__init__` call. Refactor base Motor class for dynamic 6-DOF inertia 1. **Dynamic Inertia Calculation (Major Change):** * The `__init__` method now imports the new dynamic Parallel Axis Theorem (PAT) functions from `tools.py` (e.g., `parallel_axis_theorem_I11`, `_I12`, etc.). * A new "Inertia Contract" is established: `__init__` defines new abstract attributes (e.g., `self.propellant_I_11_from_propellant_CM = Function(0)`). * Subclasses (like `SolidMotor`, `HybridMotor`) are now *required* to provide their propellant inertia relative to their *own propellant CoM* by overriding these `_from_propellant_CM` attributes. * `__init__` (lines 280-333) immediately uses the dynamic PAT functions to calculate the propellant inertia tensor (e.g., `self.propellant_I_11`) relative to the **motor's origin**, based on these "contract" attributes. * `__init__` (lines 336-351) then calculates the **total motor inertia** (`self.I_11`, `self.I_33`, etc.) relative to the **motor's origin** by adding the (constant) dry inertia. *Note: This differs completely from the GitHub version, where inertia was calculated *later* inside the `@funcify_method` definitions (`I_11`, `I_33`, etc.) and relative to the *instantaneous center of mass*.* 2. **`.eng` File Parsing Fix:** * The `__init__` method now includes logic (lines 235-240) to detect if `thrust_source` is a `.eng` file. * If true, it sets the `delimiter` to a space (" ") and `comments` to a semicolon (";"), correcting the parsing failure that occurs in the original `Function` constructor which defaults to commas. 3. **`reference_pressure` Added:** * The `__init__` signature (line 229) now accepts `reference_pressure=None`. * This value is stored as `self.reference_pressure` (line 257) to be used in vacuum thrust calculations. 4. **Modified Inertia Methods (`I_11`, `I_33`, etc.):** * The instance methods (e.g., `@funcify_method def I_11(self)`) starting at line 641 have been modified. * While the GitHub version used these methods to calculate inertia relative to the instantaneous CoM, this version's logic is updated, although it appears redundant given that the primary inertia calculation (relative to the origin) is now finalized in `__init__`. Update Rocket.draw() to visualize ClusterMotor configurations 1. **Import `ClusterMotor`:** * The file now imports `ClusterMotor` (Line 5) to check the motor type. 2. **Refactored `_draw_motor` Method (Line 234):** * This method is completely refactored. It now acts as a dispatcher, calling the new `_generate_motor_patches` helper function. * It checks `isinstance(self.rocket.motor, ClusterMotor)`. * If it's a cluster, it adds all patches generated by the helper. * If it's a simple motor, it uses the original logic (drawing the nozzle and chamber centered at y=0). * It also correctly calls `_draw_nozzle_tube` to connect the airframe to the start of the cluster or single motor. 3. **New `_generate_motor_patches` Method (Line 259):** * This is an **entirely new** helper function. * It contains the core logic for drawing clusters. * It iterates through `cluster.motors` and `cluster.positions`. * For each sub-motor, it correctly calculates the 2D plot offset (using `sub_pos[0]` for the 'xz' plane or `sub_pos[1]` for 'yz') and the longitudinal position (`sub_pos[2]`). * It re-uses the plotting logic of the individual sub-motors (e.g., `_generate_combustion_chamber`, `_generate_grains`) but applies the correct `translate=(pos_z, offset)` transform. * This allows multiple motors to be drawn side-by-side. 4. **Fix `_draw_center_of_mass_and_pressure` (Line 389):** * This method is updated to handle the new 3D `Vector` object returned by `self.rocket.center_of_mass(0)`. * It now accesses the Z-coordinate correctly using `cm_z = float(cm_vector.z.real)` instead of assuming the return value is a simple float. Update function.py Clear comments and cleaned code Updated ClusterMotor Clean comments and cleared code Hybrid Motor correction Correction of the comments and cleared the code Correction of LiquidMotor Clean comments and cleared code Correction of Motor Clean comments and clear code Correction of SolidMotor Clean comments and cleared code Correction of Rocketplots Clean comments and cleared code from copilot Correction of Rocket Clean comments and cleared code with copilot review Correction of Flight Clean comments and clear code Correction of tools Clean comments and clear code ClusterMotor initialization and add docstring Addresses pull request feedback on the new ClusterMotor class. The monolithic __init__ method was large and difficult to maintain. This commit refactors the constructor by breaking its logic into several smaller, private helper methods: - `_validate_inputs`: Handles all input validation and normalization. - `_initialize_basic_properties`: Calculates scalar values (mass, impulse, burn time). - `_initialize_thrust_and_mass`: Sets up thrust, mass flow rate, and propellant mass Functions. - `_initialize_center_of_mass`: Sets up CoM and CoPM Functions. - `_initialize_inertia_properties`: Calculates dry inertia and sets up propellant inertia Functions. This improves readability and separates concerns. Additionally: - Adds a NumPy-style docstring to the `__init__` method. - Cleans up inline comments, retaining all essential docstrings. Refactor imports in solid_motor.py for clarity Simplify return statement for inertia tensor calculation Removing the # .real generated comment Removing the generated unuseful comment Implement deprecation warning decorator Added a deprecation decorator to warn users about obsolete functions and their alternatives. make format make lint
1 parent 793e5f6 commit 25947fa

File tree

10 files changed

+2027
-1405
lines changed

10 files changed

+2027
-1405
lines changed

rocketpy/motors/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from .cluster_motor import ClusterMotor
12
from .empty_motor import EmptyMotor
23
from .fluid import Fluid
34
from .hybrid_motor import HybridMotor

rocketpy/motors/cluster_motor.py

Lines changed: 410 additions & 0 deletions
Large diffs are not rendered by default.

rocketpy/motors/hybrid_motor.py

Lines changed: 390 additions & 262 deletions
Large diffs are not rendered by default.

rocketpy/motors/liquid_motor.py

Lines changed: 18 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,17 @@
33
import numpy as np
44

55
from rocketpy.mathutils.function import funcify_method, reset_funcified_methods
6-
from rocketpy.tools import parallel_axis_theorem_from_com
76

87
from ..plots.liquid_motor_plots import _LiquidMotorPlots
9-
from ..prints.liquid_motor_prints import _LiquidMotorPrints
108
from .motor import Motor
119

1210

1311
class LiquidMotor(Motor):
1412
"""Class to specify characteristics and useful operations for Liquid
1513
motors. This class inherits from the Motor class.
16-
1714
See Also
1815
--------
1916
Motor
20-
2117
Attributes
2218
----------
2319
LiquidMotor.coordinate_system_orientation : str
@@ -157,113 +153,43 @@ class LiquidMotor(Motor):
157153
It will allow to obtain the net thrust in the Flight class.
158154
"""
159155

156+
# pylint: disable=too-many-locals, too-many-statements, too-many-arguments
160157
def __init__(
161158
self,
162159
thrust_source,
163160
dry_mass,
164161
dry_inertia,
165162
nozzle_radius,
163+
burn_time,
166164
center_of_dry_mass_position,
167165
nozzle_position=0,
168-
burn_time=None,
169166
reshape_thrust_curve=False,
170167
interpolation_method="linear",
171168
coordinate_system_orientation="nozzle_to_combustion_chamber",
172169
reference_pressure=None,
170+
tanks_mass=0,
173171
):
174-
"""Initialize LiquidMotor class, process thrust curve and geometrical
175-
parameters and store results.
172+
# Initialize the list of tanks
173+
self.positioned_tanks = []
174+
175+
# Correct the dry mass to include the mass of the tanks
176+
dry_mass = dry_mass + tanks_mass
177+
dry_inertia = (*dry_inertia, 0, 0, 0) if len(dry_inertia) == 3 else dry_inertia
176178

177-
Parameters
178-
----------
179-
thrust_source : int, float, callable, string, array, Function
180-
Motor's thrust curve. Can be given as an int or float, in which
181-
case the thrust will be considered constant in time. It can
182-
also be given as a callable function, whose argument is time in
183-
seconds and returns the thrust supplied by the motor in the
184-
instant. If a string is given, it must point to a .csv or .eng file.
185-
The .csv file can contain a single line header and the first column
186-
must specify time in seconds, while the second column specifies
187-
thrust. Arrays may also be specified, following rules set by the
188-
class Function. Thrust units are Newtons.
189-
190-
.. seealso:: :doc:`Thrust Source Details </user/motors/thrust>`
191-
dry_mass : int, float
192-
Same as in Motor class. See the :class:`Motor <rocketpy.Motor>` docs.
193-
dry_inertia : tuple, list
194-
Tuple or list containing the motor's dry mass inertia tensor
195-
components, in kg*m^2. This inertia is defined with respect to the
196-
the ``center_of_dry_mass_position`` position.
197-
Assuming e_3 is the rocket's axis of symmetry, e_1 and e_2 are
198-
orthogonal and form a plane perpendicular to e_3, the dry mass
199-
inertia tensor components must be given in the following order:
200-
(I_11, I_22, I_33, I_12, I_13, I_23), where I_ij is the
201-
component of the inertia tensor in the direction of e_i x e_j.
202-
Alternatively, the inertia tensor can be given as
203-
(I_11, I_22, I_33), where I_12 = I_13 = I_23 = 0.
204-
nozzle_radius : int, float
205-
Motor's nozzle outlet radius in meters.
206-
center_of_dry_mass_position : int, float
207-
The position, in meters, of the motor's center of mass with respect
208-
to the motor's coordinate system when it is devoid of propellant.
209-
See :doc:`Positions and Coordinate Systems </user/positions>`
210-
nozzle_position : float
211-
Motor's nozzle outlet position in meters, specified in the motor's
212-
coordinate system. See
213-
:doc:`Positions and Coordinate Systems </user/positions>` for
214-
more information.
215-
burn_time: float, tuple of float, optional
216-
Motor's burn time.
217-
If a float is given, the burn time is assumed to be between 0 and
218-
the given float, in seconds.
219-
If a tuple of float is given, the burn time is assumed to be between
220-
the first and second elements of the tuple, in seconds.
221-
If not specified, automatically sourced as the range between the
222-
first and last-time step of the motor's thrust curve. This can only
223-
be used if the motor's thrust is defined by a list of points, such
224-
as a .csv file, a .eng file or a Function instance whose source is
225-
a list.
226-
reshape_thrust_curve : boolean, tuple, optional
227-
If False, the original thrust curve supplied is not altered. If a
228-
tuple is given, whose first parameter is a new burn out time and
229-
whose second parameter is a new total impulse in Ns, the thrust
230-
curve is reshaped to match the new specifications. May be useful
231-
for motors whose thrust curve shape is expected to remain similar
232-
in case the impulse and burn time varies slightly. Default is
233-
False.
234-
interpolation_method : string, optional
235-
Method of interpolation to be used in case thrust curve is given
236-
by data set in .csv or .eng, or as an array. Options are 'spline'
237-
'akima' and 'linear'. Default is "linear".
238-
coordinate_system_orientation : string, optional
239-
Orientation of the motor's coordinate system. The coordinate system
240-
is defined by the motor's axis of symmetry. The origin of the
241-
coordinate system may be placed anywhere along such axis, such as
242-
at the nozzle area, and must be kept the same for all other
243-
positions specified. Options are "nozzle_to_combustion_chamber"
244-
and "combustion_chamber_to_nozzle". Default is
245-
"nozzle_to_combustion_chamber".
246-
reference_pressure : int, float, optional
247-
Atmospheric pressure in Pa at which the thrust data was recorded.
248-
"""
249179
super().__init__(
250180
thrust_source=thrust_source,
181+
dry_mass=dry_mass,
251182
dry_inertia=dry_inertia,
252183
nozzle_radius=nozzle_radius,
184+
burn_time=burn_time,
253185
center_of_dry_mass_position=center_of_dry_mass_position,
254-
dry_mass=dry_mass,
255186
nozzle_position=nozzle_position,
256-
burn_time=burn_time,
257187
reshape_thrust_curve=reshape_thrust_curve,
258188
interpolation_method=interpolation_method,
259189
coordinate_system_orientation=coordinate_system_orientation,
260190
reference_pressure=reference_pressure,
261191
)
262192

263-
self.positioned_tanks = []
264-
265-
# Initialize plots and prints object
266-
self.prints = _LiquidMotorPrints(self)
267193
self.plots = _LiquidMotorPlots(self)
268194

269195
@funcify_method("Time (s)", "Exhaust Velocity (m/s)")
@@ -374,36 +300,16 @@ def center_of_propellant_mass(self):
374300

375301
@funcify_method("Time (s)", "Inertia I_11 (kg m²)")
376302
def propellant_I_11(self):
377-
"""Inertia tensor 11 component of the propellant, the inertia is
378-
relative to the e_1 axis, centered at the instantaneous propellant
379-
center of mass.
303+
"""Inertia tensor 11 component of the total propellant, the inertia is
304+
relative to the e_1 axis, centered at the instantaneous total propellant
305+
center of mass. Recalculated here relative to the instantaneous CoM.
380306
381307
Returns
382308
-------
383309
Function
384-
Propellant inertia tensor 11 component at time t.
385-
386-
Notes
387-
-----
388-
The e_1 direction is assumed to be the direction perpendicular to the
389-
motor body axis.
390-
391-
References
392-
----------
393-
https://en.wikipedia.org/wiki/Moment_of_inertia#Inertia_tensor
310+
Total propellant inertia tensor 11 component at time t relative to total propellant CoM.
394311
"""
395-
I_11 = 0
396-
center_of_mass = self.center_of_propellant_mass
397-
398-
for positioned_tank in self.positioned_tanks:
399-
tank = positioned_tank.get("tank")
400-
tank_position = positioned_tank.get("position")
401-
distance = tank_position + tank.center_of_mass - center_of_mass
402-
I_11 += parallel_axis_theorem_from_com(
403-
tank.inertia, tank.fluid_mass, distance
404-
)
405-
406-
return I_11
312+
return self.propellant_I_11_from_propellant_CM
407313

408314
@funcify_method("Time (s)", "Inertia I_22 (kg m²)")
409315
def propellant_I_22(self):
@@ -497,8 +403,8 @@ def draw(self, *, filename=None):
497403
"""
498404
self.plots.draw(filename=filename)
499405

500-
def to_dict(self, **kwargs):
501-
data = super().to_dict(**kwargs)
406+
def to_dict(self, include_outputs=False):
407+
data = super().to_dict(include_outputs)
502408
data.update(
503409
{
504410
"positioned_tanks": [

0 commit comments

Comments
 (0)