From 9fe3a37c93069156ac45a6f241f5935832ddb21d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Alca=C3=B1iz?= Date: Fri, 14 Mar 2025 18:46:09 +0100 Subject: [PATCH 01/16] wind factor bug corrected the wind factor wasn't applied to the env.wind_velocity properties --- rocketpy/stochastic/stochastic_environment.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rocketpy/stochastic/stochastic_environment.py b/rocketpy/stochastic/stochastic_environment.py index 58afe0fed..e0fc33eec 100644 --- a/rocketpy/stochastic/stochastic_environment.py +++ b/rocketpy/stochastic/stochastic_environment.py @@ -184,5 +184,6 @@ def create_object(self): # get original attribute value and multiply by factor attribute_name = f"_{key.replace('_factor', '')}" value = getattr(self, attribute_name) * value + key = f"{key.replace('_factor', '')}" setattr(self.obj, key, value) return self.obj From f6efa816442a740279ebaf880458049d28799b3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Alca=C3=B1iz?= Date: Sat, 15 Mar 2025 11:46:06 +0100 Subject: [PATCH 02/16] BUG: StochasticModel visualize attributes of a uniform distribution It showed the nominal and the standard deviation values and it doesn't make sense in a uniform distribution. In a np.random.uniform the 'nominal value' is the lower bound of the distribution, and the 'standard deviation' value is the upper bound. Now, a new condition has been added for the uniform distributions where the mean and semi range are calculated and showed. This way the visualize_attribute function will show the whole range where the random values are uniformly taken in --- rocketpy/stochastic/stochastic_model.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/rocketpy/stochastic/stochastic_model.py b/rocketpy/stochastic/stochastic_model.py index c82232fcb..e44e2613d 100644 --- a/rocketpy/stochastic/stochastic_model.py +++ b/rocketpy/stochastic/stochastic_model.py @@ -486,11 +486,20 @@ def format_attribute(attr, value): ) elif isinstance(value, tuple): nominal_value, std_dev, dist_func = value - return ( - f"\t{attr.ljust(max_str_length)} " - f"{nominal_value:.5f} ± " - f"{std_dev:.5f} ({dist_func.__name__})" - ) + if dist_func == np.random.uniform: + mean = (std_dev + nominal_value) / 2 + semi_range = (std_dev - nominal_value) / 2 + return ( + f"\t{attr.ljust(max_str_length)} " + f"{mean:.5f} ± " + f"{semi_range:.5f} ({dist_func.__name__})" + ) + else: + return ( + f"\t{attr.ljust(max_str_length)} " + f"{nominal_value:.5f} ± " + f"{std_dev:.5f} ({dist_func.__name__})" + ) return None attributes = {k: v for k, v in self.__dict__.items() if not k.startswith("_")} From 04337ab79982f28196d4b3950d36407cac6752b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Alca=C3=B1iz?= Date: Sat, 15 Mar 2025 16:11:27 +0100 Subject: [PATCH 03/16] variable names corrections --- rocketpy/stochastic/stochastic_model.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rocketpy/stochastic/stochastic_model.py b/rocketpy/stochastic/stochastic_model.py index e44e2613d..e833e01bf 100644 --- a/rocketpy/stochastic/stochastic_model.py +++ b/rocketpy/stochastic/stochastic_model.py @@ -488,11 +488,11 @@ def format_attribute(attr, value): nominal_value, std_dev, dist_func = value if dist_func == np.random.uniform: mean = (std_dev + nominal_value) / 2 - semi_range = (std_dev - nominal_value) / 2 + half_range = (std_dev - nominal_value) / 2 return ( f"\t{attr.ljust(max_str_length)} " f"{mean:.5f} ± " - f"{semi_range:.5f} ({dist_func.__name__})" + f"{half_range:.5f} ({dist_func.__name__})" ) else: return ( From 18f8323f39798ae90caada573660dcf63b113769 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Alca=C3=B1iz?= Date: Sat, 15 Mar 2025 17:46:04 +0100 Subject: [PATCH 04/16] Corrections requested by the pylint test --- rocketpy/stochastic/stochastic_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rocketpy/stochastic/stochastic_model.py b/rocketpy/stochastic/stochastic_model.py index e833e01bf..39fc478b7 100644 --- a/rocketpy/stochastic/stochastic_model.py +++ b/rocketpy/stochastic/stochastic_model.py @@ -486,7 +486,7 @@ def format_attribute(attr, value): ) elif isinstance(value, tuple): nominal_value, std_dev, dist_func = value - if dist_func == np.random.uniform: + if callable(dist_func) and dist_func.__name__ == "uniform": mean = (std_dev + nominal_value) / 2 half_range = (std_dev - nominal_value) / 2 return ( From 589f50d5694a83f23eb0e3ffff652b186686c7c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Alca=C3=B1iz?= Date: Wed, 19 Mar 2025 20:04:16 +0100 Subject: [PATCH 05/16] ENH: Add pressure corrections for thrust in SolidMotor The thrust generated by a SolidMotor is now adjusted for the atmospheric pressure. To achieve that, a new attribute, 'vacuum_thrust', has been created. The 'net_thrust' is the result of 'vacuum_thrust' minus the atmospheric pressure multiplied by the nozzle area. --- rocketpy/motors/motor.py | 61 ++++++++++++++++++++++++++++--- rocketpy/motors/solid_motor.py | 15 +++++++- rocketpy/simulation/flight.py | 67 +++++++++++++++++++++++++--------- 3 files changed, 119 insertions(+), 24 deletions(-) diff --git a/rocketpy/motors/motor.py b/rocketpy/motors/motor.py index 5f4b0dc0b..27cb8885d 100644 --- a/rocketpy/motors/motor.py +++ b/rocketpy/motors/motor.py @@ -28,6 +28,8 @@ class Motor(ABC): "combustion_chamber_to_nozzle". Motor.nozzle_radius : float Radius of motor nozzle outlet in meters. + Motor.nozzle_area : float + Area of motor nozzle outlet in square meters. Motor.nozzle_position : float Motor's nozzle outlet position in meters, specified in the motor's coordinate system. See :ref:`positions` for more information. @@ -121,7 +123,11 @@ class Motor(ABC): e_3 axes in kg*m^2, as a function of time. See Motor.propellant_I_22 and Motor.propellant_I_33 for more information. Motor.thrust : Function - Motor thrust force, in Newtons, as a function of time. + Motor thrust force obtained from the thrust source, in Newtons, as a + function of time. + Motor.vacuum_thrust : Function + Motor thrust force when the rocket is in a vacuum. In Newtons, as a + function of time. Motor.total_impulse : float Total impulse of the thrust curve in N*s. Motor.max_thrust : float @@ -146,6 +152,9 @@ class Motor(ABC): Method of interpolation used in case thrust curve is given by data set in .csv or .eng, or as an array. Options are 'spline' 'akima' and 'linear'. Default is "linear". + Motor.reference_pressure : int, float + Atmospheric pressure in Pa at which the thrust data was recorded. + It will allow to obtain the net thrust in the Flight class. """ # pylint: disable=too-many-statements @@ -155,6 +164,7 @@ def __init__( dry_inertia, nozzle_radius, center_of_dry_mass_position, + reference_pressure=None, dry_mass=None, nozzle_position=0, burn_time=None, @@ -199,6 +209,8 @@ class Function. Thrust units are Newtons. (I_11, I_22, I_33), where I_12 = I_13 = I_23 = 0. nozzle_radius : int, float, optional Motor's nozzle outlet radius in meters. + reference_pressure : int, float, optional + Atmospheric pressure in Pa at which the thrust data was recorded. burn_time: float, tuple of float, optional Motor's burn time. If a float is given, the burn time is assumed to be between 0 and @@ -257,7 +269,9 @@ class Function. Thrust units are Newtons. self.interpolate = interpolation_method self.nozzle_position = nozzle_position self.nozzle_radius = nozzle_radius + self.nozzle_area = np.pi * nozzle_radius**2 self.center_of_dry_mass_position = center_of_dry_mass_position + self.reference_pressure = reference_pressure # Inertia tensor setup inertia = (*dry_inertia, 0, 0, 0) if len(dry_inertia) == 3 else dry_inertia @@ -301,16 +315,22 @@ class Function. Thrust units are Newtons. # Post process thrust self.thrust = Motor.clip_thrust(self.thrust, self.burn_time) + + # Evaluate vacuum thrust + if reference_pressure is None: + vacuum_thrust_source = self.thrust.source + else: + vacuum_thrust_source = self.get_vacuum_thrust(self.thrust.source,reference_pressure) + self.vacuum_thrust_source = vacuum_thrust_source + self.vacuum_thrust = Function( + vacuum_thrust_source, "Time (s)", "Vacuum Thrust (N)", self.interpolate, "zero" + ) # Auxiliary quantities self.burn_start_time = self.burn_time[0] self.burn_out_time = self.burn_time[1] self.burn_duration = self.burn_time[1] - self.burn_time[0] - # Define motor attributes - self.nozzle_radius = nozzle_radius - self.nozzle_position = nozzle_position - # Compute thrust metrics self.max_thrust = np.amax(self.thrust.y_array) max_thrust_index = np.argmax(self.thrust.y_array) @@ -1038,6 +1058,34 @@ def import_eng(file_name): # Return all extract content return comments, description, data_points + + def get_vacuum_thrust(self,thrust_source,reference_pressure): + """Calculate the vacuum thrust from the raw thrust and the reference + pressure at which the thrust data was recorded. + + Parameters + ---------- + thrust_source : list + A list of points representing the thrust curve. + reference_pressure : int, float + The atmospheric pressure at which the thrust data was recorded. + + Returns + ------- + vacuum_thrust_source : list + A list of points representing the vacuum thrust curve. + """ + # Initialize arrays + vacuum_thrust_source = [] + + # Reduction in thrust due to atmospheric pressure + thrust_reduction = reference_pressure * self.nozzle_area + for point in thrust_source: + time = point[0] + thrust = point[1] + vacuum_thrust_source.append([time, thrust + thrust_reduction]) + + return vacuum_thrust_source def export_eng(self, file_name, motor_name): """Exports thrust curve data points and motor description to @@ -1105,6 +1153,8 @@ def to_dict(self, include_outputs=False): "dry_I_13": self.dry_I_13, "dry_I_23": self.dry_I_23, "nozzle_radius": self.nozzle_radius, + "nozzle_area": self.nozzle_area, + "reference_pressure": self.reference_pressure, "center_of_dry_mass_position": self.center_of_dry_mass_position, "dry_mass": self.dry_mass, "nozzle_position": self.nozzle_position, @@ -1116,6 +1166,7 @@ def to_dict(self, include_outputs=False): if include_outputs: data.update( { + "vacuum_thrust": self.vacuum_thrust, "total_mass": self.total_mass, "propellant_mass": self.propellant_mass, "mass_flow_rate": self.mass_flow_rate, diff --git a/rocketpy/motors/solid_motor.py b/rocketpy/motors/solid_motor.py index 37d89130e..d194bc4c8 100644 --- a/rocketpy/motors/solid_motor.py +++ b/rocketpy/motors/solid_motor.py @@ -29,6 +29,8 @@ class SolidMotor(Motor): "combustion_chamber_to_nozzle". SolidMotor.nozzle_radius : float Radius of motor nozzle outlet in meters. + SolidMotor.nozzle_area : float + Area of motor nozzle outlet in square meters. SolidMotor.nozzle_position : float Motor's nozzle outlet position in meters, specified in the motor's coordinate system. See @@ -147,7 +149,11 @@ class SolidMotor(Motor): See SolidMotor.propellant_I_22 and SolidMotor.propellant_I_33 for more information. SolidMotor.thrust : Function - Motor thrust force, in Newtons, as a function of time. + Motor thrust force obtained from thrust source, in Newtons, as a + function of time. + SolidMotor.vacuum_thrust : Function + Motor thrust force when the rocket is in a vacuum. In Newtons, as a + function of time. SolidMotor.total_impulse : float Total impulse of the thrust curve in N*s. SolidMotor.max_thrust : float @@ -181,6 +187,9 @@ class SolidMotor(Motor): Method of interpolation used in case thrust curve is given by data set in .csv or .eng, or as an array. Options are 'spline' 'akima' and 'linear'. Default is "linear". + Solidmotor.reference_pressure : int, float + Atmospheric pressure in Pa at which the thrust data was recorded. + It will allow to obtain the net thrust in the Flight class. """ # pylint: disable=too-many-arguments @@ -198,6 +207,7 @@ def __init__( grain_separation, grains_center_of_mass_position, center_of_dry_mass_position, + reference_pressure=None, nozzle_position=0.0, burn_time=None, throat_radius=0.01, @@ -260,6 +270,8 @@ class Function. Thrust units are Newtons. The position, in meters, of the motor's center of mass with respect to the motor's coordinate system when it is devoid of propellant. See :doc:`Positions and Coordinate Systems `. + reference_pressure : int, float, optional + Atmospheric pressure in Pa at which the thrust data was recorded. nozzle_position : int, float, optional Motor's nozzle outlet position in meters, in the motor's coordinate system. See :doc:`Positions and Coordinate Systems ` @@ -309,6 +321,7 @@ class Function. Thrust units are Newtons. dry_inertia=dry_inertia, nozzle_radius=nozzle_radius, center_of_dry_mass_position=center_of_dry_mass_position, + reference_pressure=reference_pressure, dry_mass=dry_mass, nozzle_position=nozzle_position, burn_time=burn_time, diff --git a/rocketpy/simulation/flight.py b/rocketpy/simulation/flight.py index 035323533..ee50fb75c 100644 --- a/rocketpy/simulation/flight.py +++ b/rocketpy/simulation/flight.py @@ -358,6 +358,11 @@ class Flight: as array. Direction 3 is in the rocket's body axis and points in the direction of cylindrical symmetry. + Flight.net_thrust : Function + Rocket's engine net thrust as a function of time in Newton. + This is the actual thrust force experienced by the rocket. + It may be corrected with the atmospheric pressure if a reference + pressure is defined. Can be called or accessed as array. Flight.aerodynamic_lift : Function Resultant force perpendicular to rockets axis due to aerodynamic effects as a function of time. Units in N. @@ -1376,12 +1381,19 @@ def udot_rail1(self, t, u, post_processing=False): drag_coeff = self.rocket.power_on_drag.get_value_opt(free_stream_mach) # Calculate Forces - thrust = self.rocket.motor.thrust.get_value_opt(t) + pressure = self.env.pressure.get_value_opt(z) + nozzle_area = self.rocket.motor.nozzle_area + if self.rocket.motor.reference_pressure is None: + net_thrust = self.rocket.motor.thrust.get_value_opt(t) + else: + net_thrust = self.rocket.motor.vacuum_thrust.get_value_opt(t) - pressure * nozzle_area + if net_thrust < 0: + net_thrust = 0 rho = self.env.density.get_value_opt(z) R3 = -0.5 * rho * (free_stream_speed**2) * self.rocket.area * (drag_coeff) # Calculate Linear acceleration - a3 = (R3 + thrust) / total_mass_at_t - ( + a3 = (R3 + net_thrust) / total_mass_at_t - ( e0**2 - e1**2 - e2**2 + e3**2 ) * self.env.gravity.get_value_opt(z) if a3 > 0: @@ -1450,6 +1462,9 @@ def u_dot(self, t, u, post_processing=False): # pylint: disable=too-many-locals _, _, z, vx, vy, vz, e0, e1, e2, e3, omega1, omega2, omega3 = u # Determine lift force and moment R1, R2, M1, M2, M3 = 0, 0, 0, 0, 0 + # Thrust correction parameters + pressure = self.env.pressure.get_value_opt(z) + nozzle_area = self.rocket.motor.nozzle_area # Determine current behavior if self.rocket.motor.burn_start_time < t < self.rocket.motor.burn_out_time: # Motor burning @@ -1467,10 +1482,15 @@ def u_dot(self, t, u, post_processing=False): # pylint: disable=too-many-locals mass_flow_rate_at_t = self.rocket.motor.mass_flow_rate.get_value_opt(t) propellant_mass_at_t = self.rocket.motor.propellant_mass.get_value_opt(t) # Thrust - thrust = self.rocket.motor.thrust.get_value_opt(t) + if self.rocket.motor.reference_pressure is None: + net_thrust = self.rocket.motor.thrust.get_value_opt(t) + else: + net_thrust = self.rocket.motor.vacuum_thrust.get_value_opt(t) - pressure * nozzle_area + if net_thrust < 0: + net_thrust = 0 # Off center moment - M1 += self.rocket.thrust_eccentricity_x * thrust - M2 -= self.rocket.thrust_eccentricity_y * thrust + M1 += self.rocket.thrust_eccentricity_x * net_thrust + M2 -= self.rocket.thrust_eccentricity_y * net_thrust else: # Motor stopped # Inertias @@ -1483,7 +1503,7 @@ def u_dot(self, t, u, post_processing=False): # pylint: disable=too-many-locals # Mass mass_flow_rate_at_t, propellant_mass_at_t = 0, 0 # thrust - thrust = 0 + net_thrust = 0 # Retrieve important quantities # Inertias @@ -1686,7 +1706,7 @@ def u_dot(self, t, u, post_processing=False): # pylint: disable=too-many-locals + 2 * c * mass_flow_rate_at_t * omega1 ) / total_mass_at_t, - (R3 - b * propellant_mass_at_t * (alpha2 - omega1 * omega3) + thrust) + (R3 - b * propellant_mass_at_t * (alpha2 - omega1 * omega3) + net_thrust) / total_mass_at_t, ] ax, ay, az = K @ Vector(L) @@ -1711,7 +1731,7 @@ def u_dot(self, t, u, post_processing=False): # pylint: disable=too-many-locals if post_processing: self.__post_processed_variables.append( - [t, ax, ay, az, alpha1, alpha2, alpha3, R1, R2, R3, M1, M2, M3] + [t, ax, ay, az, alpha1, alpha2, alpha3, R1, R2, R3, M1, M2, M3, net_thrust] ) return u_dot @@ -1853,14 +1873,21 @@ def u_dot_generalized(self, t, u, post_processing=False): # pylint: disable=too M3 += L # Off center moment - thrust = self.rocket.motor.thrust.get_value_opt(t) + pressure = self.env.pressure.get_value_opt(z) + nozzle_area = self.rocket.motor.nozzle_area + if self.rocket.motor.reference_pressure is None: + net_thrust = self.rocket.motor.thrust.get_value_opt(t) + else: + net_thrust = self.rocket.motor.vacuum_thrust.get_value_opt(t) - pressure * nozzle_area + if net_thrust < 0: + net_thrust = 0 M1 += ( self.rocket.cp_eccentricity_y * R3 - + self.rocket.thrust_eccentricity_x * thrust + + self.rocket.thrust_eccentricity_x * net_thrust ) M2 -= ( self.rocket.cp_eccentricity_x * R3 - - self.rocket.thrust_eccentricity_y * thrust + - self.rocket.thrust_eccentricity_y * net_thrust ) M3 += self.rocket.cp_eccentricity_x * R2 - self.rocket.cp_eccentricity_y * R1 @@ -1871,7 +1898,7 @@ def u_dot_generalized(self, t, u, post_processing=False): # pylint: disable=too T00 = total_mass * r_CM T03 = 2 * total_mass_dot * (r_NOZ - r_CM) - 2 * total_mass * r_CM_dot T04 = ( - Vector([0, 0, thrust]) + Vector([0, 0, net_thrust]) - total_mass * r_CM_ddot - 2 * total_mass_dot * r_CM_dot + total_mass_ddot * (r_NOZ - r_CM) @@ -1915,7 +1942,7 @@ def u_dot_generalized(self, t, u, post_processing=False): # pylint: disable=too if post_processing: self.__post_processed_variables.append( - [t, *v_dot, *w_dot, R1, R2, R3, M1, M2, M3] + [t, *v_dot, *w_dot, R1, R2, R3, M1, M2, M3, net_thrust] ) return u_dot @@ -2207,6 +2234,13 @@ def M3(self): """Aerodynamic moment in the rocket z-axis as a Function of time. Sometimes referred to as roll moment.""" return self.__evaluate_post_process[:, [0, 12]] + + @funcify_method("Time (s)", "Net Thrust (N)", "linear", "zero") + def net_thrust(self): + """Net thrust of the rocket as a Function of time. This is the + actual thrust force experienced by the rocket. It may be corrected + with the atmospheric pressure if a reference pressure is defined.""" + return self.__evaluate_post_process[:, [0, 13]] @funcify_method("Time (s)", "Pressure (Pa)", "spline", "constant") def pressure(self): @@ -2647,13 +2681,10 @@ def total_energy(self): return self.kinetic_energy + self.potential_energy # thrust Power - @funcify_method("Time (s)", "thrust Power (W)", "spline", "zero") + @funcify_method("Time (s)", "Thrust Power (W)", "spline", "zero") def thrust_power(self): """Thrust power as a Function of time.""" - thrust = deepcopy(self.rocket.motor.thrust) - thrust = thrust.set_discrete_based_on_model(self.speed) - thrust_power = thrust * self.speed - return thrust_power + return self.net_thrust * self.speed # Drag Power @funcify_method("Time (s)", "Drag Power (W)", "spline", "zero") From a0dac7df17277b1e1be5509386c2a271b4dce07a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Alca=C3=B1iz?= Date: Wed, 19 Mar 2025 21:44:44 +0100 Subject: [PATCH 06/16] ENH: pylint recommendations done --- rocketpy/motors/motor.py | 6 +++--- rocketpy/motors/solid_motor.py | 2 +- rocketpy/simulation/flight.py | 13 +++++-------- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/rocketpy/motors/motor.py b/rocketpy/motors/motor.py index 27cb8885d..176e50ef2 100644 --- a/rocketpy/motors/motor.py +++ b/rocketpy/motors/motor.py @@ -315,7 +315,7 @@ class Function. Thrust units are Newtons. # Post process thrust self.thrust = Motor.clip_thrust(self.thrust, self.burn_time) - + # Evaluate vacuum thrust if reference_pressure is None: vacuum_thrust_source = self.thrust.source @@ -1058,7 +1058,7 @@ def import_eng(file_name): # Return all extract content return comments, description, data_points - + def get_vacuum_thrust(self,thrust_source,reference_pressure): """Calculate the vacuum thrust from the raw thrust and the reference pressure at which the thrust data was recorded. @@ -1084,7 +1084,7 @@ def get_vacuum_thrust(self,thrust_source,reference_pressure): time = point[0] thrust = point[1] vacuum_thrust_source.append([time, thrust + thrust_reduction]) - + return vacuum_thrust_source def export_eng(self, file_name, motor_name): diff --git a/rocketpy/motors/solid_motor.py b/rocketpy/motors/solid_motor.py index d194bc4c8..f75fcd33b 100644 --- a/rocketpy/motors/solid_motor.py +++ b/rocketpy/motors/solid_motor.py @@ -187,7 +187,7 @@ class SolidMotor(Motor): Method of interpolation used in case thrust curve is given by data set in .csv or .eng, or as an array. Options are 'spline' 'akima' and 'linear'. Default is "linear". - Solidmotor.reference_pressure : int, float + SolidMotor.reference_pressure : int, float Atmospheric pressure in Pa at which the thrust data was recorded. It will allow to obtain the net thrust in the Flight class. """ diff --git a/rocketpy/simulation/flight.py b/rocketpy/simulation/flight.py index ee50fb75c..d23d44753 100644 --- a/rocketpy/simulation/flight.py +++ b/rocketpy/simulation/flight.py @@ -1387,8 +1387,7 @@ def udot_rail1(self, t, u, post_processing=False): net_thrust = self.rocket.motor.thrust.get_value_opt(t) else: net_thrust = self.rocket.motor.vacuum_thrust.get_value_opt(t) - pressure * nozzle_area - if net_thrust < 0: - net_thrust = 0 + net_thrust = max(net_thrust, 0) rho = self.env.density.get_value_opt(z) R3 = -0.5 * rho * (free_stream_speed**2) * self.rocket.area * (drag_coeff) @@ -1486,8 +1485,7 @@ def u_dot(self, t, u, post_processing=False): # pylint: disable=too-many-locals net_thrust = self.rocket.motor.thrust.get_value_opt(t) else: net_thrust = self.rocket.motor.vacuum_thrust.get_value_opt(t) - pressure * nozzle_area - if net_thrust < 0: - net_thrust = 0 + net_thrust = max(net_thrust, 0) # Off center moment M1 += self.rocket.thrust_eccentricity_x * net_thrust M2 -= self.rocket.thrust_eccentricity_y * net_thrust @@ -1877,10 +1875,9 @@ def u_dot_generalized(self, t, u, post_processing=False): # pylint: disable=too nozzle_area = self.rocket.motor.nozzle_area if self.rocket.motor.reference_pressure is None: net_thrust = self.rocket.motor.thrust.get_value_opt(t) - else: + else: net_thrust = self.rocket.motor.vacuum_thrust.get_value_opt(t) - pressure * nozzle_area - if net_thrust < 0: - net_thrust = 0 + net_thrust = max(net_thrust, 0) M1 += ( self.rocket.cp_eccentricity_y * R3 + self.rocket.thrust_eccentricity_x * net_thrust @@ -2234,7 +2231,7 @@ def M3(self): """Aerodynamic moment in the rocket z-axis as a Function of time. Sometimes referred to as roll moment.""" return self.__evaluate_post_process[:, [0, 12]] - + @funcify_method("Time (s)", "Net Thrust (N)", "linear", "zero") def net_thrust(self): """Net thrust of the rocket as a Function of time. This is the From b69d40db2ea3f0d0d864d6a8182d5d23a4ad13c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Alca=C3=B1iz?= Date: Wed, 19 Mar 2025 23:23:12 +0100 Subject: [PATCH 07/16] ENH: net thrust method extended to the rest of the motor classes --- rocketpy/motors/empty_motor.py | 1 + rocketpy/motors/hybrid_motor.py | 18 +++++++++++++++++- rocketpy/motors/liquid_motor.py | 16 +++++++++++++++- rocketpy/motors/motor.py | 10 ++++++++++ rocketpy/motors/solid_motor.py | 1 + 5 files changed, 44 insertions(+), 2 deletions(-) diff --git a/rocketpy/motors/empty_motor.py b/rocketpy/motors/empty_motor.py index 4e844472b..093b77295 100644 --- a/rocketpy/motors/empty_motor.py +++ b/rocketpy/motors/empty_motor.py @@ -13,6 +13,7 @@ def __init__(self): dry_inertia=(0, 0, 0), nozzle_radius=0, center_of_dry_mass_position=0, + reference_pressure=0, dry_mass=0, nozzle_position=0, burn_time=1, diff --git a/rocketpy/motors/hybrid_motor.py b/rocketpy/motors/hybrid_motor.py index ea01e686f..a386e20da 100644 --- a/rocketpy/motors/hybrid_motor.py +++ b/rocketpy/motors/hybrid_motor.py @@ -29,6 +29,8 @@ class HybridMotor(Motor): "combustion_chamber_to_nozzle". HybridMotor.nozzle_radius : float Radius of motor nozzle outlet in meters. + HybridMotor.nozzle_area : float + Area of motor nozzle outlet in square meters. HybridMotor.nozzle_position : float Motor's nozzle outlet position in meters, specified in the motor's coordinate system. See @@ -147,7 +149,11 @@ class HybridMotor(Motor): HybridMotor.propellant_I_22 and HybridMotor.propellant_I_33 for more information. HybridMotor.thrust : Function - Motor thrust force, in Newtons, as a function of time. + Motor thrust force obtained from thrust source, in Newtons, as a + function of time. + HybridMotor.vacuum_thrust : Function + Motor thrust force when the rocket is in a vacuum. In Newtons, as a + function of time. HybridMotor.total_impulse : float Total impulse of the thrust curve in N*s. HybridMotor.max_thrust : float @@ -181,6 +187,9 @@ class HybridMotor(Motor): Method of interpolation used in case thrust curve is given by data set in .csv or .eng, or as an array. Options are 'spline' 'akima' and 'linear'. Default is "linear". + HybridMotor.reference_pressure : int, float + Atmospheric pressure in Pa at which the thrust data was recorded. + It will allow to obtain the net thrust in the Flight class. """ def __init__( # pylint: disable=too-many-arguments @@ -197,6 +206,7 @@ def __init__( # pylint: disable=too-many-arguments grain_separation, grains_center_of_mass_position, center_of_dry_mass_position, + reference_pressure=None, nozzle_position=0, burn_time=None, throat_radius=0.01, @@ -258,6 +268,8 @@ class Function. Thrust units are Newtons. The position, in meters, of the motor's center of mass with respect to the motor's coordinate system when it is devoid of propellant. See :doc:`Positions and Coordinate Systems `. + reference_pressure : int, float, optional + Atmospheric pressure in Pa at which the thrust data was recorded. nozzle_position : int, float, optional Motor's nozzle outlet position in meters, in the motor's coordinate system. See :doc:`Positions and Coordinate Systems ` @@ -308,6 +320,7 @@ class Function. Thrust units are Newtons. dry_inertia=dry_inertia, nozzle_radius=nozzle_radius, center_of_dry_mass_position=center_of_dry_mass_position, + reference_pressure=reference_pressure, dry_mass=dry_mass, nozzle_position=nozzle_position, burn_time=burn_time, @@ -321,6 +334,7 @@ class Function. Thrust units are Newtons. dry_inertia, nozzle_radius, center_of_dry_mass_position, + reference_pressure, nozzle_position, burn_time, reshape_thrust_curve, @@ -340,6 +354,7 @@ class Function. Thrust units are Newtons. grain_separation, grains_center_of_mass_position, center_of_dry_mass_position, + reference_pressure, nozzle_position, burn_time, throat_radius, @@ -657,6 +672,7 @@ def from_dict(cls, data): nozzle_radius=data["nozzle_radius"], dry_mass=data["dry_mass"], center_of_dry_mass_position=data["center_of_dry_mass_position"], + reference_pressure=data["reference_pressure"], dry_inertia=( data["dry_I_11"], data["dry_I_22"], diff --git a/rocketpy/motors/liquid_motor.py b/rocketpy/motors/liquid_motor.py index 7e763dd70..2e0dfa5a5 100644 --- a/rocketpy/motors/liquid_motor.py +++ b/rocketpy/motors/liquid_motor.py @@ -29,6 +29,8 @@ class LiquidMotor(Motor): "combustion_chamber_to_nozzle". LiquidMotor.nozzle_radius : float Radius of motor nozzle outlet in meters. + LiquidMotor.nozzle_area : float + Area of motor nozzle outlet in square meters. LiquidMotor.nozzle_position : float Motor's nozzle outlet position in meters, specified in the motor's coordinate system. See @@ -122,7 +124,11 @@ class LiquidMotor(Motor): LiquidMotor.propellant_I_22 and LiquidMotor.propellant_I_33 for more information. LiquidMotor.thrust : Function - Motor thrust force, in Newtons, as a function of time. + Motor thrust force obtained from thrust source, in Newtons, as a + function of time. + LiquidMotor.vacuum_thrust : Function + Motor thrust force when the rocket is in a vacuum. In Newtons, as a + function of time. LiquidMotor.total_impulse : float Total impulse of the thrust curve in N*s. LiquidMotor.max_thrust : float @@ -143,6 +149,9 @@ class LiquidMotor(Motor): burn_out_time and the burn_start_time. LiquidMotor.exhaust_velocity : Function Propulsion gases exhaust velocity in m/s. + LiquidMotor.reference_pressure : int, float + Atmospheric pressure in Pa at which the thrust data was recorded. + It will allow to obtain the net thrust in the Flight class. """ def __init__( @@ -152,6 +161,7 @@ def __init__( dry_inertia, nozzle_radius, center_of_dry_mass_position, + reference_pressure=None, nozzle_position=0, burn_time=None, reshape_thrust_curve=False, @@ -194,6 +204,8 @@ class Function. Thrust units are Newtons. The position, in meters, of the motor's center of mass with respect to the motor's coordinate system when it is devoid of propellant. See :doc:`Positions and Coordinate Systems ` + reference_pressure : int, float, optional + Atmospheric pressure in Pa at which the thrust data was recorded. nozzle_position : float Motor's nozzle outlet position in meters, specified in the motor's coordinate system. See @@ -236,6 +248,7 @@ class Function. Thrust units are Newtons. dry_inertia=dry_inertia, nozzle_radius=nozzle_radius, center_of_dry_mass_position=center_of_dry_mass_position, + reference_pressure=reference_pressure, dry_mass=dry_mass, nozzle_position=nozzle_position, burn_time=burn_time, @@ -500,6 +513,7 @@ def from_dict(cls, data): nozzle_radius=data["nozzle_radius"], dry_mass=data["dry_mass"], center_of_dry_mass_position=data["center_of_dry_mass_position"], + reference_pressure=data["reference_pressure"], dry_inertia=( data["dry_I_11"], data["dry_I_22"], diff --git a/rocketpy/motors/motor.py b/rocketpy/motors/motor.py index 176e50ef2..b7b8786ec 100644 --- a/rocketpy/motors/motor.py +++ b/rocketpy/motors/motor.py @@ -1227,6 +1227,7 @@ class GenericMotor(Motor): therefore for more accurate results, use the ``SolidMotor``, ``HybridMotor`` or ``LiquidMotor`` classes.""" + # pylint: disable=too-many-arguments def __init__( self, thrust_source, @@ -1236,6 +1237,7 @@ def __init__( chamber_position, propellant_initial_mass, nozzle_radius, + reference_pressure=None, dry_mass=0, center_of_dry_mass_position=None, dry_inertia=(0, 0, 0), @@ -1282,6 +1284,8 @@ def __init__( to the motor's coordinate system when it is devoid of propellant. If not specified, automatically sourced as the chamber position. See :doc:`Positions and Coordinate Systems ` + reference_pressure : int, float, optional + Atmospheric pressure in Pa at which the thrust data was recorded. dry_inertia : tuple, list Tuple or list containing the motor's dry mass inertia tensor components, in kg*m^2. This inertia is defined with respect to the @@ -1338,6 +1342,7 @@ def __init__( dry_inertia=dry_inertia, nozzle_radius=nozzle_radius, center_of_dry_mass_position=center_of_dry_mass_position, + reference_pressure=reference_pressure, dry_mass=dry_mass, nozzle_position=nozzle_position, burn_time=burn_time, @@ -1500,6 +1505,7 @@ def load_from_eng_file( dry_mass=None, burn_time=None, center_of_dry_mass_position=None, + reference_pressure=None, dry_inertia=(0, 0, 0), nozzle_position=0, reshape_thrust_curve=False, @@ -1541,6 +1547,8 @@ def load_from_eng_file( The position, in meters, of the motor's center of mass with respect to the motor's coordinate system when it is devoid of propellant. If not specified, automatically sourced as the chamber position. + reference_pressure : int, float, optional + Atmospheric pressure in Pa at which the thrust data was recorded. dry_inertia : tuple, list Tuple or list containing the motor's dry mass inertia tensor nozzle_position : int, float, optional @@ -1608,6 +1616,7 @@ def load_from_eng_file( chamber_position=chamber_position, propellant_initial_mass=propellant_initial_mass, nozzle_radius=nozzle_radius, + reference_pressure=reference_pressure, dry_mass=dry_mass, center_of_dry_mass_position=center_of_dry_mass_position, dry_inertia=dry_inertia, @@ -1645,6 +1654,7 @@ def from_dict(cls, data): chamber_position=data["chamber_position"], propellant_initial_mass=data["propellant_initial_mass"], nozzle_radius=data["nozzle_radius"], + reference_pressure=data["reference_pressure"], dry_mass=data["dry_mass"], center_of_dry_mass_position=data["center_of_dry_mass_position"], dry_inertia=( diff --git a/rocketpy/motors/solid_motor.py b/rocketpy/motors/solid_motor.py index f75fcd33b..1554964db 100644 --- a/rocketpy/motors/solid_motor.py +++ b/rocketpy/motors/solid_motor.py @@ -808,6 +808,7 @@ def from_dict(cls, data): grain_separation=data["grain_separation"], grains_center_of_mass_position=data["grains_center_of_mass_position"], center_of_dry_mass_position=data["center_of_dry_mass_position"], + reference_pressure=data["reference_pressure"], nozzle_position=data["nozzle_position"], burn_time=data["burn_time"], throat_radius=data["throat_radius"], From 345225e416a31ebca5e8a1b7324e8d5345efcf1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Alca=C3=B1iz?= Date: Thu, 20 Mar 2025 12:07:07 +0100 Subject: [PATCH 08/16] BUG: __post_processed_variables inconsistent array --- rocketpy/simulation/flight.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rocketpy/simulation/flight.py b/rocketpy/simulation/flight.py index d23d44753..b35e91336 100644 --- a/rocketpy/simulation/flight.py +++ b/rocketpy/simulation/flight.py @@ -2012,7 +2012,7 @@ def u_dot_parachute(self, t, u, post_processing=False): if post_processing: self.__post_processed_variables.append( - [t, ax, ay, az, 0, 0, 0, Dx, Dy, Dz, 0, 0, 0] + [t, ax, ay, az, 0, 0, 0, Dx, Dy, Dz, 0, 0, 0, 0] ) return [vx, vy, vz, ax, ay, az, 0, 0, 0, 0, 0, 0, 0] @@ -3087,7 +3087,7 @@ def __evaluate_post_process(self): np.array An array containing all post-processed variables evaluated at each time step. Each element of the array is a list containing: - [t, ax, ay, az, alpha1, alpha2, alpha3, R1, R2, R3, M1, M2, M3] + [t, ax, ay, az, alpha1, alpha2, alpha3, R1, R2, R3, M1, M2, M3, net_thrust] """ self.__post_processed_variables = [] for phase_index, phase in self.time_iterator(self.flight_phases): From 041dffdffc41e7c9466fd1bc83bde40bdbdbffbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Alca=C3=B1iz?= Date: Thu, 20 Mar 2025 12:23:19 +0100 Subject: [PATCH 09/16] ENH: ruff reformatting --- rocketpy/motors/motor.py | 36 ++++++++++++++++++++-------------- rocketpy/motors/solid_motor.py | 2 +- rocketpy/simulation/flight.py | 34 +++++++++++++++++++++++++++----- 3 files changed, 51 insertions(+), 21 deletions(-) diff --git a/rocketpy/motors/motor.py b/rocketpy/motors/motor.py index b7b8786ec..7eb57ad39 100644 --- a/rocketpy/motors/motor.py +++ b/rocketpy/motors/motor.py @@ -126,7 +126,7 @@ class Motor(ABC): Motor thrust force obtained from the thrust source, in Newtons, as a function of time. Motor.vacuum_thrust : Function - Motor thrust force when the rocket is in a vacuum. In Newtons, as a + Motor thrust force when the rocket is in a vacuum. In Newtons, as a function of time. Motor.total_impulse : float Total impulse of the thrust curve in N*s. @@ -320,10 +320,16 @@ class Function. Thrust units are Newtons. if reference_pressure is None: vacuum_thrust_source = self.thrust.source else: - vacuum_thrust_source = self.get_vacuum_thrust(self.thrust.source,reference_pressure) + vacuum_thrust_source = self.get_vacuum_thrust( + self.thrust.source, reference_pressure + ) self.vacuum_thrust_source = vacuum_thrust_source self.vacuum_thrust = Function( - vacuum_thrust_source, "Time (s)", "Vacuum Thrust (N)", self.interpolate, "zero" + vacuum_thrust_source, + "Time (s)", + "Vacuum Thrust (N)", + self.interpolate, + "zero", ) # Auxiliary quantities @@ -1059,21 +1065,21 @@ def import_eng(file_name): # Return all extract content return comments, description, data_points - def get_vacuum_thrust(self,thrust_source,reference_pressure): - """Calculate the vacuum thrust from the raw thrust and the reference - pressure at which the thrust data was recorded. + def get_vacuum_thrust(self, thrust_source, reference_pressure): + """Calculate the vacuum thrust from the raw thrust and the reference + pressure at which the thrust data was recorded. - Parameters - ---------- - thrust_source : list + Parameters + ---------- + thrust_source : list A list of points representing the thrust curve. - reference_pressure : int, float - The atmospheric pressure at which the thrust data was recorded. + reference_pressure : int, float + The atmospheric pressure at which the thrust data was recorded. - Returns - ------- - vacuum_thrust_source : list - A list of points representing the vacuum thrust curve. + Returns + ------- + vacuum_thrust_source : list + A list of points representing the vacuum thrust curve. """ # Initialize arrays vacuum_thrust_source = [] diff --git a/rocketpy/motors/solid_motor.py b/rocketpy/motors/solid_motor.py index 1554964db..b2a718600 100644 --- a/rocketpy/motors/solid_motor.py +++ b/rocketpy/motors/solid_motor.py @@ -152,7 +152,7 @@ class SolidMotor(Motor): Motor thrust force obtained from thrust source, in Newtons, as a function of time. SolidMotor.vacuum_thrust : Function - Motor thrust force when the rocket is in a vacuum. In Newtons, as a + Motor thrust force when the rocket is in a vacuum. In Newtons, as a function of time. SolidMotor.total_impulse : float Total impulse of the thrust curve in N*s. diff --git a/rocketpy/simulation/flight.py b/rocketpy/simulation/flight.py index b35e91336..fa120461f 100644 --- a/rocketpy/simulation/flight.py +++ b/rocketpy/simulation/flight.py @@ -362,7 +362,7 @@ class Flight: Rocket's engine net thrust as a function of time in Newton. This is the actual thrust force experienced by the rocket. It may be corrected with the atmospheric pressure if a reference - pressure is defined. Can be called or accessed as array. + pressure is defined. Can be called or accessed as array. Flight.aerodynamic_lift : Function Resultant force perpendicular to rockets axis due to aerodynamic effects as a function of time. Units in N. @@ -1386,7 +1386,10 @@ def udot_rail1(self, t, u, post_processing=False): if self.rocket.motor.reference_pressure is None: net_thrust = self.rocket.motor.thrust.get_value_opt(t) else: - net_thrust = self.rocket.motor.vacuum_thrust.get_value_opt(t) - pressure * nozzle_area + net_thrust = ( + self.rocket.motor.vacuum_thrust.get_value_opt(t) + - pressure * nozzle_area + ) net_thrust = max(net_thrust, 0) rho = self.env.density.get_value_opt(z) R3 = -0.5 * rho * (free_stream_speed**2) * self.rocket.area * (drag_coeff) @@ -1484,7 +1487,10 @@ def u_dot(self, t, u, post_processing=False): # pylint: disable=too-many-locals if self.rocket.motor.reference_pressure is None: net_thrust = self.rocket.motor.thrust.get_value_opt(t) else: - net_thrust = self.rocket.motor.vacuum_thrust.get_value_opt(t) - pressure * nozzle_area + net_thrust = ( + self.rocket.motor.vacuum_thrust.get_value_opt(t) + - pressure * nozzle_area + ) net_thrust = max(net_thrust, 0) # Off center moment M1 += self.rocket.thrust_eccentricity_x * net_thrust @@ -1729,7 +1735,22 @@ def u_dot(self, t, u, post_processing=False): # pylint: disable=too-many-locals if post_processing: self.__post_processed_variables.append( - [t, ax, ay, az, alpha1, alpha2, alpha3, R1, R2, R3, M1, M2, M3, net_thrust] + [ + t, + ax, + ay, + az, + alpha1, + alpha2, + alpha3, + R1, + R2, + R3, + M1, + M2, + M3, + net_thrust, + ] ) return u_dot @@ -1876,7 +1897,10 @@ def u_dot_generalized(self, t, u, post_processing=False): # pylint: disable=too if self.rocket.motor.reference_pressure is None: net_thrust = self.rocket.motor.thrust.get_value_opt(t) else: - net_thrust = self.rocket.motor.vacuum_thrust.get_value_opt(t) - pressure * nozzle_area + net_thrust = ( + self.rocket.motor.vacuum_thrust.get_value_opt(t) + - pressure * nozzle_area + ) net_thrust = max(net_thrust, 0) M1 += ( self.rocket.cp_eccentricity_y * R3 From 20663c369879b391370105bb442af143dd26bc21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Alca=C3=B1iz?= Date: Fri, 21 Mar 2025 10:44:04 +0100 Subject: [PATCH 10/16] Update rocketpy/motors/motor.py Co-authored-by: Gui-FernandesBR <63590233+Gui-FernandesBR@users.noreply.github.com> --- rocketpy/motors/motor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rocketpy/motors/motor.py b/rocketpy/motors/motor.py index 7eb57ad39..025d85d0a 100644 --- a/rocketpy/motors/motor.py +++ b/rocketpy/motors/motor.py @@ -152,7 +152,7 @@ class Motor(ABC): Method of interpolation used in case thrust curve is given by data set in .csv or .eng, or as an array. Options are 'spline' 'akima' and 'linear'. Default is "linear". - Motor.reference_pressure : int, float + Motor.reference_pressure : int, float, None Atmospheric pressure in Pa at which the thrust data was recorded. It will allow to obtain the net thrust in the Flight class. """ From 5eccda98b8ea1d38a3c9447c635a604678f7cc4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Alca=C3=B1iz?= Date: Sat, 22 Mar 2025 09:44:31 +0100 Subject: [PATCH 11/16] ENH: Avoid breaking change --- rocketpy/motors/empty_motor.py | 2 +- rocketpy/motors/hybrid_motor.py | 14 +++++++------- rocketpy/motors/liquid_motor.py | 10 +++++----- rocketpy/motors/motor.py | 26 +++++++++++++------------- rocketpy/motors/solid_motor.py | 10 +++++----- 5 files changed, 31 insertions(+), 31 deletions(-) diff --git a/rocketpy/motors/empty_motor.py b/rocketpy/motors/empty_motor.py index 093b77295..ff7128c11 100644 --- a/rocketpy/motors/empty_motor.py +++ b/rocketpy/motors/empty_motor.py @@ -13,13 +13,13 @@ def __init__(self): dry_inertia=(0, 0, 0), nozzle_radius=0, center_of_dry_mass_position=0, - reference_pressure=0, dry_mass=0, nozzle_position=0, burn_time=1, reshape_thrust_curve=False, interpolation_method="linear", coordinate_system_orientation="nozzle_to_combustion_chamber", + reference_pressure=0, ) # Mass properties diff --git a/rocketpy/motors/hybrid_motor.py b/rocketpy/motors/hybrid_motor.py index a386e20da..6b74540ef 100644 --- a/rocketpy/motors/hybrid_motor.py +++ b/rocketpy/motors/hybrid_motor.py @@ -206,13 +206,13 @@ def __init__( # pylint: disable=too-many-arguments grain_separation, grains_center_of_mass_position, center_of_dry_mass_position, - reference_pressure=None, nozzle_position=0, burn_time=None, throat_radius=0.01, reshape_thrust_curve=False, interpolation_method="linear", coordinate_system_orientation="nozzle_to_combustion_chamber", + reference_pressure=None, ): """Initialize Motor class, process thrust curve and geometrical parameters and store results. @@ -268,8 +268,6 @@ class Function. Thrust units are Newtons. The position, in meters, of the motor's center of mass with respect to the motor's coordinate system when it is devoid of propellant. See :doc:`Positions and Coordinate Systems `. - reference_pressure : int, float, optional - Atmospheric pressure in Pa at which the thrust data was recorded. nozzle_position : int, float, optional Motor's nozzle outlet position in meters, in the motor's coordinate system. See :doc:`Positions and Coordinate Systems ` @@ -310,6 +308,8 @@ class Function. Thrust units are Newtons. positions specified. Options are "nozzle_to_combustion_chamber" and "combustion_chamber_to_nozzle". Default is "nozzle_to_combustion_chamber". + reference_pressure : int, float, optional + Atmospheric pressure in Pa at which the thrust data was recorded. Returns ------- @@ -320,13 +320,13 @@ class Function. Thrust units are Newtons. dry_inertia=dry_inertia, nozzle_radius=nozzle_radius, center_of_dry_mass_position=center_of_dry_mass_position, - reference_pressure=reference_pressure, dry_mass=dry_mass, nozzle_position=nozzle_position, burn_time=burn_time, reshape_thrust_curve=reshape_thrust_curve, interpolation_method=interpolation_method, coordinate_system_orientation=coordinate_system_orientation, + reference_pressure=reference_pressure, ) self.liquid = LiquidMotor( thrust_source, @@ -334,12 +334,12 @@ class Function. Thrust units are Newtons. dry_inertia, nozzle_radius, center_of_dry_mass_position, - reference_pressure, nozzle_position, burn_time, reshape_thrust_curve, interpolation_method, coordinate_system_orientation, + reference_pressure, ) self.solid = SolidMotor( thrust_source, @@ -354,13 +354,13 @@ class Function. Thrust units are Newtons. grain_separation, grains_center_of_mass_position, center_of_dry_mass_position, - reference_pressure, nozzle_position, burn_time, throat_radius, reshape_thrust_curve, interpolation_method, coordinate_system_orientation, + reference_pressure, ) self.positioned_tanks = self.liquid.positioned_tanks @@ -672,7 +672,6 @@ def from_dict(cls, data): nozzle_radius=data["nozzle_radius"], dry_mass=data["dry_mass"], center_of_dry_mass_position=data["center_of_dry_mass_position"], - reference_pressure=data["reference_pressure"], dry_inertia=( data["dry_I_11"], data["dry_I_22"], @@ -692,6 +691,7 @@ def from_dict(cls, data): grains_center_of_mass_position=data["grains_center_of_mass_position"], nozzle_position=data["nozzle_position"], throat_radius=data["throat_radius"], + reference_pressure=data["reference_pressure"], ) for tank in data["positioned_tanks"]: diff --git a/rocketpy/motors/liquid_motor.py b/rocketpy/motors/liquid_motor.py index 2e0dfa5a5..314ab05b1 100644 --- a/rocketpy/motors/liquid_motor.py +++ b/rocketpy/motors/liquid_motor.py @@ -161,12 +161,12 @@ def __init__( dry_inertia, nozzle_radius, center_of_dry_mass_position, - reference_pressure=None, nozzle_position=0, burn_time=None, reshape_thrust_curve=False, interpolation_method="linear", coordinate_system_orientation="nozzle_to_combustion_chamber", + reference_pressure=None, ): """Initialize LiquidMotor class, process thrust curve and geometrical parameters and store results. @@ -204,8 +204,6 @@ class Function. Thrust units are Newtons. The position, in meters, of the motor's center of mass with respect to the motor's coordinate system when it is devoid of propellant. See :doc:`Positions and Coordinate Systems ` - reference_pressure : int, float, optional - Atmospheric pressure in Pa at which the thrust data was recorded. nozzle_position : float Motor's nozzle outlet position in meters, specified in the motor's coordinate system. See @@ -242,19 +240,21 @@ class Function. Thrust units are Newtons. positions specified. Options are "nozzle_to_combustion_chamber" and "combustion_chamber_to_nozzle". Default is "nozzle_to_combustion_chamber". + reference_pressure : int, float, optional + Atmospheric pressure in Pa at which the thrust data was recorded. """ super().__init__( thrust_source=thrust_source, dry_inertia=dry_inertia, nozzle_radius=nozzle_radius, center_of_dry_mass_position=center_of_dry_mass_position, - reference_pressure=reference_pressure, dry_mass=dry_mass, nozzle_position=nozzle_position, burn_time=burn_time, reshape_thrust_curve=reshape_thrust_curve, interpolation_method=interpolation_method, coordinate_system_orientation=coordinate_system_orientation, + reference_pressure=reference_pressure, ) self.positioned_tanks = [] @@ -513,7 +513,6 @@ def from_dict(cls, data): nozzle_radius=data["nozzle_radius"], dry_mass=data["dry_mass"], center_of_dry_mass_position=data["center_of_dry_mass_position"], - reference_pressure=data["reference_pressure"], dry_inertia=( data["dry_I_11"], data["dry_I_22"], @@ -525,6 +524,7 @@ def from_dict(cls, data): nozzle_position=data["nozzle_position"], interpolation_method=data["interpolate"], coordinate_system_orientation=data["coordinate_system_orientation"], + reference_pressure=data["reference_pressure"], ) for tank in data["positioned_tanks"]: diff --git a/rocketpy/motors/motor.py b/rocketpy/motors/motor.py index 7eb57ad39..d864ccccd 100644 --- a/rocketpy/motors/motor.py +++ b/rocketpy/motors/motor.py @@ -164,13 +164,13 @@ def __init__( dry_inertia, nozzle_radius, center_of_dry_mass_position, - reference_pressure=None, dry_mass=None, nozzle_position=0, burn_time=None, reshape_thrust_curve=False, interpolation_method="linear", coordinate_system_orientation="nozzle_to_combustion_chamber", + reference_pressure=None, ): """Initialize Motor class, process thrust curve and geometrical parameters and store results. @@ -209,8 +209,6 @@ class Function. Thrust units are Newtons. (I_11, I_22, I_33), where I_12 = I_13 = I_23 = 0. nozzle_radius : int, float, optional Motor's nozzle outlet radius in meters. - reference_pressure : int, float, optional - Atmospheric pressure in Pa at which the thrust data was recorded. burn_time: float, tuple of float, optional Motor's burn time. If a float is given, the burn time is assumed to be between 0 and @@ -248,6 +246,8 @@ class Function. Thrust units are Newtons. positions specified. Options are "nozzle_to_combustion_chamber" and "combustion_chamber_to_nozzle". Default is "nozzle_to_combustion_chamber". + reference_pressure : int, float, optional + Atmospheric pressure in Pa at which the thrust data was recorded. Returns ------- @@ -1160,13 +1160,13 @@ def to_dict(self, include_outputs=False): "dry_I_23": self.dry_I_23, "nozzle_radius": self.nozzle_radius, "nozzle_area": self.nozzle_area, - "reference_pressure": self.reference_pressure, "center_of_dry_mass_position": self.center_of_dry_mass_position, "dry_mass": self.dry_mass, "nozzle_position": self.nozzle_position, "burn_time": self.burn_time, "interpolate": self.interpolate, "coordinate_system_orientation": self.coordinate_system_orientation, + "reference_pressure": self.reference_pressure, } if include_outputs: @@ -1243,7 +1243,6 @@ def __init__( chamber_position, propellant_initial_mass, nozzle_radius, - reference_pressure=None, dry_mass=0, center_of_dry_mass_position=None, dry_inertia=(0, 0, 0), @@ -1251,6 +1250,7 @@ def __init__( reshape_thrust_curve=False, interpolation_method="linear", coordinate_system_orientation="nozzle_to_combustion_chamber", + reference_pressure=None, ): """Initialize GenericMotor class, process thrust curve and geometrical parameters and store results. @@ -1290,8 +1290,6 @@ def __init__( to the motor's coordinate system when it is devoid of propellant. If not specified, automatically sourced as the chamber position. See :doc:`Positions and Coordinate Systems ` - reference_pressure : int, float, optional - Atmospheric pressure in Pa at which the thrust data was recorded. dry_inertia : tuple, list Tuple or list containing the motor's dry mass inertia tensor components, in kg*m^2. This inertia is defined with respect to the @@ -1342,19 +1340,21 @@ def __init__( positions specified. Options are "nozzle_to_combustion_chamber" and "combustion_chamber_to_nozzle". Default is "nozzle_to_combustion_chamber". + reference_pressure : int, float, optional + Atmospheric pressure in Pa at which the thrust data was recorded. """ super().__init__( thrust_source=thrust_source, dry_inertia=dry_inertia, nozzle_radius=nozzle_radius, center_of_dry_mass_position=center_of_dry_mass_position, - reference_pressure=reference_pressure, dry_mass=dry_mass, nozzle_position=nozzle_position, burn_time=burn_time, reshape_thrust_curve=reshape_thrust_curve, interpolation_method=interpolation_method, coordinate_system_orientation=coordinate_system_orientation, + reference_pressure=reference_pressure, ) self.chamber_radius = chamber_radius @@ -1511,12 +1511,12 @@ def load_from_eng_file( dry_mass=None, burn_time=None, center_of_dry_mass_position=None, - reference_pressure=None, dry_inertia=(0, 0, 0), nozzle_position=0, reshape_thrust_curve=False, interpolation_method="linear", coordinate_system_orientation="nozzle_to_combustion_chamber", + reference_pressure=None, ): """Loads motor data from a .eng file and processes it. @@ -1553,8 +1553,6 @@ def load_from_eng_file( The position, in meters, of the motor's center of mass with respect to the motor's coordinate system when it is devoid of propellant. If not specified, automatically sourced as the chamber position. - reference_pressure : int, float, optional - Atmospheric pressure in Pa at which the thrust data was recorded. dry_inertia : tuple, list Tuple or list containing the motor's dry mass inertia tensor nozzle_position : int, float, optional @@ -1580,6 +1578,8 @@ def load_from_eng_file( positions specified. Options are "nozzle_to_combustion_chamber" and "combustion_chamber_to_nozzle". Default is "nozzle_to_combustion_chamber". + reference_pressure : int, float, optional + Atmospheric pressure in Pa at which the thrust data was recorded. Returns ------- @@ -1622,7 +1622,6 @@ def load_from_eng_file( chamber_position=chamber_position, propellant_initial_mass=propellant_initial_mass, nozzle_radius=nozzle_radius, - reference_pressure=reference_pressure, dry_mass=dry_mass, center_of_dry_mass_position=center_of_dry_mass_position, dry_inertia=dry_inertia, @@ -1630,6 +1629,7 @@ def load_from_eng_file( reshape_thrust_curve=reshape_thrust_curve, interpolation_method=interpolation_method, coordinate_system_orientation=coordinate_system_orientation, + reference_pressure=reference_pressure, ) def all_info(self): @@ -1660,7 +1660,6 @@ def from_dict(cls, data): chamber_position=data["chamber_position"], propellant_initial_mass=data["propellant_initial_mass"], nozzle_radius=data["nozzle_radius"], - reference_pressure=data["reference_pressure"], dry_mass=data["dry_mass"], center_of_dry_mass_position=data["center_of_dry_mass_position"], dry_inertia=( @@ -1673,4 +1672,5 @@ def from_dict(cls, data): ), nozzle_position=data["nozzle_position"], interpolation_method=data["interpolate"], + reference_pressure=data["reference_pressure"], ) diff --git a/rocketpy/motors/solid_motor.py b/rocketpy/motors/solid_motor.py index b2a718600..6499fcb16 100644 --- a/rocketpy/motors/solid_motor.py +++ b/rocketpy/motors/solid_motor.py @@ -207,13 +207,13 @@ def __init__( grain_separation, grains_center_of_mass_position, center_of_dry_mass_position, - reference_pressure=None, nozzle_position=0.0, burn_time=None, throat_radius=0.01, reshape_thrust_curve=False, interpolation_method="linear", coordinate_system_orientation="nozzle_to_combustion_chamber", + reference_pressure=None, ): """Initialize Motor class, process thrust curve and geometrical parameters and store results. @@ -270,8 +270,6 @@ class Function. Thrust units are Newtons. The position, in meters, of the motor's center of mass with respect to the motor's coordinate system when it is devoid of propellant. See :doc:`Positions and Coordinate Systems `. - reference_pressure : int, float, optional - Atmospheric pressure in Pa at which the thrust data was recorded. nozzle_position : int, float, optional Motor's nozzle outlet position in meters, in the motor's coordinate system. See :doc:`Positions and Coordinate Systems ` @@ -311,6 +309,8 @@ class Function. Thrust units are Newtons. positions specified. Options are "nozzle_to_combustion_chamber" and "combustion_chamber_to_nozzle". Default is "nozzle_to_combustion_chamber". + reference_pressure : int, float, optional + Atmospheric pressure in Pa at which the thrust data was recorded. Returns ------- @@ -321,13 +321,13 @@ class Function. Thrust units are Newtons. dry_inertia=dry_inertia, nozzle_radius=nozzle_radius, center_of_dry_mass_position=center_of_dry_mass_position, - reference_pressure=reference_pressure, dry_mass=dry_mass, nozzle_position=nozzle_position, burn_time=burn_time, reshape_thrust_curve=reshape_thrust_curve, interpolation_method=interpolation_method, coordinate_system_orientation=coordinate_system_orientation, + reference_pressure=reference_pressure, ) # Nozzle parameters self.throat_radius = throat_radius @@ -808,10 +808,10 @@ def from_dict(cls, data): grain_separation=data["grain_separation"], grains_center_of_mass_position=data["grains_center_of_mass_position"], center_of_dry_mass_position=data["center_of_dry_mass_position"], - reference_pressure=data["reference_pressure"], nozzle_position=data["nozzle_position"], burn_time=data["burn_time"], throat_radius=data["throat_radius"], interpolation_method=data["interpolate"], coordinate_system_orientation=data["coordinate_system_orientation"], + reference_pressure=data["reference_pressure"], ) From d3badc91598c5bb9ff09ff13c955e6ac29837333 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Alca=C3=B1iz?= Date: Sat, 29 Mar 2025 18:09:30 +0100 Subject: [PATCH 12/16] ENH: Pressure Thrust method added --- rocketpy/motors/motor.py | 57 +++++++++++++++-------------------- rocketpy/simulation/flight.py | 39 ++++++++---------------- 2 files changed, 36 insertions(+), 60 deletions(-) diff --git a/rocketpy/motors/motor.py b/rocketpy/motors/motor.py index 926cf6173..de2d33c1e 100644 --- a/rocketpy/motors/motor.py +++ b/rocketpy/motors/motor.py @@ -316,22 +316,6 @@ class Function. Thrust units are Newtons. # Post process thrust self.thrust = Motor.clip_thrust(self.thrust, self.burn_time) - # Evaluate vacuum thrust - if reference_pressure is None: - vacuum_thrust_source = self.thrust.source - else: - vacuum_thrust_source = self.get_vacuum_thrust( - self.thrust.source, reference_pressure - ) - self.vacuum_thrust_source = vacuum_thrust_source - self.vacuum_thrust = Function( - vacuum_thrust_source, - "Time (s)", - "Vacuum Thrust (N)", - self.interpolate, - "zero", - ) - # Auxiliary quantities self.burn_start_time = self.burn_time[0] self.burn_out_time = self.burn_time[1] @@ -1065,33 +1049,40 @@ def import_eng(file_name): # Return all extract content return comments, description, data_points - def get_vacuum_thrust(self, thrust_source, reference_pressure): + @cached_property + def vacuum_thrust(self): """Calculate the vacuum thrust from the raw thrust and the reference pressure at which the thrust data was recorded. + Returns + ------- + vacuum_thrust : Function + The rocket's thrust in a vaccum. + """ + if self.reference_pressure is None: + print("Reference pressure is not set, cannot calculate vacuum thrust") + return self.thrust + + return self.thrust + self.reference_pressure * self.nozzle_area + + def pressure_thrust(self, pressure): + """Computes the contribution to thrust due to the pressure difference + between the nozzle exit and the atmospheric pressure. + Parameters ---------- - thrust_source : list - A list of points representing the thrust curve. - reference_pressure : int, float - The atmospheric pressure at which the thrust data was recorded. + pressure : float + Atmospheric pressure in Pa. Returns ------- - vacuum_thrust_source : list - A list of points representing the vacuum thrust curve. + pressure_thrust : float + Thrust component resulting from the pressure difference. """ - # Initialize arrays - vacuum_thrust_source = [] - - # Reduction in thrust due to atmospheric pressure - thrust_reduction = reference_pressure * self.nozzle_area - for point in thrust_source: - time = point[0] - thrust = point[1] - vacuum_thrust_source.append([time, thrust + thrust_reduction]) + if self.reference_pressure is None: + return 0 - return vacuum_thrust_source + return (self.reference_pressure - pressure) * self.nozzle_area def export_eng(self, file_name, motor_name): """Exports thrust curve data points and motor description to diff --git a/rocketpy/simulation/flight.py b/rocketpy/simulation/flight.py index fa120461f..2b7f338d6 100644 --- a/rocketpy/simulation/flight.py +++ b/rocketpy/simulation/flight.py @@ -1382,15 +1382,10 @@ def udot_rail1(self, t, u, post_processing=False): # Calculate Forces pressure = self.env.pressure.get_value_opt(z) - nozzle_area = self.rocket.motor.nozzle_area - if self.rocket.motor.reference_pressure is None: - net_thrust = self.rocket.motor.thrust.get_value_opt(t) - else: - net_thrust = ( - self.rocket.motor.vacuum_thrust.get_value_opt(t) - - pressure * nozzle_area - ) - net_thrust = max(net_thrust, 0) + net_thrust = max( + self.motor.thrust.get_value_opt(t) + self.motor.pressure_thrust(pressure), + 0, + ) rho = self.env.density.get_value_opt(z) R3 = -0.5 * rho * (free_stream_speed**2) * self.rocket.area * (drag_coeff) @@ -1466,7 +1461,6 @@ def u_dot(self, t, u, post_processing=False): # pylint: disable=too-many-locals R1, R2, M1, M2, M3 = 0, 0, 0, 0, 0 # Thrust correction parameters pressure = self.env.pressure.get_value_opt(z) - nozzle_area = self.rocket.motor.nozzle_area # Determine current behavior if self.rocket.motor.burn_start_time < t < self.rocket.motor.burn_out_time: # Motor burning @@ -1484,14 +1478,10 @@ def u_dot(self, t, u, post_processing=False): # pylint: disable=too-many-locals mass_flow_rate_at_t = self.rocket.motor.mass_flow_rate.get_value_opt(t) propellant_mass_at_t = self.rocket.motor.propellant_mass.get_value_opt(t) # Thrust - if self.rocket.motor.reference_pressure is None: - net_thrust = self.rocket.motor.thrust.get_value_opt(t) - else: - net_thrust = ( - self.rocket.motor.vacuum_thrust.get_value_opt(t) - - pressure * nozzle_area - ) - net_thrust = max(net_thrust, 0) + net_thrust = max( + self.motor.thrust.get_value_opt(t) + self.motor.pressure_thrust(pressure), + 0, + ) # Off center moment M1 += self.rocket.thrust_eccentricity_x * net_thrust M2 -= self.rocket.thrust_eccentricity_y * net_thrust @@ -1893,15 +1883,10 @@ def u_dot_generalized(self, t, u, post_processing=False): # pylint: disable=too # Off center moment pressure = self.env.pressure.get_value_opt(z) - nozzle_area = self.rocket.motor.nozzle_area - if self.rocket.motor.reference_pressure is None: - net_thrust = self.rocket.motor.thrust.get_value_opt(t) - else: - net_thrust = ( - self.rocket.motor.vacuum_thrust.get_value_opt(t) - - pressure * nozzle_area - ) - net_thrust = max(net_thrust, 0) + net_thrust = max( + self.motor.thrust.get_value_opt(t) + self.motor.pressure_thrust(pressure), + 0, + ) M1 += ( self.rocket.cp_eccentricity_y * R3 + self.rocket.thrust_eccentricity_x * net_thrust From 1e63961b096bb8bcca4f6c181b970dfc209bf55a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Alca=C3=B1iz?= Date: Sat, 29 Mar 2025 18:33:25 +0100 Subject: [PATCH 13/16] BUG: call to the thrust function wrong --- rocketpy/simulation/flight.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/rocketpy/simulation/flight.py b/rocketpy/simulation/flight.py index 9ba829d29..f704a0f9c 100644 --- a/rocketpy/simulation/flight.py +++ b/rocketpy/simulation/flight.py @@ -1383,7 +1383,7 @@ def udot_rail1(self, t, u, post_processing=False): # Calculate Forces pressure = self.env.pressure.get_value_opt(z) net_thrust = max( - self.motor.thrust.get_value_opt(t) + self.motor.pressure_thrust(pressure), + self.rocket.motor.thrust.get_value_opt(t) + self.rocket.motor.pressure_thrust(pressure), 0, ) rho = self.env.density.get_value_opt(z) @@ -1479,12 +1479,12 @@ def u_dot(self, t, u, post_processing=False): # pylint: disable=too-many-locals propellant_mass_at_t = self.rocket.motor.propellant_mass.get_value_opt(t) # Thrust net_thrust = max( - self.motor.thrust.get_value_opt(t) + self.motor.pressure_thrust(pressure), + self.rocket.motor.thrust.get_value_opt(t) + self.rocket.motor.pressure_thrust(pressure), 0, ) # Off center moment - M1 += self.rocket.thrust_eccentricity_y * thrust - M2 -= self.rocket.thrust_eccentricity_x * thrust + M1 += self.rocket.thrust_eccentricity_y * net_thrust + M2 -= self.rocket.thrust_eccentricity_x * net_thrust else: # Motor stopped # Inertias @@ -1884,16 +1884,16 @@ def u_dot_generalized(self, t, u, post_processing=False): # pylint: disable=too # Off center moment pressure = self.env.pressure.get_value_opt(z) net_thrust = max( - self.motor.thrust.get_value_opt(t) + self.motor.pressure_thrust(pressure), + self.rocket.motor.thrust.get_value_opt(t) + self.rocket.motor.pressure_thrust(pressure), 0, ) M1 += ( self.rocket.cp_eccentricity_y * R3 - + self.rocket.thrust_eccentricity_y * thrust + + self.rocket.thrust_eccentricity_y * net_thrust ) M2 -= ( self.rocket.cp_eccentricity_x * R3 - + self.rocket.thrust_eccentricity_x * thrust + + self.rocket.thrust_eccentricity_x * net_thrust ) M3 += self.rocket.cp_eccentricity_x * R2 - self.rocket.cp_eccentricity_y * R1 From e26a9776e04bcde3566b4f903b79c9d97123b501 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Alca=C3=B1iz?= Date: Sat, 29 Mar 2025 18:49:54 +0100 Subject: [PATCH 14/16] BUG: pressure thrust evaluated when motor is turned off --- rocketpy/simulation/flight.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/rocketpy/simulation/flight.py b/rocketpy/simulation/flight.py index f704a0f9c..3b857600a 100644 --- a/rocketpy/simulation/flight.py +++ b/rocketpy/simulation/flight.py @@ -1383,7 +1383,8 @@ def udot_rail1(self, t, u, post_processing=False): # Calculate Forces pressure = self.env.pressure.get_value_opt(z) net_thrust = max( - self.rocket.motor.thrust.get_value_opt(t) + self.rocket.motor.pressure_thrust(pressure), + self.rocket.motor.thrust.get_value_opt(t) + + self.rocket.motor.pressure_thrust(pressure), 0, ) rho = self.env.density.get_value_opt(z) @@ -1479,7 +1480,8 @@ def u_dot(self, t, u, post_processing=False): # pylint: disable=too-many-locals propellant_mass_at_t = self.rocket.motor.propellant_mass.get_value_opt(t) # Thrust net_thrust = max( - self.rocket.motor.thrust.get_value_opt(t) + self.rocket.motor.pressure_thrust(pressure), + self.rocket.motor.thrust.get_value_opt(t) + + self.rocket.motor.pressure_thrust(pressure), 0, ) # Off center moment @@ -1818,8 +1820,15 @@ def u_dot_generalized(self, t, u, post_processing=False): # pylint: disable=too free_stream_mach = free_stream_speed / speed_of_sound if self.rocket.motor.burn_start_time < t < self.rocket.motor.burn_out_time: + pressure = self.env.pressure.get_value_opt(z) + net_thrust = max( + self.rocket.motor.thrust.get_value_opt(t) + + self.rocket.motor.pressure_thrust(pressure), + 0, + ) drag_coeff = self.rocket.power_on_drag.get_value_opt(free_stream_mach) else: + net_thrust = 0 drag_coeff = self.rocket.power_off_drag.get_value_opt(free_stream_mach) R3 += -0.5 * rho * (free_stream_speed**2) * self.rocket.area * drag_coeff for air_brakes in self.rocket.air_brakes: @@ -1882,11 +1891,6 @@ def u_dot_generalized(self, t, u, post_processing=False): # pylint: disable=too M3 += L # Off center moment - pressure = self.env.pressure.get_value_opt(z) - net_thrust = max( - self.rocket.motor.thrust.get_value_opt(t) + self.rocket.motor.pressure_thrust(pressure), - 0, - ) M1 += ( self.rocket.cp_eccentricity_y * R3 + self.rocket.thrust_eccentricity_y * net_thrust From e8215df35160e0e198b1a4060e25ab797234913e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Alca=C3=B1iz?= Date: Sun, 30 Mar 2025 10:43:22 +0200 Subject: [PATCH 15/16] ENH: CHANGELOG updated --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a2503d23..5fbb7353c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ Attention: The newest changes should be on top --> ### Added +- ENH: Introduce Net Thrust with pressure corrections [#789](https://github.com/RocketPy-Team/RocketPy/pull/789) ### Changed From b1de1bf39772e297d5f6baf10f98bbcbe63e7ea7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Alca=C3=B1iz?= Date: Wed, 9 Apr 2025 22:27:29 +0200 Subject: [PATCH 16/16] DOC: definition of exhaust velocity improved --- rocketpy/motors/hybrid_motor.py | 10 +++++++++- rocketpy/motors/liquid_motor.py | 8 ++++++-- rocketpy/motors/motor.py | 25 ++++++++++++++++++++----- rocketpy/motors/solid_motor.py | 10 +++++++++- 4 files changed, 44 insertions(+), 9 deletions(-) diff --git a/rocketpy/motors/hybrid_motor.py b/rocketpy/motors/hybrid_motor.py index 6b74540ef..1de9f435c 100644 --- a/rocketpy/motors/hybrid_motor.py +++ b/rocketpy/motors/hybrid_motor.py @@ -173,7 +173,10 @@ class HybridMotor(Motor): Total motor burn duration, in seconds. It is the difference between the ``burn_out_time`` and the ``burn_start_time``. HybridMotor.exhaust_velocity : Function - Propulsion gases exhaust velocity, assumed constant, in m/s. + Effective exhaust velocity of the propulsion gases in m/s. Computed + as the thrust divided by the mass flow rate. This corresponds to the + actual exhaust velocity only when the nozzle exit pressure equals the + atmospheric pressure. HybridMotor.burn_area : Function Total burn area considering all grains, made out of inner cylindrical burn area and grain top and bottom faces. Expressed @@ -386,6 +389,11 @@ def exhaust_velocity(self): ------- self.exhaust_velocity : Function Gas exhaust velocity of the motor. + + Notes + ----- + This corresponds to the actual exhaust velocity only when the nozzle + exit pressure equals the atmospheric pressure. """ return Function( self.total_impulse / self.propellant_initial_mass diff --git a/rocketpy/motors/liquid_motor.py b/rocketpy/motors/liquid_motor.py index 314ab05b1..5edaae3b8 100644 --- a/rocketpy/motors/liquid_motor.py +++ b/rocketpy/motors/liquid_motor.py @@ -148,7 +148,10 @@ class LiquidMotor(Motor): Total motor burn duration, in seconds. It is the difference between the burn_out_time and the burn_start_time. LiquidMotor.exhaust_velocity : Function - Propulsion gases exhaust velocity in m/s. + Effective exhaust velocity of the propulsion gases in m/s. Computed + as the thrust divided by the mass flow rate. This corresponds to the + actual exhaust velocity only when the nozzle exit pressure equals the + atmospheric pressure. LiquidMotor.reference_pressure : int, float Atmospheric pressure in Pa at which the thrust data was recorded. It will allow to obtain the net thrust in the Flight class. @@ -277,7 +280,8 @@ def exhaust_velocity(self): ----- The exhaust velocity is computed as the ratio of the thrust and the mass flow rate. Therefore, this will vary with time if the mass flow - rate varies with time. + rate varies with time. This corresponds to the actual exhaust velocity + only when the nozzle exit pressure equals the atmospheric pressure. """ times, thrusts = self.thrust.source[:, 0], self.thrust.source[:, 1] mass_flow_rates = self.mass_flow_rate(times) diff --git a/rocketpy/motors/motor.py b/rocketpy/motors/motor.py index de2d33c1e..6380fbe1e 100644 --- a/rocketpy/motors/motor.py +++ b/rocketpy/motors/motor.py @@ -147,7 +147,10 @@ class Motor(ABC): Total motor burn duration, in seconds. It is the difference between the burn_out_time and the burn_start_time. Motor.exhaust_velocity : Function - Propulsion gases exhaust velocity in m/s. + Effective exhaust velocity of the propulsion gases in m/s. Computed + as the thrust divided by the mass flow rate. This corresponds to the + actual exhaust velocity only when the nozzle exit pressure equals the + atmospheric pressure. Motor.interpolate : string Method of interpolation used in case thrust curve is given by data set in .csv or .eng, or as an array. Options are 'spline' @@ -409,7 +412,7 @@ def total_impulse(self): @property @abstractmethod def exhaust_velocity(self): - """Exhaust velocity of the motor gases. + """Effective exhaust velocity of the motor gases. Returns ------- @@ -428,6 +431,9 @@ def exhaust_velocity(self): - The ``LiquidMotor`` class favors the more accurate data from the Tanks's mass flow rates. Therefore the exhaust velocity is generally variable, being the ratio of the motor thrust by the mass flow rate. + + This corresponds to the actual exhaust velocity only when the nozzle + exit pressure equals the atmospheric pressure. """ @funcify_method("Time (s)", "Total mass (kg)") @@ -1060,14 +1066,18 @@ def vacuum_thrust(self): The rocket's thrust in a vaccum. """ if self.reference_pressure is None: - print("Reference pressure is not set, cannot calculate vacuum thrust") + warnings.warn( + "Reference pressure not set. Returning thrust instead.", + UserWarning, + ) return self.thrust return self.thrust + self.reference_pressure * self.nozzle_area def pressure_thrust(self, pressure): - """Computes the contribution to thrust due to the pressure difference - between the nozzle exit and the atmospheric pressure. + """Computes the contribution to thrust due to the difference between + the atmospheric pressure and the reference pressure at which the + thrust data was recorded. Parameters ---------- @@ -1383,6 +1393,11 @@ def exhaust_velocity(self): ------- self.exhaust_velocity : Function Gas exhaust velocity of the motor. + + Notes + ----- + This corresponds to the actual exhaust velocity only when the nozzle + exit pressure equals the atmospheric pressure. """ return Function( self.total_impulse / self.propellant_initial_mass diff --git a/rocketpy/motors/solid_motor.py b/rocketpy/motors/solid_motor.py index 6499fcb16..e736831bb 100644 --- a/rocketpy/motors/solid_motor.py +++ b/rocketpy/motors/solid_motor.py @@ -173,7 +173,10 @@ class SolidMotor(Motor): Total motor burn duration, in seconds. It is the difference between the ``burn_out_time`` and the ``burn_start_time``. SolidMotor.exhaust_velocity : Function - Propulsion gases exhaust velocity, assumed constant, in m/s. + Effective exhaust velocity of the propulsion gases in m/s. Computed + as the thrust divided by the mass flow rate. This corresponds to the + actual exhaust velocity only when the nozzle exit pressure equals the + atmospheric pressure. SolidMotor.burn_area : Function Total burn area considering all grains, made out of inner cylindrical burn area and grain top and bottom faces. Expressed @@ -392,6 +395,11 @@ def exhaust_velocity(self): ------- self.exhaust_velocity : Function Gas exhaust velocity of the motor. + + Notes + ----- + This corresponds to the actual exhaust velocity only when the nozzle + exit pressure equals the atmospheric pressure. """ return Function( self.total_impulse / self.propellant_initial_mass