Last updated on Jun 03, 2026.

Source code for netsse.analys.sawb

# -*- coding: utf-8 -*-
"""
Python implementation of various algorithms for sea state estimation
using the **wave-buoy analogy** (WBA), i.e. using the wave-induced
responses of ships considered as sailing wave buoys (SAWB).

.. dropdown:: Copyright (C) 2023-2026 Technical University of Denmark, R.E.G. Mounet
    :color: primary
    :icon: law

    *This code is part of the NetSSE software.*

    NetSSE is free software: you can redistribute it and/or modify it under
    the terms of the GNU General Public License as published by the Free
    Software Foundation, either version 3 of the License, or (at your
    option) any later version.

    NetSSE is distributed in the hope that it will be useful, but WITHOUT
    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
    for more details.

    You should have received a copy of the GNU General Public License along
    with this program.  If not, see https://www.gnu.org/licenses/.

    To credit the author, users are encouraged to use below reference:

    .. code-block:: text

        Mounet, R. E. G., & Nielsen, U. D. NetSSE: An open-source Python package
        for network-based sea state estimation from ships, buoys, and other
        observation platforms (version 2.2). Technical University of Denmark,
        GitLab. February 2026. https://doi.org/10.11583/DTU.26379811.

*Last updated on 20-02-2026 by R.E.G. Mounet*

"""

import numpy as np
from scipy.integrate import trapezoid
from netsse.tools.misc_func import weighted_std


