diff --git a/resultsanalysis.m b/resultsanalysis.m index d00710830cdd5c26daee872ff062dccd45975763..496b369dc46c323c266c40fc6660a68e0331c11f 100644 --- a/resultsanalysis.m +++ b/resultsanalysis.m @@ -44,70 +44,69 @@ function ResData = resultsanalysis(matFiles) ResData = ResData_; end - ResData = getallphases(ResData, OFFSET_VALUES); + % Get all correct useful phases + ResData = calcallphases(ResData); + ResData = calcinertia(ResData); + ResData = calctrueforces(ResData); + ResData = calccoeff(ResData); + % - ResData = getmeancoefficients(ResData); + plotcf(ResData); + plotclcd(ResData); end -function ResData = getallphases(ResData, offsetVal) +function ResData = calcallphases(ResData) % GETALLPHASES Determine the range for all phase offsets for i = 1:size(ResData, 1) for j = 1:size(ResData, 2) + % FIXME Refactor the rest to use this! + ResData(i, j).Front.trueFreq = getmainfrequency(ResData(i, j).Front.angles, ... + 1 / diff(ResData(i, j).time(1:2))); + ResData(i, j).Aft.trueFreq = getmainfrequency(ResData(i, j).Aft.angles, ... + 1 / diff(ResData(i, j).time(1:2))); + fprintf('\nFinding phase for element (%d,%d): %0.2fm, %0.2fHz, %0.1fm/s\n', ... i, j, ... ResData(i, j).Testcase.dx, ... ResData(i, j).Testcase.freq, ... ResData(i, j).Testcase.airspeed); - for k = 1:length(offsetVal) - ResData(i, j).Phase(k).phaseVal = offsetVal(k); - ResData(i, j).Phase(k).idx = findwingphase(ResData(i, j), offsetVal(k)); - end + % Find all phase offsets between front and aft + [offsetsVal, signalParts] = findallphasesdiff(ResData(i, j)); + ResData(i, j).AllPhases.offsets = offsetsVal; + ResData(i, j).AllPhases.parts = signalParts; end end end -function ResData = getmeancoefficients(ResData) - % GETMEANCOEFFICIENTS Returns the true mean coefficients +function ResData = calcinertia(ResData) + % CALCINERTIA Calcualte inertia forces for i = 1:size(ResData, 1) for j = 1:size(ResData, 2) - % Remove inertia forces + % Index of the Wind-off data woIdx = (j) - mod(j - 1, 4); - ResData(i, j) = removeinertia(ResData(i, j), ResData(i, woIdx), 'Front'); - ResData(i, j) = removeinertia(ResData(i, j), ResData(i, woIdx), 'Aft'); - - % Transform forces/moments into coefficients - ResData(i, j) = calccoeff(ResData(i, j)); - end - end -end + ResDataWO = ResData(i, woIdx); -function ResData = calccoeff(ResData) + for iPos = {'Front', 'Aft'} + pos = string(iPos); - POSITIONS = {'Front', 'Aft'}; - CHORD = 0.05; - SPAN = 2 * 0.2; - C_TO_K = 273.15; + % Get first and last peaks for wind-off conditions + woBounds = getboundingpeaks(ResDataWO.(pos).angles, ResDataWO.sampling); + woIdx = woBounds(1):woBounds(2); - for i = 1:length(ResData.Phase) - if ~isempty(ResData.Phase(i).idx) - for j = 1:length(POSITIONS) - dens = airdensity(ResData.Testcase.temp + C_TO_K, ResData.Testcase.press); - speed = ResData.Testcase.airspeed; + % Calculate mean force and moments due to inertia + meanForces = mean(ResDataWO.(pos).forces(woIdx)); + meanMom = mean(ResDataWO.(pos).moments(woIdx)); - coeffF = forcetocoeff(ResData.Phase(i).(POSITIONS{j}).forces, ... - dens, speed, CHORD, SPAN, 'force'); - coeffM = forcetocoeff(ResData.Phase(i).(POSITIONS{j}).moments, ... - dens, speed, CHORD, SPAN, 'moment'); - - ResData.Phase(i).(POSITIONS{j}).cF = coeffF; - ResData.Phase(i).(POSITIONS{j}).cM = coeffM; + ResData(i, j).AllPhases.(pos).inertiaF = meanForces; + ResData(i, j).AllPhases.(pos).inertiaM = meanMom; end + end end end diff --git a/utils/analysis/findallphasesdiff.m b/utils/analysis/findallphasesdiff.m new file mode 100644 index 0000000000000000000000000000000000000000..cad6ce71745f47c29f6fe0472a5b6c5b59932ceb --- /dev/null +++ b/utils/analysis/findallphasesdiff.m @@ -0,0 +1,54 @@ +function [offsetsVal, signalParts] = findallphasesdiff(ResData) + % FINDALLPHASESDIFF Return all phases differences between the two angles and their range + % Todo + + % ---------------------------------------------------------------------------------------------- + % (c) Copyright 2022 University of Liege + % Author: Thomas Lambert <t.lambert@uliege.be> + % ULiege - Aeroelasticity and Experimental Aerodynamics + % MIT License + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + % Abbreviation + period = 1 / ResData.Front.trueFreq; + nPeriodIdx = period * ResData.sampling; + + % Find peaks in the wing angles + peakLocs1 = findallpeaks(ResData.Front.angles, nPeriodIdx); + peakLocs2 = findallpeaks(ResData.Aft.angles, nPeriodIdx); + npeaks = min(length(peakLocs1), length(peakLocs2)); + + % Trim to same number of peaks + peakLocs1 = peakLocs1(1:npeaks); + peakLocs2 = peakLocs2(1:npeaks); + + % Get all offsets and round them (1° precision on the offset is plenty enough) + offsets = getphaseoffsets(peakLocs1, peakLocs2, nPeriodIdx); + offsets = round(offsets); + % Remove first and last offsets (can't get one period before and one after the offset otherwise) + offsetsVal = offsets(2:end - 1); + peakLocs = peakLocs1(2:end - 1); + + % singalPart is one period centered on the peak + % signalParts = [peakLocs(1:end - 2), peakLocs1(3:end)]; + signalParts = round([peakLocs - nPeriodIdx / 2, peakLocs + nPeriodIdx / 2]); + +end + +function peakLocs = findallpeaks(signal, nPeriodIdx) + % FINDANGLEPEAKS Find all true peaks in the signal + + maxAngle = max(signal); + + [~, peakLocs] = findpeaks(signal, ... + 'MinPeakProminence', maxAngle / 2, ... + 'MinPeakDistance', nPeriodIdx / 2); +end + +function offsets = getphaseoffsets(peaks1, peaks2, nPeriodIdx) + % GETPHASEOFFSETS Calculate phase offsets between every contiguous peaks + % Note: Offset is positive when peak2 is AFTER peak1 + + offsets = (peaks2 - peaks1) * 360 / nPeriodIdx; + +end diff --git a/utils/analysis/plotcf.m b/utils/analysis/plotcf.m new file mode 100644 index 0000000000000000000000000000000000000000..a3b54f1e197074a846e705c4818cba1b6fc25d18 --- /dev/null +++ b/utils/analysis/plotcf.m @@ -0,0 +1,93 @@ +function plotcf(ResData) + % PLOTCF Plot force coefficients + % Todo + + % ---------------------------------------------------------------------------------------------- + % (c) Copyright 2022 University of Liege + % Author: Thomas Lambert <t.lambert@uliege.be> + % ULiege - Aeroelasticity and Experimental Aerodynamics + % MIT License + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + % X-axis = flapping frequency + % Y-axis = Cf + % One figure per dx + % Plot airspeeds in different colors + + ONLY_ERR_BARS = true; + OFFSET_STEPS = 20; + + INTERESTING_PLOTS = [ + ]; + + % for i = 1: size(ResData,1) + for i = 3 + for j = 1:size(ResData, 2) + if ResData(i, j).Testcase.airspeed ~= 0 % No need to plot when cF = 0 + expParam = sprintf('d = %0.2f, f = %0.2f, V = %0.1f', ... + ResData(i, j).Testcase.dx, ... + ResData(i, j).Testcase.freq, ... + ResData(i, j).Testcase.airspeed); + + offsets = ResData(i, j).AllPhases.offsets; + cleanOffsets = -175:OFFSET_STEPS:175; + meanOffset = nan(size(cleanOffsets)); + stdOffset = nan(size(cleanOffsets)); + for iOff = 1:length(cleanOffsets) + curOffsets = cleanOffsets(iOff); + offIdx = find(abs(offsets - curOffsets) < diff(cleanOffsets(1:2)) / 2); + meanOffset(iOff) = mean(offsets(offIdx)); + stdOffset(iOff) = std(offsets(offIdx)); + meanF(iOff, :) = mean(ResData(i, j).AllPhases.Front.cF(offIdx, :), 1); + stdF(iOff, :) = std(ResData(i, j).AllPhases.Front.cF(offIdx, :), 0, 1); + meanA(iOff, :) = mean(ResData(i, j).AllPhases.Aft.cF(offIdx, :), 1); + stdA(iOff, :) = std(ResData(i, j).AllPhases.Aft.cF(offIdx, :), 0, 1); + + end + + ynegF = -stdF; + yposF = stdF; + ynegA = -stdA; + yposA = stdA; + + xneg = cleanOffsets - meanOffset - stdOffset; + xpos = cleanOffsets - meanOffset + stdOffset; + + figure('Name', 'myfig'); + subplot(211); + hold on; + if ~ONLY_ERR_BARS + plot(ResData(i, j).AllPhases.offsets, ... + ResData(i, j).AllPhases.Front.cF(:, [1, 3]), 'o'); + end + errorbar(cleanOffsets, meanF(:, 1), ynegF(:, 1), yposF(:, 1), xneg, xpos, 'o'); + errorbar(cleanOffsets, meanF(:, 3), ynegF(:, 3), yposF(:, 3), xneg, xpos, 'o'); + hold off; + title(['Front module -- ', expParam]); + + setgca(); + + subplot(212); + hold on; + if ~ONLY_ERR_BARS + plot(ResData(i, j).AllPhases.offsets, ... + ResData(i, j).AllPhases.Aft.cF(:, [1, 3]), 'o'); + end + errorbar(cleanOffsets, meanA(:, 1), ynegA(:, 1), yposA(:, 1), xneg, xpos, 'o'); + errorbar(cleanOffsets, meanA(:, 3), ynegA(:, 3), yposA(:, 3), xneg, xpos, 'o'); + hold off; + title(['Aft module -- ', expParam]); + setgca(); + legend('Location', 'bestoutside', 'orientation', 'horizontal', 'Fx', 'Fz'); + + end + end + end + +end + +function setgca() + % SETGCA Sets the axes parameters + xlim([-180, 180]); + grid on; +end diff --git a/utils/analysis/plotclcd.m b/utils/analysis/plotclcd.m new file mode 100644 index 0000000000000000000000000000000000000000..c0f1b65bcf1baa80ac7c9d2c1d6fd4557358d5f5 --- /dev/null +++ b/utils/analysis/plotclcd.m @@ -0,0 +1,84 @@ +function plotclcd(ResData) + % PLOTCLCD Plot CL/CD for each set of wing + % Todo + + % ---------------------------------------------------------------------------------------------- + % (c) Copyright 2022 University of Liege + % Author: Thomas Lambert <t.lambert@uliege.be> + % ULiege - Aeroelasticity and Experimental Aerodynamics + % MIT License + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + % X-axis = flapping frequency + % Y-axis = Cf + % One figure per dx + % Plot airspeeds in different colors + + % for i = 1: size(ResData,1) + for i = 2 + for j = 1:size(ResData, 2) + if ResData(i, j).Testcase.airspeed ~= 0 % No need to plot when cF = 0 + expParam = sprintf('d = %0.2f, f = %0.2f, V = %0.1f', ... + ResData(i, j).Testcase.dx, ... + ResData(i, j).Testcase.freq, ... + ResData(i, j).Testcase.airspeed); + + clcdF = ResData(i, j).AllPhases.Front.cF(:, 3) ./ ... + ResData(i, j).AllPhases.Front.cF(:, 1); + clcdA = ResData(i, j).AllPhases.Aft.cF(:, 3) ./ ... + ResData(i, j).AllPhases.Aft.cF(:, 1); + + offsets = ResData(i, j).AllPhases.offsets; + cleanOffsets = -175:OFFSET_STEPS:175; + meanOffset = nan(size(cleanOffsets)); + stdOffset = nan(size(cleanOffsets)); + for iOff = 1:length(cleanOffsets) + curOffsets = cleanOffsets(iOff); + offIdx = find(abs(offsets - curOffsets) < diff(cleanOffsets(1:2)) / 2); + meanOffset(iOff) = mean(offsets(offIdx)); + stdOffset(iOff) = std(offsets(offIdx)); + meanClCdF(iOff) = mean(clcdF(offIdx), 1); + stdClCdF(iOff) = std(clcdF(offIdx), 0, 1); + meanClCdA(iOff) = mean(clcdA(offIdx), 1); + stdClCdA(iOff) = std(clcdA(offIdx), 0, 1); + end + + ynegF = -stdClCdF; + yposF = stdClCdF; + ynegA = -stdClCdA; + yposA = stdClCdA; + + xneg = cleanOffsets - meanOffset - stdOffset; + xpos = cleanOffsets - meanOffset + stdOffset; + + figure('Name', 'errorbars'); + subplot(211); + hold on; + plot(offsets, clcdF, 'o'); + errorbar(cleanOffsets, meanClCdF, ynegF, yposF, xneg, xpos, 'o'); + hold off; + title(['Front module -- ', expParam]); + setgca(); + + subplot(212); + hold on; + plot(offsets, clcdA, 'o'); + errorbar(cleanOffsets, meanClCdA, ynegA, yposA, xneg, xpos, 'o'); + hold off; + title(['Aft module -- ', expParam]); + setgca(); + + pause; + + end + end + end + +end + +function setgca() + % SETGCA Sets the axes parameters + xlim([-180, 180]); + % ylim([-1.5, 1.5]); + grid on; +end diff --git a/utils/calccoeff.m b/utils/calccoeff.m new file mode 100644 index 0000000000000000000000000000000000000000..75cfef0066f2ed5a33cd9cee629864f4b89d9d27 --- /dev/null +++ b/utils/calccoeff.m @@ -0,0 +1,38 @@ +function [ResData] = calccoeff(ResData) + % CALCCOEFF Calculates force and moment coefficients. + % TODO + + % ---------------------------------------------------------------------------------------------- + % (c) Copyright 2022 University of Liege + % Author: Thomas Lambert <t.lambert@uliege.be> + % ULiege - Aeroelasticity and Experimental Aerodynamics + % MIT License + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + CHORD = 0.05; + SPAN = 2 * 0.2; + C_TO_K = 273.15; + + for i = 1:size(ResData, 1) + for j = 1:size(ResData, 2) + for iPos = {'Front', 'Aft'} + pos = string(iPos); + dens = airdensity(ResData(i, j).Testcase.temp + C_TO_K, ... + ResData(i, j).Testcase.press); + speed = ResData(i, j).Testcase.airspeed; + + for iPart = 1:size(ResData(i, j).AllPhases(1).(pos).meanF, 1) + + coeffF = forcetocoeff(ResData(i, j).AllPhases(1).(pos).meanF(iPart, :), ... + dens, speed, CHORD, SPAN, 'force'); + coeffM = forcetocoeff(ResData(i, j).AllPhases(1).(pos).meanM(iPart, :), ... + dens, speed, CHORD, SPAN, 'moment'); + + ResData(i, j).AllPhases(1).(pos).cF(iPart, :) = coeffF; + ResData(i, j).AllPhases(1).(pos).cF(iPart, :) = coeffM; + end + end + end + end + +end diff --git a/utils/calctrueforces.m b/utils/calctrueforces.m new file mode 100644 index 0000000000000000000000000000000000000000..35c966765baf660dabcd3cc5377f66244851470c --- /dev/null +++ b/utils/calctrueforces.m @@ -0,0 +1,43 @@ +function [ResData] = calctrueforces(ResData) + % CALCTRUEFORCES Calculates true forces by substracting inertia from mean raw measure. + % TODO + + % ---------------------------------------------------------------------------------------------- + % (c) Copyright 2022 University of Liege + % Author: Thomas Lambert <t.lambert@uliege.be> + % ULiege - Aeroelasticity and Experimental Aerodynamics + % MIT License + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + for i = 1:size(ResData, 1) + for j = 1:size(ResData, 2) + for iPos = {'Front', 'Aft'} + pos = string(iPos); + indexes = ResData(i, j).AllPhases.parts; + + for iPart = 1:length(indexes) + + % Do not remove inertia from inertial case! + if ResData(i, j).Testcase.airspeed ~= 0 + % Inertia + inF = ResData(i, j).AllPhases(1).(pos).inertiaF; + inM = ResData(i, j).AllPhases(1).(pos).inertiaM; + else + inF = 0; + inM = 0; + end + % Raw force + meanFraw = mean(ResData(i, j).(pos).forces(indexes(iPart, :), :)); + meanMraw = mean(ResData(i, j).(pos).moments(indexes(iPart, :), :)); + + % True value + ResData(i, j).AllPhases(1).(pos).meanF(iPart, :) = meanFraw - inF; + ResData(i, j).AllPhases(1).(pos).meanM(iPart, :) = meanMraw - inM; + + end + + end + end + end + +end