Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
182 changes: 182 additions & 0 deletions examples/upcrossing_example.ipynb

Large diffs are not rendered by default.

36 changes: 18 additions & 18 deletions mhkit/loads/extreme.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from scipy import stats
from scipy import optimize
from mhkit.wave.resource import frequency_moment
from mhkit.utils import upcrossing, custom


def global_peaks(t, data):
Expand All @@ -29,24 +30,23 @@ def global_peaks(t, data):
assert isinstance(t, np.ndarray), 't must be of type np.ndarray'
assert isinstance(data, np.ndarray), 'data must be of type np.ndarray'

# eliminate zeros
zeroMask = (data == 0)
data[zeroMask] = 0.5 * np.min(np.abs(data))
# zero up-crossings
diff = np.diff(np.sign(data))
zeroUpCrossings_mask = (diff == 2) | (diff == 1)
zeroUpCrossings_index = np.where(zeroUpCrossings_mask)[0]
zeroUpCrossings_index = np.append(zeroUpCrossings_index, len(data) - 1)
# global peaks
npeaks = len(zeroUpCrossings_index)
peaks = np.array([])
t_peaks = np.array([])
for i in range(npeaks - 1):
peak_index = np.argmax(
data[zeroUpCrossings_index[i]:zeroUpCrossings_index[i + 1]])
t_peaks = np.append(t_peaks, t[zeroUpCrossings_index[i] + peak_index])
peaks = np.append(peaks, data[zeroUpCrossings_index[i] + peak_index])
return t_peaks, peaks
# Find zero up-crossings
inds = upcrossing(t, data)

# We also include the final point in the dataset
inds = np.append(inds, len(data)-1)

# As we want to return both the time and peak
# values, look for the index at the peak.
# The call to argmax gives us the index within the
# upcrossing period. Therefore to get the index in the
# original array we need to add on the index that
# starts the zero crossing period, ind1.
func = lambda ind1, ind2: np.argmax(data[ind1:ind2]) + ind1

peak_inds = np.array(custom(t, data, func, inds), dtype=int)

return t[peak_inds], data[peak_inds]


def number_of_short_term_peaks(n, t, t_st):
Expand Down
51 changes: 51 additions & 0 deletions mhkit/tests/loads/test_extreme.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import numpy as np
import unittest
import mhkit.loads as loads
from numpy.testing import assert_allclose


class TestExtreme(unittest.TestCase):
@classmethod
def setUpClass(self):
self.t, self.signal = self._example_waveform(self)

def _example_waveform(self):
# Create simple wave form to analyse.
# This has been created to perform
# a simple independent calcuation that
# the mhkit functions can be tested against.

A = np.array([0.5, 0.6, 0.3])
T = np.array([3, 2, 1])
w = 2 * np.pi / T

t = np.linspace(0, 4.5, 100)

signal = np.zeros(t.size)
for i in range(A.size):
signal += A[i] * np.sin(w[i] * t)

return t, signal

def _example_crest_analysis(self, t, signal):
# NB: This only works due to the construction
# of our test signal. It is not suitable as
# a general approach.
grad = np.diff(signal)

# +1 to get the index at turning point
turning_points = np.flatnonzero(grad[1:] * grad[:-1] < 0) + 1

crest_inds = turning_points[signal[turning_points] > 0]
crests = signal[crest_inds]

return crests, crest_inds

def test_global_peaks(self):
peaks_t, peaks_val = loads.extreme.global_peaks(self.t, self.signal)

test_crests, test_crests_ind = self._example_crest_analysis(
self.t, self.signal)

assert_allclose(peaks_t, self.t[test_crests_ind])
assert_allclose(peaks_val, test_crests)
142 changes: 142 additions & 0 deletions mhkit/tests/utils/test_upcrossing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
from mhkit.utils import upcrossing, peaks, troughs, heights, periods, custom
import unittest
from numpy.testing import assert_allclose
import numpy as np
from scipy.optimize import fsolve


class TestUpcrossing(unittest.TestCase):
@classmethod
def setUpClass(self):
self.t = np.linspace(0, 4, 1000)