[docs] def specres(Rxy, TRF, freq, beta, opt=None): """Computes an estimate of the sea state using the spectral-residual method from Brodtkorb et al. (2017) and Nielsen et al. (2018). The heave, roll, and pitch response cross-spectra are used as input. Using the motion transfer functions of the vessel, the wave spectrum is estimated through iteration. .. warning:: Long-crested wave conditions are assumed in this sea state estimation method. .. note:: This Python code is based on a MATLAB/Simulink implementation by Astrid H. Brodtkorb. Parameters ---------- Rxy : array_like of shape (Nfreq,6) The complex-valued response spectra, given as in: ``Rxy = [heaveheave, rollroll, pitchpitch, heaveroll, heavepitch, rollpitch]``. TRF : array_like of shape (Nfreq,3*Nbeta) The transfer functions of the ship, in heave, roll, and pitch, concatenated along the second axis as in: ``TRF = [heave_TF,roll_TF,pitch_TF]`` .. note:: The phase of the complex transfer functions is not important in this implementation, and the amplitude alone can be provided without this affecting the estimation results. freq : array_like of shape (Nfreq,) The frequencies of the response spectra and the transfer functions (should match). beta : array_like of shape (Nbeta,) The discretized heading angles [deg] at which the transfer functions are known. .. tip:: For a port-starboard symmetric ship, directions from 0 deg to 180 deg only can be considered to lower the computational cost. opt: dict, optional Optional parameters controlling the SSE calculation. Available options are: - 'maxiter' : int, or array_like of shape (6,) Maximum number of iterations (default: 50). - 'tolCoef' : float, or array_like of shape (6,) Tolerance coefficient (default: 0.1). - 'gainFact' : float, or array_like of shape (6,) Gain factor, as a fraction of the maximum gain value (default: 0.5). - 'weights' : float, or array_like of shape (6,) Weights given to each response in the calculation of the final spectrum estimate (default: equal weight for each response, i.e. wij = 1/6). .. note:: `array_like` entries of the dictionary can be input for a response-specific option. In such case, the array elements must be provided in the same order as for the response spectra. Returns ------- S_wave : array_like of shape (Nfreq,) The estimated 1-D wave spectrum. beta_est : float The estimated relative wave heading [degrees]. num_it : array_like of shape (6,) The average number of iterations used per heading angle, for the individual response pairs organised as: ``num_it = [it3, it4, it5, it34, it35, it45]``. References ---------- 1. Brodtkorb, A. H., Nielsen, U. D., & Sørensen, A. J. (2018). Online wave estimation using vessel motion measurements. IFAC-PapersOnLine, 51(29), 244–249. https://doi.org/10.1016/j.ifacol.2018.09.510 2. Brodtkorb, A. H., Nielsen, U. D., & Sørensen, A. J. (2018). Sea state estimation using vessel response in dynamic positioning. Applied Ocean Research, 70, 76–86. https://doi.org/10.1016/j.apor.2017.09.005 3. Nielsen, U. D., Brodtkorb, A. H., & Sørensen, A. J. (2018). A brute-force spectral approach for wave estimation using measured vessel motions. Marine Structures, 60, 101–121. https://doi.org/10.1016/j.marstruc.2018.03.011 4. Brodtkorb, A. H., & Nielsen, U. D. (2023). Automatic sea state estimation with online trust measure based on ship response measurements. Control Engineering Practice, 130. https://doi.org/10.1016/j.conengprac.2022.105375 Example ------- >>> S_wave, beta_est, num_it = specres(Rxy, TRF, freq, beta, opt=None) """ ###### #### Deciphering inputs ###### beta_rad = np.radians(beta) Nbeta = np.shape(beta_rad)[0] # //2+1 # Transfer functions: heave_TF = TRF[:, 0:Nbeta] roll_TF = TRF[:, Nbeta : 2 * Nbeta] pitch_TF = TRF[:, 2 * Nbeta : 3 * Nbeta] # Using response cross-spectra amplitudes for iteration: heaveheave = np.abs(Rxy[:, 0]) rollroll = np.abs(Rxy[:, 1]) pitchpitch = np.abs(Rxy[:, 2]) heaveroll = np.abs(Rxy[:, 3]) heavepitch = np.abs(Rxy[:, 4]) rollpitch = np.abs(Rxy[:, 5]) # and imaginary part of response cross-spectra: I_heaveroll = np.imag(Rxy[:, 3]) I_heavepitch = np.imag(Rxy[:, 4]) ###### #### Computing the upper bound on the gains hij ###### max_heaveheave = np.amax(np.abs(np.square(heave_TF))) max_rollroll = np.amax(np.abs(np.square(roll_TF))) max_pitchpitch = np.amax(np.abs(np.square(pitch_TF))) max_heaveroll = np.amax(np.abs(heave_TF * roll_TF)) max_heavepitch = np.amax(np.abs(heave_TF * pitch_TF)) max_rollpitch = np.amax(np.abs(roll_TF * pitch_TF)) max_h33 = 2 / max_heaveheave max_h44 = 2 / max_rollroll max_h55 = 2 / max_pitchpitch max_h34 = 2 / max_heaveroll max_h35 = 2 / max_heavepitch max_h45 = 2 / max_rollpitch ###### #### Options ###### maxiter = 50 * np.ones((6,)) tolcoef = 0.1 * np.ones((6,)) gainfact = 0.5 * np.ones((6,)) wij = 1 / 6 * np.ones((6,)) if opt == None: opt = [] if "maxiter" in opt: maxiter = opt["maxiter"] if np.size(maxiter) == 1: maxiter = maxiter * np.ones((6,)) if "tolCoef" in opt: tolcoef = opt["tolCoef"] if np.size(tolcoef) == 1: tolcoef = tolcoef * np.ones((6,)) if "gainFact" in opt: gainfact = opt["gainFact"] if np.size(gainfact) == 1: gainfact = gainfact * np.ones((6,)) if "weights" in opt: wij = opt["weights"] if np.size(wij) == 1: wij = wij * np.ones((6,)) wij = wij / np.sum(wij) ###### #### Calculate tolerances ###### e_33 = tolcoef[0] * np.max(heaveheave) e_44 = tolcoef[1] * np.max(rollroll) e_55 = tolcoef[2] * np.max(pitchpitch) e_34 = tolcoef[3] * np.max(heaveroll) e_35 = tolcoef[4] * np.max(heavepitch) e_45 = tolcoef[5] * np.max(rollpitch) ###### #### Calculate the iteration gains ###### h_33 = gainfact[0] * max_h33 # hij[0] h_44 = gainfact[0] * max_h44 # hij[1] h_55 = gainfact[0] * max_h55 # hij[2] h_34 = gainfact[0] * max_h34 # hij[3] h_35 = gainfact[0] * max_h35 # hij[4] h_45 = gainfact[0] * max_h45 # hij[5] ###### #### Iteration procedure ## Iterate to find estimates of the wave spectrum #### # Initialize Nfreq = np.shape(freq)[0] S_33_hat = np.zeros((Nfreq, Nbeta)) R_33_hat = np.zeros((Nfreq, Nbeta)) S_44_hat = np.zeros((Nfreq, Nbeta)) R_44_hat = np.zeros((Nfreq, Nbeta)) S_55_hat = np.zeros((Nfreq, Nbeta)) R_55_hat = np.zeros((Nfreq, Nbeta)) S_34_hat = np.zeros((Nfreq, Nbeta)) R_34_hat = np.zeros((Nfreq, Nbeta)) S_35_hat = np.zeros((Nfreq, Nbeta)) R_35_hat = np.zeros((Nfreq, Nbeta)) S_45_hat = np.zeros((Nfreq, Nbeta)) R_45_hat = np.zeros((Nfreq, Nbeta)) Est_fp = np.zeros((6, Nbeta)) Est_Hs = np.zeros((6, Nbeta)) resid = np.zeros((6, Nbeta)) num_it = np.zeros((6, Nbeta)) for jj in range(Nbeta): Phi_w2 = np.abs(heave_TF[:, jj] ** 2) Phi_phi2 = np.abs(roll_TF[:, jj] ** 2) Phi_theta2 = np.abs(pitch_TF[:, jj] ** 2) Phi_wphi = np.abs(heave_TF[:, jj] * np.conj(roll_TF[:, jj])) Phi_wtheta = np.abs(heave_TF[:, jj] * np.conj(pitch_TF[:, jj])) Phi_phitheta = np.abs(roll_TF[:, jj] * np.conj(pitch_TF[:, jj])) # Residuals: res_33 = heaveheave res_44 = rollroll res_55 = pitchpitch res_34 = heaveroll res_35 = heavepitch res_45 = rollpitch it3 = 0 while np.sum(np.abs(res_33)) > e_33: # Heave-Heave R_33_hat[:, jj] = Phi_w2 * S_33_hat[:, jj] res_33 = heaveheave - R_33_hat[:, jj] S_33_hat[:, jj] = S_33_hat[:, jj] + h_33 * res_33 it3 = it3 + 1 if it3 >= maxiter[0]: break # print('it3 = ',it3) it4 = 0 while np.sum(np.abs(res_44)) > e_44: # Roll-Roll R_44_hat[:, jj] = Phi_phi2 * S_44_hat[:, jj] res_44 = rollroll - R_44_hat[:, jj] S_44_hat[:, jj] = S_44_hat[:, jj] + h_44 * res_44 it4 = it4 + 1 if it4 >= maxiter[1]: break # print('it4 = ',it4) it5 = 0 while np.sum(np.abs(res_55)) > e_55: # Pitch-Pitch R_55_hat[:, jj] = Phi_theta2 * S_55_hat[:, jj] res_55 = pitchpitch - R_55_hat[:, jj] S_55_hat[:, jj] = S_55_hat[:, jj] + h_55 * res_55 it5 = it5 + 1 if it5 >= maxiter[2]: break # print('it5 = ',it5) it34 = 0 while np.sum(np.abs(res_34)) > e_34: # Heave-Roll R_34_hat[:, jj] = Phi_wphi * S_34_hat[:, jj] res_34 = heaveroll - R_34_hat[:, jj] S_34_hat[:, jj] = S_34_hat[:, jj] + h_34 * res_34 it34 = it34 + 1 if it34 >= maxiter[3]: break # print('it34 = ',it34) it35 = 0 while np.sum(np.abs(res_35)) > e_35: # Heave-Pitch R_35_hat[:, jj] = Phi_wtheta * S_35_hat[:, jj] res_35 = heavepitch - R_35_hat[:, jj] S_35_hat[:, jj] = S_35_hat[:, jj] + h_35 * res_35 it35 = it35 + 1 if it35 >= maxiter[4]: break # print('it35 = ',it35) it45 = 0 while np.sum(np.abs(res_45)) > e_45: # Roll-Pitch R_45_hat[:, jj] = Phi_phitheta * S_45_hat[:, jj] res_45 = rollpitch - R_45_hat[:, jj] S_45_hat[:, jj] = S_45_hat[:, jj] + h_45 * res_45 it45 = it45 + 1 if it45 >= maxiter[5]: break # print('it45 = ',it45) num_it[:, jj] = [it3, it4, it5, it34, it35, it45] # Number of iterations # Calculate estimated sea state parameters # Hs, Tp, Direction # Peak frequencies, peak periods idx33 = np.argmax(S_33_hat[:, jj]) idx44 = np.argmax(S_44_hat[:, jj]) idx55 = np.argmax(S_55_hat[:, jj]) idx34 = np.argmax(S_34_hat[:, jj]) idx35 = np.argmax(S_35_hat[:, jj]) idx45 = np.argmax(S_45_hat[:, jj]) wave33_fp = freq[idx33] wave44_fp = freq[idx44] wave55_fp = freq[idx55] wave34_fp = freq[idx34] wave35_fp = freq[idx35] wave45_fp = freq[idx45] start = 1 stop = Nfreq # Significant wave height m0_33 = trapezoid(S_33_hat[start:stop, jj], freq[start:stop]) m0_44 = trapezoid(S_44_hat[start:stop, jj], freq[start:stop]) m0_55 = trapezoid(S_55_hat[start:stop, jj], freq[start:stop]) m0_34 = trapezoid(S_34_hat[start:stop, jj], freq[start:stop]) m0_35 = trapezoid(S_35_hat[start:stop, jj], freq[start:stop]) m0_45 = trapezoid(S_45_hat[start:stop, jj], freq[start:stop]) Hs33 = 4 * np.sqrt(np.abs(m0_33)) Hs44 = 4 * np.sqrt(np.abs(m0_44)) Hs55 = 4 * np.sqrt(np.abs(m0_55)) Hs34 = 4 * np.sqrt(np.abs(m0_34)) Hs35 = 4 * np.sqrt(np.abs(m0_35)) Hs45 = 4 * np.sqrt(np.abs(m0_45)) Est_fp[:, jj] = [ wave33_fp, wave44_fp, wave55_fp, wave34_fp, wave35_fp, wave45_fp, ] Est_Hs[:, jj] = [Hs33, Hs44, Hs55, Hs34, Hs35, Hs45] resid[:, jj] = [ np.sum(np.abs(res_33)), np.sum(np.abs(res_44)), np.sum(np.abs(res_55)), np.sum(np.abs(res_34)), np.sum(np.abs(res_35)), np.sum(np.abs(res_45)), ] ###### #### Find Direction based on cross-spectra ## Nielsen et al. 2018, Brodtkorb et al. 2018 (CAMS) #### # S_ij = np.array([S_33_hat,S_44_hat,S_55_hat,S_34_hat,S_35_hat,S_45_hat]) # meanvar_S = np.mean(np.var(S_ij,axis=0,ddof=1),axis=0) var_Hs = np.var(Est_Hs, axis=0, ddof=1) # COV_Hs = np.sqrt(np.var(Est_Hs,axis=0,ddof=1))/np.mean(Est_Hs,axis=0) # COV_fp = np.sqrt(np.var(Est_fp,axis=0,ddof=1))/np.mean(Est_fp,axis=0) # Direction estimate, in [0,180] deg alpha_idx = np.argmin(var_Hs) # alpha_idx_Hs = np.argmin(Var_Hs) # alpha_idx_fp = np.argmin(Var_fp) # if np.abs((np.pi-beta_rad[alpha_idx_fp])-beta_rad[alpha_idx_Hs]) < np.abs(beta_rad[alpha_idx_fp]-beta_rad[alpha_idx_Hs]): # alpha_idx_fp = np.argmin(np.abs(beta_rad-(np.pi-beta_rad[alpha_idx_fp]))) # alpha_idx = round((alpha_idx_Hs+alpha_idx_fp)/2) alpha = beta_rad[alpha_idx] # Use the imaginary part of the cross spectra heave/roll, roll/pitch pairs # to find port/starboard and correct estimate for head/following seas, if # nessesary # Compute largest peak of imaginary component # maxIDX34 = np.argmax(np.abs(I_heaveroll)) # Peak34 = I_heaveroll[maxIDX34,] # maxIDX35 = np.argmax(np.abs(I_heavepitch)) # Peak35 = I_heavepitch[maxIDX35] # Compute the variance of the imaginary component Int34 = trapezoid(I_heaveroll[start:stop,], freq[start:stop]) Int35 = trapezoid(I_heavepitch[start:stop,], freq[start:stop]) # print([np.sign(Peak34),np.sign(Int34),np.sign(Peak35),np.sign(Int35)]) Est_dir = alpha # initialize direction estimate # Head/following if Int35 < 0: # Following if Est_dir > np.pi / 2: # Direction estimate from min(varHs) needs correction Est_dir = np.pi - Est_dir # elif Est_dir < -np.pi/2: # Direction estimate from min(varHs) needs correction # Est_dir = -np.pi - Est_dir elif Int35 > 0: # Head if ( Est_dir < np.pi / 2 and Est_dir > 0 ): # Direction estimate from min(varHs) needs correction Est_dir = np.pi - Est_dir # elif Est_dir < 0 and Est_dir > -np.pi/2: # Est_dir = -np.pi - Est_dir alpha_idx = np.argmin(np.abs(beta_rad - Est_dir)) # Port/starboard if ( Int34 > 0 ): # Starboard: (Int34 > 0) used in APOR paper (based upon measured response of R/V Gunnerus) Est_dir = -Est_dir if Est_dir == -np.pi: Est_dir = np.abs(Est_dir) ###### #### Output the estimates ###### beta_est = np.degrees(Est_dir) # Relative wave heading estimate [deg] # 1-D wave spectrum estimate: S_wave = np.dot( wij, np.array( [ S_33_hat[:, alpha_idx], S_44_hat[:, alpha_idx], S_55_hat[:, alpha_idx], S_34_hat[:, alpha_idx], S_35_hat[:, alpha_idx], S_45_hat[:, alpha_idx], ] ), ) num_it = np.mean(num_it, axis=1) resid = resid[:, alpha_idx] # hatR_3 = R_33_hat[:,alpha_idx] # heave-heave # hatR_4 = R_44_hat[:,alpha_idx] # roll-roll # hatR_5 = R_55_hat[:,alpha_idx] # pitch-pitch # hatR_34 = R_34_hat[:,alpha_idx] # heave-roll # hatR_35 = R_35_hat[:,alpha_idx] # heave-pitch # hatR_45 = R_45_hat[:,alpha_idx] # roll-pitch # R_3 = heaveheave # R_4 = rollroll # R_5 = pitchpitch # R_34 = heaveroll # R_35 = heavepitch # R_45 = rollpitch # hatS_3 = S_33_hat[:,alpha_idx] # hatS_4 = S_44_hat[:,alpha_idx] # hatS_5 = S_55_hat[:,alpha_idx] # Hs_matrix = Est_Hs # Hs_variance = varHs return S_wave, beta_est, num_it # resid,hatS_3,hatS_4,hatS_5,Hs_matrix,Hs_variance
[docs] def genspecres(Rxy, Hxy, freq, beta, opt=None): """Computes an estimate of the point wave spectrum using a generalised version of the spectral-residual method from Brodtkorb et al. (2017) and Nielsen et al. (2018). The cross-spectra from any number of responses are used as input. Using the motion transfer functions of the vessel, the wave spectrum is estimated through iteration. .. warning:: Long-crested wave conditions are assumed in this sea state estimation method. The present version of the algorithm does not allow to distinguish waves coming from port and starboard sides. Parameters ---------- Rxy : array_like of shape (Nresp,Nfreq) The response cross-spectra. .. note:: The phase of the complex-valued response spectra is not important in this implementation, and the amplitude (absolute value) alone can be provided without this affecting the estimation results. Hxy : array_like of shape (Nresp,Nbeta,Nfreq) The RAO products for the ship, provided in the same order of response pairs as for the response cross-spectra `Rxy`. .. note:: The phase of the complex-valued transfer functions is not important in this implementation, and the amplitude alone can be provided without this affecting the estimation results. freq : array_like of shape (Nfreq,) The frequencies of the response spectra and the transfer functions (should match). beta : array_like of shape (Nbeta,) The discretized heading angles [deg] at which the transfer functions are known. .. tip:: For a port-starboard symmetric ship, directions from 0 deg to 180 deg only can be considered to lower the computational cost. opt: dict, optional Optional parameters controlling the SSE calculation. Available options are: - 'maxiter' : int, or array_like of shape (`Nresp`,) Maximum number of iterations (default: 50). - 'tolCoef' : float, or array_like of shape (`Nresp`,) Tolerance coefficient (default: 0.1). - 'gainFact' : float, or array_like of shape (`Nresp`,) Gain factor, as a fraction of the maximum gain value (default: 0.5). - 'weights' : float, or array_like of shape (`Nresp`,) Weights given to each response in the calculation of the final spectrum estimate (default: equal weight for each response, i.e. wij = 1/`Nresp`). .. note:: `array_like` entries of the dictionary can be input for a response-specific option. In such case, the array elements must be provided in the same order as for the response spectra. Returns ------- S_wave : array_like of shape (Nfreq,) The estimated 1-D wave spectrum. beta_est : float The estimated relative wave heading [degrees]. num_it : array_like of shape (Nresp,) The average number of iterations used per heading angle, for the individual response pairs. References ---------- 1. Brodtkorb, A. H., Nielsen, U. D., & Sørensen, A. J. (2018). Online wave estimation using vessel motion measurements. IFAC-PapersOnLine, 51(29), 244–249. https://doi.org/10.1016/j.ifacol.2018.09.510 2. Brodtkorb, A. H., Nielsen, U. D., & Sørensen, A. J. (2018). Sea state estimation using vessel response in dynamic positioning. Applied Ocean Research, 70, 76–86. https://doi.org/10.1016/j.apor.2017.09.005 3. Nielsen, U. D., Brodtkorb, A. H., & Sørensen, A. J. (2018). A brute-force spectral approach for wave estimation using measured vessel motions. Marine Structures, 60, 101–121. https://doi.org/10.1016/j.marstruc.2018.03.011 4. Brodtkorb, A. H., & Nielsen, U. D. (2023). Automatic sea state estimation with online trust measure based on ship response measurements. Control Engineering Practice, 130. https://doi.org/10.1016/j.conengprac.2022.105375 Example ------- >>> S_wave, beta_est, num_it = genspecres(Rxy, Hxy, freq, beta, opt=None) """ ###### #### Deciphering inputs ###### beta_rad = np.radians(beta) Nbeta = np.shape(beta_rad)[0] # //2+1 Nfreq = np.shape(freq)[0] # Transfer function amplitudes: Hxy_abs = np.abs(Hxy) # Using response cross-spectra amplitudes for iteration: Rxy_abs = np.abs(Rxy) Nresp = np.shape(Rxy_abs)[0] ###### #### Computing the upper bound on the gains hij ###### max_Hxy = np.amax(Hxy_abs, axis=(1, 2)) max_h_ij = 2 / max_Hxy ###### #### Options ###### maxiter = 50 * np.ones((Nresp,)) tolcoef = 0.1 * np.ones((Nresp,)) gainfact = 0.5 * np.ones((Nresp,)) wij = 1 / Nresp * np.ones((Nresp, 1)) if opt == None: opt = [] if "maxiter" in opt: maxiter = opt["maxiter"] if np.size(maxiter) == 1: maxiter = maxiter * np.ones((Nresp,)) if "tolCoef" in opt: tolcoef = opt["tolCoef"] if np.size(tolcoef) == 1: tolcoef = tolcoef * np.ones((Nresp,)) if "gainFact" in opt: gainfact = opt["gainFact"] if np.size(gainfact) == 1: gainfact = gainfact * np.ones((Nresp,)) if "weights" in opt: wij = opt["weights"] if np.size(wij) == 1: wij = wij * np.ones((Nresp, 1)) wij = np.reshape(wij / np.sum(wij), (Nresp, 1)) ###### #### Calculate tolerances ###### e_ij = tolcoef * np.max(Rxy_abs, axis=1) ###### #### Calculate the iteration gains ###### h_ij = gainfact * max_h_ij ###### #### Iteration procedure ## Iterate to find estimates of the wave spectrum ###### # Initialize S_ij_hat = np.zeros((Nresp, Nbeta, Nfreq)) R_ij_hat = np.zeros((Nresp, Nbeta, Nfreq)) resid = np.zeros((Nresp, Nbeta)) num_it = np.zeros((Nresp, Nbeta)) for i_beta in range(Nbeta): for i_resp in range(Nresp): # Residuals: res_ij = Rxy_abs[i_resp, :] it_ij = 0 while np.sum(np.abs(res_ij)) > e_ij[i_resp,]: R_ij_hat[i_resp, i_beta, :] = ( Hxy_abs[i_resp, i_beta, :] * S_ij_hat[i_resp, i_beta, :] ) res_ij = Rxy_abs[i_resp, :] - R_ij_hat[i_resp, i_beta, :] S_ij_hat[i_resp, i_beta, :] += h_ij[i_resp,] * res_ij it_ij += 1 if it_ij >= maxiter[i_resp,]: break # print('iteration = ', it_ij) num_it[i_resp, i_beta] = it_ij # Number of iterations resid[i_resp, i_beta] = np.sum(np.abs(res_ij)) ###### #### Calculate estimated sea state parameters ###### # Peak frequencies: idx_ij = np.argmax(S_ij_hat, axis=2) Est_fp = freq[idx_ij] # Significant wave height: start = 1 stop = Nfreq m0_ij = trapezoid(S_ij_hat[:, :, start:stop], freq[start:stop,], axis=2) Est_Hs = 4 * np.sqrt(np.abs(m0_ij)) ###### #### Find direction based on cross-spectra ## Nielsen et al. 2018, Brodtkorb et al. 2018 (CAMS) #### std_Hs = weighted_std(Est_Hs, wij, axis=0, ddof=1) # COV_Hs = np.sqrt(np.var(Est_Hs,axis=0,ddof=1))/np.mean(Est_Hs,axis=0) # COV_fp = np.sqrt(np.var(Est_fp,axis=0,ddof=1))/np.mean(Est_fp,axis=0) # Direction estimate, in [0,180] deg alpha_idx = np.argmin(std_Hs) # alpha_idx_Hs = np.argmin(Var_Hs) # alpha_idx_fp = np.argmin(Var_fp) # if np.abs((np.pi-beta_rad[alpha_idx_fp])-beta_rad[alpha_idx_Hs]) < np.abs(beta_rad[alpha_idx_fp]-beta_rad[alpha_idx_Hs]): # alpha_idx_fp = np.argmin(np.abs(beta_rad-(np.pi-beta_rad[alpha_idx_fp]))) # alpha_idx = round((alpha_idx_Hs+alpha_idx_fp)/2) alpha = beta_rad[alpha_idx] Est_dir = alpha # initialize direction estimate ###### #### Output the estimates ###### # Relative wave heading estimate [deg] (between 0-180 degrees): beta_est = np.degrees(Est_dir) # Point wave spectrum estimate: S_wave = np.sum(wij * S_ij_hat[:, alpha_idx, :], axis=0) # Number of iterations: num_it = np.mean(num_it, axis=1) # Residuals: resid = resid[:, alpha_idx] # Hs_matrix = Est_Hs # Hs_variance = varHs return S_wave, beta_est, num_it # resid,Hs_matrix,Hs_variance