Skip to content

Commit f76c55c

Browse files
committed
feat: add mag.mixin.TimeSeriesOperationSupport for simplified operations support
1 parent 66163cb commit f76c55c

File tree

4 files changed

+221
-0
lines changed

4 files changed

+221
-0
lines changed

resources/release-notes.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
## Software
77

8+
- (All) Add `mag.mixin.TimeSeriesOperationSupport` for simplified operations support
89
- (All) Fix issues with colors and legends for multiple stackedplots
910
- (IMAP) Check there is data before enabling auto-compression event in `mag.imap.view.Field`
1011

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
classdef (Abstract, HandleCompatible) TimeSeriesOperationSupport
2+
% TIMESERIESOPERATIONSUPPORT Interface adding support for operations on
3+
% "mag.TimeSeries" subclasses.
4+
5+
% Assume that this class has access to methods and properties of
6+
% "mag.TimeSeries":
7+
%#ok<*MCNPN>
8+
9+
methods (Sealed)
10+
11+
function result = join(this, those)
12+
13+
arguments
14+
this {mustBeNonempty}
15+
end
16+
17+
arguments (Repeating)
18+
those
19+
end
20+
21+
% If no "those" is provided and "this" is an array, combine all
22+
% "these" data.
23+
if isempty(those) && ~isscalar(this)
24+
25+
result = this(1).copy();
26+
result.Data = sortrows(vertcat(this.Data));
27+
28+
return;
29+
end
30+
31+
expectedClass = class(this);
32+
33+
if isscalar(this) && all(cellfun(@(x) isa(x, expectedClass), those))
34+
35+
thoseData = cellfun(@(x) x.Data, those, UniformOutput = false);
36+
joinedData = sortrows(vertcat(this.Data, thoseData{:}));
37+
38+
result = this.copy();
39+
result.Data = joinedData;
40+
else
41+
error("mag:join:UnsupportedType", """join"" is only supported between mag.TimeSeries objects.");
42+
end
43+
end
44+
end
45+
46+
methods (Hidden, Sealed)
47+
48+
function result = plus(this, that)
49+
50+
arguments
51+
this (1, 1)
52+
that
53+
end
54+
55+
operation = @(this, that) sortrows(this.Data + that.Data);
56+
result = this.performOperationOrFallbackToBuiltin(that, operation, "plus");
57+
end
58+
59+
function result = minus(this, that)
60+
61+
arguments
62+
this (1, 1)
63+
that
64+
end
65+
66+
operation = @(this, that) sortrows(this.Data - that.Data);
67+
result = this.performOperationOrFallbackToBuiltin(that, operation, "minus");
68+
end
69+
end
70+
71+
methods (Access = private)
72+
73+
function result = performOperationOrFallbackToBuiltin(this, that, operation, functionName, varargin)
74+
75+
result = [];
76+
77+
if isscalar(this) && all(isa(that, class(this)))
78+
79+
result = this.copy();
80+
result.Data = operation(this, that);
81+
82+
return;
83+
end
84+
85+
try
86+
result = builtin(functionName, this, that, varargin{:});
87+
catch exception
88+
exception.throwAsCaller();
89+
end
90+
end
91+
end
92+
end
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
classdef TestTimeSeriesWithOperationSupport < mag.TimeSeries & mag.mixin.TimeSeriesOperationSupport
2+
% TESTTIMESERIESWITHOPERATIONSUPPORT Test class for
3+
% mag.mixin.TimeSeriesOperationSupport testing.
4+
5+
methods
6+
7+
function crop(~, ~)
8+
end
9+
10+
function resample(~, ~)
11+
end
12+
13+
function downsample(~, ~)
14+
end
15+
end
16+
end
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
classdef tTimeSeriesOperationSupport < matlab.unittest.TestCase
2+
% TTIMESERIESOPERATIONSUPPORT Unit tests for
3+
% "mag.mixin.TimeSeriesOperationSupport" class.
4+
5+
properties (TestParameter)
6+
Operation = {
7+
struct(Function = @plus, Result = tTimeSeriesOperationSupport.getPlusResult()), ...
8+
struct(Function = @minus, Result = tTimeSeriesOperationSupport.getMinusResult())}
9+
end
10+
11+
methods (Test)
12+
13+
% Test that supported operations yield the expected result.
14+
function test_supportedOperations(testCase, Operation)
15+
16+
% Set up.
17+
[a, b] = testCase.createTestData();
18+
19+
% Exercise.
20+
result = Operation.Function(a, b);
21+
22+
% Verify.
23+
testCase.verifyEqual(result, Operation.Result, "Operation result should match expectation.");
24+
end
25+
26+
% Test that operation with unsupported type falls back to built-in
27+
% command.
28+
function test_operationFallbackBuiltin(testCase)
29+
30+
% Set up.
31+
a = TestTimeSeriesWithOperationSupport();
32+
33+
% Exercise and verify.
34+
testCase.verifyError(@() a + 2, "MATLAB:math:mustBeNumericCharOrLogical", ...
35+
"Operation must fall back to built-in command when unsupported.");
36+
end
37+
38+
% Test that "join" can be called on two different instances of
39+
% mag.mixin.TimeSeriesOperationSupport.
40+
function test_joinWithThat(testCase)
41+
42+
% Set up.
43+
[a, b] = testCase.createTestData();
44+
a.Data.Time = a.Data.Time + hours(1);
45+
46+
% Exercise.
47+
result = a.join(b);
48+
49+
% Verify.
50+
testCase.verifyEqual(result.Data, [b.Data; a.Data], "Joined data should match the concatenated data.");
51+
end
52+
53+
% Test that "join" can be called on an array instances of
54+
% mag.mixin.TimeSeriesOperationSupport.
55+
function test_joinWithArrayThis(testCase)
56+
57+
% Set up.
58+
[a, b] = testCase.createTestData();
59+
a.Data.Time = a.Data.Time + hours(1);
60+
61+
c = [a, b];
62+
63+
% Exercise.
64+
result = c.join();
65+
66+
% Verify.
67+
testCase.verifyEqual(result.Data, [b.Data; a.Data], "Joined data should match the concatenated data.");
68+
end
69+
70+
% Test that "join" throws an error when called with unsupported
71+
% datatypes.
72+
function test_joinUnsupportedType(testCase)
73+
74+
% Set up.
75+
a = TestTimeSeriesWithOperationSupport();
76+
77+
% Exercise and verify.
78+
testCase.verifyError(@() a.join("unsupportedType"), "mag:join:UnsupportedType", ...
79+
"Error should be thrown with unsupported type.");
80+
end
81+
end
82+
83+
methods (Static, Access = private)
84+
85+
function [a, b] = createTestData()
86+
87+
a = TestTimeSeriesWithOperationSupport();
88+
b = TestTimeSeriesWithOperationSupport();
89+
90+
a.Data = mag.test.DataTestUtilities.getScienceTimetable();
91+
b.Data = mag.test.DataTestUtilities.getScienceTimetable();
92+
end
93+
94+
function result = getPlusResult()
95+
96+
result = TestTimeSeriesWithOperationSupport();
97+
result.Data = mag.test.DataTestUtilities.getScienceTimetable();
98+
99+
result.Data = convertvars(result.Data, regexpPattern(".*"), "double");
100+
result.Data{:, :} = result.Data{:, :} * 2;
101+
end
102+
103+
function result = getMinusResult()
104+
105+
result = TestTimeSeriesWithOperationSupport();
106+
result.Data = mag.test.DataTestUtilities.getScienceTimetable();
107+
108+
result.Data = convertvars(result.Data, regexpPattern(".*"), "double");
109+
result.Data{:, :} = 0;
110+
end
111+
end
112+
end

0 commit comments

Comments
 (0)