self.signal = self._example_waveform(self, self.t)

# Approximiate points for the zero crossing,
# used as starting points in numerical
# solution.
self.zero_cross_approx = [0, 2.1, 3, 3.8]

def _example_waveform(self, t):
# Create simple wave form to analyse.
# This has been created to perform
# a simple independent calcuation that
# the mhkit functions can be tested against.

A = np.array([0.5, 0.6, 0.3])
T = np.array([3, 2, 1])
w = 2 * np.pi / T

signal = np.zeros(t.size)
for i in range(A.size):
signal += A[i] * np.sin(w[i] * t)

return signal

def _example_analysis(self, t, signal):
# NB: This only works due to the construction
# of our test signal. It is not suitable as
# a general approach.
grad = np.diff(signal)

# +1 to get the index at turning point
turning_points = np.flatnonzero(grad[1:] * grad[:-1] < 0) + 1

crest_inds = turning_points[signal[turning_points] > 0]
trough_inds = turning_points[signal[turning_points] < 0]

crests = signal[crest_inds]
troughs = signal[trough_inds]

heights = crests - troughs

zero_cross = fsolve(self._example_waveform, self.zero_cross_approx)
periods = np.diff(zero_cross)

return crests, troughs, heights, periods

def test_peaks(self):
want, _, _, _ = self._example_analysis(self.t, self.signal)

got = peaks(self.t, self.signal)

assert_allclose(got, want)

def test_troughs(self):
_, want, _, _ = self._example_analysis(self.t, self.signal)

got = troughs(self.t, self.signal)

assert_allclose(got, want)

def test_heights(self):
_, _, want, _ = self._example_analysis(self.t, self.signal)

got = heights(self.t, self.signal)

assert_allclose(got, want)

def test_periods(self):
_, _, _, want = self._example_analysis(self.t, self.signal)

got = periods(self.t, self.signal)

assert_allclose(got, want, rtol=1e-3, atol=1e-3)

def test_custom(self):
want, _, _, _ = self._example_analysis(self.t, self.signal)

# create a similar function to finding the peaks
def f(ind1, ind2): return np.max(self.signal[ind1:ind2])

got = custom(self.t, self.signal, f)

assert_allclose(got, want)

def test_peaks_with_inds(self):
want, _, _, _ = self._example_analysis(self.t, self.signal)

inds = upcrossing(self.t, self.signal)

got = peaks(self.t, self.signal, inds)

assert_allclose(got, want)

def test_trough_with_inds(self):
_, want, _, _ = self._example_analysis(self.t, self.signal)

inds = upcrossing(self.t, self.signal)

got = troughs(self.t, self.signal, inds)

assert_allclose(got, want)

def test_heights_with_inds(self):
_, _, want, _ = self._example_analysis(self.t, self.signal)

inds = upcrossing(self.t, self.signal)

got = heights(self.t, self.signal, inds)

assert_allclose(got, want)

def test_periods_with_inds(self):
_, _, _, want = self._example_analysis(self.t, self.signal)

inds = upcrossing(self.t, self.signal)

got = periods(self.t, self.signal, inds)

assert_allclose(got, want, rtol=1e-3, atol=1e-3)

def test_custom_with_inds(self):
want, _, _, _ = self._example_analysis(self.t, self.signal)

inds = upcrossing(self.t, self.signal)

# create a similar function to finding the peaks
def f(ind1, ind2): return np.max(self.signal[ind1:ind2])

got = custom(self.t, self.signal, f, inds)

assert_allclose(got, want)
1 change: 1 addition & 0 deletions mhkit/utils/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from .time_utils import matlab_to_datetime, excel_to_datetime, index_to_datetime
from .stat_utils import get_statistics, vector_statistics, unwrap_vector, magnitude_phase, unorm
from .cache import handle_caching, clear_cache
from .upcrossing import upcrossing, peaks, troughs, heights, periods, custom

_matlab = False # Private variable indicating if mhkit is run through matlab
Loading