Skip to content

Add OSWEC PTO damping optimization application - #116

Draft
basaucier wants to merge 4 commits into
WEC-Sim:devfrom
basaucier:feature/oswec-damping-optimization
Draft

basaucier wants to merge 4 commits into
WEC-Sim:devfrom
basaucier:feature/oswec-damping-optimization

Conversation

@basaucier

@basaucier basaucier commented Aug 26, 2026 •

Copy link
Copy Markdown

Summary

This draft PR adds an OSWEC PTO damping optimization application workflow.

The application connects buoy-derived wave resource characterization to WEC-Sim MCR package in order to estimate an optimal constant passive PTO damping value for the OSWEC case.

@akeeste akeeste left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work @basaucier! Some preliminary review comments included here. A few on some typical minor cleanup items and some on the workflow itself.

I recommend committing the various cluster results created in MHKiT for users. They should have the ipynb so that they can recreate results, but provide the precomputed clustered conditions as .mat files here ensures that this WEC-Sim workflow can be rerun easily without MHKiT.

Otherwise on the set-up files--these seem quite capable but first instinct is to pair them down a lot. They are quite long, which is mostly an issue for future understanding and maintenance by a broader team. Let's walk through the content in each and figure out what we need to keep

Comment thread OSWEC_Optimization_Damping/geometry/base.stl Outdated
Comment thread OSWEC_Optimization_Damping/geometry/flap.stl Outdated
Comment thread OSWEC_Optimization_Damping/hydroData/bemio.m Outdated
Comment thread OSWEC_Optimization_Damping/hydroData/oswec.out Outdated
Comment thread OSWEC_Optimization_Damping/hydroData/README.md Outdated
Comment thread OSWEC_Optimization_Damping/generate_wave_conditions.ipynb
Comment thread OSWEC_Optimization_Damping/run_oswec_batch.m Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove plots or wrap in an if statement that checks whether this is a single simulation or a batch simulation (typically whether the variable 'mcr' is present or not)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This script has a lot of extraneous code that is a lot to review and for a user to understand. The checks are not incorrect, but largely unnecessary given that the WEC-Sim output structure is very fixed and rigid. Please reduce this file greatly. The key components are:

  • for a given MCR run (wave condition), calculate the power produced.
  • After all MCR runs, calculate the mean annual energy production given power produced in each wave conditions and its weight.

Comment thread OSWEC_Optimization_Damping/cleanup_oswec_project.m Outdated
Comment thread OSWEC_Optimization_Damping/README.md

@akeeste akeeste left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @basaucier Thanks for pushing this case forward. There is still a lot of simplification that I would like to see. This is only a demonstration and WEC-Sim's inputs and outputs are fairly rigid. By hardcoding a lot of post-processing, the case will be succinct, easy to understand, and less code to maintain. Let's talk in detail next week.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bumping this comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bumping this comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file is adding a lot of complexity and code to review. Please remove it and write the MCR file directly from the MHKiT notebook. This example https://mhkit-software.github.io/MHKiT/wecsim_power_performance_example.html#2.-Write-a-WEC-Sim-batch-file-for-the-given-clusters has a short demo on writing data from python directly to the MCR file format required by WEC-Sim.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comment on the python notebook, remove outdated files and only write the most accurate cluster case.

Comment thread OSWEC_Optimization_Damping/generate_wave_conditions.ipynb

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This script has a lot of extraneous code that is a lot to review and for a user to understand. The checks are not incorrect, but largely unnecessary given that the WEC-Sim output structure is very fixed and rigid. Please reduce this file greatly. The key components are:

  • for a given MCR run (wave condition), calculate the power produced.
  • After all MCR runs, calculate the mean annual energy production given power produced in each wave conditions and its weight.

Comment on lines +246 to +282
if isstruct(output) && isfield(output, 'bodies')
bodyOut = output.bodies(1);
elseif isobject(output) && isprop(output, 'bodies')
bodyOut = output.bodies(1);
else
error('Could not find output.bodies.');
end

%% Extract body 1 pitch velocity

if ~oswecHasMember(bodyOut, 'velocity')
error('Could not find output.bodies(1).velocity.');
end

velocityRaw = oswecGetMember(bodyOut, 'velocity');

[bodyVelocity, velocityTime] = oswecSignalToArrayAndTime(velocityRaw);

flapPitchVelocity = oswecExtractDof(bodyVelocity, 5);

%% Extract body 1 pitch position if available

flapPitch = [];

if oswecHasMember(bodyOut, 'position')

positionRaw = oswecGetMember(bodyOut, 'position');

[bodyPosition, positionTime] = oswecSignalToArrayAndTime(positionRaw);

flapPitch = oswecExtractDof(bodyPosition, 5);

else

positionTime = [];

end

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unless a run fails, this information is always present

Comment on lines +496 to +584
function value = oswecGetMember(obj, name)
% Get struct field or object property.

value = obj.(name);

end


function [x, t] = oswecSignalToArrayAndTime(signal)
% Convert common signal formats to numeric array and time.

t = [];

if isa(signal, 'timeseries')
t = signal.Time;
x = signal.Data;
x = squeeze(x);
return
end

if isstruct(signal)

if isfield(signal, 'time')
t = signal.time;
elseif isfield(signal, 'Time')
t = signal.Time;
end

if isfield(signal, 'signals') && isfield(signal.signals, 'values')
x = signal.signals.values;
elseif isfield(signal, 'Data')
x = signal.Data;
elseif isfield(signal, 'data')
x = signal.data;
else
x = signal;
end

else

x = signal;

end

if istable(x)
x = table2array(x);
end

x = squeeze(x);

end


function dofSignal = oswecExtractDof(x, dof)
%Extract DOF column from WEC-Sim response matrix.
% Handles common layouts:
% N x 6 columns are DOFs
% N x 7 first column is time, columns 2:7 are DOFs
% 6 x N rows are DOFs
% 7 x N first row is time, rows 2:7 are DOFs

if isvector(x)

dofSignal = x(:);
return

end

[nRows, nCols] = size(x);

if nCols == 6
dofSignal = x(:, dof);

elseif nCols >= 7
dofSignal = x(:, dof + 1);

elseif nRows == 6
dofSignal = x(dof, :).';

elseif nRows >= 7
dofSignal = x(dof + 1, :).';

else
error('Could not extract DOF %d from signal with size %d x %d.', dof, nRows, nCols);
end

dofSignal = dofSignal(:);

end No newline at end of file

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unnecessarily length code. remove


[bodyVelocity, velocityTime] = oswecSignalToArrayAndTime(velocityRaw);

flapPitchVelocity = oswecExtractDof(bodyVelocity, 5);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this variable can be defined with only:
flapPitchVelocity = output.bodies(1).velocity(:,5);
removing the need for much of the post-processing code

Comment on lines +6 to +45
%% MCR Damping-Sweep Setup

project_folder = fileparts(mfilename('fullpath'));

% Base wave-condition file generated by Python/MHKiT.
% This can be either your array-style .mat or your wave-condition mcr .mat.
baseWaveMcrFile = fullfile(project_folder, 'wave_conditions', ...
'wave_conditions_buoy_46050_2020_2021_2022_2023_2024_2025_16_clusters.mat');

% Runtime WEC-Sim-compatible MCR file.
runtimeMcrFile = fullfile(project_folder, 'wave_conditions', ...
'oswec_runtime_damping_grid_mcr.mat');

% PTO damping values for the grid search.
% Change this line to change the damping sweep.
dampingValues = [1e4 1e5 1e6 1e7 1e8].';

% Build the runtime MCR file on the initial setup evaluation.
% During the actual MCR loop, variable mcr should already exist.
if ~exist('mcr', 'var') || ~isfield(mcr, 'cases')

runtimeMcrFile = oswecBuildDampingMCR( ...
baseWaveMcrFile, ...
dampingValues, ...
runtimeMcrFile);

data = load(runtimeMcrFile, 'mcr');
mcr = data.mcr;

end

% Determine current MCR case index.
% In WEC-Sim MCR, the loop variable is commonly imcr.
if exist('imcr', 'var')
caseIndex = imcr;
elseif isfield(mcr, 'caseNum')
caseIndex = mcr.caseNum;
else
caseIndex = 1;
end

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't want to rebuild the mcr variable every time this script runs. Point directly to the final result from the python notebook. It can be hardcoded for this case.

I suggest this pre-processing look for a single variable: pto damping, e.g. PTO_DAMPING. If this is not defined, pto.damping defaults to a certain value. If it is defined, it takes the value of PTO_DAMPING. Then a wrapper function can take in one argument, PTO_DAMPING, call wecSimMCR, and return the next mean annual energy production

Comment on lines +9 to +12
**Dependencies:**
* WEC-Sim
* MATLAB/Simulink
* Precomputed wave conditions (`.mat` files in `wave_conditions/`)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move to a products.txt file for the tests

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants