From dc1fc8f434235617564408368a0314ca46205e98 Mon Sep 17 00:00:00 2001 From: Thomas Lambert <t.lambert@uliege.be> Date: Mon, 25 Sep 2023 17:33:56 +0200 Subject: [PATCH] feat(coax): add sst and mst coax models + refact Complete refactoring of the updateupstreamvel function in order to facilitate inclusion of new models. Added Single StreamTube model and a more exact calculation of the streamtube constraction using the MST model. --- README.md | 4 +- src/classes/@ElemPerf/updateupstreamvel.m | 247 ++++++++++++++++------ src/utils/preproc/validateconfig.m | 2 +- 3 files changed, 188 insertions(+), 65 deletions(-) diff --git a/README.md b/README.md index 60a40a7..bfd21d9 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ A more exhaustive list of features can be found in the [complete documentation][rotare-doc] that will be made available with version 1.0.0. ### Types of rotors + - [x] Helicopters main or tail rotors - [x] Aircraft propellers - [ ] Wind/tidal turbines @@ -128,7 +129,8 @@ contact me directly at tlambert@uliege.be. We would like to thank Theo Delvaux for his initial implementation of the oblique flight with the `indfact` solver. We would also like to thank Johan Le -for his work on the implementation of coaxial rotors during his master thesis. +and Lyraie Rakotondratsimba for their work on the implementation of coaxial +rotors during his master thesis. Their excellent contributions facilitated considerably the final implementation of these features in Rotare. diff --git a/src/classes/@ElemPerf/updateupstreamvel.m b/src/classes/@ElemPerf/updateupstreamvel.m index 778d6a9..00506a0 100644 --- a/src/classes/@ElemPerf/updateupstreamvel.m +++ b/src/classes/@ElemPerf/updateupstreamvel.m @@ -1,24 +1,23 @@ -function updateupstreamvel(self, PrevOpRot) - % UPDATEUPSTREAMVEL Update axial and tangential upstream velocities. - % This function is used to update upstream velocities of the current - % rotor based on the previous rotor inforations. +function updateupstreamvel(self, PrevOpRot, Mod) + % UPDATEUPSTREAMVEL Update upstream velocities to account for previous rotor downwash. + % This function updates the axial and tangential upstream velocity of the current elements by + % taking into account the downwash of the previous rotor. + % + % Notes: + % At the moment, only the Simplified Multiple Stream Tube (SMST) model is implemented. It + % assumes: + % - no pre-contraction caused by the second rotor + % ----- % % Syntax: - % ElPerf.updateupstreamvel(PrevOpRot) updates upstream velocities in an ElPerf - % object according to the performance of the previous Operating Rotor `PrevOpRot`. + % ElPerf.updateupstreamvel(PrevOpRot) updates upstream velocities in an ElemPerf object + % according to the performance of the previous Operating Rotor `PrevOpRot`. % % Inputs: - % - prevOpRot : previous Operating Rotor object + % prevOpRot : previous Operating Rotor object % % Outputs: - % Updates the following properties of the ElemPerf object: - % - self.upstreamVelAx : Upstream axial velocity above rotor disk, [m/s] - % - self.upstreamVelTg : Upstream tangential velocity above rotor disk, [m/s] - % - % It is assumed that : - % - a simplified multiple stream tube model is adopted - % - there is no contraction between upstream and rotor levels for the current rotor - % - the swirl is taken into account + % Updated values for upstreamVelAx and upstreamVelTg for the current ElemPerf object % % See also: ElemPerf. % @@ -26,7 +25,8 @@ function updateupstreamvel(self, PrevOpRot) % ---------------------------------------------------------------------------------------------- % (c) Copyright 2022-2023 University of Liege - % Author: Thomas Lambert <t.lambert@uliege.be> + % Author: Lyraie Rakotondratsimba + % Co-Author/Maintainer: Thomas Lambert <t.lambert@uliege.be> % ULiege - Aeroelasticity and Experimental Aerodynamics % MIT License % Repo: https://gitlab.uliege.be/rotare/rotare @@ -34,52 +34,173 @@ function updateupstreamvel(self, PrevOpRot) % Issues: https://gitlab.uliege.be/rotare/rotare/-/issues %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - % axial velocity at the previous rotor disk - prevRotVelAx = PrevOpRot.ElPerf.upstreamVelAx + PrevOpRot.ElPerf.indVelAx; - - % axial downstream velocity for previous rotor - prevDownVelAx = PrevOpRot.ElPerf.upstreamVelAx + 2 * PrevOpRot.ElPerf.indVelAx; - - % contraction variables - % radius ratio of of previous rotor contraction - vec_areaRatio = prevRotVelAx ./ prevDownVelAx; - % area ratio of previous rotor contraction - vec_radiusRatio = sqrt(vec_areaRatio); - - vcRadius = PrevOpRot.Rot.Bl.y(end) * vec_radiusRatio(1, end); % vena contracta radius - vcRadIdx = find(self.Rot.Bl.y <= vcRadius, 1, 'last'); - - % tangential velocity at the previous rotor disk - prevDownVelTg = PrevOpRot.ElPerf.dQ ./ (PrevOpRot.Rot.Bl.y .* ... - PrevOpRot.ElPerf.massflow) .* (1 ./ vec_areaRatio); - - % Mapping of velocities from initial upper rotor splitting to contracted splitting - % contracted previous rotor velocities at current rotor elements - yqueryAx = interp1(PrevOpRot.Rot.Bl.y .* vec_radiusRatio, prevDownVelAx, ... - self.Rot.Bl.y, 'linear', 0); - yqueryTg = interp1(PrevOpRot.Rot.Bl.y .* vec_radiusRatio, prevDownVelTg, ... - self.Rot.Bl.y, 'linear', 0); - - % Update upstream axial velocity with previous rotor wake - self.upstreamVelAx(1, 1:vcRadIdx) = yqueryAx(1, 1:vcRadIdx); - - % Update upstream tangential velocity with previous rotor wake - self.upstreamVelTg(1, 1:vcRadIdx) = yqueryTg(1, 1:vcRadIdx); - - % Verification of mass flow conservation - % total flow mass at previous rotor level - debit_u2 = trapz(2 * pi * PrevOpRot.Op.Flow.rho * PrevOpRot.Rot.Bl.dy * ... - PrevOpRot.Rot.Bl.y .* prevRotVelAx); - % total flow mass at downstream of previous rotor - There is contraction of wake - area_contracted = PrevOpRot.Rot.Bl.dy * PrevOpRot.Rot.Bl.y .* vec_areaRatio; - debit_u3 = trapz(2 * pi * PrevOpRot.Op.Flow.rho * area_contracted .* prevDownVelAx); - % total flow mass at upstream of current rotor - debit_d1 = trapz(2 * pi * self.Op.Flow.rho * ... - self.Rot.Bl.dy * self.Rot.Bl.y(1, 1:vcRadIdx) .* ... - self.upstreamVelAx(1, 1:vcRadIdx)); - % comparison of the flow masses in percentage - % Remove the below semicolons to display the results before running the code : - diff = 100 * (debit_u2 - debit_u3) / debit_u2; - diff2 = 100 * (debit_u3 - debit_d1) / debit_u3; + % Map the velocities according to the appropriate mode + switch Mod.coax.model + case 'none' + Mod.coax.contraType = 'none'; + prevWake = calccontraction(PrevOpRot, Mod.coax); + mapvelocities(self, prevWake, length(self.Rot.Bl.y)); + case 'sst' + sst(self, PrevOpRot, Mod.coax); + case 'mst' + mst(self, PrevOpRot, Mod.coax, false); + case 'smst' + mst(self, PrevOpRot, Mod.coax, true); + end + +end + +% ==================================== Helper functions ============================================ + +function prevWake = calccontraction(PrevOpRot, ModCoax) + % CALCCONTRACTION Calculates the wake characteristics of the first rotor + + % Velocity at the disk of previous rotor + prevRot.velAx = PrevOpRot.ElPerf.upstreamVelAx + PrevOpRot.ElPerf.indVelAx; + prevRot.velTg = PrevOpRot.ElPerf.indVelTg; + prevRot.upstreamVel = PrevOpRot.ElPerf.upstreamVelAx; + if strcmpi(ModCoax.model, 'sst') + prevRot.velAx = mean(prevRot.velAx); + prevRot.velTg = 0; + prevRot.upstreamVel = mean(PrevOpRot.ElPerf.upstreamVelAx); + end + + % Velocities in the wake of previous rotor after contraction + switch ModCoax.contraType + case 'none' + prevWake.velAx = prevRot.velAx; + prevWake.velTg = prevRot.velTg; + prevWake.areaR = 1; + case 'farfield' + prevWake.velAx = 2 * prevRot.velAx - prevRot.upstreamVel; + prevWake.velTg = 2 * prevRot.velTg; % FIXME? + prevWake.areaR = prevRot.velAx ./ prevWake.velAx; + % prevWake.VelTg = PrevOpRot.ElPerf.dQ ./ (PrevOpRot.Rot.Bl.y .* ... + % PrevOpRot.ElPerf.massflow) .* (1 ./ areaRatio); + case 'value' + prevWake.areaR = Mod.coax.contraVal; + prevWake.velAx = prevRot.velAx ./ prevWake.areaR; + prevWake.velTg = prevRot.velTg ./ prevRot.velTg; + end + + % This assumes annulus contraction, but not thickness contraction as well + % It must be recalculated in general MST model + prevWake.y = PrevOpRot.Rot.Bl.y .* sqrt(prevWake.areaR); + prevWake.dy = PrevOpRot.Rot.Bl.dy .* sqrt(prevWake.areaR); +end + +function sst(self, PrevOpRot, ModCoax) + % SSTMODEL Implement the Simple Stream Tube model. + % Only consider the mean axial velocity and the entire streamtube contraction + + prevWake = calccontraction(PrevOpRot, ModCoax); + prevWake.spinDir = self.Rot.Bl.spinDir * PrevOpRot.Rot.Bl.spinDir; + + % Resize properly and discard the swirl velocity + prevWake.velAx = ones(size(prevWake.y)) * prevWake.velAx; + prevWake.velTg = zeros(size(prevWake.y)); + + % Map velocities + vcRadIdx = calccradius(prevWake.y, self.Rot.Bl.y); + mapvelocities(self, prevWake, vcRadIdx); + + % Check if massflows are conserved + massflowcheck(self, PrevOpRot, prevWake, vcRadIdx, ModCoax); + +end + +function mst(self, PrevOpRot, ModCoax, simple) + % MSTMODEL Implement the Multiple Stream Tube model. + % Consider individual annulus contraction, as well as the effect on the swirl velocity + + % Base calculation with only annulus contraction, no tube narrowing + prevWake = calccontraction(PrevOpRot, ModCoax); + prevWake.spinDir = self.Rot.Bl.spinDir * PrevOpRot.Rot.Bl.spinDir; + + if ~simple + % Contract both annulus and tube thickness + yPrevExt = zeros(size(PrevOpRot.Rot.Bl.y)); % External radius of previous tube + yPrevExt(1) = PrevOpRot.Rot.Bl.y(1) - PrevOpRot.Rot.Bl.dy(1) / 2; % Hub + + yw = zeros(size(PrevOpRot.Rot.Bl.y)); % Annulus center in wake + dyw = zeros(size(PrevOpRot.Rot.Bl.y)); % Annulus thickness in wake + for i = 1:numel(PrevOpRot.Rot.Bl.y) + if i > 1 + yPrevExt(i) = yw(i - 1) + dyw(i - 1) / 2; + end + + dyw(i) = -yPrevExt(i) + sqrt(yPrevExt(i)^2 + ... + 2 * PrevOpRot.Rot.Bl.y(i) .* PrevOpRot.Rot.Bl.dy .* ... + prevWake.areaR(i)); + + yw(i) = yPrevExt(i) + dyw(i) / 2; + end + prevWake.y = yw; + prevWake.dy = dyw; + + end + + % Map velocities + vcRadIdx = calccradius(prevWake.y, self.Rot.Bl.y); + mapvelocities(self, prevWake, vcRadIdx); + + % Check if massflows are conserved + massflowcheck(self, PrevOpRot, prevWake, vcRadIdx, ModCoax); +end + +function vcIdxOnLower = calccradius(vcY, y) + % CALCVCRADIUS Calculate the radius of the vena contracta and its index on lower rotor + + vcIdxOnLower = find(y <= vcY(end), 1, 'last'); +end + +function mapvelocities(self, prevWake, vcRadIdx) + % MAPVELOCITIES Map the velocities of the previous rotor wake onto the current rotor + + self.upstreamVelAx(1:vcRadIdx) = interp1(prevWake.y, prevWake.velAx, ... + self.Rot.Bl.y(1:vcRadIdx), 'spline', 0); + self.upstreamVelTg(1:vcRadIdx) = interp1(prevWake.y, prevWake.spinDir * prevWake.velTg, ... + self.Rot.Bl.y(1:vcRadIdx), 'spline', 0); +end + +function massflowcheck(self, PrevOpRot, prevWake, vcRadIdx, ModCoax) + % MASSFLOWCHECK Verify if the massflow is conserved after inteprolation in the vena contracta + % The following three massflows should be roughly equal: + % 1. Mass flow accros entire previous rotor disk + % 2. Mass flow in wake after contraction of slipstream + % 3. Mass flow on lower rotor elements inside vena contracta after interpolation of wake + + % 1. Entire previous rotor disk + if strcmpi(ModCoax.model, 'sst') + mf_prevRot = trapz(2 * pi * PrevOpRot.Rot.Bl.y .* PrevOpRot.Rot.Bl.dy .* ... + PrevOpRot.Op.Flow.rho .* ... + mean(PrevOpRot.ElPerf.upstreamVelAx + PrevOpRot.ElPerf.indVelAx)); + else + mf_prevRot = PrevOpRot.totalmassflow; + end + + % 2. Contracted wake + mf_prevWake = trapz(2 * pi * prevWake.y .* prevWake.dy .* PrevOpRot.Op.Flow.rho .* ... + prevWake.velAx); + % 3. Lower rotor elements inside vena contracta + mf_curRot = trapz(2 * pi * self.Rot.Bl.y(1, 1:vcRadIdx) .* self.Rot.Bl.dy .* ... + PrevOpRot.Op.Flow.rho .* self.upstreamVelAx(1:vcRadIdx)); + + % Error in mass flow conservation + err_prevRot = (mf_prevRot - mf_prevWake) / mf_prevRot; % prevRot disk/wake + err_intrpCurr = (mf_prevWake - mf_curRot) / mf_prevWake; % prevRot wake/current vena contracta + + % close all; + % figure + % hold on + % plot(PrevOpRot.Rot.Bl.y, PrevOpRot.ElPerf.upstreamVelAx + PrevOpRot.ElPerf.indVelAx) + % plot(prevWake.y, prevWake.velAx) + % plot(self.Rot.Bl.y, self.upstreamVelAx,'-o') + % legend('upper', 'vc','lower') + % pause + + % Throw error if mass flow not conserved + assert(abs(err_prevRot) < 1e-12); % Should be 0 in theory + assert(abs(err_intrpCurr) < 2e-2); % Should be small end diff --git a/src/utils/preproc/validateconfig.m b/src/utils/preproc/validateconfig.m index 413ac30..4471240 100644 --- a/src/utils/preproc/validateconfig.m +++ b/src/utils/preproc/validateconfig.m @@ -207,7 +207,7 @@ function Mod = checkmod(Mod, configFile, nRotors, DEF) if strcmp(Mod.coax.contraType, 'value') validateattributes(Mod.coax.contraVal, ... {'double'}, ... - {'scalar', 'positive'}, ... + {'scalar', 'positive', '<=', 1}, ... configFile, 'Mod.Num.convCrit'); end end -- GitLab