-
-
Notifications
You must be signed in to change notification settings - Fork 194
ENH: Implementing 3-dof-simulation #745
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
e557e17
a768648
9b00c57
87e9180
4524219
57b4732
81ce869
0673076
47c9f2f
f6ad658
33cb63a
1d2d4dc
fe21271
b18b241
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -37,6 +37,8 @@ | |
Parachute, | ||
RailButtons, | ||
Rocket, | ||
BaseRocket, | ||
PointMassRocket, | ||
Tail, | ||
TrapezoidalFins, | ||
) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,222 @@ | ||
from functools import cached_property | ||
import numpy as np | ||
import csv | ||
import math | ||
from typing import Callable # Import Callable from the typing module | ||
|
||
from rocketpy.mathutils.function import Function, funcify_method | ||
from .motor import Motor | ||
|
||
class PointMassMotor(Motor): | ||
"""Class representing a motor modeled as a point mass. | ||
|
||
Inherits from the Motor class and simplifies the model to a thrust-producing | ||
object without detailed structural components. The total mass of the motor | ||
will vary with propellant consumption, similar to a standard motor. However, | ||
its inertia components and the center of propellant mass are considered zero | ||
and fixed at the motor's reference point, respectively. | ||
""" | ||
|
||
def __init__( | ||
self, | ||
thrust_source, | ||
dry_mass: float, | ||
propellant_initial_mass: float, # Now explicitly required | ||
burn_time: float = None, | ||
propellant_final_mass: float = None, | ||
reshape_thrust_curve: bool = False, | ||
interpolation_method: str = "linear", | ||
): | ||
"""Initialize the PointMassMotor class. | ||
|
||
This motor simplifies the physical model by considering its mass to be | ||
concentrated at a single point, effectively setting all its inertia | ||
components to zero. Propellant mass variation and exhaust velocity | ||
are still simulated and derived from the thrust and propellant consumption | ||
characteristics, similar to the base Motor class. | ||
|
||
Parameters | ||
---------- | ||
thrust_source : int, float, callable, str, numpy.ndarray, Function | ||
Thrust source. Can be a constant value (int, float), a callable | ||
function of time, a path to a CSV file, a NumPy array, or a | ||
RocketPy `Function` object. | ||
dry_mass : float | ||
Total dry mass of the motor in kg. | ||
propellant_initial_mass : float | ||
Initial mass of the propellant in kg. This is a required parameter | ||
as the point mass motor will still simulate propellant consumption. | ||
burn_time : float, optional | ||
Total burn time of the motor in seconds. Required if `thrust_source` | ||
is a constant value or a callable function, and `propellant_final_mass` | ||
is not provided. If `thrust_source` is a CSV, array, or Function, | ||
the burn time is derived from it. | ||
propellant_final_mass : float, optional | ||
Final mass of the propellant in kg. Required if `thrust_source` | ||
is a callable function and `burn_time` is not provided. If not | ||
provided, it is calculated by the base Motor class. | ||
reshape_thrust_curve : bool, optional | ||
Whether to reshape the thrust curve to start at t=0 and end at | ||
burn_time. Defaults to False. | ||
interpolation_method : str, optional | ||
Interpolation method for the thrust curve, if applicable. | ||
Defaults to 'linear'. | ||
|
||
Raises | ||
------ | ||
ValueError | ||
If insufficient data is provided for mass flow rate calculation. | ||
TypeError | ||
If an invalid type is provided for `thrust_source`. | ||
""" | ||
# Input validation: Ensure critical parameters for mass flow are present | ||
if isinstance(thrust_source, (int, float, Callable)): | ||
if propellant_initial_mass is None: | ||
raise ValueError( | ||
"For constant or callable thrust, 'propellant_initial_mass' is required." | ||
) | ||
if burn_time is None and propellant_final_mass is None: | ||
raise ValueError( | ||
"For constant or callable thrust, either 'burn_time' or " | ||
"'propellant_final_mass' must be provided." | ||
) | ||
|
||
elif isinstance(thrust_source, (Function, np.ndarray, str)): | ||
if propellant_initial_mass is None: | ||
raise ValueError( | ||
"For thrust from a Function, NumPy array, or CSV, 'propellant_initial_mass' is required." | ||
) | ||
else: | ||
raise TypeError( | ||
"Invalid 'thrust_source' type. Must be int, float, callable, str, numpy.ndarray, or Function." | ||
) | ||
|
||
self.propellant_initial_mass = propellant_initial_mass | ||
self.propellant_final_mass = propellant_final_mass | ||
|
||
# Call the superclass constructor, passing center_of_dry_mass_position=0 | ||
super().__init__( | ||
thrust_source=thrust_source, | ||
dry_inertia=(0, 0, 0), # Inertia is zero for a point mass | ||
nozzle_radius=0, # Nozzle radius is irrelevant for a point mass model | ||
center_of_dry_mass_position=0, # Pass 0 directly to the superclass | ||
dry_mass=dry_mass, | ||
nozzle_position=0, # Nozzle is at the motor's origin | ||
burn_time=burn_time, | ||
reshape_thrust_curve=reshape_thrust_curve, # Pass the user-provided value | ||
interpolation_method=interpolation_method, | ||
coordinate_system_orientation="nozzle_to_combustion_chamber", # Standard orientation | ||
) | ||
|
||
# Removed the thrust method. It will now be inherited directly from the Motor base class, | ||
# which already correctly handles the conversion of thrust_source to a Function | ||
# and exposes it as a cached property. | ||
|
||
# Removed the total_mass override. The base Motor class's total_mass property | ||
# will now correctly calculate dry_mass + propellant_mass(t), which is the desired | ||
# varying mass behavior for the point mass motor. | ||
|
||
# Removed the center_of_dry_mass_position override. It is now passed directly | ||
# to the super().__init__ as 0. | ||
|
||
@cached_property | ||
@funcify_method("Time (s)", "Propellant Mass (kg)") | ||
def center_of_propellant_mass(self) -> Function: | ||
"""Returns the position of the center of mass of the propellant. | ||
|
||
For a point mass motor, the propellant's center of mass is considered | ||
to be at the origin (0) of the motor's coordinate system. | ||
|
||
Returns | ||
------- | ||
Function | ||
A Function object representing the center of propellant mass (always 0). | ||
""" | ||
return 0 | ||
|
||
# Removed exhaust_velocity override. It will now be inherited from the Motor base class, | ||
# which calculates it dynamically based on thrust and mass_flow_rate. | ||
|
||
@cached_property | ||
@funcify_method("Time (s)", "Inertia (kg·m²)") | ||
def propellant_I_11(self) -> Function: | ||
"""Returns the propellant moment of inertia around the x-axis. | ||
|
||
For a point mass motor, this is always zero. | ||
|
||
Returns | ||
------- | ||
Function | ||
A Function object representing zero propellant inertia. | ||
""" | ||
return 0 | ||
|
||
@cached_property | ||
@funcify_method("Time (s)", "Inertia (kg·m²)") | ||
def propellant_I_12(self) -> Function: | ||
"""Returns the propellant product of inertia I_xy. | ||
|
||
For a point mass motor, this is always zero. | ||
|
||
Returns | ||
------- | ||
Function | ||
A Function object representing zero propellant inertia. | ||
""" | ||
return 0 | ||
|
||
@cached_property | ||
@funcify_method("Time (s)", "Inertia (kg·m²)") | ||
def propellant_I_13(self) -> Function: | ||
"""Returns the propellant product of inertia I_xz. | ||
|
||
For a point mass motor, this is always zero. | ||
|
||
Returns | ||
------- | ||
Function | ||
A Function object representing zero propellant inertia. | ||
""" | ||
return 0 | ||
|
||
@cached_property | ||
@funcify_method("Time (s)", "Inertia (kg·m²)") | ||
def propellant_I_22(self) -> Function: | ||
"""Returns the propellant moment of inertia around the y-axis. | ||
|
||
For a point mass motor, this is always zero. | ||
|
||
Returns | ||
------- | ||
Function | ||
A Function object representing zero propellant inertia. | ||
""" | ||
return 0 | ||
|
||
@cached_property | ||
@funcify_method("Time (s)", "Inertia (kg·m²)") | ||
def propellant_I_23(self) -> Function: | ||
"""Returns the propellant product of inertia I_yz. | ||
|
||
For a point mass motor, this is always zero. | ||
|
||
Returns | ||
------- | ||
Function | ||
A Function object representing zero propellant inertia. | ||
""" | ||
return 0 | ||
|
||
@cached_property | ||
@funcify_method("Time (s)", "Inertia (kg·m²)") | ||
def propellant_I_33(self) -> Function: | ||
"""Returns the propellant moment of inertia around the z-axis. | ||
|
||
For a point mass motor, this is always zero. | ||
|
||
Returns | ||
------- | ||
Function | ||
A Function object representing zero propellant inertia. | ||
""" | ||
return 0 |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,7 +24,88 @@ | |
from rocketpy.rocket.parachute import Parachute | ||
from rocketpy.tools import parallel_axis_theorem_from_com | ||
|
||
class BaseRocket: | ||
"""Base class for a rocket model with minimal attributes and methods.""" | ||
def __init__(self, mass): | ||
self.mass = mass | ||
self.motor = None | ||
def add_motor(self, motor): | ||
self.motor = motor | ||
self.evaluate_total_mass() | ||
def evaluate_total_mass(self): | ||
if self.motor: | ||
return self.mass + self.motor.total_mass | ||
return self.mass | ||
|
||
|
||
Comment on lines
+27
to
+40
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This class seems useless right now. You can just remove it |
||
class PointMassRocket(BaseRocket): | ||
"""Rocket modeled as a point mass for 3-DOF simulations.""" | ||
Comment on lines
+41
to
+42
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe having this inherit from the Rocket class will make the implementation easier. Then all_info and plots and prints will already be defined |
||
|
||
def __init__(self, mass, drag_coefficient=0.4, radius=0.05): | ||
super().__init__(mass) | ||
|
||
# Basic configuration | ||
self.drag_coefficient = drag_coefficient | ||
self.radius = radius # in meters | ||
self.area = math.pi * self.radius**2 | ||
|
||
# Coordinate system configuration | ||
self.coordinate_system_orientation = "tail_to_nose" | ||
self.center_of_dry_mass_position = 0 # arbitrary for point mass | ||
self.dry_mass = mass # dry_mass = structure + dry motor, here it's same as base mass | ||
self.nozzle_position = 0 # or another reference point like -1 * length/2 | ||
|
||
# Components | ||
self.parachutes = [] | ||
self._controllers = [] | ||
self.air_brakes = [] | ||
self.sensors = Components() | ||
self.aerodynamic_surfaces = Components() | ||
self.rail_buttons = Components() | ||
self.surfaces_cp_to_cdm = {} | ||
|
||
# Drag models | ||
self.power_on_drag = Function( | ||
drag_coefficient, | ||
"Mach Number", | ||
"Power-On Drag Coefficient", | ||
interpolation="constant" | ||
) | ||
self.power_off_drag = Function( | ||
drag_coefficient * 1.2, | ||
"Mach Number", | ||
"Power-Off Drag Coefficient", | ||
interpolation="constant" | ||
) | ||
def add_parachute( | ||
self, name, cd_s, trigger, sampling_rate=100, lag=0, noise=(0, 0, 0) | ||
): | ||
parachute = Parachute(name, cd_s, trigger, sampling_rate, lag, noise) | ||
self.parachutes.append(parachute) | ||
self.total_mass = None | ||
self.evaluate_total_mass() | ||
return self.parachutes[-1] | ||
|
||
def evaluate_total_mass(self): | ||
"""Returns Function of total mass (dry + motor).""" | ||
if self.motor is None: | ||
print("Please associate this rocket with a motor!") | ||
return False | ||
|
||
motor_mass_func = ( | ||
self.motor.total_mass if hasattr(self.motor.total_mass, "get_value_opt") | ||
else Function(lambda t: self.motor.total_mass) | ||
) | ||
Comment on lines
+95
to
+98
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This check seems to be in place in order to convert to a |
||
|
||
self.total_mass = Function( | ||
lambda t: self.mass + motor_mass_func(t), | ||
inputs="Time (s)", | ||
outputs="Total Mass (Rocket + Motor + Propellant) (kg)", | ||
title="Total Mass (Rocket + Motor + Propellant)" | ||
) | ||
return self.total_mass | ||
|
||
|
||
# pylint: disable=too-many-instance-attributes, too-many-public-methods, too-many-instance-attributes | ||
class Rocket: | ||
"""Keeps rocket information. | ||
|
Uh oh!
There was an error while loading. Please reload this page.