-
Notifications
You must be signed in to change notification settings - Fork 24
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 #151 from MShabara/upcrossing
Feature: Add Upcrossing Analysis Module (Ported from MHKiT-Python PR #252)
- Loading branch information
Showing
11 changed files
with
541 additions
and
0 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
Binary file not shown.
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,164 @@ | ||
classdef upcrossing_Test < matlab.unittest.TestCase | ||
properties | ||
t | ||
signal | ||
zeroCrossApprox | ||
end | ||
|
||
methods (TestClassSetup) | ||
% Shared setup for the entire test class | ||
function setupTestClass(testCase) | ||
% Define time vector | ||
testCase.t = linspace(0, 4, 1000); | ||
|
||
% Define signal | ||
testCase.signal = testCase.exampleWaveform_(testCase.t); | ||
|
||
% Approximate zero crossings | ||
testCase.zeroCrossApprox = [0, 2.1, 3, 3.8]; | ||
end | ||
|
||
end | ||
|
||
methods | ||
function signal = exampleWaveform_(~, t) | ||
% Generate a simple waveform form to analyse | ||
% This has been created to perform | ||
% a simple independent calcuation that | ||
% the mhkit functions can be tested against. | ||
A = [0.5, 0.6, 0.3]; | ||
T = [3, 2, 1]; | ||
w = 2 * pi ./ T; | ||
|
||
signal = zeros(size(t)); | ||
for i = 1:length(A) | ||
signal = signal + A(i) * sin(w(i) * t); | ||
end | ||
end | ||
|
||
function [crests, troughs, heights, periods] = exampleAnalysis_(testCase, signal) | ||
% NB: This only works due to the construction | ||
% of our test signal. It is not suitable as | ||
% a general approach. | ||
|
||
% Gradient-based turning point analysis | ||
grad = diff(signal); | ||
|
||
% +1 to get the index at turning point | ||
turningPoints = find(grad(1:end-1) .* grad(2:end) < 0) + 1; | ||
|
||
crestInds = turningPoints(signal(turningPoints) > 0); | ||
troughInds = turningPoints(signal(turningPoints) < 0); | ||
|
||
crests = signal(crestInds); | ||
troughs = signal(troughInds); | ||
heights = crests - troughs; | ||
|
||
% Numerical zero-crossing solution | ||
zeroCross = zeros(size(testCase.zeroCrossApprox)); | ||
for i = 1:length(testCase.zeroCrossApprox) | ||
zeroCross(i) = fzero(@(x) testCase.exampleWaveform_(x), ... | ||
testCase.zeroCrossApprox(i)); | ||
end | ||
|
||
periods = diff(zeroCross); | ||
end | ||
end | ||
|
||
methods (Test) | ||
%% Test functions without indices (inds) | ||
function test_peaks(testCase) | ||
[want, ~, ~, ~] = testCase.exampleAnalysis_(testCase.signal); | ||
got = uc_peaks(testCase.t, testCase.signal); | ||
|
||
testCase.verifyEqual(got, want, 'AbsTol', 1e-3); | ||
end | ||
|
||
function test_troughs(testCase) | ||
[~, want, ~, ~] = testCase.exampleAnalysis_(testCase.signal); | ||
got = uc_troughs(testCase.t, testCase.signal); | ||
|
||
testCase.verifyEqual(got, want, 'AbsTol', 1e-3); | ||
end | ||
|
||
function test_heights(testCase) | ||
[~, ~, want, ~] = testCase.exampleAnalysis_(testCase.signal); | ||
|
||
got = uc_heights(testCase.t, testCase.signal); | ||
|
||
testCase.verifyEqual(got, want, 'AbsTol', 1e-3); | ||
end | ||
|
||
function test_periods(testCase) | ||
[~, ~, ~, want] = testCase.exampleAnalysis_(testCase.signal); | ||
|
||
got = uc_periods(testCase.t, testCase.signal); | ||
|
||
testCase.verifyEqual(got, want, 'AbsTol', 2e-3); | ||
end | ||
|
||
function test_custom(testCase) | ||
[want, ~, ~, ~] = testCase.exampleAnalysis_(testCase.signal); | ||
|
||
% create a similar function to finding the peaks | ||
f = @(ind1, ind2) max(testCase.signal(ind1:ind2)); | ||
|
||
got = uc_custom(testCase.t, testCase.signal, f); | ||
|
||
testCase.verifyEqual(got, want, 'AbsTol', 1e-3); | ||
end | ||
|
||
%% Test functions with indcies | ||
function test_peaks_with_inds(testCase) | ||
[want, ~, ~, ~] = testCase.exampleAnalysis_(testCase.signal); | ||
|
||
inds = upcrossing(testCase.t, testCase.signal); | ||
|
||
got = uc_peaks(testCase.t, testCase.signal, inds); | ||
|
||
testCase.verifyEqual(got, want, 'AbsTol', 1e-3); | ||
end | ||
|
||
function test_trough_with_inds(testCase) | ||
[~, want, ~, ~] = testCase.exampleAnalysis_(testCase.signal); | ||
|
||
inds = upcrossing(testCase.t, testCase.signal); | ||
|
||
got = uc_troughs(testCase.t, testCase.signal, inds); | ||
|
||
testCase.verifyEqual(got, want, 'AbsTol', 1e-3); | ||
end | ||
|
||
function test_heights_with_inds(testCase) | ||
[~, ~, want, ~] = testCase.exampleAnalysis_(testCase.signal); | ||
|
||
inds = upcrossing(testCase.t, testCase.signal); | ||
|
||
got = uc_heights(testCase.t, testCase.signal, inds); | ||
|
||
testCase.verifyEqual(got, want, 'AbsTol', 1e-3); | ||
end | ||
|
||
function test_periods_with_inds(testCase) | ||
[~, ~, ~, want] = testCase.exampleAnalysis_(testCase.signal); | ||
inds = upcrossing(testCase.t, testCase.signal); | ||
|
||
got = uc_periods(testCase.t, testCase.signal,inds); | ||
|
||
testCase.verifyEqual(got, want, 'AbsTol', 2e-3); | ||
end | ||
|
||
function test_custom_with_inds(testCase) | ||
[want, ~, ~, ~] = testCase.exampleAnalysis_(testCase.signal); | ||
inds = upcrossing(testCase.t, testCase.signal); | ||
|
||
% create a similar function to finding the peaks | ||
f = @(ind1, ind2) max(testCase.signal(ind1:ind2)); | ||
|
||
got = uc_custom(testCase.t, testCase.signal, f, inds); | ||
|
||
testCase.verifyEqual(got, want, 'AbsTol', 1e-3); | ||
end | ||
|
||
end | ||
end |
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,91 @@ | ||
# Upcrossing Analysis Functions | ||
|
||
This module contains a collection of functions that facilitate **upcrossing analysis** for time-series data. It provides tools to find zero upcrossings, peaks, troughs, heights, periods, and allows for custom user-defined calculations between zero crossings. | ||
|
||
## Key Functions | ||
|
||
### `upcrossing(t, data)` | ||
Finds the zero upcrossing points in the given time-series data. | ||
|
||
**Parameters:** | ||
- `t` (array): Time array. | ||
- `data` (array): Signal time-series data. | ||
|
||
**Returns:** | ||
- `inds` (array): Indices of zero upcrossing points. | ||
|
||
--- | ||
|
||
### `peaks(t, data, inds)` | ||
Finds the peaks between zero upcrossings. | ||
|
||
**Parameters:** | ||
- `t` (array): Time array. | ||
- `data` (array): Signal time-series data. | ||
- `inds` (array, optional): Indices of the upcrossing points. | ||
|
||
**Returns:** | ||
- `peaks` (array): Peak values between the zero upcrossings. | ||
|
||
--- | ||
|
||
### `troughs(t, data, inds)` | ||
Finds the troughs between zero upcrossings. | ||
|
||
**Parameters:** | ||
- `t` (array): Time array. | ||
- `data` (array): Signal time-series data. | ||
- `inds` (array, optional): Indices of the upcrossing points. | ||
|
||
**Returns:** | ||
- `troughs` (array): Trough values between the zero upcrossings. | ||
|
||
--- | ||
|
||
### `heights(t, data, inds)` | ||
Calculates the height between zero upcrossings. The height is defined as the difference between the maximum and minimum values between each pair of zero crossings. | ||
|
||
**Parameters:** | ||
- `t` (array): Time array. | ||
- `data` (array): Signal time-series data. | ||
- `inds` (array, optional): Indices of the upcrossing points. | ||
|
||
**Returns:** | ||
- `heights` (array): Height values between the zero upcrossings. | ||
|
||
--- | ||
|
||
### `periods(t, data, inds)` | ||
Calculates the period between zero upcrossings. The period is the difference in time between each pair of consecutive upcrossings. | ||
|
||
**Parameters:** | ||
- `t` (array): Time array. | ||
- `data` (array): Signal time-series data. | ||
- `inds` (array, optional): Indices of the upcrossing points. | ||
|
||
**Returns:** | ||
- `periods` (array): Period values between the zero upcrossings. | ||
|
||
--- | ||
|
||
### `custom(t, data, func, inds)` | ||
Applies a custom user-defined function between zero upcrossing points. | ||
|
||
**Parameters:** | ||
- `t` (array): Time array. | ||
- `data` (array): Signal time-series data. | ||
- `func` (function handle): A custom function that will be applied between the zero crossing periods. | ||
- `inds` (array, optional): Indices of the upcrossing points. | ||
|
||
**Returns:** | ||
- `custom_vals` (array): Custom values calculated between the zero crossings. | ||
|
||
--- | ||
|
||
## Author(s) | ||
- **mbruggs** - Python | ||
- **akeeste** - Python | ||
- **mshabara** - Matlab | ||
|
||
## Date | ||
- 12/12/2024 |
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,37 @@ | ||
function vals = uc_apply_(t, data, f, inds) | ||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | ||
% | ||
% Apply a function over defined intervals in time series data. | ||
% | ||
% Parameters | ||
% ------------ | ||
% t: array | ||
% Array of time values. | ||
% data: array | ||
% Array of data values. | ||
% f: function handle | ||
% Function that takes two indices (start, end) and returns a scalar value. | ||
% inds: array, optional | ||
% Indices array defining the intervals. If not provided, intervals will be | ||
% computed using the upcrossing function. | ||
% | ||
% Returns | ||
% --------- | ||
% vals: array | ||
% Array of values resulting from applying the function over the defined intervals. | ||
% | ||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | ||
|
||
if nargin < 4 % nargin: returns the number of function input arguments given in the call | ||
% If inds is not provided, compute using upcrossing | ||
inds = upcrossing(t, data); | ||
end | ||
|
||
n = numel(inds) - 1; % Number of intervals | ||
vals = NaN(1, n); % Initialize the output array | ||
|
||
for i = 1:n | ||
vals(i) = f(inds(i), inds(i+1)); % Apply the function to each pair of indices | ||
end | ||
|
||
end |
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,29 @@ | ||
function custom_vals = uc_custom(t, data, func, inds) | ||
% Applies a custom function to the time-series data between upcrossing points. | ||
% | ||
% Parameters: | ||
%------------ | ||
% t: array | ||
% Array of time values. | ||
% data: array | ||
% Array of data values. | ||
% func: function handle | ||
% Function to apply between the zero crossing periods. | ||
% inds: Array, optional | ||
% Indices for the upcrossing. | ||
% | ||
% Returns: | ||
% --------- | ||
% custom_vals: array | ||
% Custom values of the time-series | ||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | ||
|
||
if nargin < 4 | ||
inds = upcrossing(t, data); | ||
end | ||
if ~isa(func, 'function_handle') | ||
error('func must be a function handle'); | ||
end | ||
|
||
custom_vals = uc_apply_(t, data, func, inds); | ||
end |
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,29 @@ | ||
function heights = uc_heights(t, data, inds) | ||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | ||
% | ||
% Calculates the height between zero crossings. | ||
% | ||
% The height is defined as the max value - min value | ||
% between the zero crossing points. | ||
% | ||
% Parameters | ||
% ------------ | ||
% t: array | ||
% Array of time values. | ||
% data: array | ||
% Array of data values. | ||
% inds: array, optional | ||
% Indices for the upcrossing. | ||
% | ||
% Returns: | ||
% ------------ | ||
% heights: Height values of the time-series | ||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | ||
|
||
if nargin < 3 | ||
inds = upcrossing(t, data); | ||
end | ||
|
||
heights = uc_apply_(t, data, @(ind1, ind2) max(data(ind1:ind2)) - min(data(ind1:ind2)), inds); | ||
end | ||
|
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,27 @@ | ||
function peaks = uc_peaks(t, data, inds) | ||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | ||
|
||
% Finds the peaks between zero crossings. | ||
% | ||
% Parameters: | ||
% ------------ | ||
% t: array | ||
% Time array. | ||
% data: array | ||
% Signal time-series. | ||
% inds: Optional, array | ||
% indices for the upcrossing. | ||
% | ||
% Returns: | ||
% ------------ | ||
% peaks: array | ||
% Peak values of the time-series | ||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | ||
|
||
if nargin < 3 | ||
inds = upcrossing(t, data); | ||
end | ||
|
||
peaks = uc_apply_(t, data, @(ind1, ind2) max(data(ind1:ind2)), inds); | ||
end | ||
|
Oops, something went wrong.