-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #33 from TUDelft-DataDrivenControl/dev
Dev
- Loading branch information
Showing
21 changed files
with
617 additions
and
1,259 deletions.
There are no files selected for viewing
171 changes: 171 additions & 0 deletions
171
Examples/observability_analysis/bin/calculateCostRose.m
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
function [costMatrix] = calculateCostRose(layouts,trueRange,relSearchRange) | ||
addpath(genpath('../../FLORISSE_M')) | ||
addpath('bin') | ||
|
||
%% Setup | ||
% Construct farms | ||
for i = 1:length(layouts) | ||
florisRunner{i} = snsGenerateFLORIS(layouts{i}); | ||
end | ||
|
||
tic | ||
nLayouts = length(layouts); | ||
nWS = length(trueRange.WS); | ||
nTI = length(trueRange.TI); | ||
nWD = length(trueRange.WD); | ||
if rem(nWD,2) == 0 | ||
disp('WARNING: RECOMMENDED TO DISCRETIZE WD AT AN UNEVEN NUMBER: '' trueRange.WD ''.') | ||
end | ||
|
||
for Layouti = 1:nLayouts | ||
florisRunnerTrue = copy(florisRunner{Layouti}); | ||
disp(['Determining all roses for layout{' num2str(Layouti) '} (' num2str(Layouti) '/' num2str(nLayouts) ').']) | ||
for WSi = 1:nWS | ||
wsTrue = trueRange.WS(WSi); | ||
florisRunnerTrue.layout.ambientInflow.Vref = wsTrue; | ||
disp([' Determining rose for WS_true = ' num2str(trueRange.WS(WSi)) ' m/s (' num2str(WSi) '/' num2str(nWS) ').']) | ||
for TIi = 1:nTI | ||
tiTrue = trueRange.TI(TIi); | ||
florisRunnerTrue.layout.ambientInflow.TI0 = tiTrue; | ||
disp([' Calculating observability for TI_true = ' num2str(tiTrue) ' (' num2str(TIi) '/' num2str(nTI) ').']); % Progress | ||
|
||
% Determine estimation errors/cost over all WDs for this layout, WS and TI | ||
costFunInfo{Layouti,WSi,TIi} = costRoseOverWDs(florisRunnerTrue,trueRange.WD,relSearchRange); | ||
end | ||
end | ||
|
||
% % Check for symmetry in the observability circle | ||
% if max(abs(diff(sumJ(Layouti,1,1,1:(nWD-1)/2)-sumJ(Layouti,1,1,(nWD-1)/2+1:end-1)))) < 1e-6 | ||
% disp(['The observability rose of layout{' num2str(Layouti) '} appears to be symmetrical.']); | ||
% else | ||
% disp(['The observability rose of layout{' num2str(Layouti) '} appears to be non-symmetrical.']); | ||
% end | ||
end | ||
toc | ||
|
||
% Create the output matrix | ||
costMatrix = struct(... | ||
'trueRange',trueRange,... | ||
'relSearchRange',relSearchRange,... | ||
'costFunInfo',{costFunInfo}); | ||
costMatrix.layouts = layouts; | ||
end | ||
|
||
function [costFunInfo] = costRoseOverWDs(florisRunnerIn,trueRangeWD,relSearchRange) | ||
% THIS FUNCTION CALCULATES THE SENSITIVITY FOR ONE SPECIFIC FLORISRUNNER | ||
% OBJECT OVER THE COMPLETE WIND ROSE | ||
|
||
% Import variables | ||
florisRunnerTrue = florisRunnerIn; | ||
|
||
nTurbs = florisRunnerTrue.layout.nTurbs; | ||
wsTrue = florisRunnerTrue.layout.ambientInflow.Vref; | ||
wdTrue = florisRunnerTrue.layout.ambientInflow.windDirection; | ||
tiTrue = florisRunnerTrue.layout.ambientInflow.TI0; | ||
|
||
nWdSensitivity = length(trueRangeWD); | ||
nSubWD = length(relSearchRange.WD); | ||
nSubWS = length(relSearchRange.WS); | ||
nSubTI = length(relSearchRange.TI); | ||
|
||
dwd_lengthscale = relSearchRange.WD(end)-relSearchRange.WD(1); | ||
dws_lengthscale = relSearchRange.WS(end)-relSearchRange.WS(1); | ||
dti_lengthscale = relSearchRange.TI(end)-relSearchRange.TI(1); | ||
|
||
% Evaluate the cost function for the entire wind rose | ||
parfor WDi = 1:nWdSensitivity | ||
florisRunnerLocal = copy(florisRunnerTrue); % Copy florisRunner obj | ||
|
||
wdTrue = trueRangeWD(WDi); % Update wdTrue | ||
[powerTrue,uTrue] = evalForWD(florisRunnerTrue,wdTrue,wdTrue); % Calculate true power from FLORIS | ||
[dxSqrd,msePwr,mseU,mseUwse] = deal(zeros(nSubTI,nSubWS,nSubWD)); % Initialize empty matrices | ||
|
||
for WSii = 1:nSubWS | ||
wsEst = wsTrue + relSearchRange.WS(WSii); % Update estimated WS | ||
florisRunnerLocal.layout.ambientInflow.Vref = wsEst; | ||
dws = abs(wsTrue - wsEst); | ||
|
||
for TIii = 1:nSubTI | ||
tiEst = tiTrue + relSearchRange.TI(TIii); | ||
florisRunnerLocal.layout.ambientInflow.TI0 = tiEst; % Update estimated TI | ||
dti = abs(tiTrue - tiEst); | ||
|
||
for WDii = 1:nSubWD | ||
wdEst = wdTrue + relSearchRange.WD(WDii); | ||
dwd = abs(wdEst-wdTrue); % Distance between arguments | ||
if dwd > pi % Radial distance | ||
dwd = 2*pi - dwd; | ||
end | ||
if dwd < 0 || dwd > pi | ||
error('Something went wrong here.') | ||
end | ||
|
||
[powerOut,uOut] = evalForWD(florisRunnerLocal,wdTrue,wdEst); | ||
uOutWSE = uOut*((cos(-dwd))^(1.88/3)); % WS predicted by WSE under yawed power signal | ||
mseUwse(TIii,WSii,WDii) = mean((uOutWSE-uTrue).^2); | ||
msePwr(TIii,WSii,WDii) = mean((powerOut-powerTrue).^2); | ||
mseU(TIii,WSii,WDii) = mean((uOut-uTrue).^2); | ||
|
||
% Fix numerical errors | ||
if msePwr(TIii,WSii,WDii) < 10*eps | ||
msePwr(TIii,WSii,WDii) = 0; | ||
end | ||
if mseU(TIii,WSii,WDii) < 10*eps | ||
mseU(TIii,WSii,WDii) = 0; | ||
end | ||
if mseUwse(TIii,WSii,WDii) < 10*eps | ||
mseUwse(TIii,WSii,WDii) = 0; | ||
end | ||
|
||
% Calculate squared distancedxSqrd | ||
dxSqrd_tmp = 0; | ||
if nSubWD > 1 | ||
dxSqrd_tmp = dxSqrd_tmp + (dwd / dwd_lengthscale)^2; | ||
end | ||
if nSubWS > 1 | ||
dxSqrd_tmp = dxSqrd_tmp + (dws / dws_lengthscale)^2; | ||
end | ||
if nSubTI > 1 | ||
dxSqrd_tmp = dxSqrd_tmp + (dti / dti_lengthscale)^2; | ||
end | ||
dxSqrd(TIii,WSii,WDii) = dxSqrd_tmp; | ||
end | ||
end | ||
end | ||
|
||
% Collect useful outputs | ||
costFunInfo{WDi} = struct(... | ||
'msePwr',msePwr,... | ||
'mseU',mseU,... | ||
'mseUwse',mseUwse,... | ||
'dxSqrd',dxSqrd,... | ||
'nTurbs',nTurbs,... | ||
'wsTrue',wsTrue,... | ||
'wdTrue',wdTrue,... | ||
'tiTrue',tiTrue,... | ||
'relSearchRange',relSearchRange... | ||
); | ||
end | ||
end | ||
|
||
function [powerOut,uOut] = evalForWD(florisRunnerIn,windDirectionTrue,windDirectionEval) | ||
% Update and run | ||
florisRunnerLocal = copy(florisRunnerIn); | ||
florisRunnerLocal.clearOutput(); | ||
florisRunnerLocal.layout.ambientInflow.windDirection = windDirectionEval; | ||
|
||
% % Maintain same relative yaw angle in wind-aligned frame | ||
% florisRunnerLocal.controlSet.yawAngleWFArray = ... | ||
% florisRunnerLocal.controlSet.yawAngleWFArray + ... | ||
% noiseYawAngles * randn(1,florisRunnerLocal.layout.nTurbs); | ||
|
||
% % Maintain same relative yaw angle in inertial frame | ||
florisRunnerLocal.controlSet.yawAngleIFArray = ... | ||
windDirectionTrue * ones(1,florisRunnerLocal.layout.nTurbs); | ||
% disp(florisRunnerLocal.controlSet.yawAngleIFArray) | ||
|
||
% Run and export power & WS | ||
florisRunnerLocal.run(); | ||
powerOut = [florisRunnerLocal.turbineResults.power]; | ||
uOut = [florisRunnerLocal.turbineConditions.avgWS]; | ||
end |
118 changes: 118 additions & 0 deletions
118
Examples/observability_analysis/bin/calculateObsvRose.m
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
function structOut = calculateObsvRose(costIn,jFunctionString,mFunctionString,deadzone) | ||
addpath('bin') | ||
|
||
trueRange = costIn.trueRange; | ||
nLayouts = size(costIn.costFunInfo,1); | ||
|
||
nWS = length(trueRange.WS); | ||
nTI = length(trueRange.TI); | ||
nWD = length(trueRange.WD); | ||
|
||
relSearchRange = costIn.relSearchRange; | ||
nSubWS = length(relSearchRange.WS); | ||
nSubTI = length(relSearchRange.TI); | ||
nSubWD = length(relSearchRange.WD); | ||
dwd_ls = relSearchRange.WD(end)-relSearchRange.WD(1); | ||
dws_ls = relSearchRange.WS(end)-relSearchRange.WS(1); | ||
dti_ls = relSearchRange.TI(end)-relSearchRange.TI(1); | ||
|
||
% Numerical correction | ||
if nSubWS == 1 && dws_ls == 0 | ||
dws_ls = 1; | ||
end | ||
|
||
%% GO THROUGH LOOPS | ||
O = zeros(nLayouts,nWS,nTI,nWD); % Initialize empty tensor | ||
for Layouti = 1:nLayouts | ||
try | ||
nTurbs = costIn.costFunInfo{Layouti,1,1}{1}.nTurbs; | ||
catch | ||
nTurbs = costIn.costFunInfo{1}.nTurbs; | ||
end | ||
% TRUE OUTER LOOPS | ||
for WSi = 1:nWS | ||
wsTrue = trueRange.WS(WSi); | ||
for TIi = 1:nTI | ||
tiTrue = trueRange.TI(TIi); | ||
for WDi = 1:nWD | ||
wdTrue = trueRange.WD(WDi); | ||
try | ||
costFunInfoLocal = costIn.costFunInfo{Layouti,WSi,TIi}{WDi}; | ||
catch | ||
costFunInfoLocal = costIn.costFunInfo{WDi}; | ||
end | ||
|
||
% ESTIMATE INNER LOOPS | ||
for WSii = 1:nSubWS | ||
wsEst = wsTrue + relSearchRange.WS(WSii); | ||
dws = abs(wsTrue - wsEst); | ||
for TIii = 1:nSubTI | ||
tiEst = tiTrue + relSearchRange.TI(TIii); | ||
dti = abs(tiTrue - tiEst); | ||
for WDii = 1:nSubWD | ||
wdEst = wdTrue + relSearchRange.WD(WDii); | ||
dwd = abs(wdEst-wdTrue); % Distance between arguments | ||
if dwd > pi % Radial distance | ||
dwd = 2*pi - dwd; | ||
end | ||
if dwd < 0 || dwd > pi | ||
error('Something went wrong here.') | ||
end | ||
msePwr = costFunInfoLocal.msePwr(TIii,WSii,WDii); | ||
mseU = costFunInfoLocal.mseU(TIii,WSii,WDii); | ||
mseUwse = costFunInfoLocal.mseUwse(TIii,WSii,WDii); % WSE equivalent | ||
|
||
rmsePwr = sqrt(msePwr); | ||
rmsePwrDimless = rmsePwr/(wsTrue^3); | ||
|
||
dxSqrd = 0; | ||
if nSubWD > 1 | ||
dxSqrd = dxSqrd + (dwd/dwd_ls)^2; | ||
end | ||
if nSubWS > 1 | ||
dxSqrd = dxSqrd + (dws/dws_ls)^2; | ||
end | ||
if nSubTI > 1 | ||
dxSqrd = dxSqrd + (dti/dti_ls)^2; | ||
end | ||
|
||
%% ----- COST FUNCTION | ||
J = eval(jFunctionString); | ||
costJ(TIii,WSii,WDii) = J; | ||
obsvM(TIii,WSii,WDii) = eval(mFunctionString); | ||
end | ||
end | ||
end | ||
|
||
% Filtering | ||
Mfiltered = obsvM; | ||
if deadzone.apply | ||
Mfiltered(abs(relSearchRange.TI) < deadzone.TI,... | ||
abs(relSearchRange.WS) < deadzone.WS,... | ||
abs(relSearchRange.WD) < deadzone.WD) = Inf; | ||
|
||
costJ(abs(relSearchRange.TI) < deadzone.TI,... | ||
abs(relSearchRange.WS) < deadzone.WS,... | ||
abs(relSearchRange.WD) < deadzone.WD) = Inf; | ||
end | ||
|
||
% Save outputs of M | ||
costJ_array{Layouti,WSi,TIi,WDi} = costJ; | ||
obsvM_array{Layouti,WSi,TIi,WDi} = obsvM; | ||
obsvMfilt_array{Layouti,WSi,TIi,WDi} = Mfiltered; | ||
|
||
% Calculate observability O | ||
[obsvO,idx] = min(Mfiltered(:)); % Pick O as worst case scenario (lowest value of M) | ||
O(Layouti,WSi,TIi,WDi) = obsvO; | ||
end | ||
end | ||
end | ||
end | ||
|
||
% Construct output matrix | ||
structOut = costIn; % Copy input information to output matrix | ||
structOut.deadzone = deadzone; % Append deadzone to struct | ||
structOut.J = costJ_array; % Add J to output struct | ||
structOut.M = obsvM_array; % Add M to output struct | ||
structOut.Mfilt = obsvMfilt_array; % Add filtered M to output struct | ||
structOut.O = O; % Add observability to output struct |
Oops, something went wrong.