Commit 97d997c1 authored by Ferdinand Fischer's avatar Ferdinand Fischer
Browse files

compose is working now for functions with multiple independent variables.

Thus, also the TimeDelayOperator is working for such functions.
parent d9795f1a
...@@ -231,11 +231,11 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete < handle & matlab.mi ...@@ -231,11 +231,11 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete < handle & matlab.mi
end end
methods (Access = public) methods (Access = public)
function [d, I, d_size] = compositionDomain(obj, g, varargin) function [d, I, d_size] = compositionDomain(obj, domainName)
assert(isscalar(g)); assert(isscalar(obj));
d = g.on(); d = obj.on();
% the evaluation of obj.on( compositionDomain ) is done by: % the evaluation of obj.on( compositionDomain ) is done by:
d_size = size(d); d_size = size(d);
...@@ -250,45 +250,93 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete < handle & matlab.mi ...@@ -250,45 +250,93 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete < handle & matlab.mi
deltaCOD = diff(d); deltaCOD = diff(d);
assert(misc.alln(deltaCOD >= 0), 'The domain for the composition f(g(.)) must be monotonically increasing'); assert(misc.alln(deltaCOD >= 0), 'The domain for the composition f(g(.)) must be monotonically increasing');
d = quantity.Domain('grid', d, 'name', domainName);
end end
function obj_hat = compose(obj, g, varargin) function obj_hat = compose(obj, g, optionalArgs)
% COMPOSE compose two functions % COMPOSE compose two functions
% OBJ_hat = compose(obj, G, varargin) composes the function % OBJ_hat = compose(obj, G, varargin) composes the function
% defined by OBJ with the function given by G. In particular, % defined by OBJ with the function given by G. In particular,
% f_hat(z,t) = f( g(z,t) ) % f_hat(z,t) = f( z, g(z,t) )
% if f(t) = obj, g is G and f_hat is OBJ_hat. % if f(t) = obj, g is G and f_hat is OBJ_hat.
arguments
assert(nargin(obj) == 1 ); obj
g quantity.Discrete;
optionalArgs.domain quantity.Domain = obj(1).domain;
end
myCompositionDomain = optionalArgs.domain;
originalDomain = obj(1).domain;
assert( length( myCompositionDomain ) == 1 );
[idx, logOfDomain] = originalDomain.gridIndex(myCompositionDomain);
assert( isequal( originalDomain(idx), myCompositionDomain ), ...
'Composition of functions: The domains for the composition must be equal. A grid join is not implemented yet.');
assert( any( logOfDomain ) )
% 2) get the composition domain:
% For the argument y of a function f(y) which should be
% composed by y = g(z,t) a new dommain will be created on the
% basis of evaluation of g(z,t).
[composeOnDomain, I, domainSize] = ... [composeOnDomain, I, domainSize] = ...
obj.compositionDomain(g, varargin{:}); g.compositionDomain(myCompositionDomain.name);
% check if the composition domain is in the range of definition % check if the composition domain is in the range of definition
% of obj. % of obj.
if( obj.domain.lower > composeOnDomain(1) || ... if ~composeOnDomain.isSubDomainOf( myCompositionDomain )
obj.domain.upper < composeOnDomain(end) )
warning('quantity:Discrete:compose', .... warning('quantity:Discrete:compose', ....
'The composition domain is not a subset of obj.domain! The missing values will be extrapolated.'); 'The composition domain is not a subset of obj.domain! The missing values will be extrapolated.');
end end
% 3) evaluation on the new grid: % 3) evaluation on the new grid:
newValues = obj.on( composeOnDomain ); % the order of the domains is important. At first, the
% domains which will not be replaced are taken. The last
% domain must be the composed domain. For example: a function
% f(x, y, z, t), where y should be composed with g(z, t) will
% be resorted to f_(x, z, t, y) and then evaluated with y =
% g(z,t)
evaluationDomain = [originalDomain( ~logOfDomain ), composeOnDomain ];
newValues = obj.on( evaluationDomain );
% reshape the new values into a 2-d array so that the first
% dimension is any domain but the composition domain and the
% last dimension is the composition domain
sizeOldDomain = prod( [originalDomain( ~logOfDomain ).n] );
sizeComposeDomain = composeOnDomain.gridLength;
newValues = reshape(newValues, [sizeOldDomain, sizeComposeDomain]);
% 4) reorder the computed values, dependent on the sort % 4) reorder the computed values, dependent on the sort
% position % position fo the new domain
newValues(I) = newValues; newValues(:,I) = newValues(:,:);
% 5) rearrange the computed values, to have the same dimension % 5) rearrange the computed values, to have the same dimension
% as the required domain % as the required domain
newValues = reshape( newValues, domainSize); % *) consider the domain
% f( z, g(z,t) ) = f(z, g(zeta,t) )|_{zeta = z}
tmpDomain = [originalDomain( ~logOfDomain ), g(1).domain ];
% newValues will be reshaped into the form
% f(z, t, zeta)
newValues = reshape( newValues, [tmpDomain.gridLength, 1] );
% *) now the common domains, i.e., zeta = z must be merged:
% for this, find the index of the common domain in list of
% temporary combined domain
intersectDomain = intersect( {originalDomain( ~logOfDomain ).name}, ...
{g(1).domain.name} );
if ~isempty(intersectDomain)
idx = 1:length(tmpDomain);
idxCommon = idx(strcmp({tmpDomain.name}, intersectDomain));
% take the diagonal values of the common domain, i.e., z = zeta
newValues = misc.diagNd( newValues, idxCommon );
end
% *) build a new valueDiscrete on the correct grid.
obj_hat = quantity.Discrete( newValues, ... obj_hat = quantity.Discrete( newValues, ...
'name', [obj.name '°' g.name], ... 'name', [obj.name '°' g.name], ...
'size', size(obj), ... 'size', size(obj), ...
'domain', g.domain()); 'domain', tmpDomain.join);
end end
...@@ -364,7 +412,7 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete < handle & matlab.mi ...@@ -364,7 +412,7 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete < handle & matlab.mi
% the new order will be done in the next step. % the new order will be done in the next step.
originalOrderedDomain(gridPermuteIdx) = myDomain; originalOrderedDomain(gridPermuteIdx) = myDomain;
value = obj.obj2value(originalOrderedDomain); value = obj.obj2value(originalOrderedDomain);
value = permute(reshape(value, [cellfun(@(v) numel(v), {originalOrderedDomain.grid}), size(obj)]), ... value = permute(reshape(value, [originalOrderedDomain.gridLength, size(obj)]), ...
[gridPermuteIdx, numel(gridPermuteIdx)+(1:ndims(obj))]); [gridPermuteIdx, numel(gridPermuteIdx)+(1:ndims(obj))]);
end end
end end
......
...@@ -43,8 +43,6 @@ classdef Domain < handle & matlab.mixin.CustomDisplay ...@@ -43,8 +43,6 @@ classdef Domain < handle & matlab.mixin.CustomDisplay
obj.grid = myParser.Results.grid(:); obj.grid = myParser.Results.grid(:);
obj.name = myParser.Results.name; obj.name = myParser.Results.name;
myParser.unmatchedWarning(); myParser.unmatchedWarning();
else
obj = quantity.Domain.empty();
end end
end end
...@@ -53,7 +51,11 @@ classdef Domain < handle & matlab.mixin.CustomDisplay ...@@ -53,7 +51,11 @@ classdef Domain < handle & matlab.mixin.CustomDisplay
end end
function n = get.n(obj) function n = get.n(obj)
n = length(obj.grid); if isempty(obj.grid)
n = [];
else
n = length(obj.grid);
end
end end
function lower = get.lower(obj) function lower = get.lower(obj)
...@@ -174,11 +176,15 @@ classdef Domain < handle & matlab.mixin.CustomDisplay ...@@ -174,11 +176,15 @@ classdef Domain < handle & matlab.mixin.CustomDisplay
% [idx, log] = gridIndex(obj, names) searches in the name % [idx, log] = gridIndex(obj, names) searches in the name
% properties of obj for the "names" and returns its index as % properties of obj for the "names" and returns its index as
% "idx" and its logical index as "log" % "idx" and its logical index as "log"
arguments
if nargin == 1 obj
names = {obj.name}; names = {obj.name};
end end
if isa(names, 'quantity.Domain')
names = {names.name};
end
names = misc.ensureIsCell(names); names = misc.ensureIsCell(names);
idx = zeros(1, length(names)); idx = zeros(1, length(names));
...@@ -201,6 +207,20 @@ classdef Domain < handle & matlab.mixin.CustomDisplay ...@@ -201,6 +207,20 @@ classdef Domain < handle & matlab.mixin.CustomDisplay
end end
end % gridIndex end % gridIndex
function i = isempty(obj)
i = any(size(obj) == 0);
i = i || any( cellfun(@isempty, {obj.grid} ) );
end
function i = isSubDomainOf(obj, d)
% ISSUBDOMAIN
% i = isSubDomainOf(OBJ, D) checks if the domain OBJ is a
% sub-domain of D.
i = obj.lower >= d.lower && ...
obj.upper <= d.upper;
end % isSubDomainOf
function matGrid = ndgrid(obj) function matGrid = ndgrid(obj)
% ndgrid calles ndgrid for the default grid, if no other grid % ndgrid calles ndgrid for the default grid, if no other grid
% is specified. Empty grid as input returns empty cell as % is specified. Empty grid as input returns empty cell as
......
...@@ -39,7 +39,7 @@ classdef TimeDelayOperator < handle & matlab.mixin.Copyable ...@@ -39,7 +39,7 @@ classdef TimeDelayOperator < handle & matlab.mixin.Copyable
obj.isZero = optionalArgs.isZero; obj.isZero = optionalArgs.isZero;
end end
function d = applyTo(obj, h) function d = applyTo(obj, h, optionalArgs)
%APPLYON apply the operator on a function %APPLYON apply the operator on a function
% D = applyOn(OBJ, H) computes the application of the % D = applyOn(OBJ, H) computes the application of the
% operator OBJ to the function H. % operator OBJ to the function H.
...@@ -47,8 +47,11 @@ classdef TimeDelayOperator < handle & matlab.mixin.Copyable ...@@ -47,8 +47,11 @@ classdef TimeDelayOperator < handle & matlab.mixin.Copyable
arguments arguments
obj obj
h quantity.Discrete; h quantity.Discrete;
optionalArgs.domain quantity.Domain = h(1).domain;
end end
assert( length(optionalArgs.domain) == 1);
n = size(obj, 1); n = size(obj, 1);
m = size(obj, 2); assert(m == size(h, 1)); m = size(obj, 2); assert(m == size(h, 1));
l = size(h, 2); l = size(h, 2);
...@@ -59,7 +62,7 @@ classdef TimeDelayOperator < handle & matlab.mixin.Copyable ...@@ -59,7 +62,7 @@ classdef TimeDelayOperator < handle & matlab.mixin.Copyable
for j = 1 : m for j = 1 : m
for k = 1 : l for k = 1 : l
if ~obj(i, j).isZero if ~obj(i, j).isZero
d(i, k) = d(i, k) + h(j, k).compose( obj.t + obj(i, j).coefficient ); d(i, k) = d(i, k) + h(j, k).compose( obj.t + obj(i, j).coefficient, 'domain', optionalArgs.domain );
end end
end end
end end
......
...@@ -19,7 +19,6 @@ testCase.verifyWarning(@()f.compose(h), 'quantity:Discrete:compose') ...@@ -19,7 +19,6 @@ testCase.verifyWarning(@()f.compose(h), 'quantity:Discrete:compose')
% verify f(tau) = sin( tau * 2pi ); g(z, t) = t + z^2 % verify f(tau) = sin( tau * 2pi ); g(z, t) = t + z^2
% f( g(z, t ) ) = sin( (t + z^2 ) * 2pi ) % f( g(z, t ) ) = sin( (t + z^2 ) * 2pi )
tau = quantity.Domain('grid', linspace(-1, 3), 'name', 'tau' ); tau = quantity.Domain('grid', linspace(-1, 3), 'name', 'tau' );
z = quantity.Domain('grid', linspace(0, 1, 51), 'name', 'z'); z = quantity.Domain('grid', linspace(0, 1, 51), 'name', 'z');
...@@ -27,12 +26,26 @@ f = quantity.Discrete( sin( tau.grid * 2 * pi ), 'domain', tau ); ...@@ -27,12 +26,26 @@ f = quantity.Discrete( sin( tau.grid * 2 * pi ), 'domain', tau );
g = quantity.Discrete( ( z.grid.^2 ) + t.grid', 'domain', [z, t] ); g = quantity.Discrete( ( z.grid.^2 ) + t.grid', 'domain', [z, t] );
fg = f.compose(g); fg = f.compose(g);
F = quantity.Function(@(z,t) sin( ( t + z.^2 ) * 2 * pi ), ... F = quantity.Function(@(z,t) sin( ( t + z.^2 ) * 2 * pi ), ...
'domain', [z, t], 'name', 'f(g) as function handle'); 'domain', [z, t], 'name', 'f(g) as function handle');
testCase.verifyEqual(fg.on, F.on, 'AbsTol', 1e-2) testCase.verifyEqual(fg.on, F.on, 'AbsTol', 1e-2)
% verify f(z, tau) °_tau g(z, t) -> f(z, g(z,t))
% f(z, tau) = z + tau;
% g(z, t) = z + t
% f(z, g(z,t) ) = 2*z + t
z = quantity.Domain('grid', linspace(0, 1, 11), 'name', 'z');
t = quantity.Domain('grid', linspace(0, 1, 21), 'name', 't');
tau = quantity.Domain('grid', linspace(0, 2, 31), 'name', 'tau');
f = quantity.Discrete( z.grid + tau.grid', 'domain', [z, tau]);
g = quantity.Discrete( z.grid + t.grid', 'domain', [z, t]);
fog = f.compose(g, 'domain', tau);
FoG = quantity.Discrete( 2 * z.grid + t.grid', 'domain', [z, t]);
testCase.verifyEqual( fog.on, FoG.on, 'AbsTol', 5e-16)
end end
function testCastDiscrete2Function(testCase) function testCastDiscrete2Function(testCase)
...@@ -1232,10 +1245,10 @@ function testSubs(testCase) ...@@ -1232,10 +1245,10 @@ function testSubs(testCase)
myTestArray = ones(21, 41, 2, 3); myTestArray = ones(21, 41, 2, 3);
myTestArray(:,1,1,1) = linspace(0, 1, 21); myTestArray(:,1,1,1) = linspace(0, 1, 21);
myTestArray(:,:,2,:) = 2; myTestArray(:,:,2,:) = 2;
d1 = quantity.Domain('grid', linspace(0, 1, 21), 'name', 'z'); z = quantity.Domain('grid', linspace(0, 1, 21), 'name', 'z');
d2 = quantity.Domain('grid', linspace(0, 1, 41), 'name', 'zeta'); zeta = quantity.Domain('grid', linspace(0, 1, 41), 'name', 'zeta');
quan = quantity.Discrete(myTestArray, 'domain', [d1, d2]); quan = quantity.Discrete(myTestArray, 'domain', [z, zeta]);
quanZetaZ = quan.subs({'z', 'zeta'}, {'zeta', 'z'}); quanZetaZ = quan.subs({'z', 'zeta'}, {'zeta', 'z'});
quan10 = quan.subs({'z', 'zeta'}, {1, 0}); quan10 = quan.subs({'z', 'zeta'}, {1, 0});
......
...@@ -71,11 +71,19 @@ testCase.verifyEqual( D.numGridElements, prod([length(Z), length(Z)])); ...@@ -71,11 +71,19 @@ testCase.verifyEqual( D.numGridElements, prod([length(Z), length(Z)]));
end end
function testDomainEmpty(testCase) function testEmpty(testCase)
d = quantity.Domain(); d = quantity.Domain();
e = quantity.Domain.empty();
o = quantity.Domain('grid', 1, 'name', '');
testCase.verifyTrue( isempty(d) ) testCase.verifyTrue( isempty(d) )
testCase.verifyTrue( isempty(e) )
testCase.verifyTrue( isempty([d, e]))
testCase.verifyTrue( isempty([d, d]))
testCase.verifyFalse( isempty(o) )
testCase.verifyFalse( isempty([o e]) )
testCase.verifyTrue( isempty([o d]) )
end end
......
...@@ -76,7 +76,7 @@ function testApplyTo(testCase) ...@@ -76,7 +76,7 @@ function testApplyTo(testCase)
H2 = subs(H2, 'zeta', 0); H2 = subs(H2, 'zeta', 0);
testCase.verifyEqual(H.on(), H2.on(), 'AbsTol', 5e-3); testCase.verifyEqual(H.on(), H2.on(), 'AbsTol', 5e-3);
% test a diagonal operator %% test a diagonal operator
h = quantity.Discrete( {sin(tau.grid * 2 * pi); ... h = quantity.Discrete( {sin(tau.grid * 2 * pi); ...
sin(tau.grid * 2 * pi)}, 'domain', tau ); sin(tau.grid * 2 * pi)}, 'domain', tau );
H = quantity.Discrete( {sin(z.grid * 0 + 2 * pi + t.grid' * 2 * pi); ... H = quantity.Discrete( {sin(z.grid * 0 + 2 * pi + t.grid' * 2 * pi); ...
...@@ -86,8 +86,23 @@ function testApplyTo(testCase) ...@@ -86,8 +86,23 @@ function testApplyTo(testCase)
testCase.verifyEqual( H.on(), H_.on(), 'AbsTol', 5e-3) testCase.verifyEqual( H.on(), H_.on(), 'AbsTol', 5e-3)
%% test application of the operator to a function h(z,t)
% h(z,tau) = z + sin( tau * 2 * pi)
% D[h] = z + sin( ( t + g(z) ) * 2* pi )
% g(z) = z
% D[h] = z + sin( ( t + z ) * 2 * pi )
h = quantity.Discrete( z.grid + sin(tau.grid' * 2 * pi), ...
'domain', [z, tau]);
Dh = quantity.Discrete( z.grid + sin( ( z.grid + t.grid') * 2 * pi ), ...
'domain', [z, t]);
D = testCase.TestData.delay.variable;
Dh2 = D.applyTo( h, 'domain', tau );
testCase.verifyEqual( Dh.on, Dh2.on, 'AbsTol', 2e-3)
end end
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment