Skip to content

Commit 23f8381

Browse files
authored
Merge pull request #176 from simmsa/ndbc_examples_wave_module_only
Split PR #167 into wave module changes
2 parents c2ee87e + c1fc25f commit 23f8381

14 files changed

Lines changed: 1084 additions & 921 deletions

changelog.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
# Current development
22

3+
## PR 176 - Wave Module Native MATLAB Implementation
4+
5+
- Authors: @MShabara, @simmsa
6+
- Convert wave module functions to native MATLAB code:
7+
- wave/resource/standardize_wave_spectra_frequency.m:
8+
- wave/resource/frequency_moment.m:
9+
- wave/resource/energy_period.m:
10+
- wave/resource/significant_wave_height.m:
11+
- wave/resource/jonswap_spectrum.m:
12+
- wave/resource/pierson_moskowitz_spectrum.m:
13+
- wave/resource/surface_elevation.m:
14+
315
## PR 153 - Mooring Module
416

517
- Author: @hivanov-nrel

examples/SWAN_example.html

Lines changed: 9 additions & 9 deletions
Large diffs are not rendered by default.

examples/SWAN_example.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
% # An ASCII block file ('SWANOUTBlock.DAT')
1818
% # A binary block file ('SWANOUT.mat')
1919

20-
swan_path = "./examples/data/wave/SWAN/";
20+
swan_path = "./data/wave/SWAN/";
2121
swan_table_file = append(swan_path,"SWANOUT.DAT");
2222
swan_block_file = append(swan_path,"SWANOUTBlock.DAT");
2323
swan_block_mat_file = append(swan_path,"SWANOUT.mat") ;

examples/SWAN_example.mlx

9.89 KB
Binary file not shown.

examples/wave_example.html

Lines changed: 674 additions & 674 deletions
Large diffs are not rendered by default.

examples/wave_example.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
% We can use MHKiT to load data downloaded from <https://www.ndbc.noaa.gov/
77
% https://www.ndbc.noaa.gov>.
88

9-
relative_file_name = './examples/data/wave/data.txt';
9+
relative_file_name = './data/wave/data.txt';
1010
current_dir = fileparts(matlab.desktop.editor.getActiveFilename);
1111
full_file_name = fullfile(current_dir, relative_file_name);
1212
ndbc_data = read_NDBC_file(full_file_name);

examples/wave_example.mlx

1.18 KB
Binary file not shown.
Lines changed: 37 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,54 @@
1-
function Te=energy_period(S,varargin)
1+
function Te = energy_period(S, varargin)
22

33
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
44
%
5+
% Calculate wave energy period (Te) seconds from power spectral density (PSD)
56
%
67
% Parameters
78
% ------------
8-
% S: Spectral Density (m^2/Hz)
9-
% Pandas data frame
10-
% To make a pandas data frame from user supplied frequency and spectra
11-
% use py.mhkit_python_utils.pandas_dataframe.spectra_to_pandas(frequency,spectra)
12-
%
13-
% OR
14-
%
15-
% structure of form:
16-
% S.spectrum: Spectral Density (m^2/Hz)
17-
%
18-
% S.type: String of the spectra type, i.e. Bretschneider,
19-
% time series, date stamp etc.
20-
%
21-
% S.frequency: frequency (Hz)
9+
% S: structure or numeric array
10+
% If structure:
11+
% S.spectrum - Spectral density [m^2/Hz]
12+
% S.frequency - Frequency [Hz]
13+
% If numeric:
14+
% S is spectral density array (vector or matrix)
15+
% varargin{1} must contain frequency vector
2216
%
2317
% frequency_bins: vector (optional)
24-
% Bin widths for frequency of S. Required for unevenly sized bins
25-
%
18+
% Bin widths for frequency of S. Required for unevenly sized bins
2619
%
2720
% Returns
2821
% ---------
29-
% Te: float
30-
% Wave energy Period (s)
22+
% Te: double
23+
% Wave energy period [seconds] indexed by S.columns
3124
%
32-
%
33-
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
25+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3426

35-
% assign feq_bin
36-
if nargin == 2
37-
freq_bins = py.numpy.array(varargin{1});
38-
elseif nargin == 1
39-
freq_bins = py.None;
40-
else
41-
ME = MException('MATLAB:energy_period','Incorrect number of input arguments');
42-
throw(ME);
27+
arguments
28+
S
4329
end
4430

45-
S_py = typecast_spectra_to_mhkit_python(S);
31+
arguments (Repeating)
32+
varargin
33+
end
4634

