From 6f965b81f29a172e320fac03d4b0dd3bbe257a1e Mon Sep 17 00:00:00 2001 From: Lucas Prates Date: Fri, 13 Sep 2024 17:15:14 -0300 Subject: [PATCH 01/11] MNT: refactoring coefficients argument on __init__ --- .../rocket/aero_surface/generic_surface.py | 124 +++--- .../aero_surface/linear_generic_surface.py | 357 +++++++++--------- 2 files changed, 250 insertions(+), 231 deletions(-) diff --git a/rocketpy/rocket/aero_surface/generic_surface.py b/rocketpy/rocket/aero_surface/generic_surface.py index ff86bd942..006c3dabe 100644 --- a/rocketpy/rocket/aero_surface/generic_surface.py +++ b/rocketpy/rocket/aero_surface/generic_surface.py @@ -17,15 +17,10 @@ def __init__( self, reference_area, reference_length, - cL=0, - cQ=0, - cD=0, - cm=0, - cn=0, - cl=0, + coefficients="all_null", center_of_pressure=(0, 0, 0), name="Generic Surface", - ): + ): # pylint: disable=invalid-name """Create a generic aerodynamic surface, defined by its aerodynamic coefficients. This surface is used to model any aerodynamic surface that does not fit the predefined classes. @@ -51,24 +46,27 @@ def __init__( reference_length : int, float Reference length of the aerodynamic surface. Has the unit of meters. Commonly defined as the rocket's diameter. - cL : str, callable, optional - Lift coefficient. Can be a path to a CSV file or a callable. - Default is 0. - cQ : str, callable, optional - Side force coefficient. Can be a path to a CSV file or a callable. - Default is 0. - cD : str, callable, optional - Drag coefficient. Can be a path to a CSV file or a callable. - Default is 0. - cm : str, callable, optional - Pitch moment coefficient. Can be a path to a CSV file or a callable. - Default is 0. - cn : str, callable, optional - Yaw moment coefficient. Can be a path to a CSV file or a callable. - Default is 0. - cl : str, callable, optional - Roll moment coefficient. Can be a path to a CSV file or a callable. - Default is 0. + coefficients: dict, optional + List of coefficients. Default is "all_null", which creates a + dict with every coefficient set to 0. The valid coefficients are:\n + cL: str, callable, optional + Lift coefficient. Can be a path to a CSV file or a callable. + Default is 0.\n + cQ: str, callable, optional + Side force coefficient. Can be a path to a CSV file or a callable. + Default is 0.\n + cD: str, callable, optional + Drag coefficient. Can be a path to a CSV file or a callable. + Default is 0.\n + cm: str, callable, optional + Pitch moment coefficient. Can be a path to a CSV file or a callable. + Default is 0.\n + cn: str, callable, optional + Yaw moment coefficient. Can be a path to a CSV file or a callable. + Default is 0.\n + cl: str, callable, optional + Roll moment coefficient. Can be a path to a CSV file or a callable. + Default is 0.\n center_of_pressure : tuple, list, optional Application point of the aerodynamic forces and moments. The center of pressure is defined in the local coordinate system of the @@ -76,6 +74,7 @@ def __init__( name : str, optional Name of the aerodynamic surface. Default is 'GenericSurface'. """ + self.reference_area = reference_area self.reference_length = reference_length self.center_of_pressure = center_of_pressure @@ -85,12 +84,55 @@ def __init__( self.cpz = center_of_pressure[2] self.name = name - self.cL = self._process_input(cL, "cL") - self.cD = self._process_input(cD, "cD") - self.cQ = self._process_input(cQ, "cQ") - self.cm = self._process_input(cm, "cm") - self.cn = self._process_input(cn, "cn") - self.cl = self._process_input(cl, "cl") + default_coefficients = self._get_default_coefficients() + coefficients = self._complete_coefficients(coefficients, default_coefficients) + for coeff, coeff_value in coefficients.items(): + value = self._process_input(coeff_value, coeff) + setattr(self, coeff, value) + + def _get_default_coefficients(self): + """Returns default coefficients + + Returns + ------- + default_coefficients: dict + Dictionary whose keys are the coefficients names and keys + are the default values. + """ + default_coefficients = { + "cL": 0, + "cQ": 0, + "cD": 0, + "cm": 0, + "cn": 0, + "cl": 0, + } + return default_coefficients + + def _complete_coefficients(self, input_coefficients, default_coefficients): + """Completes coefficients dictionary from user input in __init__ + + Parameters + ---------- + input_coefficients : str, dict + Coefficients dictionary passed by the user. If the user only specifies some + of the coefficients, the + + Returns + ------- + coefficients: dict + Coefficients dictionary used to setup coefficients attributes + """ + + if input_coefficients == "all_null": + coefficients = default_coefficients + else: # complete user dictionary with null values for the coefficients + coefficients = input_coefficients + for coeff, value in default_coefficients.items(): + if coeff not in coefficients.keys(): + coefficients[coeff] = value + + return coefficients def _compute_from_coefficients( self, @@ -169,12 +211,8 @@ def compute_forces_and_moments( stream_mach, rho, cp, + omega, reynolds, - omega1, - omega2, - omega3, - *args, - **kwargs, ): """Computes the forces and moments acting on the aerodynamic surface. Used in each time step of the simulation. This method is valid for @@ -194,8 +232,8 @@ def compute_forces_and_moments( Center of pressure coordinates in the body frame. reynolds : float Reynolds number. - omega1, omega2, omega3 : float - Angular velocities around the x, y, z axes. + omega: tuple of float + Tuple containing angular velocities around the x, y, z axes. Returns ------- @@ -218,9 +256,9 @@ def compute_forces_and_moments( beta, stream_mach, reynolds, - omega1, - omega2, - omega3, + omega[0], + omega[1], + omega[2], ) # Conversion from aerodynamic frame to body frame @@ -270,7 +308,7 @@ def _process_input(self, input_data, coeff_name): ) return input_data elif callable(input_data): - # Check if callable has 7 inputs (alpha, beta, mach, reynolds, pitch_rate, yaw_rate, roll_rate) + # Check if callable ha#s 7 inputs (alpha, beta, mach, reynolds, pitch_rate, yaw_rate, roll_rate) if input_data.__code__.co_argcount != 7: raise ValueError( f"{coeff_name} function must have 7 input arguments" @@ -320,7 +358,7 @@ def __load_csv(self, file_path, coeff_name): reader = csv.reader(file) header = next(reader) except (FileNotFoundError, IOError) as e: - raise ValueError(f"Error reading {coeff_name} CSV file: {e}") + raise ValueError(f"Error reading {coeff_name} CSV file: {e}") from e if not header: raise ValueError(f"Invalid or empty CSV file for {coeff_name}.") diff --git a/rocketpy/rocket/aero_surface/linear_generic_surface.py b/rocketpy/rocket/aero_surface/linear_generic_surface.py index 863f999d9..b1c56a511 100644 --- a/rocketpy/rocket/aero_surface/linear_generic_surface.py +++ b/rocketpy/rocket/aero_surface/linear_generic_surface.py @@ -11,45 +11,10 @@ def __init__( self, reference_area, reference_length, - cL_0=0, - cL_alpha=0, - cL_beta=0, - cL_p=0, - cL_q=0, - cL_r=0, - cQ_0=0, - cQ_alpha=0, - cQ_beta=0, - cQ_p=0, - cQ_q=0, - cQ_r=0, - cD_0=0, - cD_alpha=0, - cD_beta=0, - cD_p=0, - cD_q=0, - cD_r=0, - cm_0=0, - cm_alpha=0, - cm_beta=0, - cm_p=0, - cm_q=0, - cm_r=0, - cn_0=0, - cn_alpha=0, - cn_beta=0, - cn_p=0, - cn_q=0, - cn_r=0, - cl_0=0, - cl_alpha=0, - cl_beta=0, - cl_p=0, - cl_q=0, - cl_r=0, + coefficients="all_null", center_of_pressure=(0, 0, 0), name="Generic Linear Surface", - ): + ): # pylint: disable=invalid-name """Create a generic linear aerodynamic surface, defined by its aerodynamic coefficients derivatives. This surface is used to model any aerodynamic surface that does not fit the predefined classes. @@ -75,112 +40,115 @@ def __init__( reference_length : int, float Reference length of the aerodynamic surface. Has the unit of meters. Commonly defined as the rocket's diameter. - cL_0 : callable, str, optional - Coefficient of lift at zero angle of attack. Default is 0. - cL_alpha : callable, str, optional - Coefficient of lift derivative with respect to angle of attack. - Default is 0. - cL_beta : callable, str, optional - Coefficient of lift derivative with respect to sideslip angle. - Default is 0. - cL_p : callable, str, optional - Coefficient of lift derivative with respect to roll rate. - Default is 0. - cL_q : callable, str, optional - Coefficient of lift derivative with respect to pitch rate. - Default is 0. - cL_r : callable, str, optional - Coefficient of lift derivative with respect to yaw rate. - Default is 0. - cQ_0 : callable, str, optional - Coefficient of pitch moment at zero angle of attack. - Default is 0. - cQ_alpha : callable, str, optional - Coefficient of pitch moment derivative with respect to angle of - attack. Default is 0. - cQ_beta : callable, str, optional - Coefficient of pitch moment derivative with respect to sideslip - angle. Default is 0. - cQ_p : callable, str, optional - Coefficient of pitch moment derivative with respect to roll rate. - Default is 0. - cQ_q : callable, str, optional - Coefficient of pitch moment derivative with respect to pitch rate. - Default is 0. - cQ_r : callable, str, optional - Coefficient of pitch moment derivative with respect to yaw rate. - Default is 0. - cD_0 : callable, str, optional - Coefficient of drag at zero angle of attack. Default is 0. - cD_alpha : callable, str, optional - Coefficient of drag derivative with respect to angle of attack. - Default is 0. - cD_beta : callable, str, optional - Coefficient of drag derivative with respect to sideslip angle. - Default is 0. - cD_p : callable, str, optional - Coefficient of drag derivative with respect to roll rate. - Default is 0. - cD_q : callable, str, optional - Coefficient of drag derivative with respect to pitch rate. - Default is 0. - cD_r : callable, str, optional - Coefficient of drag derivative with respect to yaw rate. - Default is 0. - cm_0 : callable, str, optional - Coefficient of pitch moment at zero angle of attack. - Default is 0. - cm_alpha : callable, str, optional - Coefficient of pitch moment derivative with respect to angle of - attack. Default is 0. - cm_beta : callable, str, optional - Coefficient of pitch moment derivative with respect to sideslip - angle. Default is 0. - cm_p : callable, str, optional - Coefficient of pitch moment derivative with respect to roll rate. - Default is 0. - cm_q : callable, str, optional - Coefficient of pitch moment derivative with respect to pitch rate. - Default is 0. - cm_r : callable, str, optional - Coefficient of pitch moment derivative with respect to yaw rate. - Default is 0. - cn_0 : callable, str, optional - Coefficient of yaw moment at zero angle of attack. - Default is 0. - cn_alpha : callable, str, optional - Coefficient of yaw moment derivative with respect to angle of - attack. Default is 0. - cn_beta : callable, str, optional - Coefficient of yaw moment derivative with respect to sideslip angle. - Default is 0. - cn_p : callable, str, optional - Coefficient of yaw moment derivative with respect to roll rate. - Default is 0. - cn_q : callable, str, optional - Coefficient of yaw moment derivative with respect to pitch rate. - Default is 0. - cn_r : callable, str, optional - Coefficient of yaw moment derivative with respect to yaw rate. - Default is 0. - cl_0 : callable, str, optional - Coefficient of roll moment at zero angle of attack. - Default is 0. - cl_alpha : callable, str, optional - Coefficient of roll moment derivative with respect to angle of - attack. Default is 0. - cl_beta : callable, str, optional - Coefficient of roll moment derivative with respect to sideslip - angle. Default is 0. - cl_p : callable, str, optional - Coefficient of roll moment derivative with respect to roll rate. - Default is 0. - cl_q : callable, str, optional - Coefficient of roll moment derivative with respect to pitch rate. - Default is 0. - cl_r : callable, str, optional - Coefficient of roll moment derivative with respect to yaw rate. - Default is 0. + coefficients: dict, optional + List of coefficients. Default is "all_null", which creates a + dict with every coefficient set to 0. The valid coefficients are:\n + cL_0: callable, str, optional + Coefficient of lift at zero angle of attack. Default is 0.\n + cL_alpha: callable, str, optional + Coefficient of lift derivative with respect to angle of attack. + Default is 0.\n + cL_beta: callable, str, optional + Coefficient of lift derivative with respect to sideslip angle. + Default is 0.\n + cL_p: callable, str, optional + Coefficient of lift derivative with respect to roll rate. + Default is 0.\n + cL_q: callable, str, optional + Coefficient of lift derivative with respect to pitch rate. + Default is 0.\n + cL_r: callable, str, optional + Coefficient of lift derivative with respect to yaw rate. + Default is 0.\n + cQ_0: callable, str, optional + Coefficient of pitch moment at zero angle of attack. + Default is 0.\n + cQ_alpha: callable, str, optional + Coefficient of pitch moment derivative with respect to angle of + attack. Default is 0.\n + cQ_beta: callable, str, optional + Coefficient of pitch moment derivative with respect to sideslip + angle. Default is 0.\n + cQ_p: callable, str, optional + Coefficient of pitch moment derivative with respect to roll rate. + Default is 0.\n + cQ_q: callable, str, optional + Coefficient of pitch moment derivative with respect to pitch rate. + Default is 0.\n + cQ_r: callable, str, optional + Coefficient of pitch moment derivative with respect to yaw rate. + Default is 0.\n + cD_0: callable, str, optional + Coefficient of drag at zero angle of attack. Default is 0.\n + cD_alpha: callable, str, optional + Coefficient of drag derivative with respect to angle of attack. + Default is 0.\n + cD_beta: callable, str, optional + Coefficient of drag derivative with respect to sideslip angle. + Default is 0.\n + cD_p: callable, str, optional + Coefficient of drag derivative with respect to roll rate. + Default is 0.\n + cD_q: callable, str, optional + Coefficient of drag derivative with respect to pitch rate. + Default is 0.\n + cD_r: callable, str, optional + Coefficient of drag derivative with respect to yaw rate. + Default is 0.\n + cm_0: callable, str, optional + Coefficient of pitch moment at zero angle of attack. + Default is 0.\n + cm_alpha: callable, str, optional + Coefficient of pitch moment derivative with respect to angle of + attack. Default is 0.\n + cm_beta: callable, str, optional + Coefficient of pitch moment derivative with respect to sideslip + angle. Default is 0.\n + cm_p: callable, str, optional + Coefficient of pitch moment derivative with respect to roll rate. + Default is 0.\n + cm_q: callable, str, optional + Coefficient of pitch moment derivative with respect to pitch rate. + Default is 0.\n + cm_r: callable, str, optional + Coefficient of pitch moment derivative with respect to yaw rate. + Default is 0.\n + cn_0: callable, str, optional + Coefficient of yaw moment at zero angle of attack. + Default is 0.\n + cn_alpha: callable, str, optional + Coefficient of yaw moment derivative with respect to angle of + attack. Default is 0.\n + cn_beta: callable, str, optional + Coefficient of yaw moment derivative with respect to sideslip angle. + Default is 0.\n + cn_p: callable, str, optional + Coefficient of yaw moment derivative with respect to roll rate. + Default is 0.\n + cn_q: callable, str, optional + Coefficient of yaw moment derivative with respect to pitch rate. + Default is 0.\n + cn_r: callable, str, optional + Coefficient of yaw moment derivative with respect to yaw rate. + Default is 0.\n + cl_0: callable, str, optional + Coefficient of roll moment at zero angle of attack. + Default is 0.\n + cl_alpha: callable, str, optional + Coefficient of roll moment derivative with respect to angle of + attack. Default is 0.\n + cl_beta: callable, str, optional + Coefficient of roll moment derivative with respect to sideslip + angle. Default is 0.\n + cl_p: callable, str, optional + Coefficient of roll moment derivative with respect to roll rate. + Default is 0.\n + cl_q: callable, str, optional + Coefficient of roll moment derivative with respect to pitch rate. + Default is 0.\n + cl_r: callable, str, optional + Coefficient of roll moment derivative with respect to yaw rate. + Default is 0.\n center_of_pressure : tuple, optional Application point of the aerodynamic forces and moments. The center of pressure is defined in the local coordinate system of the @@ -188,54 +156,66 @@ def __init__( name : str Name of the aerodynamic surface. Default is 'GenericSurface'. """ - self.reference_area = reference_area - self.reference_length = reference_length - self.center_of_pressure = center_of_pressure - self.cp = center_of_pressure - self.cpx = center_of_pressure[0] - self.cpy = center_of_pressure[1] - self.cpz = center_of_pressure[2] - self.name = name - - self.cL_0 = self._process_input(cL_0, "cL_0") - self.cL_alpha = self._process_input(cL_alpha, "cL_alpha") - self.cL_beta = self._process_input(cL_beta, "cL_beta") - self.cL_p = self._process_input(cL_p, "cL_p") - self.cL_q = self._process_input(cL_q, "cL_q") - self.cL_r = self._process_input(cL_r, "cL_r") - self.cQ_0 = self._process_input(cQ_0, "cQ_0") - self.cQ_alpha = self._process_input(cQ_alpha, "cQ_alpha") - self.cQ_beta = self._process_input(cQ_beta, "cQ_beta") - self.cQ_p = self._process_input(cQ_p, "cQ_p") - self.cQ_q = self._process_input(cQ_q, "cQ_q") - self.cQ_r = self._process_input(cQ_r, "cQ_r") - self.cD_0 = self._process_input(cD_0, "cD_0") - self.cD_alpha = self._process_input(cD_alpha, "cD_alpha") - self.cD_beta = self._process_input(cD_beta, "cD_beta") - self.cD_p = self._process_input(cD_p, "cD_p") - self.cD_q = self._process_input(cD_q, "cD_q") - self.cD_r = self._process_input(cD_r, "cD_r") - self.cl_0 = self._process_input(cl_0, "cl_0") - self.cl_alpha = self._process_input(cl_alpha, "cl_alpha") - self.cl_beta = self._process_input(cl_beta, "cl_beta") - self.cl_p = self._process_input(cl_p, "cl_p") - self.cl_q = self._process_input(cl_q, "cl_q") - self.cl_r = self._process_input(cl_r, "cl_r") - self.cm_0 = self._process_input(cm_0, "cm_0") - self.cm_alpha = self._process_input(cm_alpha, "cm_alpha") - self.cm_beta = self._process_input(cm_beta, "cm_beta") - self.cm_p = self._process_input(cm_p, "cm_p") - self.cm_q = self._process_input(cm_q, "cm_q") - self.cm_r = self._process_input(cm_r, "cm_r") - self.cn_0 = self._process_input(cn_0, "cn_0") - self.cn_alpha = self._process_input(cn_alpha, "cn_alpha") - self.cn_beta = self._process_input(cn_beta, "cn_beta") - self.cn_p = self._process_input(cn_p, "cn_p") - self.cn_q = self._process_input(cn_q, "cn_q") - self.cn_r = self._process_input(cn_r, "cn_r") + + super().__init__( + reference_area=reference_area, + reference_length=reference_length, + coefficients=coefficients, + center_of_pressure=center_of_pressure, + name=name, + ) self.compute_all_coefficients() + def _get_default_coefficients(self): + """Returns default coefficients + + Returns + ------- + default_coefficients: dict + Dictionary whose keys are the coefficients names and keys + are the default values. + """ + default_coefficients = { + "cL_0": 0, + "cL_alpha": 0, + "cL_beta": 0, + "cL_p": 0, + "cL_q": 0, + "cL_r": 0, + "cQ_0": 0, + "cQ_alpha": 0, + "cQ_beta": 0, + "cQ_p": 0, + "cQ_q": 0, + "cQ_r": 0, + "cD_0": 0, + "cD_alpha": 0, + "cD_beta": 0, + "cD_p": 0, + "cD_q": 0, + "cD_r": 0, + "cm_0": 0, + "cm_alpha": 0, + "cm_beta": 0, + "cm_p": 0, + "cm_q": 0, + "cm_r": 0, + "cn_0": 0, + "cn_alpha": 0, + "cn_beta": 0, + "cn_p": 0, + "cn_q": 0, + "cn_r": 0, + "cl_0": 0, + "cl_alpha": 0, + "cl_beta": 0, + "cl_p": 0, + "cl_q": 0, + "cl_r": 0, + } + return default_coefficients + def compute_forcing_coefficient(self, c_0, c_alpha, c_beta): """Compute the forcing coefficient from the derivatives of the aerodynamic coefficients.""" @@ -297,6 +277,7 @@ def total_coefficient( def compute_all_coefficients(self): """Compute all the aerodynamic coefficients from the derivatives.""" + # pylint: disable=invalid-name self.cLf = self.compute_forcing_coefficient( self.cL_0, self.cL_alpha, self.cL_beta ) From 6cec6c335b7dfcf2b26d48d5d28366f3f924f937 Mon Sep 17 00:00:00 2001 From: Lucas Prates Date: Fri, 13 Sep 2024 17:17:05 -0300 Subject: [PATCH 02/11] MNT: cleaning input arguments from compute_forces_and_moments method --- rocketpy/rocket/aero_surface/aero_surface.py | 3 +-- rocketpy/rocket/aero_surface/fins/fins.py | 10 +++------- rocketpy/simulation/flight.py | 5 ++--- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/rocketpy/rocket/aero_surface/aero_surface.py b/rocketpy/rocket/aero_surface/aero_surface.py index 5b98f6260..15ca14f1d 100644 --- a/rocketpy/rocket/aero_surface/aero_surface.py +++ b/rocketpy/rocket/aero_surface/aero_surface.py @@ -99,8 +99,7 @@ def compute_forces_and_moments( rho, cp, *args, - **kwargs, - ): + ): # pylint: disable=unused-argument """Computes the forces and moments acting on the aerodynamic surface. Used in each time step of the simulation. This method is valid for the barrowman aerodynamic models. diff --git a/rocketpy/rocket/aero_surface/fins/fins.py b/rocketpy/rocket/aero_surface/fins/fins.py index b4aa2f790..7de9bd441 100644 --- a/rocketpy/rocket/aero_surface/fins/fins.py +++ b/rocketpy/rocket/aero_surface/fins/fins.py @@ -373,13 +373,9 @@ def compute_forces_and_moments( stream_mach, rho, cp, - _, - omega1, - omega2, - omega3, + omega, *args, - **kwargs, - ): + ): # pylint: disable=arguments-differ """Computes the forces and moments acting on the aerodynamic surface. Parameters @@ -408,7 +404,7 @@ def compute_forces_and_moments( * self.reference_area * (self.reference_length) ** 2 * cld_omega.get_value_opt(stream_mach) - * omega3 + * omega[2] # note to STANO: this was omega3 / 2 ) M3 = M3_forcing - M3_damping diff --git a/rocketpy/simulation/flight.py b/rocketpy/simulation/flight.py index 0eba7308e..3d1e870e8 100644 --- a/rocketpy/simulation/flight.py +++ b/rocketpy/simulation/flight.py @@ -1723,6 +1723,7 @@ def u_dot_generalized( v = Vector([vx, vy, vz]) # CDM velocity vector e = [e0, e1, e2, e3] # Euler parameters/quaternions w = Vector([omega1, omega2, omega3]) # Angular velocity vector + omega = [omega1, omega2, omega3] # Retrieve necessary quantities ## Rocket mass @@ -1818,10 +1819,8 @@ def u_dot_generalized( comp_stream_mach, rho, comp_cp, + omega, comp_reynolds, - omega1, - omega2, - omega3, ) R1 += X R2 += Y From d1c2e5f0e57126f540f7e895e8ede764f114ea0e Mon Sep 17 00:00:00 2001 From: Lucas Prates Date: Fri, 13 Sep 2024 17:17:58 -0300 Subject: [PATCH 03/11] DOC: adapting notebook to the new signature of general_surfaces --- docs/notebooks/coeff_testing.ipynb | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/docs/notebooks/coeff_testing.ipynb b/docs/notebooks/coeff_testing.ipynb index 80ea83b55..5bd822164 100644 --- a/docs/notebooks/coeff_testing.ipynb +++ b/docs/notebooks/coeff_testing.ipynb @@ -458,25 +458,31 @@ "gennose = GenericSurface(\n", " reference_area=np.pi * calisto.radius**2,\n", " reference_length=2 * calisto.radius,\n", - " cL=\"nose_cL.csv\",\n", - " cQ=\"nose_cQ.csv\",\n", + " coefficients={\n", + " \"cL\": \"nose_cL.csv\",\n", + " \"cQ\": \"nose_cQ.csv\",\n", + " },\n", " center_of_pressure=(0, 0, 0),\n", " name=\"nose\",\n", ")\n", "genfin = GenericSurface(\n", " reference_area=np.pi * calisto.radius**2,\n", " reference_length=2 * calisto.radius,\n", - " cL=\"fins_cL.csv\",\n", - " cQ=\"fins_cQ.csv\",\n", - " cl=\"fins_roll.csv\",\n", + " coefficients={\n", + " \"cL\": \"fins_cL.csv\",\n", + " \"cQ\": \"fins_cQ.csv\",\n", + " \"cl\": \"fins_roll.csv\",\n", + " },\n", " center_of_pressure=(0, 0, 0),\n", " name=\"fins\",\n", ")\n", "gentail = GenericSurface(\n", " reference_area=np.pi * calisto.radius**2,\n", " reference_length=2 * calisto.radius,\n", - " cL=\"tail_cL.csv\",\n", - " cQ=\"tail_cQ.csv\",\n", + " coefficients={\n", + " \"cL\": \"tail_cL.csv\",\n", + " \"cQ\": \"tail_cQ.csv\",\n", + " },\n", " center_of_pressure=(0, 0, 0),\n", " name=\"tail\",\n", ")\n", @@ -2451,7 +2457,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.0" + "version": "3.11.2" }, "vscode": { "interpreter": { From 5839d5351443836c495322f5831d59234bfa9cd8 Mon Sep 17 00:00:00 2001 From: Lucas Prates Date: Fri, 13 Sep 2024 17:37:29 -0300 Subject: [PATCH 04/11] MNT: removing unnecessary pylint flags and fixing small errors --- rocketpy/rocket/aero_surface/fins/fins.py | 2 +- rocketpy/rocket/aero_surface/generic_surface.py | 4 ++-- rocketpy/rocket/aero_surface/linear_generic_surface.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/rocketpy/rocket/aero_surface/fins/fins.py b/rocketpy/rocket/aero_surface/fins/fins.py index 7de9bd441..867f106b0 100644 --- a/rocketpy/rocket/aero_surface/fins/fins.py +++ b/rocketpy/rocket/aero_surface/fins/fins.py @@ -404,7 +404,7 @@ def compute_forces_and_moments( * self.reference_area * (self.reference_length) ** 2 * cld_omega.get_value_opt(stream_mach) - * omega[2] # note to STANO: this was omega3 + * omega[2] / 2 ) M3 = M3_forcing - M3_damping diff --git a/rocketpy/rocket/aero_surface/generic_surface.py b/rocketpy/rocket/aero_surface/generic_surface.py index 006c3dabe..051cb5f28 100644 --- a/rocketpy/rocket/aero_surface/generic_surface.py +++ b/rocketpy/rocket/aero_surface/generic_surface.py @@ -20,7 +20,7 @@ def __init__( coefficients="all_null", center_of_pressure=(0, 0, 0), name="Generic Surface", - ): # pylint: disable=invalid-name + ): """Create a generic aerodynamic surface, defined by its aerodynamic coefficients. This surface is used to model any aerodynamic surface that does not fit the predefined classes. @@ -308,7 +308,7 @@ def _process_input(self, input_data, coeff_name): ) return input_data elif callable(input_data): - # Check if callable ha#s 7 inputs (alpha, beta, mach, reynolds, pitch_rate, yaw_rate, roll_rate) + # Check if callable has 7 inputs (alpha, beta, mach, reynolds, pitch_rate, yaw_rate, roll_rate) if input_data.__code__.co_argcount != 7: raise ValueError( f"{coeff_name} function must have 7 input arguments" diff --git a/rocketpy/rocket/aero_surface/linear_generic_surface.py b/rocketpy/rocket/aero_surface/linear_generic_surface.py index b1c56a511..1c2befd7b 100644 --- a/rocketpy/rocket/aero_surface/linear_generic_surface.py +++ b/rocketpy/rocket/aero_surface/linear_generic_surface.py @@ -14,7 +14,7 @@ def __init__( coefficients="all_null", center_of_pressure=(0, 0, 0), name="Generic Linear Surface", - ): # pylint: disable=invalid-name + ): """Create a generic linear aerodynamic surface, defined by its aerodynamic coefficients derivatives. This surface is used to model any aerodynamic surface that does not fit the predefined classes. From b4cd83dd3d2d471bed3a509e4bd68e105139aaa1 Mon Sep 17 00:00:00 2001 From: Lucas Prates Date: Fri, 13 Sep 2024 19:05:26 -0300 Subject: [PATCH 05/11] MNT: applying suggested changes --- rocketpy/rocket/aero_surface/generic_surface.py | 16 ++++++++++------ rocketpy/simulation/flight.py | 3 +-- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/rocketpy/rocket/aero_surface/generic_surface.py b/rocketpy/rocket/aero_surface/generic_surface.py index 051cb5f28..2e9e30e19 100644 --- a/rocketpy/rocket/aero_surface/generic_surface.py +++ b/rocketpy/rocket/aero_surface/generic_surface.py @@ -1,3 +1,4 @@ +import copy import csv import math @@ -17,7 +18,7 @@ def __init__( self, reference_area, reference_length, - coefficients="all_null", + coefficients, center_of_pressure=(0, 0, 0), name="Generic Surface", ): @@ -47,7 +48,7 @@ def __init__( Reference length of the aerodynamic surface. Has the unit of meters. Commonly defined as the rocket's diameter. coefficients: dict, optional - List of coefficients. Default is "all_null", which creates a + List of coefficients. Default is , which creates a dict with every coefficient set to 0. The valid coefficients are:\n cL: str, callable, optional Lift coefficient. Can be a path to a CSV file or a callable. @@ -116,18 +117,21 @@ def _complete_coefficients(self, input_coefficients, default_coefficients): ---------- input_coefficients : str, dict Coefficients dictionary passed by the user. If the user only specifies some - of the coefficients, the + of the coefficients, the remaining are completed with class default + values + default_coefficients : dict + Default coefficients of the class Returns ------- - coefficients: dict - Coefficients dictionary used to setup coefficients attributes + coefficients : dict + Coefficients dictionary used to setup coefficient attributes """ if input_coefficients == "all_null": coefficients = default_coefficients else: # complete user dictionary with null values for the coefficients - coefficients = input_coefficients + coefficients = copy.deepcopy(input_coefficients) for coeff, value in default_coefficients.items(): if coeff not in coefficients.keys(): coefficients[coeff] = value diff --git a/rocketpy/simulation/flight.py b/rocketpy/simulation/flight.py index 3d1e870e8..6528381ec 100644 --- a/rocketpy/simulation/flight.py +++ b/rocketpy/simulation/flight.py @@ -1723,7 +1723,6 @@ def u_dot_generalized( v = Vector([vx, vy, vz]) # CDM velocity vector e = [e0, e1, e2, e3] # Euler parameters/quaternions w = Vector([omega1, omega2, omega3]) # Angular velocity vector - omega = [omega1, omega2, omega3] # Retrieve necessary quantities ## Rocket mass @@ -1819,7 +1818,7 @@ def u_dot_generalized( comp_stream_mach, rho, comp_cp, - omega, + w, comp_reynolds, ) R1 += X From 873de973ae63a601be35f883326c34bf1edd984d Mon Sep 17 00:00:00 2001 From: Lucas Prates Date: Fri, 13 Sep 2024 19:58:04 -0300 Subject: [PATCH 06/11] MNT: creating coefficient checking and changing default --- .../rocket/aero_surface/generic_surface.py | 46 ++++++++++++++----- .../aero_surface/linear_generic_surface.py | 4 +- 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/rocketpy/rocket/aero_surface/generic_surface.py b/rocketpy/rocket/aero_surface/generic_surface.py index 2e9e30e19..48fd14f32 100644 --- a/rocketpy/rocket/aero_surface/generic_surface.py +++ b/rocketpy/rocket/aero_surface/generic_surface.py @@ -47,9 +47,9 @@ def __init__( reference_length : int, float Reference length of the aerodynamic surface. Has the unit of meters. Commonly defined as the rocket's diameter. - coefficients: dict, optional - List of coefficients. Default is , which creates a - dict with every coefficient set to 0. The valid coefficients are:\n + coefficients: dict + List of coefficients. If a coefficient is omitted, it is set to 0. + The valid coefficients are:\n cL: str, callable, optional Lift coefficient. Can be a path to a CSV file or a callable. Default is 0.\n @@ -86,6 +86,7 @@ def __init__( self.name = name default_coefficients = self._get_default_coefficients() + self._check_coefficients(coefficients, default_coefficients) coefficients = self._complete_coefficients(coefficients, default_coefficients) for coeff, coeff_value in coefficients.items(): value = self._process_input(coeff_value, coeff) @@ -111,7 +112,8 @@ def _get_default_coefficients(self): return default_coefficients def _complete_coefficients(self, input_coefficients, default_coefficients): - """Completes coefficients dictionary from user input in __init__ + """Creates a copy of the input coefficients dict and fill it with missing + keys with default values Parameters ---------- @@ -127,17 +129,37 @@ def _complete_coefficients(self, input_coefficients, default_coefficients): coefficients : dict Coefficients dictionary used to setup coefficient attributes """ - - if input_coefficients == "all_null": - coefficients = default_coefficients - else: # complete user dictionary with null values for the coefficients - coefficients = copy.deepcopy(input_coefficients) - for coeff, value in default_coefficients.items(): - if coeff not in coefficients.keys(): - coefficients[coeff] = value + coefficients = copy.deepcopy(input_coefficients) + for coeff, value in default_coefficients.items(): + if coeff not in coefficients.keys(): + coefficients[coeff] = value return coefficients + def _check_coefficients(self, input_coefficients, default_coefficients): + """Check if input coefficients have only valid keys + + Parameters + ---------- + input_coefficients : str, dict + Coefficients dictionary passed by the user. If the user only specifies some + of the coefficients, the remaining are completed with class default + values + default_coefficients : dict + Default coefficients of the class + + Raises + ------ + ValueError + Raises a value error if the input coefficient has an invalid key + """ + for key in input_coefficients.keys(): + if key not in default_coefficients.keys(): + raise ValueError( + "Invalid coefficient name used in key! Check the " + + "documentation for valid names." + ) + def _compute_from_coefficients( self, rho, diff --git a/rocketpy/rocket/aero_surface/linear_generic_surface.py b/rocketpy/rocket/aero_surface/linear_generic_surface.py index 1c2befd7b..46d79d85b 100644 --- a/rocketpy/rocket/aero_surface/linear_generic_surface.py +++ b/rocketpy/rocket/aero_surface/linear_generic_surface.py @@ -41,8 +41,8 @@ def __init__( Reference length of the aerodynamic surface. Has the unit of meters. Commonly defined as the rocket's diameter. coefficients: dict, optional - List of coefficients. Default is "all_null", which creates a - dict with every coefficient set to 0. The valid coefficients are:\n + List of coefficients. If a coefficient is omitted, it is set to 0. + The valid coefficients are:\n cL_0: callable, str, optional Coefficient of lift at zero angle of attack. Default is 0.\n cL_alpha: callable, str, optional From ce1fe231fb8252ef3f5062ac156dd4629747846c Mon Sep 17 00:00:00 2001 From: Lucas Prates Date: Fri, 13 Sep 2024 20:01:05 -0300 Subject: [PATCH 07/11] MNT: removing coefficient default of linear generic surface --- rocketpy/rocket/aero_surface/linear_generic_surface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rocketpy/rocket/aero_surface/linear_generic_surface.py b/rocketpy/rocket/aero_surface/linear_generic_surface.py index 46d79d85b..35d5ae31e 100644 --- a/rocketpy/rocket/aero_surface/linear_generic_surface.py +++ b/rocketpy/rocket/aero_surface/linear_generic_surface.py @@ -11,7 +11,7 @@ def __init__( self, reference_area, reference_length, - coefficients="all_null", + coefficients, center_of_pressure=(0, 0, 0), name="Generic Linear Surface", ): From 60fd27ed4e2d303dc55cb236d803ac4b3cbb74fd Mon Sep 17 00:00:00 2001 From: MateusStano Date: Sat, 14 Sep 2024 13:48:52 +0200 Subject: [PATCH 08/11] BUG: use surface z instead of cm z in reynolds calculation --- 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 6528381ec..a026183e3 100644 --- a/rocketpy/simulation/flight.py +++ b/rocketpy/simulation/flight.py @@ -1806,10 +1806,10 @@ def u_dot_generalized( # TODO: Reynolds is only used in generic surfaces. This calculation # should be moved to the surface class for efficiency comp_reynolds = ( - self.env.density.get_value_opt(z) + self.env.density.get_value_opt(comp_z) * comp_stream_speed * aero_surface.reference_length - / self.env.dynamic_viscosity.get_value_opt(z) + / self.env.dynamic_viscosity.get_value_opt(comp_z) ) # Forces and moments X, Y, Z, M, N, L = aero_surface.compute_forces_and_moments( From f7b4b03d59eb058cba2686bc81f8434c55f3cef4 Mon Sep 17 00:00:00 2001 From: MateusStano Date: Sat, 14 Sep 2024 14:01:17 +0200 Subject: [PATCH 09/11] ENH: add generic surfaces change to u_dot --- rocketpy/simulation/flight.py | 111 +++++++++++++--------------------- 1 file changed, 42 insertions(+), 69 deletions(-) diff --git a/rocketpy/simulation/flight.py b/rocketpy/simulation/flight.py index a026183e3..6fafc1e85 100644 --- a/rocketpy/simulation/flight.py +++ b/rocketpy/simulation/flight.py @@ -1464,16 +1464,18 @@ def u_dot( a32 = 2 * (e2 * e3 + e0 * e1) a33 = 1 - 2 * (e1**2 + e2**2) # Transformation matrix: (123) -> (XYZ) - K = [[a11, a12, a13], [a21, a22, a23], [a31, a32, a33]] + K = Matrix([[a11, a12, a13], [a21, a22, a23], [a31, a32, a33]]) + Kt = K.transpose # Calculate Forces and Moments # Get freestream speed wind_velocity_x = self.env.wind_velocity_x.get_value_opt(z) wind_velocity_y = self.env.wind_velocity_y.get_value_opt(z) + speed_of_sound = self.env.speed_of_sound.get_value_opt(z) free_stream_speed = ( (wind_velocity_x - vx) ** 2 + (wind_velocity_y - vy) ** 2 + (vz) ** 2 ) ** 0.5 - free_stream_mach = free_stream_speed / self.env.speed_of_sound.get_value_opt(z) + free_stream_mach = free_stream_speed / speed_of_sound # Determine aerodynamics forces # Determine Drag Force @@ -1507,76 +1509,47 @@ def u_dot( vy_b = a12 * vx + a22 * vy + a32 * vz vz_b = a13 * vx + a23 * vy + a33 * vz # Calculate lift and moment for each component of the rocket - for aero_surface, position in self.rocket.aerodynamic_surfaces: - comp_cp = ( - position - self.rocket.center_of_dry_mass_position - ) * self.rocket._csys - aero_surface.cpz - reference_area = aero_surface.reference_area - reference_length = aero_surface.reference_length + velocity_in_body_frame = Vector([vx_b, vy_b, vz_b]) + w = Vector([omega1, omega2, omega3]) + for aero_surface, _ in self.rocket.aerodynamic_surfaces: + # Component cp relative to CDM in body frame + comp_cp = self.rocket.surfaces_cp_to_cdm[aero_surface] # Component absolute velocity in body frame - comp_vx_b = vx_b + comp_cp * omega2 - comp_vy_b = vy_b - comp_cp * omega1 - comp_vz_b = vz_b - # Wind velocity at component - comp_z = z + comp_cp + comp_vb = velocity_in_body_frame + (w ^ comp_cp) + # Wind velocity at component altitude + comp_z = z + (K @ comp_cp).z comp_wind_vx = self.env.wind_velocity_x.get_value_opt(comp_z) comp_wind_vy = self.env.wind_velocity_y.get_value_opt(comp_z) # Component freestream velocity in body frame - comp_wind_vx_b = a11 * comp_wind_vx + a21 * comp_wind_vy - comp_wind_vy_b = a12 * comp_wind_vx + a22 * comp_wind_vy - comp_wind_vz_b = a13 * comp_wind_vx + a23 * comp_wind_vy - comp_stream_vx_b = comp_wind_vx_b - comp_vx_b - comp_stream_vy_b = comp_wind_vy_b - comp_vy_b - comp_stream_vz_b = comp_wind_vz_b - comp_vz_b - comp_stream_speed = ( - comp_stream_vx_b**2 + comp_stream_vy_b**2 + comp_stream_vz_b**2 - ) ** 0.5 - # Component attack angle and lift force - comp_attack_angle = 0 - comp_lift, comp_lift_xb, comp_lift_yb = 0, 0, 0 - if comp_stream_vx_b**2 + comp_stream_vy_b**2 != 0: - # normalize component stream velocity in body frame - comp_stream_vz_bn = comp_stream_vz_b / comp_stream_speed - if -1 * comp_stream_vz_bn < 1: - comp_attack_angle = np.arccos(-comp_stream_vz_bn) - c_lift = aero_surface.cl.get_value_opt( - comp_attack_angle, free_stream_mach - ) - # component lift force magnitude - comp_lift = ( - 0.5 * rho * (comp_stream_speed**2) * reference_area * c_lift - ) - # component lift force components - lift_dir_norm = (comp_stream_vx_b**2 + comp_stream_vy_b**2) ** 0.5 - comp_lift_xb = comp_lift * (comp_stream_vx_b / lift_dir_norm) - comp_lift_yb = comp_lift * (comp_stream_vy_b / lift_dir_norm) - # add to total lift force - R1 += comp_lift_xb - R2 += comp_lift_yb - # Add to total moment - M1 -= (comp_cp) * comp_lift_yb - M2 += (comp_cp) * comp_lift_xb - # Calculates Roll Moment - try: - clf_delta, cld_omega, cant_angle_rad = aero_surface.roll_parameters - M3_forcing = ( - (1 / 2 * rho * free_stream_speed**2) - * reference_area - * reference_length - * clf_delta.get_value_opt(free_stream_mach) - * cant_angle_rad - ) - M3_damping = ( - (1 / 2 * rho * free_stream_speed) - * reference_area - * (reference_length) ** 2 - * cld_omega.get_value_opt(free_stream_mach) - * omega3 - / 2 - ) - M3 += M3_forcing - M3_damping - except AttributeError: - pass + comp_wind_vb = Kt @ Vector([comp_wind_vx, comp_wind_vy, 0]) + comp_stream_velocity = comp_wind_vb - comp_vb + comp_stream_speed = abs(comp_stream_velocity) + comp_stream_mach = comp_stream_speed / speed_of_sound + # Reynolds at component altitude + # TODO: Reynolds is only used in generic surfaces. This calculation + # should be moved to the surface class for efficiency + comp_reynolds = ( + self.env.density.get_value_opt(comp_z) + * comp_stream_speed + * aero_surface.reference_length + / self.env.dynamic_viscosity.get_value_opt(comp_z) + ) + # Forces and moments + X, Y, Z, M, N, L = aero_surface.compute_forces_and_moments( + comp_stream_velocity, + comp_stream_speed, + comp_stream_mach, + rho, + comp_cp, + w, + comp_reynolds, + ) + R1 += X + R2 += Y + R3 += Z + M1 += M + M2 += N + M3 += L # Off center moment M3 += self.rocket.cp_eccentricity_x * R2 - self.rocket.cp_eccentricity_y * R1 @@ -1663,7 +1636,7 @@ def u_dot( (R3 - b * propellant_mass_at_t * (alpha2 - omega1 * omega3) + thrust) / total_mass_at_t, ] - ax, ay, az = np.dot(K, L) + ax, ay, az = K @ Vector(L) az -= self.env.gravity.get_value_opt(z) # Include gravity # Create u_dot From 7f29049a2b52dc53dcb7b6b66c1ec84c2b13e78a Mon Sep 17 00:00:00 2001 From: Lucas Prates Date: Sat, 14 Sep 2024 10:39:03 -0300 Subject: [PATCH 10/11] MNT: applying suggested review changes --- rocketpy/rocket/aero_surface/fins/fins.py | 5 ----- rocketpy/rocket/aero_surface/generic_surface.py | 16 ++++++++-------- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/rocketpy/rocket/aero_surface/fins/fins.py b/rocketpy/rocket/aero_surface/fins/fins.py index 867f106b0..1efeed64f 100644 --- a/rocketpy/rocket/aero_surface/fins/fins.py +++ b/rocketpy/rocket/aero_surface/fins/fins.py @@ -378,11 +378,6 @@ def compute_forces_and_moments( ): # pylint: disable=arguments-differ """Computes the forces and moments acting on the aerodynamic surface. - Parameters - ---------- - stream_speed : int, float - Speed of the flow stream in the body frame. - """ R1, R2, R3, M1, M2, _ = super().compute_forces_and_moments( stream_velocity, diff --git a/rocketpy/rocket/aero_surface/generic_surface.py b/rocketpy/rocket/aero_surface/generic_surface.py index 48fd14f32..b7c803fa3 100644 --- a/rocketpy/rocket/aero_surface/generic_surface.py +++ b/rocketpy/rocket/aero_surface/generic_surface.py @@ -153,12 +153,12 @@ def _check_coefficients(self, input_coefficients, default_coefficients): ValueError Raises a value error if the input coefficient has an invalid key """ - for key in input_coefficients.keys(): - if key not in default_coefficients.keys(): - raise ValueError( - "Invalid coefficient name used in key! Check the " - + "documentation for valid names." - ) + invalid_keys = set(input_coefficients) - set(default_coefficients) + if invalid_keys: + raise ValueError( + f"Invalid coefficient name(s) used in key(s): {', '.join(invalid_keys)}. " + "Check the documentation for valid names." + ) def _compute_from_coefficients( self, @@ -256,10 +256,10 @@ def compute_forces_and_moments( Air density. cp : Vector Center of pressure coordinates in the body frame. + omega: tuple[float, float, float] + Tuple containing angular velocities around the x, y, z axes. reynolds : float Reynolds number. - omega: tuple of float - Tuple containing angular velocities around the x, y, z axes. Returns ------- From c7a41799d6db739de2242e4bf20fd70d5426585e Mon Sep 17 00:00:00 2001 From: Lucas Prates Date: Sat, 14 Sep 2024 10:53:18 -0300 Subject: [PATCH 11/11] MNT: completing compute_forces_and_moments docs for fins class --- rocketpy/rocket/aero_surface/fins/fins.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/rocketpy/rocket/aero_surface/fins/fins.py b/rocketpy/rocket/aero_surface/fins/fins.py index 1efeed64f..e6cfb84ea 100644 --- a/rocketpy/rocket/aero_surface/fins/fins.py +++ b/rocketpy/rocket/aero_surface/fins/fins.py @@ -378,7 +378,28 @@ def compute_forces_and_moments( ): # pylint: disable=arguments-differ """Computes the forces and moments acting on the aerodynamic surface. + Parameters + ---------- + stream_velocity : tuple of float + The velocity of the airflow relative to the surface. + stream_speed : float + The magnitude of the airflow speed. + stream_mach : float + The Mach number of the airflow. + rho : float + Air density. + cp : Vector + Center of pressure coordinates in the body frame. + omega: tuple[float, float, float] + Tuple containing angular velocities around the x, y, z axes. + + Returns + ------- + tuple of float + The aerodynamic forces (lift, side_force, drag) and moments + (pitch, yaw, roll) in the body frame. """ + R1, R2, R3, M1, M2, _ = super().compute_forces_and_moments( stream_velocity, stream_speed,