1function q = merge(o, p, legacy) % --*-- Unitary tests --*--
2
3% Merge method for dseries objects.
4%
5% INPUTS
6% - o                [dseries]
7% - p                [dseries]
8% - legacy           [logical]      revert to legacy behaviour if `true` (default is `false`),
9%
10% OUTPUTS
11% - q                [dseries]
12%
13% REMARKS
14% If dseries objects o and p have common variables, the variables
15% in p take precedence except if p has NaNs (the exception can be
16% removed by setting the third argument, legacy, equal to true).
17
18% Copyright © 2013-2020 Dynare Team
19%
20% This file is part of Dynare.
21%
22% Dynare is free software: you can redistribute it and/or modify
23% it under the terms of the GNU General Public License as published by
24% the Free Software Foundation, either version 3 of the License, or
25% (at your option) any later version.
26%
27% Dynare is distributed in the hope that it will be useful,
28% but WITHOUT ANY WARRANTY; without even the implied warranty of
29% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
30% GNU General Public License for more details.
31%
32% You should have received a copy of the GNU General Public License
33% along with Dynare.  If not, see <http://www.gnu.org/licenses/>.
34
35if ~isdseries(p) || ~isdseries(o)
36    error('dseries::merge: Both inputs must be dseries objects!')
37end
38
39if isempty(o) && ~isempty(p)
40    q = p;
41    return
42elseif ~isempty(o) && isempty(p)
43    q = o;
44    return
45elseif isempty(o) && isempty(p)
46    q = p;
47    return
48end
49
50if nargin<3
51    legacy = false;
52end
53
54if ~isequal(frequency(o), frequency(p))
55    if isempty(inputname(1))
56        error('dseries::merge: Cannot merge dseries objects (frequencies are different)!')
57    else
58        error(['dseries::merge: Cannot merge ' inputname(1) ' and ' inputname(2) ' (frequencies are different)!'])
59    end
60end
61
62q = dseries();
63
64[q.name, IBC, ~] = unique([o.name; p.name], 'last');
65
66if ~legacy
67    [list_of_common_variables, iO, iP] = intersect(o.name, p.name);
68end
69
70tex = [o.tex; p.tex];
71q.tex = tex(IBC);
72
73ops = [o.ops; p.ops];
74q.ops = ops(IBC);
75
76otagnames = fieldnames(o.tags);
77ptagnames = fieldnames(p.tags);
78qtagnames = union(otagnames, ptagnames);
79if isempty(qtagnames)
80    q.tags = struct();
81else
82    for i=1:length(qtagnames)
83        if ismember(qtagnames{i}, otagnames) && ismember(qtagnames{i}, ptagnames)
84            q.tags.(qtagnames{i}) = vertcat(o.tags.(otagnames{i}), p.tags.(ptagnames{i}));
85        elseif ismember(qtagnames{i}, otagnames)
86            q.tags.(qtagnames{i}) = vertcat(o.tags.(qtagnames{i}), cell(vobs(p), 1));
87        elseif ismember(qtagnames{i}, ptagnames)
88            q.tags.(qtagnames{i}) = vertcat(cell(vobs(o), 1), p.tags.(qtagnames{i}));
89        else
90            error('dseries::horzcat: This is a bug!')
91        end
92        q.tags.(qtagnames{i}) = q.tags.(qtagnames{i})(IBC);
93    end
94end
95
96if nobs(o) == 0
97    q = copy(p);
98elseif nobs(p) == 0
99    q = copy(o);
100elseif firstdate(o) >= firstdate(p)
101    diff = firstdate(o) - firstdate(p);
102    q_nobs = max(nobs(o) + diff, nobs(p));
103    q.data = NaN(q_nobs, vobs(q));
104    Z1 = [NaN(diff, vobs(o)); o.data];
105    if nobs(q) > nobs(o) + diff
106        Z1 = [Z1; NaN(nobs(q)-(nobs(o) + diff), vobs(o))];
107    end
108    Z2 = p.data;
109    if nobs(q) > nobs(p)
110        Z2 = [Z2; NaN(nobs(q) - nobs(p), vobs(p))];
111    end
112    Z = [Z1 Z2];
113    q.data = Z(:,IBC);
114    if ~legacy
115        if ~isempty(list_of_common_variables)
116            for i=1:length(iP)
117                jO = iO(i);
118                jP = iP(i);
119                jQ = find(strcmp(o.name{jO}, q.name));
120                id = isnan(q.data(:,jQ)) & ~isnan(Z1(:,jO)) & isnan(Z2(:,jP));
121                q.data(id, jQ) = Z1(id,jO);
122            end
123        end
124    end
125    q_init = firstdate(p);
126else
127    diff = firstdate(p) - firstdate(o);
128    q_nobs = max(nobs(p) + diff, nobs(o));
129    q.data = NaN(q_nobs, vobs(q));
130    Z1 = [NaN(diff, vobs(p)); p.data];
131    if nobs(q) > nobs(p) + diff
132        Z1 = [Z1; NaN(nobs(q)-(nobs(p) + diff), vobs(p))];
133    end
134    Z2 = o.data;
135    if nobs(q) > nobs(o)
136        Z2 = [Z2; NaN(nobs(q) - nobs(o), vobs(o))];
137    end
138    Z = [Z2 Z1];
139    q.data = Z(:,IBC);
140    if ~legacy
141        if ~isempty(list_of_common_variables)
142            for i=1:length(iP)
143                jO = iO(i);
144                jP = iP(i);
145                jQ = find(strcmp(o.name{jO}, q.name));
146                id = isnan(q.data(:,jQ)) & isnan(Z1(:,jP)) & ~isnan(Z2(:,jO));
147                q.data(id, jQ) = Z2(id,jO);
148            end
149        end
150    end
151    q_init = firstdate(o);
152end
153
154q.dates = q_init:q_init+(nobs(q)-1);
155
156%@test:1
157%$ % Define a datasets.
158%$ A = rand(10,2); B = randn(10,1);
159%$
160%$ % Define names
161%$ A_name = {'A1';'A2'}; B_name = {'A1'};
162%$
163%$ % Instantiate two time series objects and merge.
164%$ try
165%$    ts1 = dseries(A,[],A_name,[]);
166%$    ts1.tag('type');
167%$    ts1.tag('type', 'A1', 'Stock');
168%$    ts1.tag('type', 'A2', 'Flow');
169%$    ts2 = dseries(B,[],B_name,[]);
170%$    ts2.tag('type');
171%$    ts2.tag('type', 'A1', 'Flow');
172%$    ts3 = merge(ts1,ts2);
173%$    t(1) = true;
174%$ catch
175%$    t = false;
176%$ end
177%$
178%$ if t(1)
179%$    t(2) = dassert(ts3.vobs,2);
180%$    t(3) = dassert(ts3.nobs,10);
181%$    t(4) = dassert(ts3.data,[B, A(:,2)],1e-15);
182%$    t(5) = dassert(ts3.tags.type, {'Flow';'Flow'});
183%$ end
184%$ T = all(t);
185%@eof:1
186
187%@test:2
188%$ % Define a datasets.
189%$ A = rand(10,2); B = randn(10,1);
190%$
191%$ % Define names
192%$ A_name = {'A1';'A2'}; B_name = {'B1'};
193%$
194%$ % Instantiate two time series objects and merge them.
195%$ try
196%$    ts1 = dseries(A,[],A_name,[]);
197%$    ts1.tag('t1');
198%$    ts1.tag('t1', 'A1', 'Stock');
199%$    ts1.tag('t1', 'A2', 'Flow');
200%$    ts2 = dseries(B,[],B_name,[]);
201%$    ts2.tag('t2');
202%$    ts2.tag('t2', 'B1', 1);
203%$    ts3 = merge(ts1,ts2);
204%$    t(1) = true;
205%$ catch
206%$    t = false;
207%$ end
208%$
209%$ if length(t)>1
210%$    t(2) = dassert(ts3.vobs,3);
211%$    t(3) = dassert(ts3.nobs,10);
212%$    t(4) = dassert(ts3.data,[A, B],1e-15);
213%$    t(5) = dassert(ts3.tags.t1, {'Flow';'Flow';[]});
214%$    t(6) = dassert(ts3.tags.t2, {[];[];1});
215%$ end
216%$ T = all(t);
217%@eof:2
218
219%@test:3
220%$ % Define two dseries objects.
221%$ y = dseries(ones(4,1),'1989Q1', 'u');
222%$ z = dseries(2*ones(4,1),'1990Q1', 'u');
223%$
224%$ % Merge the two objects.
225%$ try
226%$    x = merge(y, z);
227%$    t(1) = true;
228%$ catch
229%$    t = false;
230%$ end
231%$
232%$ if t(1)
233%$    t(2) = dassert(x.vobs,1);
234%$    t(3) = dassert(x.name{1},'u');
235%$    t(4) = all(x.data(5:end)==2) && all(x.data(1:4)==1);
236%$    t(5) = all(x.dates==dates('1989Q1'):dates('1990Q4'));
237%$ end
238%$ T = all(t);
239%@eof:3
240
241%@test:4
242%$ % Define two dseries objects.
243%$ y = dseries(ones(4,1),'1989Q1', 'u');
244%$ z = dseries([NaN(4,1); 2*ones(4,1)],'1989Q1', 'u');
245%$
246%$ % Merge the two objects.
247%$ try
248%$    x = merge(y, z);
249%$    t(1) = true;
250%$ catch
251%$    t = false;
252%$ end
253%$
254%$ if t(1)
255%$    t(2) = dassert(x.vobs,1);
256%$    t(3) = dassert(x.name{1},'u');
257%$    t(4) = all(x.data(5:end)==2) && all(x.data(1:4)==1);
258%$    t(5) = all(x.dates==dates('1989Q1'):dates('1990Q4'));
259%$ end
260%$ T = all(t);
261%@eof:4
262
263%@test:5
264%$ % Define two dseries objects.
265%$ y = dseries(ones(8,1),'1938Q4', 'u');
266%$ z = dseries(NaN(8,1),'1938Q4', 'u');
267%$
268%$ % Inderectly call merge method via subsasgn.
269%$ try
270%$    y.u = z.u;
271%$    t(1) = true;
272%$ catch
273%$    t = false;
274%$ end
275%$
276%$ if t(1)
277%$    t(2) = dassert(y.vobs, 1);
278%$    t(3) = dassert(y.name{1}, 'u');
279%$    t(4) = all(isnan(y.data));
280%$    t(5) = dassert(y.nobs, z.nobs);
281%$    t(6) = dassert(y.dates(1), z.dates(1));
282%$ end
283%$ T = all(t);
284%@eof:5