47-
Te = py.mhkit.wave.resource.energy_period(S_py, pyargs('frequency_bins',freq_bins));
35+
% Calculate moments using frequency_moment function
36+
if isstruct(S)
37+
% Pass struct directly
38+
m0 = frequency_moment(S, 0, varargin{:});
39+
m_neg1 = frequency_moment(S, -1, varargin{:});
40+
elseif isnumeric(S)
41+
% Pass numeric spectrum with frequency vector
42+
if nargin < 2
43+
error('When S is numeric, frequency vector must be provided as second argument');
44+
end
45+
m0 = frequency_moment(S, 0, varargin{:});
46+
m_neg1 = frequency_moment(S, -1, varargin{:});
47+
else
48+
error('Input S must be a struct or numeric array');
49+
end
4850

49-
Te = typecast_from_mhkit_python(Te).data;
51+
% Calculate energy period
52+
Te = m_neg1 ./ m0;
53+
54+
end
Lines changed: 55 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,68 @@
1-
function m=frequency_moment(S,N,varargin)
1+
function m = frequency_moment(S, N, varargin)
22

33
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4-
% Calculates the Nth frequency moment of the spectrum
54
%
6-
% Parameters
7-
% ------------
8-
% S: Spectral Density (m^2/Hz)
9-
% Pandas data frame
10-
% To make a pandas data frame from user supplied frequency and spectra
11-
% use py.mhkit_python_utils.pandas_dataframe.spectra_to_pandas(frequency,spectra)
12-
%
13-
% OR
14-
%
15-
% structure of form:
16-
% S.spectrum: Spectral Density (m^2/Hz)
17-
%
18-
% S.type: String of the spectra type, i.e. Bretschneider,
19-
% time series, date stamp etc.
5+
% Calculates the Nth frequency moment of the spectrum
206
%
21-
% S.frequency: frequency (Hz)
7+
% Parameters
8+
% -----------
9+
% S: structure or numeric array
10+
% If structure:
11+
% S.spectrum - Spectral density [m^2/Hz]
12+
% S.frequency - Frequency [Hz]
13+
% If numeric:
14+
% S is spectral density array (vector or matrix)
15+
% varargin{1} must contain frequency vector
2216
%
23-
% N: int
24-
% Moment (0 for 0th, 1 for 1st ....)
17+
% N: int
18+
% Moment (0 for 0th, 1 for 1st ....)
2519
%
26-
% frequency_bins: vector (optional)
27-
% Bin widths for frequency of S. Required for unevenly sized bins
20+
% frequency_bins: vector (optional)
21+
% Bin widths for frequency of S. Required for unevenly sized bins
2822
%
2923
% Returns
30-
% ---------
31-
% m: double
32-
%
33-
%
24+
% -------
25+
% m: double
26+
% Nth Frequency Moment indexed by S.columns
3427
%
35-
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
28+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3629

37-
S_py = typecast_spectra_to_mhkit_python(S);
30+
arguments
31+
S
32+
N {mustBeNumeric, mustBeFinite, mustBeInteger}
33+
end
3834

39-
if nargin == 3
40-
m = py.mhkit.wave.resource.frequency_moment(S_py, int32(N),pyargs('frequency_bins',py.numpy.array(varargin{1})));
41-
elseif nargin == 2
42-
m = py.mhkit.wave.resource.frequency_moment(S_py, int32(N));
43-
else
44-
ME = MException('MATLAB:frequency_moment','Incorrect number of arguments');
45-
throw(ME);
35+
arguments (Repeating)
36+
varargin
4637
end
4738

48-
m = typecast_from_mhkit_python(m).data;
39+
% Extract spectrum and frequency
40+
if isstruct(S)
41+
spectrum = S.spectrum;
42+
frequency = S.frequency;
43+
elseif isnumeric(S)
44+
if nargin < 3
45+
error('When S is numeric, frequency vector must be provided as third argument');
46+
end
47+
spectrum = S;
48+
frequency = varargin{1};
49+
varargin(1) = [];
50+
else
51+
error('Input S must be a struct or numeric array');
52+
end
53+
54+
% Standardize frequency, spectrum, and frequency bins
55+
if ~isempty(varargin)
56+
[frequency, spectrum, freq_bins] = standardize_wave_spectra_frequency(frequency, spectrum, varargin{1});
57+
else
58+
[frequency, spectrum, freq_bins] = standardize_wave_spectra_frequency(frequency, spectrum);
59+
end
60+
61+
if isscalar(freq_bins)
62+
freq_bins = freq_bins(:);
63+
end
64+
65+
% Calculate Nth moment: m_N = sum(f^N * S * df)
66+
m = sum((frequency.^N) .* spectrum .* freq_bins, 1);
67+
68+
end
Lines changed: 68 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,90 @@
1-
function S=jonswap_spectrum(frequency,Tp,Hs,varargin)
1+
function S = jonswap_spectrum(frequency, Tp, Hs, gamma)
22

33
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4-
% Calculates JONSWAP spectrum from Hasselmann et al (1973)
4+
%
5+
% Calculates JONSWAP Spectrum from IEC TS 62600-2 ED2 Annex C.2 (2019)
56
%
67
% Parameters
78
% ------------
8-
%
9-
% Frequency: float
10-
% Wave frequency (Hz)
9+
% frequency: vector
10+
% Frequency [Hz]
1111
%
1212
% Tp: float
13-
% Peak Period (s)
13+
% Peak period [s]
1414
%
1515
% Hs: float
16-
% Significant Wave Height (s)
16+
% Significant wave height [m]
1717
%
1818
% gamma: float (optional)
19-
% Gamma
19+
% Peak enhancement factor
2020
%
2121
% Returns
2222
% ---------
23-
% S: structure
24-
%
25-
%
26-
% S.spectrum=Spectral Density (m^2/Hz)
27-
%
28-
% S.type=String of the spectra type, i.e. Bretschneider,
29-
% time series, date stamp etc.
23+
% S: structure with fields
24+
% .spectrum = Spectral density [m^2/Hz]
25+
% .frequency = Frequency [Hz]
26+
% .type = 'JONSWAP (Hs,Tp)'
3027
%
31-
% S.frequency= frequency (Hz)
32-
%
33-
% S.Te
34-
%
35-
% S.Hm0
36-
%
37-
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
28+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3829

39-
if (isa(frequency,'py.numpy.ndarray') ~= 1)
40-
frequency = py.numpy.array(frequency);
30+
arguments
31+
frequency {mustBeNumeric, mustBeFinite}
32+
Tp {mustBeNumeric, mustBeFinite, mustBePositive}
33+
Hs {mustBeNumeric, mustBeFinite, mustBePositive}
34+
gamma {mustBeNumeric, mustBeFinite, mustBePositive} = []
4135
end
4236

43-
if nargin == 3
44-
S_py=py.mhkit.wave.resource.jonswap_spectrum(frequency,Tp,Hs);
45-
elseif nargin == 4
46-
S_py=py.mhkit.wave.resource.jonswap_spectrum(frequency, Tp, Hs, pyargs('gamma', varargin{1}));
37+
% Ensure column vector and sort (match MHKiT-Python behavior)
38+
frequency = frequency(:);
39+
f = sort(frequency);
40+
41+
% Constants (same as MHKiT-Python)
42+
B_PM = (5 / 4) * (1 / Tp)^4;
43+
A_PM = B_PM * (Hs / 2)^2;
44+
45+
% Avoid divide by zero if the 0 frequency is provided
46+
% The zero frequency should always have 0 amplitude, otherwise
47+
% we end up with a mean offset when computing the surface elevation.
48+
S_f = zeros(size(f));
49+
if f(1) == 0.0
50+
inds = 2:length(f); % Skip first element like MHKiT-Python
51+
else
52+
inds = 1:length(f); % Use all elements
53+
end
54+
55+
S_f(inds) = A_PM * f(inds).^(-5) .* exp(-B_PM * f(inds).^(-4));
56+
57+
% Gamma computation (match MHKiT-Python logic)
58+
if isempty(gamma)
59+
TpsqrtHs = Tp / sqrt(Hs);
60+
if TpsqrtHs <= 3.6
61+
gamma = 5;
62+
elseif TpsqrtHs > 5
63+
gamma = 1;
64+
else
65+
gamma = exp(5.75 - 1.15 * TpsqrtHs);
66+
end
4767
end
4868

49-
S_py = typecast_from_mhkit_python(S_py);
69+
% Cutoff frequencies for gamma function (match MHKiT-Python)
70+
siga = 0.07;
71+
sigb = 0.09;
72+
73+
% Peak frequency
74+
fp = 1 / Tp;
75+
lind = f <= fp;
76+
hind = f > fp;
77+
Gf = zeros(size(f));
78+
Gf(lind) = gamma .^ exp(-((f(lind) - fp).^2) ./ (2 * siga^2 * fp^2));
79+
Gf(hind) = gamma .^ exp(-((f(hind) - fp).^2) ./ (2 * sigb^2 * fp^2));
5080

51-
S = struct();
81+
C = 1 - 0.287 * log(gamma);
82+
Sf = C * S_f .* Gf;
5283

53-
S.frequency = S_py.index.data;
54-
S.spectrum = S_py.data;
55-
S.type = S_py.columns{1};
84+
% Output structure (match MHKiT-Python naming style)
85+
name = sprintf('JONSWAP (%.1fm,%.1fs)', Hs, Tp);
86+
S.frequency = f;
87+
S.spectrum = Sf;
88+
S.type = name;
89+
90+
end

0 commit comments

Comments
 (0)