From 60cff1a7c361c4b597ebfa66cb72d7c6191401d7 Mon Sep 17 00:00:00 2001 From: Mauco03 <marco.gaibotti@skywarder.eu> Date: Fri, 18 Apr 2025 17:07:28 +0200 Subject: [PATCH 01/12] [coeff-transport] Implementing coefficient manager class --- classes/misc/Coefficient.m | 94 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 classes/misc/Coefficient.m diff --git a/classes/misc/Coefficient.m b/classes/misc/Coefficient.m new file mode 100644 index 0000000..d5d60e2 --- /dev/null +++ b/classes/misc/Coefficient.m @@ -0,0 +1,94 @@ +classdef Coefficient + % Motor: Manages the coefficients associated with a rocket + % + % Constructor: + % - Coefficient: Creates an instance of the Coefficient class. + % Loaded config: - + % Loaded data: aeroCoefficients.mat + % Arguments: + % - filePath: char, path to coefficient file to load + % - name: (optional) coefficient name. Used when dynamic + % derivatives are also needed + % + % Coefficients are retrieved from DM in the following format: + % Coefficient matrix: double (15, nAlpha, nMach, nBeta, nAlt, nAbk, nXcg) + % Where the 15 coefficients are + % - CA Axial force + % - CYB Side force derivative wrt beta + % - CY0 Side force + % - CNA Normal force derivative wrt alpha + % - CN0 Normal force + % - Cl Rolling moment + % - Clp Rolling moment derivative wrt roll rate + % - Cma Pitching moment derivative wrt alpha + % - Cm0 Pitching moment + % - Cmad Pitching moment derivative wrt alpha dot + % - Cmq Pitching moment derivative wrt pitch rate + % - Cnb Yawing moment derivative wrt beta + % - Cn0 Yawing moment + % - Cnr Yawing moment derivative wrt yaw rate + % - Cnp Yawing moment derivative wrt roll rate + % + % NOTE: When dynamic derivatives are not loaded, the matrix will be + % 6-D, as there will be no need to interpolate wrt to the xcg: moment + % transport formulas will be used instead + + properties + static % Static coefficients: [CA, CY, CN, Cl, Cm, Cn] + dynamic % Dynamic derivatives: [Clp, Cmad, Cmq, Cnr, Cnp] + + finsCN % Fins-only CN + geometry % Reference geometry + state % Flight envelope + end + + properties(Access = private) + loadDynamic % Whether to load dynamic derivatives. Adds XCG dependece + end + + methods + function obj = Coefficient(filePath, name) + arguments + filePath (1, :) char + name (1, :) char = 'generic' + end + + obj = obj.loadData(filePath, name); + end + end + + methods(Access = private) + function obj = loadData(obj, filePath, name) + % Load the coefficients from the specified file + % filePath: char, path to coefficient file to load + % name: (optional) coefficient name. Used when dynamic + % derivatives are also needed + % + % NOTE: Coefficient alpha and beta derivatives are not loaded + % as coefficients are already interpolated linearly + + + if ~exist(filePath, 'file'), return; end + warning('off'); % Disable warning in case coefficients are not present + dataCoeffs = load(filePath, name); + warning('on'); + + if ~isfield(dataCoeffs, name), return; end + dataCoeffs = dataCoeffs.(name); + + obj.loadDynamic = ~strcmp(name, 'generic'); + obj.finsCN = dataCoeffs.finsCN; + obj.geometry = dataCoeffs.geometry; + obj.state = dataCoeffs.state; + + % Load CA, CY, CN, Cl, Cm, Cn + obj.static = dataCoeffs.total([1, 3, 5, 6, 9, 13], :, :, :, :, :, :); + + if obj.loadDynamic + % Load Clp, Cmad, Cmq, Cnr, Cnp + obj.dynamic = dataCoeffs.total([7, 10, 11, 14, 15], :, :, :, :, :, :); + end + end + end +end + -- GitLab From d8578d7fb9a56073eb4b3df1582193e3888d12ff Mon Sep 17 00:00:00 2001 From: Mauco03 <marco.gaibotti@skywarder.eu> Date: Fri, 18 Apr 2025 22:36:19 +0200 Subject: [PATCH 02/12] [coeff-transport] Implemented coefficient transport system for xcg --- classes/misc/Coefficient.m | 155 +++++++++++++++++++++++++++++++++++-- 1 file changed, 148 insertions(+), 7 deletions(-) diff --git a/classes/misc/Coefficient.m b/classes/misc/Coefficient.m index d5d60e2..a896b41 100644 --- a/classes/misc/Coefficient.m +++ b/classes/misc/Coefficient.m @@ -29,23 +29,46 @@ classdef Coefficient % - Cnr Yawing moment derivative wrt yaw rate % - Cnp Yawing moment derivative wrt roll rate % + % Coefficients are then stored in the following format: + % static: double (6, nAlpha, nMach, nBeta, nAlt, nAbk) + % Where the 6 coefficients are: + % - [CA, CY, CN, Cl, Cm, Cn] + % dynamic: double (5, nAlpha, nMach, nBeta, nAlt, nAbk, nXcg) + % - [Clp, Cmad, Cmq, Cnr, Cnp] + % % NOTE: When dynamic derivatives are not loaded, the matrix will be % 6-D, as there will be no need to interpolate wrt to the xcg: moment % transport formulas will be used instead + % + % NOTE: Coefficients in an interpolants are stored on the last + % dimension, instead of the first, to improve performance + + properties(Dependent) + static double % Static coefficients: [CA, CY, CN, Cl, Cm, Cn] + dynamic double % Dynamic derivatives: [Clp, Cmad, Cmq, Cnr, Cnp] + + geometry struct % Reference geometry + state struct % Flight envelope + end properties - static % Static coefficients: [CA, CY, CN, Cl, Cm, Cn] - dynamic % Dynamic derivatives: [Clp, Cmad, Cmq, Cnr, Cnp] + finsCN double % Fins-only CN + end - finsCN % Fins-only CN - geometry % Reference geometry - state % Flight envelope + properties(Access = private) + STATIC + DYNAMIC + GEOMETRY % Cached variable for geometry + STATE % Cached variable for state end properties(Access = private) - loadDynamic % Whether to load dynamic derivatives. Adds XCG dependece + loadDynamic (1, 1) logical % Whether to load dynamic derivatives. Adds XCG dependece + + staticInterpolant (1, 1) griddedInterpolant + dynamicInterpolant (1, 1) griddedInterpolant end - + methods function obj = Coefficient(filePath, name) arguments @@ -55,6 +78,86 @@ classdef Coefficient obj = obj.loadData(filePath, name); end + + function coefficients = get(obj, alpha, mach, beta, altitude, airbakes, xcg) + % GET: Retrieve aerodynamic coefficients for specified flight conditions + % + % This method interpolates the aerodynamic coefficients based on + % the provided flight conditions and adjusts them for the given + % center of gravity (xcg). + % + % Arguments: + % - alpha double, angle of attack [rad] + % - mach double, Mach number [-] + % - beta double, sideslip angle [rad] + % - altitude double, altitude [m] + % - airbakes double, airbrake deployment [-] + % - xcg double, center of gravity position [m] + % + % Returns: + % - coefficients: double (11, 1), aerodynamic coefficients + % [CA, CY, CN, Cl, Cm, Cn, Clp, Cmad, Cmq, Cnr, Cnp] + % + % NOTE: Static coefficients are interpolated and adjusted for + % the specified xcg. Dynamic derivatives are included only if + % loadDynamic is true. + + coefficients = zeros(11, 1); + coefficients(1:6) = obj.staticInterpolant(alpha, mach, beta, altitude, airbakes); + % Transporting static coefficients to new xcg + + % C_B = C_A + d / c * [0; -CN; CY]_A <- NOTE: Non torna il meno su CN + d = xcg - obj.GEOMETRY.xcg(1); + l = obj.GEOMETRY.diameter; + + CY = coefficients(2); CN = coefficients(3); + forceCoeffs = [0; CN; CY]; + + coefficients(4:6) = coefficients(4:6) + d/l * forceCoeffs; + + if ~obj.loadDynamic, return; end + coefficients(7:11) = obj.dynamicInterpolant(alpha, mach, beta, altitude, airbakes, xcg); + end + end + + methods % Getters + function value = get.static(obj) + value = obj.STATIC; + end + + function value = get.dynamic(obj) + value = obj.DYNAMIC; + end + + function value = get.geometry(obj) + value = obj.GEOMETRY; + end + + function value = get.state(obj) + value = obj.STATE; + end + end + + methods % Setters + function obj = set.static(obj, value) + obj.STATIC = value(:, :, :, :, :, :, 1); + obj = obj.updateInterpolants(); + end + + function obj = set.dynamic(obj, value) + obj.DYNAMIC = value(:, :, :, :, :, :, :); + obj = obj.updateInterpolants(); + end + + function obj = set.geometry(obj, value) + obj.GEOMETRY = value; + obj = obj.updateInterpolants(); + end + + function obj = set.state(obj, value) + obj.STATE = value; + obj = obj.updateInterpolants(); + end end methods(Access = private) @@ -89,6 +192,44 @@ classdef Coefficient obj.dynamic = dataCoeffs.total([7, 10, 11, 14, 15], :, :, :, :, :, :); end end + + function ready = checkProperties(obj) + % Check if STATIC, DYNAMIC, GEOMETRY, and STATE are non-empty + ready = ~isempty(obj.STATIC) && ... + ~isempty(obj.GEOMETRY) && ~isempty(obj.STATE) && ... + ~(isempty(obj.DYNAMIC) && obj.loadDynamic); + end + + function obj = updateInterpolants(obj) + % Update static and dynamic interpolants + if ~obj.checkProperties(), return; end + + %% Retrieve flight conditions + alpha = obj.state.alphas*pi/180; % Converting to rad + mach = obj.state.machs; + beta = obj.state.betas*pi/180; % Converting to rad + altitude = obj.state.altitudes; + airbakes = obj.state.hprot/obj.state.hprot(end); % Normalizing on height + + %% Create interpolants + staticCoeffs = permute(obj.static, [2, 3, 4, 5, 6, 1]); + + gridVecs = {alpha, mach, beta, altitude, airbakes}; + obj.staticInterpolant = griddedInterpolant(gridVecs, staticCoeffs, 'linear', 'nearest'); + + if ~obj.loadDynamic, return; end + + xcg = obj.geometry.xcg; + dynamicCoeffs = permute(obj.dynamic, [2, 3, 4, 5, 6, 7, 1]); + + if xcg(2) < xcg(1) % Flip xcg in case of descending order + xcg = flip(xcg); + dynamicCoeffs = flip(dynamicCoeffs, 7); + end + + gridVecs = {alpha, mach, beta, altitude, airbakes, xcg}; + obj.dynamicInterpolant = griddedInterpolant(gridVecs, dynamicCoeffs, 'linear', 'nearest'); + end end end -- GitLab From 65d160b64ad75c8b5d38e20a741e38f7af3b4af1 Mon Sep 17 00:00:00 2001 From: Mauco03 <marco.gaibotti@skywarder.eu> Date: Sat, 19 Apr 2025 20:28:32 +0200 Subject: [PATCH 03/12] [coeff-transport] Updated comment --- classes/misc/Coefficient.m | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/classes/misc/Coefficient.m b/classes/misc/Coefficient.m index a896b41..f48b71d 100644 --- a/classes/misc/Coefficient.m +++ b/classes/misc/Coefficient.m @@ -1,5 +1,5 @@ classdef Coefficient - % Motor: Manages the coefficients associated with a rocket + % Coefficient: Manages the coefficients associated with a rocket % % Constructor: % - Coefficient: Creates an instance of the Coefficient class. @@ -56,10 +56,10 @@ classdef Coefficient end properties(Access = private) - STATIC - DYNAMIC - GEOMETRY % Cached variable for geometry - STATE % Cached variable for state + STATIC % Cached variable for static coefficients + DYNAMIC % Cached variable for dynamic derivatives + GEOMETRY % Cached variable for geometry + STATE % Cached variable for state end properties(Access = private) -- GitLab From 59db294dfd27fc2869f2050e524b4539664cef8e Mon Sep 17 00:00:00 2001 From: Mauco03 <marco.gaibotti@skywarder.eu> Date: Sun, 20 Apr 2025 10:45:11 +0200 Subject: [PATCH 04/12] [coeff-transport][Rocket] Added option to load dynamic derivatives --- classes/@Rocket/Rocket.m | 30 +++++++++++++++---- .../config/rocketConfig.m | 5 ++++ .../config/rocketConfig.m | 5 ++++ .../config/rocketConfig.m | 5 ++++ .../config/rocketConfig.m | 5 ++++ .../config/rocketConfig.m | 6 ++++ .../config/rocketConfig.m | 5 ++++ .../config/rocketConfig.m | 5 ++++ .../config/rocketConfig.m | 5 ++++ .../config/rocketConfig.m | 9 ++++-- .../config/rocketConfig.m | 5 ++++ 11 files changed, 78 insertions(+), 7 deletions(-) diff --git a/classes/@Rocket/Rocket.m b/classes/@Rocket/Rocket.m index a06283b..7e39e87 100644 --- a/classes/@Rocket/Rocket.m +++ b/classes/@Rocket/Rocket.m @@ -50,9 +50,11 @@ classdef Rocket < Config properties pitot Pitot % [-] Pitot component parachutes cell % [-] (nParachutes, nStages) Parachutes onboard + + dynamicDerivatives (1, 1) logical % [-] True if dynamic derivatives will be loaded - coefficients struct % [-] Aerodynamic coefficients - coefficientsHighAOA struct % [-] Aerodynamic coefficients at high angle of attack + coefficients Coefficient % [-] Aerodynamic coefficients + coefficientsHighAOA Coefficient % [-] Aerodynamic coefficients at high angle of attack end properties(Access = private) @@ -138,9 +140,20 @@ classdef Rocket < Config obj.parachutes = paraLoader.parachutes; if options.loadCoefficients - obj = obj.loadData(); + if obj.dynamicDerivatives, coeffName = obj.motor.name; + else, coeffName = 'generic'; + end + + obj.coefficients = Coefficient( ... + fullfile(mission.dataPath, 'aeroCoefficients.mat'), ... + coeffName); + + obj.coefficientsHighAOA = Coefficient( ... + fullfile(mission.dataPath, 'aeroCoefficientsHighAOA.mat'), ... + coeffName); + answer = ''; - if isempty(obj.coefficients) || isempty(obj.coefficientsHighAOA) + if isempty(obj.coefficients.static) || isempty(obj.coefficientsHighAOA.static) answer = questdlg(['Coefficient matrices not found. ' ... 'Do you want to create new matrices?']); elseif options.checkGeometry && ~all(obj.checkGeometry()) @@ -153,7 +166,14 @@ classdef Rocket < Config parserPath = fullfile(mission.msaPath, 'autoMatricesProtub'); addpath(genpath(parserPath)); mainAutoMatProtub(obj); - obj = obj.loadData(); + + obj.coefficients = Coefficient( ... + fullfile(mission.dataPath, 'aeroCoefficients.mat'), ... + coeffName); + + obj.coefficientsHighAOA = Coefficient( ... + fullfile(mission.dataPath, 'aeroCoefficientsHighAOA.mat'), ... + coeffName); case 'Cancel' error('Rocket creation aborted') otherwise diff --git a/missions/2021_Lynx_Portugal_October/config/rocketConfig.m b/missions/2021_Lynx_Portugal_October/config/rocketConfig.m index 4df34a1..160c5b0 100644 --- a/missions/2021_Lynx_Portugal_October/config/rocketConfig.m +++ b/missions/2021_Lynx_Portugal_October/config/rocketConfig.m @@ -16,6 +16,11 @@ rocket.inertiaNoMotor = [0.0786; 12.972; 12.972]; % [kg*m^2] OVERRIDE iner rocket.xCgNoMotor = 1.6025; % [m] OVERRIDE xCg without motor rocket.lengthCenterNoMot = 1.7640; % [m] OVERRIDE Center length - no nose, no motor +% If dynamic derivatives are loaded, coefficient will depend on rocket xcg +% When false, coefficients are saved with current motor's name +% When true, coefficients are saved as 'generic' +rocket.dynamicDerivatives = false; % [-] True if dynamic derivatives will be loaded + %% PLD - Includes Parafoil + Nose parafoil = Parafoil(); diff --git a/missions/2021_Lynx_Roccaraso_September/config/rocketConfig.m b/missions/2021_Lynx_Roccaraso_September/config/rocketConfig.m index 83f6476..d0dc3df 100644 --- a/missions/2021_Lynx_Roccaraso_September/config/rocketConfig.m +++ b/missions/2021_Lynx_Roccaraso_September/config/rocketConfig.m @@ -16,6 +16,11 @@ rocket.inertiaNoMotor = [0.06; 7.511; 7.512]; % [kg*m^2] OVERRIDE inertia rocket.xCgNoMotor = 1.1986; % [m] OVERRIDE xCg without motor rocket.lengthCenterNoMot = 1.7840; % [m] OVERRIDE Center length - no nose, no motor +% If dynamic derivatives are loaded, coefficient will depend on rocket xcg +% When false, coefficients are saved with current motor's name +% When true, coefficients are saved as 'generic' +rocket.dynamicDerivatives = false; % [-] True if dynamic derivatives will be loaded + %% PLD - Includes Parafoil + Nose parafoil = Parafoil(); diff --git a/missions/2022_Pyxis_Portugal_October/config/rocketConfig.m b/missions/2022_Pyxis_Portugal_October/config/rocketConfig.m index 8cdcf2b..2f949bf 100644 --- a/missions/2022_Pyxis_Portugal_October/config/rocketConfig.m +++ b/missions/2022_Pyxis_Portugal_October/config/rocketConfig.m @@ -16,6 +16,11 @@ rocket.inertiaNoMotor = [0.06303; 9.62497; 9.62524]; % [kg*m^2] OVERRIDE i rocket.xCgNoMotor = 1.2239; % [m] OVERRIDE xCg without motor rocket.lengthCenterNoMot = 1.4470; % [m] OVERRIDE Center length - no nose, no motor +% If dynamic derivatives are loaded, coefficient will depend on rocket xcg +% When false, coefficients are saved with current motor's name +% When true, coefficients are saved as 'generic' +rocket.dynamicDerivatives = false; % [-] True if dynamic derivatives will be loaded + %% PLD - Includes Parafoil + Nose parafoil = Parafoil(); diff --git a/missions/2022_Pyxis_Roccaraso_September/config/rocketConfig.m b/missions/2022_Pyxis_Roccaraso_September/config/rocketConfig.m index bec3d20..ee3a3c7 100644 --- a/missions/2022_Pyxis_Roccaraso_September/config/rocketConfig.m +++ b/missions/2022_Pyxis_Roccaraso_September/config/rocketConfig.m @@ -18,6 +18,11 @@ rocket.xCgNoMotor = 1.2957; % [m] % settings.Lcenter - motors(iMotor).L/1000 rocket.lengthCenterNoMot = 1.61; % [m] OVERRIDE Center length - no nose, no motor +% If dynamic derivatives are loaded, coefficient will depend on rocket xcg +% When false, coefficients are saved with current motor's name +% When true, coefficients are saved as 'generic' +rocket.dynamicDerivatives = false; % [-] True if dynamic derivatives will be loaded + %% PLD - Includes Parafoil + Nose parafoil = Parafoil(); diff --git a/missions/2023_Gemini_Portugal_October/config/rocketConfig.m b/missions/2023_Gemini_Portugal_October/config/rocketConfig.m index 610966a..401c415 100644 --- a/missions/2023_Gemini_Portugal_October/config/rocketConfig.m +++ b/missions/2023_Gemini_Portugal_October/config/rocketConfig.m @@ -15,6 +15,12 @@ rocket.inertiaNoMotor = [0.06535397; 17.21019828; 17.21056483]; % [kg*m^2] rocket.xCgNoMotor = 1.254; % [m] OVERRIDE xCg without motor rocket.lengthCenterNoMot = 1.517; % [m] OVERRIDE Center length - no nose, no motor +% If dynamic derivatives are loaded, coefficient will depend on rocket xcg +% When false, coefficients are saved with current motor's name +% When true, coefficients are saved as 'generic' +rocket.dynamicDerivatives = false; % [-] True if dynamic derivatives will be loaded + + %% PLD - Includes Parafoil + Nose parafoil = Parafoil(); diff --git a/missions/2023_Gemini_Roccaraso_September/config/rocketConfig.m b/missions/2023_Gemini_Roccaraso_September/config/rocketConfig.m index 600065a..fc2715b 100644 --- a/missions/2023_Gemini_Roccaraso_September/config/rocketConfig.m +++ b/missions/2023_Gemini_Roccaraso_September/config/rocketConfig.m @@ -15,6 +15,11 @@ rocket.inertiaNoMotor = [0.06535397; 12.07664659; 12.07701314]; % [kg*m^2] rocket.xCgNoMotor = 1.149; % [m] OVERRIDE xCg without motor rocket.lengthCenterNoMot = 1.517; % [m] OVERRIDE Center length - no nose, no motor +% If dynamic derivatives are loaded, coefficient will depend on rocket xcg +% When false, coefficients are saved with current motor's name +% When true, coefficients are saved as 'generic' +rocket.dynamicDerivatives = false; % [-] True if dynamic derivatives will be loaded + %% PLD - Includes Parafoil + Nose parafoil = Parafoil(); diff --git a/missions/2024_Lyra_Portugal_October/config/rocketConfig.m b/missions/2024_Lyra_Portugal_October/config/rocketConfig.m index 2369c08..f87640c 100644 --- a/missions/2024_Lyra_Portugal_October/config/rocketConfig.m +++ b/missions/2024_Lyra_Portugal_October/config/rocketConfig.m @@ -9,6 +9,11 @@ rocket.inertiaNoMotor = []; % [kg*m^ rocket.xCgNoMotor = []; % [m] OVERRIDE xCg without motor rocket.lengthCenterNoMot = []; % [m] OVERRIDE Center length - no nose, no motor +% If dynamic derivatives are loaded, coefficient will depend on rocket xcg +% When false, coefficients are saved with current motor's name +% When true, coefficients are saved as 'generic' +rocket.dynamicDerivatives = false; % [-] True if dynamic derivatives will be loaded + %% PLD - Includes Parafoil + Nose parafoil = Parafoil(); diff --git a/missions/2024_Lyra_Roccaraso_September/config/rocketConfig.m b/missions/2024_Lyra_Roccaraso_September/config/rocketConfig.m index b2ca5cb..2939db0 100644 --- a/missions/2024_Lyra_Roccaraso_September/config/rocketConfig.m +++ b/missions/2024_Lyra_Roccaraso_September/config/rocketConfig.m @@ -15,6 +15,11 @@ rocket.lengthCenterNoMot = []; % [m] % rocket.xCgNoMotor = 1.28; % [m] OVERRIDE xCg without motor % rocket.lengthCenterNoMot = 1.778; % [m] OVERRIDE Center length - no nose, no motor +% If dynamic derivatives are loaded, coefficient will depend on rocket xcg +% When false, coefficients are saved with current motor's name +% When true, coefficients are saved as 'generic' +rocket.dynamicDerivatives = false; % [-] True if dynamic derivatives will be loaded + %% PLD - Includes Parafoil + Nose parafoil = Parafoil(); diff --git a/missions/2025_Orion_Portugal_October/config/rocketConfig.m b/missions/2025_Orion_Portugal_October/config/rocketConfig.m index c879160..6a13ec2 100644 --- a/missions/2025_Orion_Portugal_October/config/rocketConfig.m +++ b/missions/2025_Orion_Portugal_October/config/rocketConfig.m @@ -3,12 +3,17 @@ %% ROCKET - OVERRIDES BAYS CONFIG rocket = Rocket(); -rocket.diameter = 0.15; % [m] Rocket diameter +rocket.diameter = 0.15; % [m] Rocket diameter % rocket.massNoMotor = []; % [kg] OVERRIDE mass without motor % rocket.inertiaNoMotor = []; % [kg*m^2] OVERRIDE inertia without motor - body axes reference % rocket.xcgNoMotor = []; % [m] OVERRIDE xCg without motor % rocket.lengthCenterNoMot = []; % [m] OVERRIDE Center length - no nose, no motor +% If dynamic derivatives are loaded, coefficient will depend on rocket xcg +% When false, coefficients are saved with current motor's name +% When true, coefficients are saved as 'generic' +rocket.dynamicDerivatives = false; % [-] True if dynamic derivatives will be loaded + %% PRF - Includes Parafoil + Nose parafoil = Parafoil(); @@ -69,7 +74,7 @@ airbrakes.servoTau = 0.0374588; %% MOTOR motor = Motor(); -motor.name = 'HRE_ARM_OPT_3_Update'; +motor.name = 'HRE_ARM_EuRoC_2024'; motor.cutoffTime = []; % [s] OVERRIDE Cutoff time motor.ignitionTransient = 0.3; % [s] Ignition transient diff --git a/missions/2025_Orion_Roccaraso_September/config/rocketConfig.m b/missions/2025_Orion_Roccaraso_September/config/rocketConfig.m index dfc372f..c4e1ffc 100644 --- a/missions/2025_Orion_Roccaraso_September/config/rocketConfig.m +++ b/missions/2025_Orion_Roccaraso_September/config/rocketConfig.m @@ -15,6 +15,11 @@ rocket.lengthCenterNoMot = []; % [m] % rocket.xCgNoMotor = 1.28; % [m] OVERRIDE xCg without motor % rocket.lengthCenterNoMot = 1.778; % [m] OVERRIDE Center length - no nose, no motor +% If dynamic derivatives are loaded, coefficient will depend on rocket xcg +% When false, coefficients are saved with current motor's name +% When true, coefficients are saved as 'generic' +rocket.dynamicDerivatives = false; % [-] True if dynamic derivatives will be loaded + %% PLD - Includes Parafoil + Nose parafoil = Parafoil(); -- GitLab From 1d8b632e76a3c93cca2c7595d6ccce97930e548c Mon Sep 17 00:00:00 2001 From: Mauco03 <marco.gaibotti@skywarder.eu> Date: Sun, 20 Apr 2025 10:46:01 +0200 Subject: [PATCH 05/12] [coeff-transpot][Coefficient] Handles singleton dimension cases and improved performance --- classes/misc/Coefficient.m | 108 +++++++++++++++++++++++++------------ 1 file changed, 75 insertions(+), 33 deletions(-) diff --git a/classes/misc/Coefficient.m b/classes/misc/Coefficient.m index f48b71d..a230fbf 100644 --- a/classes/misc/Coefficient.m +++ b/classes/misc/Coefficient.m @@ -55,6 +55,11 @@ classdef Coefficient finsCN double % Fins-only CN end + properties(SetAccess = private) + isReady (1, 1) logical % Whether all coefficients are loaded and of the right size + isDynamic (1, 1) logical % Whether to load dynamic derivatives. Adds XCG dependece + end + properties(Access = private) STATIC % Cached variable for static coefficients DYNAMIC % Cached variable for dynamic derivatives @@ -63,8 +68,6 @@ classdef Coefficient end properties(Access = private) - loadDynamic (1, 1) logical % Whether to load dynamic derivatives. Adds XCG dependece - staticInterpolant (1, 1) griddedInterpolant dynamicInterpolant (1, 1) griddedInterpolant end @@ -77,6 +80,8 @@ classdef Coefficient end obj = obj.loadData(filePath, name); + obj.isReady = obj.checkProperties(); + obj = obj.updateInterpolants(); end function coefficients = get(obj, alpha, mach, beta, altitude, airbakes, xcg) @@ -102,6 +107,8 @@ classdef Coefficient % the specified xcg. Dynamic derivatives are included only if % loadDynamic is true. + if ~obj.isReady, error('Canno interpolate coefficients: check that all dimensions match'); end + coefficients = zeros(11, 1); coefficients(1:6) = obj.staticInterpolant(alpha, mach, beta, altitude, airbakes); % Transporting static coefficients to new xcg @@ -115,7 +122,7 @@ classdef Coefficient coefficients(4:6) = coefficients(4:6) + d/l * forceCoeffs; - if ~obj.loadDynamic, return; end + if ~obj.isDynamic, return; end coefficients(7:11) = obj.dynamicInterpolant(alpha, mach, beta, altitude, airbakes, xcg); end end @@ -140,23 +147,27 @@ classdef Coefficient methods % Setters function obj = set.static(obj, value) - obj.STATIC = value(:, :, :, :, :, :, 1); - obj = obj.updateInterpolants(); + obj.STATIC = value(:, :, :, :, :, :, 1); + obj.isReady = obj.checkProperties(); + obj = obj.updateInterpolants(); end function obj = set.dynamic(obj, value) obj.DYNAMIC = value(:, :, :, :, :, :, :); - obj = obj.updateInterpolants(); + obj.isReady = obj.checkProperties(); + obj = obj.updateInterpolants(); end function obj = set.geometry(obj, value) obj.GEOMETRY = value; - obj = obj.updateInterpolants(); + obj.isReady = obj.checkProperties(); + obj = obj.updateInterpolants(); end function obj = set.state(obj, value) - obj.STATE = value; - obj = obj.updateInterpolants(); + obj.STATE = value; + obj.isReady = obj.checkProperties(); + obj = obj.updateInterpolants(); end end @@ -179,17 +190,17 @@ classdef Coefficient if ~isfield(dataCoeffs, name), return; end dataCoeffs = dataCoeffs.(name); - obj.loadDynamic = ~strcmp(name, 'generic'); + obj.isDynamic = ~strcmp(name, 'generic'); obj.finsCN = dataCoeffs.finsCN; - obj.geometry = dataCoeffs.geometry; - obj.state = dataCoeffs.state; + obj.GEOMETRY = dataCoeffs.geometry; + obj.STATE = dataCoeffs.state; % Load CA, CY, CN, Cl, Cm, Cn - obj.static = dataCoeffs.total([1, 3, 5, 6, 9, 13], :, :, :, :, :, :); + obj.STATIC = dataCoeffs.total([1, 3, 5, 6, 9, 13], :, :, :, :, :, :); - if obj.loadDynamic + if obj.isDynamic % Load Clp, Cmad, Cmq, Cnr, Cnp - obj.dynamic = dataCoeffs.total([7, 10, 11, 14, 15], :, :, :, :, :, :); + obj.DYNAMIC = dataCoeffs.total([7, 10, 11, 14, 15], :, :, :, :, :, :); end end @@ -197,39 +208,70 @@ classdef Coefficient % Check if STATIC, DYNAMIC, GEOMETRY, and STATE are non-empty ready = ~isempty(obj.STATIC) && ... ~isempty(obj.GEOMETRY) && ~isempty(obj.STATE) && ... - ~(isempty(obj.DYNAMIC) && obj.loadDynamic); + ~(isempty(obj.DYNAMIC) && obj.isDynamic); + + if ~ready, return; end + + alpha = obj.STATE.alphas; + mach = obj.STATE.machs; + beta = obj.STATE.betas; + altitude = obj.STATE.altitudes; + airbakes = obj.STATE.hprot; + xcg = obj.GEOMETRY.xcg; + + gridVecs = {alpha, mach, beta, altitude, airbakes, xcg}; + dims = cellfun(@(x) length(x), gridVecs); + dims(dims == 0) = 1; % Empty case is the same as scalar case + + staticDimsReady = all(size(obj.STATIC, 2:6) == dims(1:end-1)); + dynamicDimsReady = true; + + if obj.isDynamic + dynamicDimsReady = all(size(obj.DYNAMIC, 2:7) == dims); + end + + ready = ready && staticDimsReady && dynamicDimsReady; end function obj = updateInterpolants(obj) % Update static and dynamic interpolants - if ~obj.checkProperties(), return; end + if ~obj.isReady, return; end %% Retrieve flight conditions - alpha = obj.state.alphas*pi/180; % Converting to rad - mach = obj.state.machs; - beta = obj.state.betas*pi/180; % Converting to rad - altitude = obj.state.altitudes; - airbakes = obj.state.hprot/obj.state.hprot(end); % Normalizing on height + alpha = obj.STATE.alphas*pi/180; % Converting to rad + mach = obj.STATE.machs; + beta = obj.STATE.betas*pi/180; % Converting to rad + altitude = obj.STATE.altitudes; + xcg = obj.GEOMETRY.xcg; + + if isempty(obj.STATE.hprot), airbakes = 0; + else, airbakes = obj.STATE.hprot/obj.STATE.hprot(end); % Normalizing on height + end + + gridVecs = {alpha, mach, beta, altitude, airbakes, xcg}; + + % Find singleton dims (last dimension is coefficients and will not be of length 1) + singletonDims = cellfun(@(x) isscalar(x) || isempty(x), gridVecs); + gridVecs(singletonDims) = deal({[0, 1]}); %% Create interpolants - staticCoeffs = permute(obj.static, [2, 3, 4, 5, 6, 1]); + staticCoeffs = permute(obj.STATIC, [2, 3, 4, 5, 6, 1]); + staticCoeffs = repmat(staticCoeffs, singletonDims(1:end-1) + 1); % Exclude xcg for static coefficients - gridVecs = {alpha, mach, beta, altitude, airbakes}; - obj.staticInterpolant = griddedInterpolant(gridVecs, staticCoeffs, 'linear', 'nearest'); + obj.staticInterpolant = griddedInterpolant(gridVecs(1:end-1), staticCoeffs, 'linear', 'nearest'); - if ~obj.loadDynamic, return; end + if ~obj.isDynamic, return; end - xcg = obj.geometry.xcg; - dynamicCoeffs = permute(obj.dynamic, [2, 3, 4, 5, 6, 7, 1]); + %% Handle dynamic derivatives + dynamicCoeffs = permute(obj.DYNAMIC, [2, 3, 4, 5, 6, 7, 1]); - if xcg(2) < xcg(1) % Flip xcg in case of descending order - xcg = flip(xcg); + % Flip xcg in case of descending order + if ~isscalar(xcg) && xcg(2) < xcg(1) + gridVecs{end} = flip(xcg); dynamicCoeffs = flip(dynamicCoeffs, 7); end - gridVecs = {alpha, mach, beta, altitude, airbakes, xcg}; obj.dynamicInterpolant = griddedInterpolant(gridVecs, dynamicCoeffs, 'linear', 'nearest'); end end -end - +end \ No newline at end of file -- GitLab From b426f4da8e8bf3ac0811f6ef3cc30f73f0bb734d Mon Sep 17 00:00:00 2001 From: Mauco03 <marco.gaibotti@skywarder.eu> Date: Sun, 20 Apr 2025 12:16:17 +0200 Subject: [PATCH 06/12] [coeff-transport] Added summary for different coefficients --- classes/@Rocket/Rocket.m | 14 +++- classes/@Rocket/checkGeometry.m | 136 ++++++++++++++++++-------------- 2 files changed, 85 insertions(+), 65 deletions(-) diff --git a/classes/@Rocket/Rocket.m b/classes/@Rocket/Rocket.m index 7e39e87..b904ff4 100644 --- a/classes/@Rocket/Rocket.m +++ b/classes/@Rocket/Rocket.m @@ -153,12 +153,18 @@ classdef Rocket < Config coeffName); answer = ''; + if isempty(obj.coefficients.static) || isempty(obj.coefficientsHighAOA.static) answer = questdlg(['Coefficient matrices not found. ' ... 'Do you want to create new matrices?']); - elseif options.checkGeometry && ~all(obj.checkGeometry()) - answer = questdlg(['Coefficient matrices differ from rocket geometry. ' ... - 'Do you want to create new matrices?']); + elseif options.checkGeometry + [check, summary] = obj.checkGeometry(); + + if ~all(check) + disp(summary); + answer = questdlg(['Coefficient matrices differ from rocket geometry. ' ... + 'Do you want to create new matrices?']); + end end switch answer @@ -184,7 +190,7 @@ classdef Rocket < Config methods [coeffsValues, angle0] = interpCoeffs(obj, t, alpha, mach, beta, alt, c) - checks = checkGeometry(obj) + [checks, summary] = checkGeometry(obj) img = plot(obj) end diff --git a/classes/@Rocket/checkGeometry.m b/classes/@Rocket/checkGeometry.m index e61422f..6608a6d 100644 --- a/classes/@Rocket/checkGeometry.m +++ b/classes/@Rocket/checkGeometry.m @@ -1,11 +1,16 @@ -function checks = checkGeometry(obj) +function [checks, summary] = checkGeometry(obj) % checkGeometry - This methods checks if the rocket geometry % is consistent with the geometry of the % aerodynamic matrices % % OUTPUTS: % - checks (n fields of geometry, 1): boolean value of the geometry checks - + + names = ["Initial xcg"; "Final xcg"; "Ogive type"; "Diameter"; "Nose Length"; + "Nose p Mod"; "Nose c Mod"; "Center Length"; "Fins root chord"; + "Fins free chord"; "Fins height"; "Fins XLE"; "Fins number"; + "Boat Length"; "Boat final diameter"; "Boat type"]; + xCgRocket = round([ ... obj.xcg(1); ... obj.xcg(end) ... @@ -15,69 +20,78 @@ function checks = checkGeometry(obj) obj.coefficients.geometry.xcg(1); obj.coefficients.geometry.xcg(end); ], 3); + + noseTypeCheck = strcmp(obj.coefficients.geometry.ogType, obj.parafoil.noseType); + + geometryRocket = round([ + obj.diameter; + obj.parafoil.noseLength; + 0; 0; % Placeholders for nose p and c mod + obj.lengthCenter; + obj.rear.finsRootChord; + obj.rear.finsFreeChord; + obj.rear.finsHeight; + obj.rear.finsDeltaXFreeChord; + obj.rear.nPanel; + obj.rear.boatLength; + obj.rear.boatFinalDiameter; + ], 3); - if (obj.parafoil.noseCMod & obj.parafoil.nosePMod) == 0 % KARMAN ogive case, no modified p and c coefficients - geometryRocket = round([ - obj.diameter; - obj.parafoil.noseLength; - obj.lengthCenter; - obj.rear.finsRootChord; - obj.rear.finsFreeChord; - obj.rear.finsHeight; - obj.rear.finsDeltaXFreeChord; - obj.rear.nPanel; - obj.rear.boatLength; - obj.rear.boatFinalDiameter; - ], 3); - - geometryTest = round([ - obj.coefficients.geometry.diameter; - obj.coefficients.geometry.lNose; - obj.coefficients.geometry.lCenter; - obj.coefficients.geometry.chord1; - obj.coefficients.geometry.chord2; - obj.coefficients.geometry.height; - obj.coefficients.geometry.deltaXLE; - obj.coefficients.geometry.nPanel; - obj.coefficients.geometry.boatL; - obj.coefficients.geometry.boatD; - ], 3); - else % MHAAK ogive case, modified p and c coefficients - geometryRocket = round([ - obj.diameter; - obj.parafoil.noseLength; - obj.lengthCenter; + geometryTest = round([ + obj.coefficients.geometry.diameter; + obj.coefficients.geometry.lNose; + 0; 0; % Placeholders for nose p and c mod + obj.coefficients.geometry.lCenter; + obj.coefficients.geometry.chord1; + obj.coefficients.geometry.chord2; + obj.coefficients.geometry.height; + obj.coefficients.geometry.deltaXLE; + obj.coefficients.geometry.nPanel; + obj.coefficients.geometry.boatL; + obj.coefficients.geometry.boatD; + ], 3); + + if (obj.parafoil.noseCMod && obj.parafoil.nosePMod) + geometryRocket([3, 4]) = round([ ... obj.parafoil.noseCMod; - obj.parafoil.nosePMod; - obj.rear.finsRootChord; - obj.rear.finsFreeChord; - obj.rear.finsHeight; - obj.rear.finsDeltaXFreeChord; - obj.rear.nPanel; - obj.rear.boatLength; - obj.rear.boatFinalDiameter; - ], 3); - - geometryTest = round([ - obj.coefficients.geometry.diameter; - obj.coefficients.geometry.lNose; - obj.coefficients.geometry.lCenter; - obj.coefficients.geometry.cMod; - obj.coefficients.geometry.pMod; - obj.coefficients.geometry.chord1; - obj.coefficients.geometry.chord2; - obj.coefficients.geometry.height; - obj.coefficients.geometry.deltaXLE; - obj.coefficients.geometry.nPanel; - obj.coefficients.geometry.boatL; - obj.coefficients.geometry.boatD; - ], 3); + obj.parafoil.nosePMod;], 4); + + geometryTest([3, 4]) = round([ ... + obj.parafoil.noseCMod; + obj.parafoil.nosePMod;], 4); end - + + boatTypeCheck = strcmp(obj.coefficients.geometry.boatType, obj.rear.boatType); + checks = [ - xCgRocket == xCgTest; - strcmp(obj.coefficients.geometry.ogType, obj.parafoil.noseType); + ~obj.dynamicDerivatives | (xCgRocket == xCgTest); + noseTypeCheck; geometryRocket == geometryTest; - strcmp(obj.coefficients.geometry.boatType, obj.rear.boatType) + boatTypeCheck; ]; + + if nargout == 2 + % Summarize data and leave placeholders + ogiveType = ["CONE", "OGIVE", "POWER", "HAACK", "KARMAN", "MHAACK"]; + boatType = ["CONE", "OGIVE"]; + + % Convert text to DATCOM standard. !TODO: Si farebbe meglio con un + % enum + boatRocket = find(strcmp(obj.rear.boatType, boatType), 1, 'first') - 1; + boatTest = find(strcmp(obj.coefficients.geometry.boatType, boatType), 1, 'first') - 1; + + ogRocket = find(strcmp(obj.parafoil.noseType, ogiveType), 1, 'first') - 1; + ogTest = find(strcmp(obj.coefficients.geometry.ogType, ogiveType), 1, 'first') - 1; + + rocketData = [xCgRocket; ogRocket; geometryRocket; boatRocket]; + testData = [xCgTest; ogTest; geometryTest; boatTest]; + checkNames = names(~checks); + + if isempty(checkNames) + summary = table; + else + summary = table(rocketData(~checks), testData(~checks), ... + 'RowNames', checkNames, 'VariableNames', ["Rocket", "Coefficients"]); + end + end end \ No newline at end of file -- GitLab From eb8921893427d12d3e4761c710e6615ed7e4e639 Mon Sep 17 00:00:00 2001 From: Mauco03 <marco.gaibotti@skywarder.eu> Date: Sun, 20 Apr 2025 15:19:39 +0200 Subject: [PATCH 07/12] [coeff-transpot] Added total coefficients --- classes/misc/Coefficient.m | 70 +++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 39 deletions(-) diff --git a/classes/misc/Coefficient.m b/classes/misc/Coefficient.m index a230fbf..4af9ad6 100644 --- a/classes/misc/Coefficient.m +++ b/classes/misc/Coefficient.m @@ -10,7 +10,7 @@ classdef Coefficient % - name: (optional) coefficient name. Used when dynamic % derivatives are also needed % - % Coefficients are retrieved from DM in the following format: + % Coefficients are retrieved from DATCOM in the following format: % Coefficient matrix: double (15, nAlpha, nMach, nBeta, nAlt, nAbk, nXcg) % Where the 15 coefficients are % - CA Axial force @@ -44,9 +44,7 @@ classdef Coefficient % dimension, instead of the first, to improve performance properties(Dependent) - static double % Static coefficients: [CA, CY, CN, Cl, Cm, Cn] - dynamic double % Dynamic derivatives: [Clp, Cmad, Cmq, Cnr, Cnp] - + total double % Coefficients stored in DATCOM format geometry struct % Reference geometry state struct % Flight envelope end @@ -55,14 +53,19 @@ classdef Coefficient finsCN double % Fins-only CN end + properties(Dependent, SetAccess = private) + static double % Static coefficients: [CA, CY, CN, Cl, Cm, Cn] + dynamic double % Dynamic derivatives: [Clp, Cmad, Cmq, Cnr, Cnp] + end + + properties(SetAccess = private) isReady (1, 1) logical % Whether all coefficients are loaded and of the right size isDynamic (1, 1) logical % Whether to load dynamic derivatives. Adds XCG dependece end properties(Access = private) - STATIC % Cached variable for static coefficients - DYNAMIC % Cached variable for dynamic derivatives + TOTAL % Cached variable for total GEOMETRY % Cached variable for geometry STATE % Cached variable for state end @@ -75,9 +78,11 @@ classdef Coefficient methods function obj = Coefficient(filePath, name) arguments - filePath (1, :) char + filePath (1, :) char = '' name (1, :) char = 'generic' end + + if isempty(filePath), return; end obj = obj.loadData(filePath, name); obj.isReady = obj.checkProperties(); @@ -128,12 +133,17 @@ classdef Coefficient end methods % Getters + function value = get.total(obj) + value = obj.TOTAL; + end + function value = get.static(obj) - value = obj.STATIC; + value = obj.TOTAL([1, 3, 5, 6, 9, 13], :, :, :, :, :, 1); end function value = get.dynamic(obj) - value = obj.DYNAMIC; + if ~obj.isDynamic, value = []; return; end + value = obj.TOTAL([7, 10, 11, 14, 15], :, :, :, :, :, :); end function value = get.geometry(obj) @@ -146,16 +156,11 @@ classdef Coefficient end methods % Setters - function obj = set.static(obj, value) - obj.STATIC = value(:, :, :, :, :, :, 1); - obj.isReady = obj.checkProperties(); - obj = obj.updateInterpolants(); - end - - function obj = set.dynamic(obj, value) - obj.DYNAMIC = value(:, :, :, :, :, :, :); - obj.isReady = obj.checkProperties(); - obj = obj.updateInterpolants(); + function obj = set.total(obj, value) + obj.TOTAL = value; + obj.isDynamic = size(value, 7) ~= 1; + obj.isReady = obj.checkProperties(); + obj = obj.updateInterpolants(); end function obj = set.geometry(obj, value) @@ -190,25 +195,19 @@ classdef Coefficient if ~isfield(dataCoeffs, name), return; end dataCoeffs = dataCoeffs.(name); - obj.isDynamic = ~strcmp(name, 'generic'); obj.finsCN = dataCoeffs.finsCN; obj.GEOMETRY = dataCoeffs.geometry; obj.STATE = dataCoeffs.state; - % Load CA, CY, CN, Cl, Cm, Cn - obj.STATIC = dataCoeffs.total([1, 3, 5, 6, 9, 13], :, :, :, :, :, :); - - if obj.isDynamic - % Load Clp, Cmad, Cmq, Cnr, Cnp - obj.DYNAMIC = dataCoeffs.total([7, 10, 11, 14, 15], :, :, :, :, :, :); - end + % Load coefficients + obj.total = dataCoeffs.total; end function ready = checkProperties(obj) % Check if STATIC, DYNAMIC, GEOMETRY, and STATE are non-empty - ready = ~isempty(obj.STATIC) && ... + ready = ~isempty(obj.static) && ... ~isempty(obj.GEOMETRY) && ~isempty(obj.STATE) && ... - ~(isempty(obj.DYNAMIC) && obj.isDynamic); + ~(isempty(obj.dynamic) && obj.isDynamic); if ~ready, return; end @@ -223,14 +222,7 @@ classdef Coefficient dims = cellfun(@(x) length(x), gridVecs); dims(dims == 0) = 1; % Empty case is the same as scalar case - staticDimsReady = all(size(obj.STATIC, 2:6) == dims(1:end-1)); - dynamicDimsReady = true; - - if obj.isDynamic - dynamicDimsReady = all(size(obj.DYNAMIC, 2:7) == dims); - end - - ready = ready && staticDimsReady && dynamicDimsReady; + ready = all(size(obj.total, 2:7) == dims); end function obj = updateInterpolants(obj) @@ -255,7 +247,7 @@ classdef Coefficient gridVecs(singletonDims) = deal({[0, 1]}); %% Create interpolants - staticCoeffs = permute(obj.STATIC, [2, 3, 4, 5, 6, 1]); + staticCoeffs = permute(obj.static, [2, 3, 4, 5, 6, 1]); staticCoeffs = repmat(staticCoeffs, singletonDims(1:end-1) + 1); % Exclude xcg for static coefficients obj.staticInterpolant = griddedInterpolant(gridVecs(1:end-1), staticCoeffs, 'linear', 'nearest'); @@ -263,7 +255,7 @@ classdef Coefficient if ~obj.isDynamic, return; end %% Handle dynamic derivatives - dynamicCoeffs = permute(obj.DYNAMIC, [2, 3, 4, 5, 6, 7, 1]); + dynamicCoeffs = permute(obj.dynamic, [2, 3, 4, 5, 6, 7, 1]); % Flip xcg in case of descending order if ~isscalar(xcg) && xcg(2) < xcg(1) -- GitLab From 089ef921e07c9330a856353678779d19c3023a81 Mon Sep 17 00:00:00 2001 From: Mauco03 <marco.gaibotti@skywarder.eu> Date: Sun, 20 Apr 2025 16:04:06 +0200 Subject: [PATCH 08/12] [coeff-transport][ballistic] Implemented new get method for coefficients and removed XCPlon and XCPlat --- functions/odeFunctions/ballistic.m | 66 +++++------------------------- 1 file changed, 11 insertions(+), 55 deletions(-) diff --git a/functions/odeFunctions/ballistic.m b/functions/odeFunctions/ballistic.m index 0e16abd..d08b306 100644 --- a/functions/odeFunctions/ballistic.m +++ b/functions/odeFunctions/ballistic.m @@ -96,11 +96,13 @@ if theta < tb m = interpLinear(rocket.motor.time, rocket.mass, theta); T = interpLinear(rocket.motor.time, rocket.motor.thrust, theta); Pe = interpLinear(rocket.motor.time, rocket.motor.pe, theta); + xcg = interpLinear(rocket.motor.time, rocket.xcg, theta); T = T + rocket.motor.ae*(Pe - P); I = interpLinear(rocket.motor.time, rocket.inertia, theta); Idot = interpLinear(rocket.motor.time, rocket.inertiaDot, theta); else % for theta >= tb the flight condition is the empty one (no interpolation needed) + xcg = rocket.xcg(end); m = rocket.cutoffMass; T = 0; @@ -143,53 +145,20 @@ betaOut = beta; %% INTERPOLATE AERODYNAMIC COEFFICIENTS: -if abs(alpha)>25*pi/180 || abs(beta)>25*pi/180 - coeffsValues = interpN( rocket.coefficientsHighAOA.total,... - {rocket.coefficientsHighAOA.state.alphas, rocket.coefficientsHighAOA.state.machs, ... - rocket.coefficientsHighAOA.state.betas, rocket.coefficientsHighAOA.state.altitudes}, ... - [alpha, mach, beta, absoluteAltitude]); - angle0 = [alpha beta]; +if abs(alpha) > rocket.coefficients.state.alphas(end)*pi/180 || ... + abs(beta) > rocket.coefficients.state.betas(end)*pi/180 + coeffsValues = rocket.coefficientsHighAOA.get(alpha, mach, beta, absoluteAltitude, extension, xcg); else - %[coeffsValues, angle0] = rocket.interpCoeffs(theta, alpha, mach, beta, absoluteAltitude, opening); - [coeffsValues, angle0] = interpCoeffs(rocket.coefficients, theta, rocket.motor.cutoffTime, ... - alpha, mach, beta, absoluteAltitude, extension); + coeffsValues = rocket.coefficients.get(alpha, mach, beta, absoluteAltitude, extension, xcg); end % Retrieve Coefficients -CA = coeffsValues(1); CYB = coeffsValues(2); CY0 = coeffsValues(3); -CNA = coeffsValues(4); CN0 = coeffsValues(5); Cl = coeffsValues(6); -Clp = coeffsValues(7); Cma = coeffsValues(8); Cm0 = coeffsValues(9); -Cmad = coeffsValues(10); Cmq = coeffsValues(11); Cnb = coeffsValues(12); -Cn0 = coeffsValues(13); Cnr = coeffsValues(14); Cnp = coeffsValues(15); - -% Clb = coeffsValues(16); -% XCP_value = coeffsValues(16); - -% compute CN,CY,Cm,Cn (linearized with respect to alpha and beta): -alpha0 = angle0(1); beta0 = angle0(2); - -CN = (CN0 + CNA*(alpha - alpha0)); -CY = (CY0 + CYB*(beta - beta0)); -Cm = (Cm0 + Cma*(alpha - alpha0)); -Cn = (Cn0 + Cnb*(beta - beta0)); - -% XCPlon = Cm/CN; -if abs(alpha) <= pi/180 - XCPlon = Cma/CNA; -else - XCPlon = Cm/CN; -end - -% XCPlat = Cn/CY; -if abs(beta) <= pi/180 - XCPlat = Cnb/CYB; -else - XCPlat = Cn/CY; -end +CA = coeffsValues(1); CY = coeffsValues(2); CN = coeffsValues(3); +Cl = coeffsValues(4); Cm = coeffsValues(5); Cn = coeffsValues(6); +Clp = coeffsValues(7); Cmad = coeffsValues(8); Cmq = coeffsValues(9); +Cnr = coeffsValues(10); Cnp = coeffsValues(11); -% if Cn == 0 && CY == 0 -% XCPlat = -5; -% end +XCPtot = NaN; %% RAMP / FREE FLIGHT if altitude < environment.effectiveRampAltitude % No torque on the launchpad @@ -209,14 +178,10 @@ if altitude < environment.effectiveRampAltitude % No betaOut = NaN; fY = 0; fZ = 0; - XCPlon = NaN; - XCPlat = NaN; if T < Fg % No velocity untill T = Fg du = 0; end - - XCPtot = NaN; else %% FORCES % first computed in the body-frame reference system @@ -255,8 +220,6 @@ else CMaTot = cos(phi)*Cm - sin(phi)*Cn; if CFaTot ~= 0 XCPtot = CMaTot/CFaTot; - else - XCPtot = XCPlon; end dAngle = 0; @@ -331,19 +294,12 @@ if nargout == 2 || ~isempty(wrapper) parout.accelerometer.body_acc = ([-fX+T, fY, -fZ]')/m; parout.coeff.CA = CA; - parout.coeff.CYB = CYB; - parout.coeff.CNA = CNA; parout.coeff.Cl = Cl; parout.coeff.Clp = Clp; - %parout.coeff.Clb = Clb; - parout.coeff.Cma = Cma; parout.coeff.Cmad = Cmad; parout.coeff.Cmq = Cmq; - parout.coeff.Cnb = Cnb; parout.coeff.Cnr = Cnr; parout.coeff.Cnp = Cnp; - parout.coeff.XCPlon = XCPlon; - parout.coeff.XCPlat = XCPlat; parout.coeff.XCPtot = XCPtot; parout.uncertanty = []; -- GitLab From 90b1689a2f35c9e5cca62ca2bd755bf79e28391e Mon Sep 17 00:00:00 2001 From: Mauco03 <marco.gaibotti@skywarder.eu> Date: Sun, 20 Apr 2025 21:17:15 +0200 Subject: [PATCH 09/12] [coeff-transport][Wind] Added internal griddedInterpolant to wind --- classes/misc/Wind.m | 116 ++++++++++++++++++++++++++------------------ 1 file changed, 69 insertions(+), 47 deletions(-) diff --git a/classes/misc/Wind.m b/classes/misc/Wind.m index f3eb9a9..f3d6707 100644 --- a/classes/misc/Wind.m +++ b/classes/misc/Wind.m @@ -9,6 +9,7 @@ classdef Wind < Config % - mission: Mission, mission object % - varIn: (optional) config source. Alternative to config.m % file + properties(Dependent) altitudes (1, :) @@ -25,6 +26,9 @@ classdef Wind < Config end properties(Access = private) + isReady (1, 1) logical % true if all properties are set + dataInerpolant (1, 1) griddedInterpolant % [-] Interpolates [magnitude, azimuth, elevation] + ALTITUDES MAGNITUDE_DISTRIBUTION = '' @@ -41,77 +45,71 @@ classdef Wind < Config elevation magnitude end - - properties(Access = private) - checks (1, 3) logical % true if #[magnitude, azimuth, elevation] == #altitudes - end properties(Constant, Access = protected) configName = 'windConfig.m' variableName = 'windCustom' end - methods % Getters / Setters - function obj = set.altitudes(obj, value) - if length(value) ~= length(obj.ALTITUDES) - obj.ALTITUDES = value; - obj.magnitude = obj.updateDistribution(obj.MAGNITUDE_DISTRIBUTION, obj.MAGNITUDE_PARAMETERS); - obj.azimuth = obj.updateDistribution(obj.AZIMUTH_DISTRIBUTION, obj.AZIMUTH_PARAMETERS); - obj.elevation = obj.updateDistribution(obj.ELEVATION_DISTRIBUTION, obj.ELEVATION_PARAMETERS); - else - obj.ALTITUDES = value; - end + methods % Getters + function value = get.altitudes(obj), value = obj.ALTITUDES; end + + function value = get.magnitudeDistribution(obj), value = obj.MAGNITUDE_DISTRIBUTION; end + function value = get.azimuthDistribution(obj), value = obj.AZIMUTH_DISTRIBUTION; end + function value = get.elevationDistribution(obj), value = obj.ELEVATION_DISTRIBUTION; end + + function value = get.magnitudeParameters(obj), value = obj.MAGNITUDE_PARAMETERS; end + function value = get.azimuthParameters(obj), value = obj.AZIMUTH_PARAMETERS; end + function value = get.elevationParameters(obj), value = obj.ELEVATION_PARAMETERS; end + end - obj.checks(1) = length(obj.magnitude) == length(obj.ALTITUDES); - obj.checks(2) = length(obj.azimuth) == length(obj.ALTITUDES); - obj.checks(3) = length(obj.elevation) == length(obj.ALTITUDES); + methods % Setters + function obj = set.altitudes(obj, value) + obj.ALTITUDES = value; + obj = obj.updateAll(); end function obj = set.magnitudeDistribution(obj, value) obj.MAGNITUDE_DISTRIBUTION = value; + obj.isReady = obj.checkProperties(); obj.magnitude = obj.updateDistribution(value, obj.MAGNITUDE_PARAMETERS); - obj.checks(1) = length(obj.magnitude) == length(obj.ALTITUDES); + obj = obj.updateInterpolant(); end function obj = set.magnitudeParameters(obj, value) obj.MAGNITUDE_PARAMETERS = value; + obj.isReady = obj.checkProperties(); obj.magnitude = obj.updateDistribution(obj.MAGNITUDE_DISTRIBUTION, value); - obj.checks(1) = length(obj.magnitude) == length(obj.ALTITUDES); + obj = obj.updateInterpolant(); end function obj = set.azimuthDistribution(obj, value) obj.AZIMUTH_DISTRIBUTION = value; + obj.isReady = obj.checkProperties(); obj.azimuth = obj.updateDistribution(value, obj.AZIMUTH_PARAMETERS); - obj.checks(2) = length(obj.azimuth) == length(obj.ALTITUDES); + obj = obj.updateInterpolant(); end function obj = set.azimuthParameters(obj, value) obj.AZIMUTH_PARAMETERS = value; + obj.isReady = obj.checkProperties(); obj.azimuth = obj.updateDistribution(obj.AZIMUTH_DISTRIBUTION, value); - obj.checks(2) = length(obj.azimuth) == length(obj.ALTITUDES); + obj = obj.updateInterpolant(); end function obj = set.elevationDistribution(obj, value) obj.ELEVATION_DISTRIBUTION = value; + obj.isReady = obj.checkProperties(); obj.elevation = obj.updateDistribution(value, obj.ELEVATION_PARAMETERS); - obj.checks(3) = length(obj.elevation) == length(obj.ALTITUDES); + obj = obj.updateInterpolant(); end function obj = set.elevationParameters(obj, value) obj.ELEVATION_PARAMETERS = value; + obj.isReady = obj.checkProperties(); obj.elevation = obj.updateDistribution(obj.ELEVATION_DISTRIBUTION, value); - obj.checks(3) = length(obj.elevation) == length(obj.ALTITUDES); + obj = obj.updateInterpolant(); end - - function value = get.altitudes(obj), value = obj.ALTITUDES; end - - function value = get.magnitudeDistribution(obj), value = obj.MAGNITUDE_DISTRIBUTION; end - function value = get.azimuthDistribution(obj), value = obj.AZIMUTH_DISTRIBUTION; end - function value = get.elevationDistribution(obj), value = obj.ELEVATION_DISTRIBUTION; end - - function value = get.magnitudeParameters(obj), value = obj.MAGNITUDE_PARAMETERS; end - function value = get.azimuthParameters(obj), value = obj.AZIMUTH_PARAMETERS; end - function value = get.elevationParameters(obj), value = obj.ELEVATION_PARAMETERS; end end methods @@ -123,18 +121,8 @@ classdef Wind < Config obj@Config(mission, varIn); end - function obj = updateAll(obj) - obj.magnitude = obj.updateDistribution(obj.MAGNITUDE_DISTRIBUTION, obj.MAGNITUDE_PARAMETERS); - obj.azimuth = obj.updateDistribution(obj.AZIMUTH_DISTRIBUTION, obj.AZIMUTH_PARAMETERS); - obj.elevation = obj.updateDistribution(obj.ELEVATION_DISTRIBUTION, obj.ELEVATION_PARAMETERS); - - obj.checks(1) = length(obj.magnitude) == length(obj.ALTITUDES); - obj.checks(2) = length(obj.azimuth) == length(obj.ALTITUDES); - obj.checks(3) = length(obj.elevation) == length(obj.ALTITUDES); - end - function [uw, vw, ww] = getVels(obj, z) - if ~all(obj.checks) + if ~obj.isReady error(['Parameters and distributions must be the same ' ... 'size as altitudes or scalar']); end @@ -144,18 +132,52 @@ classdef Wind < Config vw = round( - obj.magnitude * sin(obj.azimuth) * cos(obj.elevation), 6); ww = round( - obj.magnitude * (-sin(obj.elevation)), 6); else - mag = interpLinear(obj.altitudes, obj.magnitude, z, true); - az = interpLinear(obj.altitudes, obj.azimuth, z, true); - el = interpLinear(obj.altitudes, obj.elevation, z, true); + data = obj.dataInerpolant(z); + mag = data(1); az = data(2); el = data(3); uw = round( - mag * cos(az) * cos(el), 6); vw = round( - mag * sin(az) * cos(el), 6); ww = round( - mag * (-sin(el)), 6); end end + + function obj = updateAll(obj) + obj.magnitude = obj.updateDistribution(obj.MAGNITUDE_DISTRIBUTION, obj.MAGNITUDE_PARAMETERS); + obj.azimuth = obj.updateDistribution(obj.AZIMUTH_DISTRIBUTION, obj.AZIMUTH_PARAMETERS); + obj.elevation = obj.updateDistribution(obj.ELEVATION_DISTRIBUTION, obj.ELEVATION_PARAMETERS); + + obj.isReady = obj.checkProperties(); + obj = obj.updateInterpolant(); + end + + function obj = updateInterpolant(obj) + % UPDATEINTERPOLANT: Interpolates [magnitude; azimuth; elevation] based on altitude + % + % Returns: + % - interpolant: griddedInterpolant for [magnitude; azimuth; elevation] + + if ~obj.isReady, return; end + if isscalar(obj.ALTITUDES), return; end + + % Prepare data for interpolation + data = [obj.magnitude; obj.azimuth; obj.elevation]; + gridVec = obj.ALTITUDES; + + % Create gridded interpolant + obj.dataInerpolant = griddedInterpolant(gridVec, data', 'linear', 'nearest'); + end end methods(Access = private) + function ready = checkProperties(obj) + % Check if STATIC, DYNAMIC, GEOMETRY, and STATE are non-empty + + ready = ... + length(obj.magnitude) == length(obj.ALTITUDES) && ... + length(obj.azimuth) == length(obj.ALTITUDES) && ... + length(obj.elevation) == length(obj.ALTITUDES); + end + function vec = updateDistribution(obj, dist, parameters) s = length(obj.ALTITUDES); vec = []; -- GitLab From 1e412e1072b2aea9426eec55022bdc932f591868 Mon Sep 17 00:00:00 2001 From: Mauco03 <marco.gaibotti@skywarder.eu> Date: Sun, 20 Apr 2025 21:36:38 +0200 Subject: [PATCH 10/12] [coeff-transport] Improved coefficient generation --- classes/@Rocket/Rocket.m | 11 ++--------- classes/misc/Coefficient.m | 5 +++-- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/classes/@Rocket/Rocket.m b/classes/@Rocket/Rocket.m index b904ff4..019b137 100644 --- a/classes/@Rocket/Rocket.m +++ b/classes/@Rocket/Rocket.m @@ -171,15 +171,8 @@ classdef Rocket < Config case 'Yes' parserPath = fullfile(mission.msaPath, 'autoMatricesProtub'); addpath(genpath(parserPath)); - mainAutoMatProtub(obj); - - obj.coefficients = Coefficient( ... - fullfile(mission.dataPath, 'aeroCoefficients.mat'), ... - coeffName); - - obj.coefficientsHighAOA = Coefficient( ... - fullfile(mission.dataPath, 'aeroCoefficientsHighAOA.mat'), ... - coeffName); + [obj.coefficients, obj.coefficientsHighAOA] = ... + mainAutoMatProtub(obj); case 'Cancel' error('Rocket creation aborted') otherwise diff --git a/classes/misc/Coefficient.m b/classes/misc/Coefficient.m index 4af9ad6..f7eb51b 100644 --- a/classes/misc/Coefficient.m +++ b/classes/misc/Coefficient.m @@ -112,7 +112,7 @@ classdef Coefficient % the specified xcg. Dynamic derivatives are included only if % loadDynamic is true. - if ~obj.isReady, error('Canno interpolate coefficients: check that all dimensions match'); end + if ~obj.isReady, error('Cannot interpolate coefficients: check that all dimensions match'); end coefficients = zeros(11, 1); coefficients(1:6) = obj.staticInterpolant(alpha, mach, beta, altitude, airbakes); @@ -138,11 +138,12 @@ classdef Coefficient end function value = get.static(obj) + if isempty(obj.TOTAL), value = []; return; end value = obj.TOTAL([1, 3, 5, 6, 9, 13], :, :, :, :, :, 1); end function value = get.dynamic(obj) - if ~obj.isDynamic, value = []; return; end + if ~obj.isDynamic || isempty(obj.TOTAL), value = []; return; end value = obj.TOTAL([7, 10, 11, 14, 15], :, :, :, :, :, :); end -- GitLab From 7237a4fc0a55268a969b260ec02a989f81a245d0 Mon Sep 17 00:00:00 2001 From: Mauco03 <marco.gaibotti@skywarder.eu> Date: Mon, 21 Apr 2025 12:31:14 +0200 Subject: [PATCH 11/12] [coeff-transport][Mission] Added prp, gnc and dissile path to mission --- classes/Mission.m | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/classes/Mission.m b/classes/Mission.m index 12cb70f..749a695 100644 --- a/classes/Mission.m +++ b/classes/Mission.m @@ -23,6 +23,9 @@ classdef Mission < Config configPath char dataPath char msaPath char + prpPath char + gncPath char + dissilePath char end properties(Access = private) @@ -98,6 +101,12 @@ classdef Mission < Config obj.dataPath = fullfile(obj.currentPath, obj.name, 'data'); obj.msaPath = trimPath( ... fullfile(obj.currentPath, '..', '..', '..', 'msa-toolkit')); + obj.prpPath = trimPath( ... + fullfile(obj.currentPath, '..', '..', '..', 'prp-toolkit')); + obj.gncPath = trimPath( ... + fullfile(obj.currentPath, '..', '..', '..', 'matlab-simulator')); + obj.dissilePath = trimPath( ... + fullfile(obj.currentPath, '..', '..', '..', 'dissilematcom')); end function updateMatlabPath(obj) -- GitLab From a2e17ddf31f82791e2036148792a3c3ef94b809a Mon Sep 17 00:00:00 2001 From: Mauco03 <marco.gaibotti@skywarder.eu> Date: Mon, 21 Apr 2025 12:31:51 +0200 Subject: [PATCH 12/12] [coeff-transport] Applied suggestions for MR --- classes/@Rocket/Rocket.m | 27 +++++++++++++++++++++++++-- classes/@Rocket/checkGeometry.m | 4 ++-- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/classes/@Rocket/Rocket.m b/classes/@Rocket/Rocket.m index 019b137..825e836 100644 --- a/classes/@Rocket/Rocket.m +++ b/classes/@Rocket/Rocket.m @@ -140,8 +140,10 @@ classdef Rocket < Config obj.parachutes = paraLoader.parachutes; if options.loadCoefficients - if obj.dynamicDerivatives, coeffName = obj.motor.name; - else, coeffName = 'generic'; + if obj.dynamicDerivatives + coeffName = obj.motor.name; + else + coeffName = 'generic'; end obj.coefficients = Coefficient( ... @@ -169,6 +171,27 @@ classdef Rocket < Config switch answer case 'Yes' + % Check for required tools + isMsa = exist(mission.msaPath, "file"); + isDissile = exist(mission.dissilePath, "file"); + + if ~all([isMsa, isDissile]) + msg = 'The following toolkits are required to update coefficients:'; + + if ~isMsa + msg = sprintf(... + '%s\n - <a href="https://git.skywarder.eu/afd/msa/msa-toolkit">msa-toolkit</a>:\n\t In %s\n', ... + msg, mission.msaPath); + end + + if ~isDissile + msg = sprintf('%s\n - <a href="https://git.skywarder.eu/afd/msa/dissilematcom">dissilematcom</a>:\n\t In %s\n', ... + msg, mission.dissilePath); + end + error(msg); + end + + % Start coefficient creation parserPath = fullfile(mission.msaPath, 'autoMatricesProtub'); addpath(genpath(parserPath)); [obj.coefficients, obj.coefficientsHighAOA] = ... diff --git a/classes/@Rocket/checkGeometry.m b/classes/@Rocket/checkGeometry.m index 6608a6d..d07a5cf 100644 --- a/classes/@Rocket/checkGeometry.m +++ b/classes/@Rocket/checkGeometry.m @@ -75,8 +75,8 @@ function [checks, summary] = checkGeometry(obj) ogiveType = ["CONE", "OGIVE", "POWER", "HAACK", "KARMAN", "MHAACK"]; boatType = ["CONE", "OGIVE"]; - % Convert text to DATCOM standard. !TODO: Si farebbe meglio con un - % enum + % Convert text to DATCOM standard. !TODO: Would be more robust if + % enums were used boatRocket = find(strcmp(obj.rear.boatType, boatType), 1, 'first') - 1; boatTest = find(strcmp(obj.coefficients.geometry.boatType, boatType), 1, 'first') - 1; -- GitLab