|
1 | 1 | from functools import cached_property |
2 | 2 | import numpy as np |
3 | | -from ..mathutils.function import Function, funcify_method |
4 | | -from .motor import Motor |
| 3 | +import csv |
| 4 | +import math |
| 5 | +from typing import Callable # Import Callable from the typing module |
5 | 6 |
|
6 | | -class PointMassMotor(Motor): |
| 7 | +from rocketpy.mathutils.function import Function, funcify_method |
| 8 | +from .motor import Motor |
| 9 | + |
| 10 | +class PointMassMotor(Motor): |
7 | 11 | """Class representing a motor modeled as a point mass. |
| 12 | +
|
8 | 13 | Inherits from the Motor class and simplifies the model to a thrust-producing |
9 | | - object without detailed structural components.""" |
| 14 | + object without detailed structural components. The total mass of the motor |
| 15 | + will vary with propellant consumption, similar to a standard motor. However, |
| 16 | + its inertia components and the center of propellant mass are considered zero |
| 17 | + and fixed at the motor's reference point, respectively. |
| 18 | + """ |
10 | 19 |
|
11 | 20 | def __init__( |
12 | 21 | self, |
13 | 22 | thrust_source, |
14 | | - dry_mass, |
15 | | - thrust_curve=None, |
16 | | - propellant_initial_mass=None, |
17 | | - propellant_final_mass=None, |
18 | | - burn_time=None, |
19 | | - center_of_dry_mass_position=0, |
20 | | - reshape_thrust_curve=False, |
21 | | - interpolation_method="linear", |
22 | | - coordinate_system_orientation="nozzle_to_combustion_chamber", |
| 23 | + dry_mass: float, |
| 24 | + propellant_initial_mass: float, # Now explicitly required |
| 25 | + burn_time: float = None, |
| 26 | + propellant_final_mass: float = None, |
| 27 | + reshape_thrust_curve: bool = False, |
| 28 | + interpolation_method: str = "linear", |
23 | 29 | ): |
24 | 30 | """Initialize the PointMassMotor class. |
25 | | - |
| 31 | +
|
| 32 | + This motor simplifies the physical model by considering its mass to be |
| 33 | + concentrated at a single point, effectively setting all its inertia |
| 34 | + components to zero. Propellant mass variation and exhaust velocity |
| 35 | + are still simulated and derived from the thrust and propellant consumption |
| 36 | + characteristics, similar to the base Motor class. |
| 37 | +
|
26 | 38 | Parameters |
27 | 39 | ---------- |
28 | | - thrust_source : int, float, callable, string, array, Function |
29 | | - Thrust source similar to the Motor class. |
| 40 | + thrust_source : int, float, callable, str, numpy.ndarray, Function |
| 41 | + Thrust source. Can be a constant value (int, float), a callable |
| 42 | + function of time, a path to a CSV file, a NumPy array, or a |
| 43 | + RocketPy `Function` object. |
30 | 44 | dry_mass : float |
31 | 45 | Total dry mass of the motor in kg. |
32 | | - thrust_curve : Function, np.array, or str (csv file), optional |
33 | | - Required if thrust_source is a csv file, Function, or np.array. |
34 | | - propellant_initial_mass : float, optional |
35 | | - Required if thrust_source is a csv file, Function, or np.array. |
| 46 | + propellant_initial_mass : float |
| 47 | + Initial mass of the propellant in kg. This is a required parameter |
| 48 | + as the point mass motor will still simulate propellant consumption. |
| 49 | + burn_time : float, optional |
| 50 | + Total burn time of the motor in seconds. Required if `thrust_source` |
| 51 | + is a constant value or a callable function, and `propellant_final_mass` |
| 52 | + is not provided. If `thrust_source` is a CSV, array, or Function, |
| 53 | + the burn time is derived from it. |
36 | 54 | propellant_final_mass : float, optional |
37 | | - Required if thrust_source is callable. |
38 | | - burn_time : float or tuple of float, optional |
39 | | - Required if thrust_source is callable or if a thrust value is given. |
40 | | - center_of_dry_mass_position : float, optional |
41 | | - Initial position of the motor, default is 0. |
42 | | - interpolation_method : string, optional |
43 | | - Interpolation method for thrust curve, default is 'linear'. |
| 55 | + Final mass of the propellant in kg. Required if `thrust_source` |
| 56 | + is a callable function and `burn_time` is not provided. If not |
| 57 | + provided, it is calculated by the base Motor class. |
| 58 | + reshape_thrust_curve : bool, optional |
| 59 | + Whether to reshape the thrust curve to start at t=0 and end at |
| 60 | + burn_time. Defaults to False. |
| 61 | + interpolation_method : str, optional |
| 62 | + Interpolation method for the thrust curve, if applicable. |
| 63 | + Defaults to 'linear'. |
| 64 | +
|
| 65 | + Raises |
| 66 | + ------ |
| 67 | + ValueError |
| 68 | + If insufficient data is provided for mass flow rate calculation. |
| 69 | + TypeError |
| 70 | + If an invalid type is provided for `thrust_source`. |
44 | 71 | """ |
45 | | - if isinstance(thrust_source, (Function, np.ndarray, str)): |
46 | | - if thrust_curve is None or propellant_initial_mass is None: |
47 | | - raise ValueError("thrust_curve and propellant_initial_mass are required for csv, Function, or np.array inputs.") |
48 | | - elif callable(thrust_source): |
49 | | - if any(param is None for param in [thrust_curve, propellant_initial_mass, burn_time, propellant_final_mass]): |
50 | | - raise ValueError("thrust_curve, propellant_initial_mass, burn_time, and propellant_final_mass are required for callable inputs.") |
51 | | - elif isinstance(thrust_source, (int, float)): |
52 | | - if any(param is None for param in [thrust_curve, propellant_initial_mass, burn_time]): |
53 | | - raise ValueError("thrust_curve, propellant_initial_mass, and burn_time are required when a thrust value is given.") |
| 72 | + # Input validation: Ensure critical parameters for mass flow are present |
| 73 | + if isinstance(thrust_source, (int, float, Callable)): |
| 74 | + if propellant_initial_mass is None: |
| 75 | + raise ValueError( |
| 76 | + "For constant or callable thrust, 'propellant_initial_mass' is required." |
| 77 | + ) |
| 78 | + if burn_time is None and propellant_final_mass is None: |
| 79 | + raise ValueError( |
| 80 | + "For constant or callable thrust, either 'burn_time' or " |
| 81 | + "'propellant_final_mass' must be provided." |
| 82 | + ) |
54 | 83 |
|
55 | | - self._propellant_initial_mass = propellant_initial_mass |
| 84 | + elif isinstance(thrust_source, (Function, np.ndarray, str)): |
| 85 | + if propellant_initial_mass is None: |
| 86 | + raise ValueError( |
| 87 | + "For thrust from a Function, NumPy array, or CSV, 'propellant_initial_mass' is required." |
| 88 | + ) |
| 89 | + else: |
| 90 | + raise TypeError( |
| 91 | + "Invalid 'thrust_source' type. Must be int, float, callable, str, numpy.ndarray, or Function." |
| 92 | + ) |
| 93 | + |
| 94 | + self.propellant_initial_mass = propellant_initial_mass |
| 95 | + self.propellant_final_mass = propellant_final_mass |
| 96 | + |
| 97 | + # Call the superclass constructor, passing center_of_dry_mass_position=0 |
56 | 98 | super().__init__( |
57 | 99 | thrust_source=thrust_source, |
58 | | - dry_inertia=(0, 0, 0), |
59 | | - nozzle_radius=0, |
60 | | - center_of_dry_mass_position=center_of_dry_mass_position, |
| 100 | + dry_inertia=(0, 0, 0), # Inertia is zero for a point mass |
| 101 | + nozzle_radius=0, # Nozzle radius is irrelevant for a point mass model |
| 102 | + center_of_dry_mass_position=0, # Pass 0 directly to the superclass |
61 | 103 | dry_mass=dry_mass, |
62 | | - nozzle_position=0, |
| 104 | + nozzle_position=0, # Nozzle is at the motor's origin |
63 | 105 | burn_time=burn_time, |
64 | | - reshape_thrust_curve=reshape_thrust_curve, |
| 106 | + reshape_thrust_curve=reshape_thrust_curve, # Pass the user-provided value |
65 | 107 | interpolation_method=interpolation_method, |
66 | | - coordinate_system_orientation=coordinate_system_orientation, |
67 | | - ) |
68 | | - @funcify_method("Time (s)", "Thrust (N)") |
69 | | - def thrust(self): |
70 | | - """Returns the thrust of the motor as a function of time.""" |
71 | | - return self.thrust_source |
72 | | - |
73 | | - @funcify_method("Time (s)", "Acceleration (m/s^2)") |
74 | | - def total_mass(self): |
75 | | - """Returns the constant total mass of the point mass motor.""" |
76 | | - return self.dry_mass |
77 | | - |
78 | | - @funcify_method("Time (s)", "Acceleration (m/s^2)") |
79 | | - def acceleration(self): |
80 | | - """Computes the acceleration of the motor as thrust divided by mass.""" |
81 | | - return self.thrust() / self.total_mass |
| 108 | + coordinate_system_orientation="nozzle_to_combustion_chamber", # Standard orientation |
| 109 | + ) |
82 | 110 |
|
83 | | - @funcify_method("Time (s)", "Propellant Mass (kg)") |
84 | | - def center_of_propellant_mass(self): |
85 | | - return 0 |
| 111 | + # Removed the thrust method. It will now be inherited directly from the Motor base class, |
| 112 | + # which already correctly handles the conversion of thrust_source to a Function |
| 113 | + # and exposes it as a cached property. |
| 114 | + |
| 115 | + # Removed the total_mass override. The base Motor class's total_mass property |
| 116 | + # will now correctly calculate dry_mass + propellant_mass(t), which is the desired |
| 117 | + # varying mass behavior for the point mass motor. |
86 | 118 |
|
87 | | - @funcify_method("Time (s)", "Exhaust Velocity (m/s)") |
88 | | - def exhaust_velocity(self): |
89 | | - return 2000 # m/s, estimated value |
| 119 | + # Removed the center_of_dry_mass_position override. It is now passed directly |
| 120 | + # to the super().__init__ as 0. |
90 | 121 |
|
| 122 | + @cached_property |
91 | 123 | @funcify_method("Time (s)", "Propellant Mass (kg)") |
92 | | - def propellant_initial_mass(self): |
93 | | - return self._propellant_initial_mass |
| 124 | + def center_of_propellant_mass(self) -> Function: |
| 125 | + """Returns the position of the center of mass of the propellant. |
| 126 | +
|
| 127 | + For a point mass motor, the propellant's center of mass is considered |
| 128 | + to be at the origin (0) of the motor's coordinate system. |
94 | 129 |
|
95 | | - @property |
96 | | - def is_point_mass(self): |
97 | | - return True |
98 | | - |
| 130 | + Returns |
| 131 | + ------- |
| 132 | + Function |
| 133 | + A Function object representing the center of propellant mass (always 0). |
| 134 | + """ |
| 135 | + return 0 |
| 136 | + |
| 137 | + # Removed exhaust_velocity override. It will now be inherited from the Motor base class, |
| 138 | + # which calculates it dynamically based on thrust and mass_flow_rate. |
| 139 | + |
| 140 | + @cached_property |
99 | 141 | @funcify_method("Time (s)", "Inertia (kg·m²)") |
100 | | - def propellant_I_11(self): |
101 | | - return 0 |
| 142 | + def propellant_I_11(self) -> Function: |
| 143 | + """Returns the propellant moment of inertia around the x-axis. |
102 | 144 |
|
| 145 | + For a point mass motor, this is always zero. |
| 146 | +
|
| 147 | + Returns |
| 148 | + ------- |
| 149 | + Function |
| 150 | + A Function object representing zero propellant inertia. |
| 151 | + """ |
| 152 | + return 0 |
| 153 | + |
| 154 | + @cached_property |
103 | 155 | @funcify_method("Time (s)", "Inertia (kg·m²)") |
104 | | - def propellant_I_12(self): |
| 156 | + def propellant_I_12(self) -> Function: |
| 157 | + """Returns the propellant product of inertia I_xy. |
| 158 | +
|
| 159 | + For a point mass motor, this is always zero. |
| 160 | +
|
| 161 | + Returns |
| 162 | + ------- |
| 163 | + Function |
| 164 | + A Function object representing zero propellant inertia. |
| 165 | + """ |
105 | 166 | return 0 |
106 | 167 |
|
| 168 | + @cached_property |
107 | 169 | @funcify_method("Time (s)", "Inertia (kg·m²)") |
108 | | - def propellant_I_13(self): |
| 170 | + def propellant_I_13(self) -> Function: |
| 171 | + """Returns the propellant product of inertia I_xz. |
| 172 | +
|
| 173 | + For a point mass motor, this is always zero. |
| 174 | +
|
| 175 | + Returns |
| 176 | + ------- |
| 177 | + Function |
| 178 | + A Function object representing zero propellant inertia. |
| 179 | + """ |
109 | 180 | return 0 |
110 | 181 |
|
| 182 | + @cached_property |
111 | 183 | @funcify_method("Time (s)", "Inertia (kg·m²)") |
112 | | - def propellant_I_22(self): |
| 184 | + def propellant_I_22(self) -> Function: |
| 185 | + """Returns the propellant moment of inertia around the y-axis. |
| 186 | +
|
| 187 | + For a point mass motor, this is always zero. |
| 188 | +
|
| 189 | + Returns |
| 190 | + ------- |
| 191 | + Function |
| 192 | + A Function object representing zero propellant inertia. |
| 193 | + """ |
113 | 194 | return 0 |
114 | 195 |
|
| 196 | + @cached_property |
115 | 197 | @funcify_method("Time (s)", "Inertia (kg·m²)") |
116 | | - def propellant_I_23(self): |
| 198 | + def propellant_I_23(self) -> Function: |
| 199 | + """Returns the propellant product of inertia I_yz. |
| 200 | +
|
| 201 | + For a point mass motor, this is always zero. |
| 202 | +
|
| 203 | + Returns |
| 204 | + ------- |
| 205 | + Function |
| 206 | + A Function object representing zero propellant inertia. |
| 207 | + """ |
117 | 208 | return 0 |
118 | 209 |
|
| 210 | + @cached_property |
119 | 211 | @funcify_method("Time (s)", "Inertia (kg·m²)") |
120 | | - def propellant_I_33(self): |
| 212 | + def propellant_I_33(self) -> Function: |
| 213 | + """Returns the propellant moment of inertia around the z-axis. |
| 214 | +
|
| 215 | + For a point mass motor, this is always zero. |
| 216 | +
|
| 217 | + Returns |
| 218 | + ------- |
| 219 | + Function |
| 220 | + A Function object representing zero propellant inertia. |
| 221 | + """ |
121 | 222 | return 0 |
0 commit comments