diff --git a/+export/Data.m b/+export/Data.m index a5e3c03b17e97bf7bf80319605d7f7e229ec19d2..86723d6096ae932a29fad7ced0202d06d6386aab 100644 --- a/+export/Data.m +++ b/+export/Data.m @@ -3,9 +3,9 @@ classdef (Abstract) Data < handle & matlab.mixin.Heterogeneous % Detailed explanation goes here properties - basepath = '.'; - foldername = '.'; - filename = 'data'; + basepath char = '.'; + foldername char = '.'; + filename char = 'data'; N = 201; end @@ -53,7 +53,7 @@ classdef (Abstract) Data < handle & matlab.mixin.Heterogeneous for k = 1:length(obj) if ~obj(k).isempty - if ~isdir(obj(k).absolutepath) + if ~isfolder(obj(k).absolutepath) mkdir(obj(k).absolutepath); end diff --git a/+fractional/derivative.m b/+fractional/derivative.m new file mode 100644 index 0000000000000000000000000000000000000000..d99fc000ac694d55dd10521b07af6373ccfe94cb --- /dev/null +++ b/+fractional/derivative.m @@ -0,0 +1,39 @@ +function [h, t] = derivative(y, p, t0, t, optArg) +% DERIVATIVE calculates a fractional derivative +% [h, t] = derivative(y, p, t0, t, optArg) comput the p-th fractional derivative of a symbolic +% expression y. The lower terminal is t0 and the symbolic independent variable is t. By the +% name-value-pair argument, the "type" of the fractional derivative can be specified. Use "RL" for +% the Riemann-Liouville derivative and "C" for the Caputo derivative. +arguments + y sym + p (1,1) double {mustBePositive}; + t0 (1,1) double; + t (1,1) sym; + optArg.type = "RL" +end + +if p < 0 + error('a must be positive! Use fintegral instead?'); +end + +if misc.nearInt(p) + h = diff(y, round(p)); + return +end + +n = ceil(p); +tau = sym( string(t) + "_"); + +if optArg.type == "C" + h = 1/gamma(n-p) * int((t-tau)^(n-p-1) * subs(diff(y, t, n), t, tau), tau, t0, t); + %DaY = 1/gamma(1+n-a) * ((t - t0)^(n-a) * subs( diff(y, n), t, t0) ... + % + int((t-tau)^(n-a) * subs(diff(y, 1+n), t, tau), tau, t0, t)); % Integration by parts + +elseif optArg.type == "RL" + h = 1 / gamma(n-p) * diff( int( (t-tau)^(n-p-1) * subs(y, t, tau), tau, t0, t), t, n); +else + error("The derivative of the type " + optArg.type + " is not implemented. Only C for Caputo and RL for Riemann-Liouville are available") +end + + +end \ No newline at end of file diff --git a/+fractional/monomialFractionalDerivative.m b/+fractional/monomialFractionalDerivative.m new file mode 100644 index 0000000000000000000000000000000000000000..23689944550178b97f5a17d43a20dfb103a84361 --- /dev/null +++ b/+fractional/monomialFractionalDerivative.m @@ -0,0 +1,25 @@ +function [y, m] = monomialFractionalDerivative(t, t0, nu, p, optArg) +%MONOMIALFRACTIONALDERIVATIVE compute fractional derivative of a monomial +% [y, m] = monomialFractionalDerivative(t, a, nu, p, optArg) compute the p-fractional derivative of +% the monomial m = (t-a)^nu. At this, t must be a symbolic variable, t0 is the lower terminal as +% double value, nu is the exponent as double, p is the fractional order as double the type of the +% derivative can be specified by the name-value-pair "type". So far online the "RL" : +% Riemann-Liouville derivative is implemented. +arguments + t (1,1) sym; % symbolic variable for the monomial + t0 (1,1) double; % expansion point of the monome + nu (1,1) double; % order of the monome + p (1,1) double; % order of the fractional derivative + optArg.type string = "RL"; % Type of the fractional derivative +end + +m = (t-t0)^nu; + +switch optArg.type + + case "RL" + y = gamma( 1+nu ) / gamma( 1+nu-p ) * ( t - t0 )^(nu-p); + otherwise + error("Not jet implemented, only the RL - Rieman Liouville derivative is available") +end +end \ No newline at end of file diff --git a/+fractional/simulate.m b/+fractional/simulate.m new file mode 100644 index 0000000000000000000000000000000000000000..c842f73f95f675911ddbafb42f3098c0acf2022f --- /dev/null +++ b/+fractional/simulate.m @@ -0,0 +1,47 @@ +function [y, w, x] = simulate(stsp, u, optArgs) + +arguments + stsp ss; + u quantity.Discrete; + optArgs.timeDomain quantity.EquidistantDomain = u(1).domain; + optArgs.x0 = zeros(size(stsp.A, 1), 1); + optArgs.stepSize (1,1) double = u(1).domain.stepSize; + optArgs.tol = 1.0e-6; + optArgs.itmax = 500; + optArgs.spatialDomain = quantity.Domain("z", linspace(0, 1, size(stsp.A, 1))); + optArgs.silent (1,1) logical = false; +end + + +if ~exist('MT_FDE_PI1_Im.m', 'file') + error("The fractional solver MT_FDE_PI1_Im must be in the matlab path. It can be downloaded from https://de.mathworks.com/matlabcentral/fileexchange/66603-solving-multiterm-fractional-differential-equations-fde"); +end + +if isempty( stsp.E ) + E = eye(size(stsp.A,1)); +else + E = stsp.E; +end + +assert( stsp.UserData.alpha <= 1 && stsp.UserData.alpha > 0, ... + 'Fractional order must hold 0 < alpha <= 1!'); + +f_fun = @(t, x) stsp.A * x + stsp.B * u.at(t); +J_fun = @(t, x) stsp.A; % Jacobian matrix of f_fun, calculated by hand. It always has the same structure. + +[~, x] = FDE_PI1_Im_Ver_10(stsp.UserData.alpha, f_fun, J_fun, ... + optArgs.timeDomain.lower, ... + optArgs.timeDomain.upper, ... + optArgs.x0, ... + optArgs.stepSize, ... + [], ... + optArgs.tol, ... + optArgs.itmax, .... + optArgs.silent); + +w = quantity.Discrete(x, [optArgs.spatialDomain, optArgs.timeDomain]); +x = quantity.Discrete(x', optArgs.timeDomain); + +y = stsp.C * x + stsp.D * u; + +end \ No newline at end of file diff --git a/+misc/+ss/combineInputSignals.m b/+misc/+ss/combineInputSignals.m index 5dc153c73603161083e77e3cdd65a393b8bdd526..140e411c468e11125f6d38d392a7a8b10d07ec21 100644 --- a/+misc/+ss/combineInputSignals.m +++ b/+misc/+ss/combineInputSignals.m @@ -38,7 +38,7 @@ myParser = misc.Parser(); for it = 1 : numel(myInputNameUnique) myParser.addParameter(myInputNameUniqueValidFieldName{it}, ... quantity.Discrete(zeros(numel(t), myInputLength(it)), ... - 'grid', t, 'gridName', 't', 'name', myInputNameUnique{it}), ... + quantity.Domain("t", t), 'name', myInputNameUnique{it}), ... @(v) isa(v, 'quantity.Discrete') & isvector(v) & (numel(v)==myInputLength(it))); end diff --git a/+misc/+ss/planPolynomialTrajectory.m b/+misc/+ss/planPolynomialTrajectory.m index 92dab9ef5a148e3210251b284c08d69b096b7b3b..1414951f805608618e5423ec0f072f3b9d170336 100644 --- a/+misc/+ss/planPolynomialTrajectory.m +++ b/+misc/+ss/planPolynomialTrajectory.m @@ -2,7 +2,7 @@ function [u, y, x] = planPolynomialTrajectory(sys, t, optArgs) %FLATKERNELCOMPUTATION solves the transition x(0) -> x(T) for a given %state space based on a flat output % -% [u,x,Cx] = FLATKERNELCOMPUTATION(system, xT, T) calculates +% [u,x,Cx] = planPolynomialTrajectory(system, xT, T) calculates % the input u, the states x and the output Cx for a given state space % system symbolically. The calculation is based on the flat output based approach % for trajectory planning. The system for which the diff --git a/+misc/+ss/planTrajectory.m b/+misc/+ss/planTrajectory.m index d252e45e0de22e6a349381cc1a526a62cef67227..1cfb386dcc68d95a2428a036ad47c7066a0c984a 100644 --- a/+misc/+ss/planTrajectory.m +++ b/+misc/+ss/planTrajectory.m @@ -28,18 +28,19 @@ x1 = optArgs.x1; t = t(:); t0 = t(1); t1 = t(end); +tDomain = quantity.Domain({optArgs.domainName}, t); %% Compute the state transition matrix: Phi(t,tau) = expm(A*(t-tau) ) % Att0 = sys.A * quantity.Symbolic( sym('t') - sym('tau'), 'grid', {t, t}, 'gridName', {'t', 'tau'} ); -Phi_t0 = expm(sys.A * quantity.Discrete( t - t0, 'grid', {t}, 'gridName', {optArgs.domainName} )); +Phi_t0 = expm(sys.A * quantity.Discrete( t - t0, tDomain)); -invPhi_t1 = expm(sys.A * quantity.Discrete( t1 - t, 'grid', {t}, 'gridName', {optArgs.domainName} )); +invPhi_t1 = expm(sys.A * quantity.Discrete( t1 - t, tDomain)); %% compute the gramian controllability matrix % W1 = misc.ss.gramian(sys, t, t0, optArgs.domainName); % int_t0^t1 expm(A*(tau-t0)) * b * b^T * expm(A^T(tau-t0)) dtau -W1 = cumInt( Phi_t0 * sys.b * sys.b' * expm(sys.A' * quantity.Discrete( t - t0, 'grid', {t}, 'gridName', {optArgs.domainName} )), ... +W1 = cumInt( Phi_t0 * sys.b * sys.b' * expm(sys.A' * quantity.Discrete( t - t0, tDomain)), ... Phi_t0(1).domain, t0, optArgs.domainName); W1_t1 = W1.at(t1); diff --git a/+misc/+ss/setPointChange.m b/+misc/+ss/setPointChange.m new file mode 100644 index 0000000000000000000000000000000000000000..21084271049a6230626a57b23dd97987e920b388 --- /dev/null +++ b/+misc/+ss/setPointChange.m @@ -0,0 +1,47 @@ +function [f, a] = setPointChange(phi0, phi1, t0, t1, n, optArgs) +%SETPOINTCHANGE polynomial fit of an initial and end value with a certain smoothness +% +% [f, a] = setPointChange(PHI0, PHI1, t0, t1, n, optArgs) plans a function f which connects the +% initial value PHI0 with the end value PHI1, so that f(t0) = PHI0 and f(t1) = PHI1 as well as +% d^k / dt^k f(t)|_{t=t0,t1} = 0, k = 1, 2, ..., n +% The name of the variable can be set with "var" as name value pair. +arguments + phi0 (1,1) double; + phi1 (1,1) double; + t0 (1,1) double; + t1 (1,1) double; + n (1,1) double; + optArgs.var = sym( "t" ); +end + + +N = 2*n+2; + +M0 = zeros(n+1, N); +M1 = zeros(n+1, N); + +coefficient = @(k, i, t) factorial( i + k -1 ) / factorial( i - 1) * t^(i-1); + +for k = 0:n + for i = 1:( N - k ) + M0(k+1, i + k) = coefficient(k, i, t0); + M1(k+1, i + k) = coefficient(k, i, t1); + end +end + +a = [M0; M1] \ [phi0; zeros(n,1); phi1; zeros(n,1)]; + +if misc.issym( optArgs.var ) + t = optArgs.var; +else + t = sym( optArgs.var ); +end + +f = sym(0); + +for i = 1 : N + f = f + a(i) * t^(i-1); +end + +end + diff --git a/+misc/+ss/simulationOutput2Quantity.m b/+misc/+ss/simulationOutput2Quantity.m index bc2efcc054f2a735052bf6fa7aa0d9d7b806c026..ebcf86e877b0c150df74ee65e105e8f9bdbf13c3 100644 --- a/+misc/+ss/simulationOutput2Quantity.m +++ b/+misc/+ss/simulationOutput2Quantity.m @@ -110,16 +110,14 @@ for idx = 1 : numel(signalNames) z = quantity.Domain("z", thisGrid{1}); end quantityStruct = misc.setfield(quantityStruct, signalNames{idx}, ... - quantity.Discrete(thisSignalSorted, ... - 'name', signalNames{idx}, ... - "domain", [t, z])); + quantity.Discrete(thisSignalSorted, [t, z], ... + 'name', signalNames{idx})); else quantityStruct = misc.setfield(quantityStruct, signalNames{idx}, ... quantity.Discrete( ... - simOutput(:, strcmp(outputNamesUnenumerated, signalNames{idx})), ... - 'name', signalNames{idx}, .... - 'domain', t)); + simOutput(:, strcmp(outputNamesUnenumerated, signalNames{idx})), t, ... + 'name', signalNames{idx})); end end end diff --git a/+misc/Backstepping.m b/+misc/Backstepping.m index 8cad2dee311ed7aa2df24f3b2842270385560e4c..64d4d3b1092a2d2856e5549adac75ab69922e9ae 100644 --- a/+misc/Backstepping.m +++ b/+misc/Backstepping.m @@ -115,7 +115,7 @@ classdef Backstepping < handle & matlab.mixin.Copyable end xOriginal = quantity.Discrete(... ones(101, size(obj.kernel, 1)), ... - 'grid', {linspace(0, 1, 101)}, 'gridName', {'z'}); + quantity.Domain('z', linspace(0, 1, 101))); xTilde = obj.transform(xOriginal); xOriginalAgain = obj.transformInverse(xTilde); myError = max(median(abs(xOriginal-xOriginalAgain))); @@ -238,11 +238,11 @@ classdef Backstepping < handle & matlab.mixin.Copyable % create quantities as output parameters K_dzeta = quantity.Discrete(K_dzetaMat, ... - 'domain', quantityDomain , ... + quantityDomain , ... 'name', "d_{zeta}" + thisKernel(1).name); K_dz = quantity.Discrete(K_dzMat, ... - 'domain', quantityDomain , ... + quantityDomain , ... 'name', "d_z" + thisKernel(1).name); end % gradient() @@ -269,11 +269,11 @@ classdef Backstepping < handle & matlab.mixin.Copyable syms z zeta if isequal(obj.integralBounds, {0, 'z'}) domainSelector = quantity.Discrete(quantity.Symbolic(zeta<=z, ... - 'domain', obj.kernel(1).domain)); + obj.kernel(1).domain)); elseif isequal(obj.integralBounds, {'z', 1}) domainSelector = quantity.Discrete(quantity.Symbolic(zeta>=z, ... - 'domain', obj.kernel(1).domain)); + obj.kernel(1).domain)); end end % get.domainSelector() diff --git a/+misc/Gain.m b/+misc/Gain.m index f3f4ca4b167106a223b2d13c54f23f15d7282b42..3c74b0cf88f21967c392b09adba4ee2cdaf9bf63 100644 --- a/+misc/Gain.m +++ b/+misc/Gain.m @@ -256,6 +256,20 @@ classdef Gain < handle & matlab.mixin.Copyable end end % strrepOutputType() + function obj = extendOutputType(obj, position, newText) + % extendOutputType adds a pre- or a postfix to obj.outputType + assert(any(strcmp(position, {'front', 'back'}))); + assert(ischar(newText) || isstring(newText), 'prefix must be a char-array or string'); + + for it = 1 : numel(obj.outputType) + if strcmp(position, 'front') + obj.outputType{it} = [newText, obj.outputType{it}]; + elseif strcmp(position, 'back') + obj.outputType{it} = [obj.outputType{it}, newText]; + end + end + end % extendOutputType() + function obj = strrepInputType(obj, oldText, newText) % replace strings in type assert(ischar(oldText) || isstring(oldText), 'oldText must be a char-array or string'); @@ -263,6 +277,18 @@ classdef Gain < handle & matlab.mixin.Copyable obj.inputType = strrep(obj.inputType, oldText, newText); end % strrepInputType() + function obj = extendInputType(obj, position, newText) + % extendInputType adds a pre- or a postfix to obj.inputType + assert(any(strcmp(position, {'front', 'back'}))); + assert(ischar(newText) || isstring(newText), 'prefix must be a char-array or string'); + + if strcmp(position, 'front') + obj.inputType = [newText, obj.inputType]; + elseif strcmp(position, 'back') + obj.inputType = [obj.inputType, newText]; + end + end % extendInputType() + function OutputName = get.OutputName(obj) OutputName = cell(sum(obj.lengthOutput), 1); myCounter = 1; diff --git a/+misc/Gains.m b/+misc/Gains.m index e323495f329e0eb63e84fb019f8786b86ab86348..19bc477700704a9e7d5198404e1b0718b764dbc5 100644 --- a/+misc/Gains.m +++ b/+misc/Gains.m @@ -378,6 +378,13 @@ classdef Gains < handle & matlab.mixin.Copyable end end % strrepOutputType() + function obj = extendOutputType(obj, position, newText) + % extendOutputType adds a pre- or a postfix to all obj.outputType{:} + for it = 1 : obj.numTypes + obj.gain(it).extendOutputType(position, newText); + end + end % extendOutputType() + function obj = strrepInputType(obj, oldText, newText) if ~isempty(obj) % replace strings in type @@ -387,6 +394,13 @@ classdef Gains < handle & matlab.mixin.Copyable end end % strrepInputType() + function obj = extendInputType(obj, position, newText) + % extendInputType adds a pre- or a postfix to all obj.inputType{:} + for it = 1 : obj.numTypes + obj.gain(it).extendInputType(position, newText); + end + end % extendInputType() + %% set gain value function set.value(obj, valueTemp) for it = 1 : obj.numTypes diff --git a/+misc/Parser.m b/+misc/Parser.m index b8d34560ae708cff0607fe5c6f82c3dafb8a7adc..cefb692927c27cfd3aa80cbd1c8a5c6c532d47af 100644 --- a/+misc/Parser.m +++ b/+misc/Parser.m @@ -15,8 +15,10 @@ classdef Parser < inputParser obj.PartialMatching = false; end % Parser() - function i = isDefault(obj, name) - i = any(ismember(obj.UsingDefaults(:), {name})); + function result = isDefault(obj, name) + % isDefault returns true if for the Parameter specified by NAME is the default value is + % used, and returns false otherwise. + result = any(ismember(obj.UsingDefaults(:), {name})); end % isDefault() function parse2ws(obj, varargin) diff --git a/+misc/alln.m b/+misc/alln.m index 485da5bee56fd6de158f6e57966fc8fb4cd5815a..2c1d184564bcaeaed4f15bdd9350280f5a8e80fb 100644 --- a/+misc/alln.m +++ b/+misc/alln.m @@ -23,4 +23,6 @@ import misc.alln V = all(V(:)); +warning("depricated: use all(arg, 'all') instead of misc.alln(arg)"); + end \ No newline at end of file diff --git a/+misc/binomial.m b/+misc/binomial.m new file mode 100644 index 0000000000000000000000000000000000000000..a382b634772300767bf3ce5d5cb90a487d44d8ae --- /dev/null +++ b/+misc/binomial.m @@ -0,0 +1,16 @@ +function b = binomial(p, k) +%UNTITLED Summary of this function goes here +% Detailed explanation goes here +arguments + p (1,1) double; + k (1,1) double {mustBeInteger}; +end + +if p < 0 && k ~= 0 + b = (-1)^k * misc.binomial( -p + k -1, k); +else + b = gamma(p+1) / gamma(k+1) / gamma(p-k + 1); +end + +end + diff --git a/+misc/functionArguments.m b/+misc/functionArguments.m new file mode 100644 index 0000000000000000000000000000000000000000..6e7de0bc7a8d746cff61661ef924febddc2c3076 --- /dev/null +++ b/+misc/functionArguments.m @@ -0,0 +1,10 @@ +function names = functionArguments(func) +%FUNCTIONARGUMENTS extracts the arguments of a function handle +% names = misc.functionArguments(func) returns the arguments of the function handle func as the +% string array names +%% Example +% names = functionArguments(@(z, zeta, t) z*zeta+t) + +names = split(string(regexp(func2str(func), "(?<=@\().*(?=\))", "match")), ","); +end + diff --git a/+misc/indexGrid.m b/+misc/indexGrid.m new file mode 100644 index 0000000000000000000000000000000000000000..2e5d270cb991294d2c3ef79a1924665f77819bdd --- /dev/null +++ b/+misc/indexGrid.m @@ -0,0 +1,18 @@ +function myGrid = indexGrid(mySize) +% INDEXGRID creates a cell array of grid vectors. For every element of the mySize integer array one +% cell contain 1 : 1 : mySize(it) is created. +%% Example +% myGrid = misc.indexGrid([1, 2, 3]); +% myGrid{:} +% ans = +% 1 +% ans = +% 1 2 +% ans = +% 1 2 3 + +myGrid = cell(numel(mySize), 1); +for it = 1 : numel(myGrid) + myGrid{it} = 1 : 1 : mySize(it); +end +end % indexGrid() \ No newline at end of file diff --git a/+misc/nearInt.m b/+misc/nearInt.m new file mode 100644 index 0000000000000000000000000000000000000000..22d4d6a69eee104092e5243b95743a6612a40281 --- /dev/null +++ b/+misc/nearInt.m @@ -0,0 +1,7 @@ +function result = nearInt(number, tolerance) + if nargin == 1 + result = numeric.near(number, round(number)); + else + result = numeric.near(number, round(number), tolerance); + end +end \ No newline at end of file diff --git a/+misc/solveVolterraIntegroDifferentialIVP.m b/+misc/solveVolterraIntegroDifferentialIVP.m index 62773ea4a9be85384833c01a7478fc6646bbf8ce..0bec272d5f771b1d03b34a41d08419d5feeca265 100644 --- a/+misc/solveVolterraIntegroDifferentialIVP.m +++ b/+misc/solveVolterraIntegroDifferentialIVP.m @@ -65,7 +65,7 @@ if NameValueArgs.D.abs.MAX() >= 1e3*eps c5a = permute(numeric.trapz_fast_nDim(z.grid(1:zIdx), integrand), [2, 3, 1]); end % for zIdx = 2 : z.n - N = quantity.Discrete(permute(Ndouble, [3, 1, 2]), 'domain', z, 'name', "N"); + N = quantity.Discrete(permute(Ndouble, [3, 1, 2]), z, 'name', "N"); for it = 1 : 2 % improve by result by successive approximation for which % Cnew = C(z) + int_0^z D(z, zeta) N(zeta) dzeta @@ -87,7 +87,7 @@ else z.grid.', ... ic(:)); - N = quantity.Discrete(reshape(myN, [z.n, n, m]), 'domain', z, 'name', "N"); + N = quantity.Discrete(reshape(myN, [z.n, n, m]), z, 'name', "N"); end diff --git a/+mustBe/scalarOrEmpty.m b/+mustBe/scalarOrEmpty.m new file mode 100644 index 0000000000000000000000000000000000000000..1000287d959d5bcfdaf13f2e5cdd5e8b33f5e24b --- /dev/null +++ b/+mustBe/scalarOrEmpty.m @@ -0,0 +1,7 @@ +function scalarOrEmpty(A) +%mustBe.scalarOrEmpty Validates if the argument is empty or a scalar value + + if numel(A) ~= 1 && ~isempty(A) + error('Value assigned to Data property must be a scalar value or empty.'); + end +end \ No newline at end of file diff --git a/+numeric/femMatrices.m b/+numeric/femMatrices.m index d84d0e9ef1bb388e3fe9e35f6f15287f2c96a499..abb7e78f278e6e6394405d53ce9c4b0ec33dd4c8 100644 --- a/+numeric/femMatrices.m +++ b/+numeric/femMatrices.m @@ -1,73 +1,72 @@ -function [M, K, L, P, PHI] = femMatrices(varargin) +function [M, K, L, P, PHI, dphi] = femMatrices(spatialDomain, optArgs) %FEMMATRICES computes the discretization matrices obtained by FE-Method -% [M, K, L, P, PHI] = femMatrices(varargin) computes the matrices required -% for the approximation of a system using FEM. For instance, the rhs of a pde -% dz (alpha(z) dz x(z)) + beta(z) dz x(z) + gamma(z) -% is approximated as K x(z) -% The matrices that will be returned as described in the following: +% [M, K, L, P, PHI, DPHI] = femMatrices(SPATIALDOMAIN, OPTARGS) computes the matrices required for +% the approximation of a system using FEM. For instance, the rhs of a pde +% PDE[ x(z) ] = dz (alpha(z) dz x(z)) + beta(z) dz x(z) + gamma(z) x(z) + b(z) u +% y = c(z) x(z) +% is approximated as K x(z) The matrices that will be returned are: % The mass-matrix M is computed using % M = int phi(z) phi(z) % The stiffness matrix K is computed with -% K0 = int phi(z) gamma(z) phi(z) -% K1 = int phi(z) beta(z) (dz phi(z)) -% K2 = int - (dz phi(z)) alpha(z) (dz phi(z)) -% K = K0 + K1 + K2 +% K0 = int phi(z) gamma(z) phi(z) K1 = int phi(z) beta(z) (dz phi(z)) K2 = int - (dz phi(z)) +% alpha(z) (dz phi(z)) K = K0 + K1 - K2 % The input matrix L: % L = int( phi(z) * b(z) ) % The output matrix P: % P = int( c(z) * phi'(z) ) % -% PHI is the trial function used for the discretization. +% PHI is the trial function used for the discretization and DPHI is the derivative of the trial +% function. % -% The input arguments can be given as name-value-pairs. The parameters -% are described in the following, the default values are written in (.). -% 'grid' (linspace(0, 1, 11)): grid of resulting finite elements -% 'Ne'(111): the implementation uses numerical integration for each element. -% The parameter 'Ne' defines the grid for this integration. -% 'alpha' : the system parameter alpha, should be a quantity.Discrete. -% 'beta' : the system parameter beta, should be a quantity.Discrete -% 'gamma' : the system parameter gamma, should be a quantity.Discrete -% 'b' : the input vector, should be a quantity.Discrete -% 'c' : the output vector, should be a quantity.Discrete - -myParser = misc.Parser(); -myParser.addOptional('grid', linspace(0, 1, 11)); -myParser.addOptional('Ne', 111); -myParser.addOptional('alpha', quantity.Discrete.empty(1,0)); -myParser.addOptional('beta', quantity.Discrete.empty(1,0)); -myParser.addOptional('gamma', quantity.Discrete.empty(1,0)); -myParser.addOptional('b', quantity.Discrete.empty(1,0)); -myParser.addOptional('c', quantity.Discrete.empty(0,1)); -myParser.addOptional('silent', false); -myParser.parse(varargin{:}); - -% set the system properties -z = myParser.Results.grid(:); -N = numel(z); -z0 = z(1); -z1 = z(end); +% The argument SPATIALDOMAIN is mandatory and must be a quantity.Domain object. The further system +% parameters as +% "alpha", "beta", "gamma", "b", "c" +% must be quantity.Discrete objects. With the parameter +% "Ne" +% as positive integer number one can specify the number of discretization points for one element. +% The parameter +% "silent" +% is a logical flag to supress the progressbar. +% + +arguments + spatialDomain (1,1) quantity.Domain; + optArgs.Ne (1,1) double {mustBeInteger} = 111; + optArgs.alpha quantity.Discrete {mustBe.scalarOrEmpty} = quantity.Discrete.empty(1,0); + optArgs.beta quantity.Discrete {mustBe.scalarOrEmpty} = quantity.Discrete.empty(1,0); + optArgs.gamma quantity.Discrete {mustBe.scalarOrEmpty} = quantity.Discrete.empty(1,0); + optArgs.b quantity.Discrete = quantity.Discrete.empty(1,0); + optArgs.c quantity.Discrete = quantity.Discrete.empty(1,0)'; + optArgs.silent logical = false; +end + +% set the system properties to local variables, for better readability and (maybe) faster access +z = spatialDomain.grid; +N = spatialDomain.n; +z0 = spatialDomain.lower; +z1 = spatialDomain.upper; + pbar = misc.ProgressBar('name', 'FEM', 'terminalValue', N-1, 'steps', N-1, ... - 'initialStart', true, 'silent', myParser.Results.silent); + 'initialStart', true, 'silent', optArgs.silent); -alf = myParser.Results.alpha; -bta = myParser.Results.beta; -gma = myParser.Results.gamma; -b = myParser.Results.b; -c = myParser.Results.c; +alf = optArgs.alpha; +bta = optArgs.beta; +gma = optArgs.gamma; +b = optArgs.b; +c = optArgs.c; p = size(b, 2); % number of the input values m = size(c, 1); % number of the output values % define the properties of one finite element -element.N = myParser.Results.Ne; -element.delta = (z1 - z0) / (N - 1); -element.z = linspace(0, element.delta, element.N)'; +element = quantity.EquidistantDomain("z", 0, (z1 - z0) / (N - 1), ... + 'stepNumber', optArgs.Ne); %% verify that grid is homogenious -% Yet, this function only supports homogenious grids for most output parameters. -% Only, the mass matrix M is calculated correct for nonhomogenious grids. The -% following line ensures, that if the grid is not homogenious, only M is returned. -isGridHomogenious = all(diff(z) - element.delta < 10*eps); +% Yet, this function only supports homogenious grids for most output parameters. Only, the mass +% matrix M is calculated correct for nonhomogenious grids. The following line ensures, that if the +% grid is not homogenious, only M is returned. +isGridHomogenious = all(diff(z) - element.upper < 10*eps); if ~isGridHomogenious assert(nargout <= 1, 'non-homogenious grids only supported for mass-matrix'); end @@ -81,22 +80,22 @@ end % | ------------ | % \ deltaZ_k / -PHI = quantity.Discrete({(element.delta - element.z) / element.delta; ... - (element.z / element.delta) }, ... - 'size', [2, 1], 'gridName', 'z', 'grid', element.z, 'name', 'phi'); +PHI = quantity.Discrete({(element.upper - element.grid) / element.upper; ... + (element.grid / element.upper) }, ... + element, 'size', [2, 1], 'name', 'phi'); %% compute the mass matrix M0 = 1 / 6 * [2, 1; 1, 2]; -element.diff = diff(z); +zDiff = diff(z); %% compute the stiffness matrix -% initialize the trial functions for the computation for element matrices. -% for further comments, see the description in the loop -pp = reshape(double(PHI * PHI'), element.N, 4); +% initialize the trial functions for the computation for element matrices. for further comments, see +% the description in the loop +pp = reshape(double(PHI * PHI'), element.n, 4); phi = double(PHI); -dphi = [ -1 / element.delta; ... - 1 / element.delta ]; +dphi = [ -1 / element.upper; ... + 1 / element.upper ]; dphidphi = reshape(dphi * dphi', 1, 4); % initialize the element matrices @@ -122,51 +121,48 @@ flags.c = ~isempty(c); for k = 1:N-1 pbar.raise(k); idxk=[k, k+1]; - zk = linspace(z(k), z(k+1), element.N)'; + zk = linspace(z(k), z(k+1), element.n)'; if flags.gamma % compute the elements for K0 = int phi(z) * gamma(z) * phi'(z) - % the matrix phi(z) * phi'(z) is the same for each element. Thus, - % this matrix is computed a priori. To simplify the multiplication - % with the scalar value gamma(z), the matrix phi*phi' is vectorized - % and the result of phi(z)phi'(z) alpha(z) is reshaped as a matrix. + % the matrix phi(z) * phi'(z) is the same for each element. Thus, this matrix is computed + % a priori. To simplify the multiplication with the scalar value gamma(z), the matrix + % phi*phi' is vectorized and the result of phi(z)phi'(z) alpha(z) is reshaped as a matrix. k0 = misc.multArray(pp, gma.on(zk), 3, 2, 1); K0 = reshape( numeric.trapz_fast(zk, k0'), 2, 2); end if flags.beta % compute the elements for K1 = int phi(z) * beta(z) * dz phi'(z)) - % as the values dz phi(z) are constants, the multiplication can be. - % simplified. + % as the values dz phi(z) are constants, the multiplication can be. simplified. k1 = misc.multArray(phi, bta.on(zk) * dphi', 3, 3, 1); - K1 = reshape( numeric.trapz_fast(zk, reshape(k1, element.N, 4, 1)' ), 2, 2); + K1 = reshape( numeric.trapz_fast(zk, reshape(k1, element.n, 4, 1)' ), 2, 2); end if flags.alpha % compute the elements for K2 = int dz phi(z) * alpha(z) * dz phi'(z) - % as the matrix dzphi*dzphi' is constnat the multiplication - % is simplified by vectorization of dzphi*dzphi' and a reshape to - % obtain the correct result. + % as the matrix dzphi*dzphi' is constnat the multiplication is simplified by vectorization + % of dzphi*dzphi' and a reshape to obtain the correct result. k2 = alf.on(zk) * dphidphi; K2 = reshape( numeric.trapz_fast(zk, k2'), 2, 2); end if flags.b % compute the input matrix L = int phi(z) * b(z) - l = reshape( misc.multArray(phi, b.on(zk), 3, 2, 1), element.N, 2*p); + l = reshape( misc.multArray(phi, b.on(zk), 3, 2, 1), element.n, 2*p); L0 = reshape( numeric.trapz_fast(zk, l'), 2, p); end if flags.c % compute the output matrix P = int c(z) * phi(z)' - c0 = reshape( misc.multArray(c.on(zk), phi, 3, 3, 1), element.N, 2*m); + c0 = reshape( misc.multArray(c.on(zk), phi, 3, 3, 1), element.n, 2*m); P0 = reshape( numeric.trapz_fast(zk, c0'), m, 2); end % assemble the matrices with the elements: - M(idxk, idxk) = M(idxk, idxk) + M0 * element.diff(k); + M(idxk, idxk) = M(idxk, idxk) + M0 * zDiff(k); - K(idxk, idxk) = K(idxk, idxk) + K0 + K1 + K2; + K(idxk, idxk) = K(idxk, idxk) + K0 + K1 - K2; L(idxk, :) = L(idxk, :) + L0; P(:, idxk) = P(:, idxk) + P0; end diff --git a/+quantity/BasicVariable.m b/+quantity/BasicVariable.m index 8aa5af204359e3fd08bbfb280bb66e5cfec7ab81..d0177ea0ae018d09caddba02c6d921567726f6b8 100644 --- a/+quantity/BasicVariable.m +++ b/+quantity/BasicVariable.m @@ -33,8 +33,8 @@ end methods %% CONSTRUCTOR -function obj = BasicVariable(valueContinuous, myDomain, varargin) - +function obj = BasicVariable(valueContinuous, varargin) + warning("DEPRICATED!: Use signals.BasicVariable.") parentVarargin = {}; if nargin > 0 && ~isempty(varargin) @@ -111,6 +111,7 @@ function obj = BasicVariable(valueContinuous, myDomain, varargin) if size(obj_j.derivatives, 1) < k+1 || isempty(obj_j.derivatives(k+1,:)) % create a new object for the derivative of obj: Dij = obj_j.copy(); + Dij.offset = 0; Dij.name = ['d/dt (' num2str(k) ') ']; Dij.valueDiscrete = []; Dij.diffShift = Dij.diffShift + k; @@ -168,7 +169,6 @@ function obj = BasicVariable(valueContinuous, myDomain, varargin) bi.K = K; b.derivatives(i) = bi; end - end end @@ -211,4 +211,4 @@ methods (Static) end end -end \ No newline at end of file +end diff --git a/+quantity/Discrete.m b/+quantity/Discrete.m index 9969feb822978626b405d2a5f075d4105d64d28b..17f27dfb9877c4f758c87bf7cea4466cacc143b2 100644 --- a/+quantity/Discrete.m +++ b/+quantity/Discrete.m @@ -6,10 +6,6 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete ... valueDiscrete double; end - properties (Hidden, Access = protected, Dependent) - doNotCopy; - end - properties % ID of the figure handle in which the handle is plotted figureID double = 1; @@ -29,9 +25,9 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete ... % Grid for the evaluation of the continuous quantity. For the % example with the function f(x,t), the grid would be % {[<spatial domain>], [<temporal domain>]} - % whereas <spatial domain> is the discret description of the - % spatial domain and <temporal domain> the discrete description of - % the temporal domain. + % whereas <spatial domain> is the discrete description of the + % spatial domain x and <temporal domain> the discrete description of + % the temporal domain t. grid; % in set.grid it is ensured that, grid is a (1,:)-cell-array end @@ -48,9 +44,8 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete ... % size(valueOriginal) == size(obj) and % size(valueOriginal{it}) == gridSize % Example: valueOrigin = { f(Z, T), g(Z, T) } is a cell array - % wich contains the functions f(z,t) and g(z,t) evaluated on - % the discrete domain (Z x T). Then, the name-value-pair - % parameter 'domain' must be set with quantity.Domain + % which contains the functions f(z,t) and g(z,t) evaluated on + % the discrete domain (Z x T). Then, myDOmain must be quantity.Domain % objects, according to the domains Z and T. % OR % 2) a double-array with @@ -99,9 +94,12 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete ... % the case if all values are empty. This is required for % the initialization of quantity.Function and % quantity.Symbolic objects - assert( misc.alln( cellfun(@isempty, valueOriginal ) ) || ... - numGridElements(myDomain) == numel(valueOriginal{1}), ... - 'grids do not fit to valueOriginal'); + assert(isempty(myDomain) ... % constant case + || all( cellfun(@isempty, valueOriginal ), 'all' ) ... % empty case + || isequal([myDomain.n], size(valueOriginal{1}, 1 : max(1, numel(myDomain)))) ... % usual case + || (isrow(valueOriginal{1}) && ... % row-vector case (including next line) + isequal([1, myDomain.n], size(valueOriginal{1}, 1 : max(1, numel(myDomain)+1)))), ... + 'grids do not fit to valueOriginal'); % allow initialization of empty objects valueOriginalSize = size(valueOriginal); @@ -163,9 +161,6 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete ... itIs = isempty(obj(1).domain); end % isConstant() - function doNotCopy = get.doNotCopy(obj) - doNotCopy = obj.doNotCopyPropertiesName(); - end function valueDiscrete = get.valueDiscrete(obj) % check if the value discrete for this object % has already been computed. @@ -175,7 +170,7 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete ... end valueDiscrete = obj.valueDiscrete; end - + %------------------- % --- converters --- %------------------- @@ -189,7 +184,7 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete ... headers{i+1} = obj(i).name + "" + num2str(i); end exportData = export.dd(... - 'M', [obj.grid{:}, obj.valueDiscrete], ... + 'M', [obj(1).grid{:}, obj.valueDiscrete], ... 'header', headers, varargin{:}); elseif obj.nargin == 2 error('Not yet implemented') @@ -235,6 +230,9 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete ... % end [obj.name] = deal(newName); end % setName() + + + end methods (Access = public) @@ -248,7 +246,7 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete ... for i = 1:numel(obj) - data = obj(i).valueDiscrete(); + data = obj(i).valueDiscrete; zeros = find( abs(data) <= optArg.tol); upCrossing = find( data(1:end-1) <= 0 & data(2:end) > 0); @@ -275,8 +273,7 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete ... data = {[ obj.valueDiscrete ], obj(1).domain.name, ... obj(1).domain.grid, obj(1).name}; h = misc.hash(data); - - end + end % hash() function d = compositionDomain(obj, domainName) @@ -293,7 +290,7 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete ... function obj_hat = compose(obj, g, optionalArgs) % COMPOSE compose two functions - % OBJ_hat = compose(obj, G, varargin) composes the function + % OBJ_hat = compose(obj, G, varargin) composes the function f % defined by OBJ with the function given by G. In particular, % f_hat(z,t) = f( z, g(z,t) ) % if f(t) = obj, g is G and f_hat is OBJ_hat. @@ -398,7 +395,7 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete ... 'name', obj.name + "°" + g.name, ... 'size', size(obj)); - end + end % compose() function value = on(obj, myDomain, gridNames) % ON evaluation of the quantity on a certain domain. @@ -420,7 +417,8 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete ... if nargin == 1 % case 0: no domain was specified, hence the value is requested % on the default grid defined by obj(1).domain. - value = obj.obj2value(obj(1).domain); + value = reshape(cat(numel(obj(1).domain)+1, obj(:).valueDiscrete), ... + [obj(1).domain.gridLength(), size(obj)]); elseif nargin == 2 && (iscell(myDomain) || isnumeric(myDomain)) % case 1: a domain is specified by myDomain as agrid @@ -439,8 +437,7 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete ... for k = 1:length(newGrid) myDomain(k) = quantity.Domain(gridNames{k}, newGrid{k}); end - value = reshape(obj.obj2value(myDomain), ... - [myDomain.gridLength, size(obj)]); + value = obj.obj2value(myDomain); else % Since in the remaining cases the order of the domains is not % neccessarily equal to the order in obj(1).domain, this is @@ -463,7 +460,8 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete ... 'The cell entries for a new grid have to be vectors') newGrid = myDomain; - myDomain = quantity.Domain.empty(); + clear('myDomain'); + myDomain(1:length(newGrid)) = quantity.Domain(); for k = 1:length(newGrid) myDomain(k) = quantity.Domain(gridNames{k}, newGrid{k}); end @@ -480,6 +478,8 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete ... % compute the permutation index, in order to bring the % new domain in the same order as the original one. gridPermuteIdx = obj(1).domain.getPermutationIdx(myDomain); + + assert(any(gridPermuteIdx ~= 0), "grid could not be found.") end % get the valueDiscrete data for this object. Apply the % permuted myDomain. Then the obj2value will be evaluated @@ -487,8 +487,7 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete ... % the new order will be done in the next step. originalOrderedDomain(gridPermuteIdx) = myDomain; value = obj.obj2value(originalOrderedDomain); - value = permute(reshape(value, [originalOrderedDomain.gridLength, size(obj)]), ... - [gridPermuteIdx, numel(gridPermuteIdx)+(1:ndims(obj))]); + value = permute(value, [gridPermuteIdx, numel(gridPermuteIdx)+(1:ndims(obj))]); end end % if isempty(obj) end % on() @@ -497,17 +496,17 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete ... % get the interpolant of the obj; if isempty(obj) value = zeros(size(obj)); - indexGrid = arrayfun(@(s)linspace(1,s,s), size(obj), 'UniformOutput', false); + indexGrid = misc.indexGrid(size(obj)); interpolant = numeric.interpolant(... [indexGrid{:}], value); else myGrid = obj(1).grid; value = obj.obj2value(); - indexGrid = arrayfun(@(s)linspace(1,s,s), size(obj), 'UniformOutput', false); + indexGrid = misc.indexGrid(size(obj)); interpolant = numeric.interpolant(... [myGrid, indexGrid{:}], value); end - end + end % interpolant() function assertSameGrid(a, varargin) @@ -584,20 +583,20 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete ... end end - function obj = sort(obj, varargin) + function newObj = sort(obj, varargin) %SORT sorts the grid of the object in a desired order % obj = sortGrid(obj) sorts the grid in alphabetical order. % obj = sort(obj, 'descend') sorts the grid in descending % alphabetical order. - + newObj = obj.copy(); % only sort the grids if there is something to sort - if ~isempty(obj) && obj(1).nargin > 1 + if ~isempty(newObj) && newObj(1).nargin > 1 - [sortedDomain, I] = obj(1).domain.sort(varargin{:}); - [obj.domain] = deal(sortedDomain); + [sortedDomain, I] = newObj(1).domain.sort(varargin{:}); + [newObj.domain] = deal(sortedDomain); - for k = 1:numel(obj) - obj(k).valueDiscrete = permute(obj(k).valueDiscrete, I); + for k = 1:numel(newObj) + newObj(k).valueDiscrete = permute(obj(k).valueDiscrete, I); end end end% sort() @@ -849,7 +848,6 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete ... inverse = quantity.Discrete(... repmat(obj(1).grid{obj(1).domain.gridIndex(gridName)}(:), [1, size(obj)]), ... quantity.Domain([obj(1).name], obj.on()), ... - 'size', size(obj), ... 'name', gridName); end % invert() @@ -970,6 +968,13 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete ... assert(numel(values) == numel(gridName2Replace), ... 'gridName2Replace and values must be of same size'); + % set the newDomain once. If obj(1).domain is a quantity.Equidistant domain, it can + % not be mixed with other quantity.Domains in an array. Hence, it must be casted to + % a quantity.Domain. The following strange form of concatenation an empty Domain + % with the required domain, ensures that the result is an array of quantity.Domain + % objects. + newDomain = [quantity.Domain.empty, obj(1).domain]; + % here substitution starts: % The first (gridName2Replace{1}, values{1})-pair is % replaced. If there are more cell-elements in those inputs @@ -998,7 +1003,6 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete ... % Do not use "return", since, later subs might need to be % called recursively! newValue = obj.on(); - newDomain = obj(1).domain; elseif any(strcmp(values{1}, obj(1).gridName)) % if for a quantity f(z, zeta) this method is % called with subs(f, zeta, z), then g(z) = f(z, z) @@ -1021,7 +1025,7 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete ... newDomainForOn(all(1:1:numel(newDomainForOn) ~= domainIndices(:)))]; else % this is the default case. just grid name is changed. - newDomain = obj(1).domain; + %newDomain = obj(1).domain; newDomain(obj(1).domain.gridIndex(gridName2Replace{1})) = ... quantity.Domain(values{1}, ... obj(1).domain(obj(1).domain.gridIndex(gridName2Replace{1})).grid); @@ -1032,7 +1036,6 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete ... % if values{1} is a scalar, then obj is evaluated and % the resulting quantity looses that spatial grid and % gridName - newDomain = obj(1).domain; newDomain = newDomain(~strcmp(gridName2Replace{1}, [newDomain.name])); % newGrid is the similar to the original grid, but the % grid of gridName2Replace is removed. @@ -1047,14 +1050,16 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete ... elseif isnumeric(values{1}) && numel(values{1}) > 1 % if values{1} is a double vector, then the grid is % replaced. - newDomain = obj(1).domain; newDomain(obj(1).domain.gridIndex(gridName2Replace{1})) = ... quantity.Domain(gridName2Replace{1}, values{1}); newValue = obj.on(newDomain); else error('value must specify a gridName or a gridPoint'); - end + end % if ischar(values{1}) || isstring(values{1}) if isempty(newDomain) + % TODO@Jakob: Is it possible that this case occurrs? + % If I see it right, the only case where the newDomain is not set is the case + % where a error is thrown. solution = newValue; else solution = quantity.Discrete(newValue, newDomain, ... @@ -1075,7 +1080,7 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete ... % at() evaluates the object at one point and returns it as array % with the same size as size(obj). value = reshape(obj.on(point), size(obj)); - end + end % at() function value = atIndex(obj, varargin) % ATINDEX returns the valueDiscrete at the requested index. @@ -1118,7 +1123,7 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete ... value = reshape([value{:}], [outputSize, size(obj)]); end - end + end % atIndex() function n = nargin(obj) % FIXME: check if all funtions in this object have the same @@ -1323,17 +1328,12 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete ... function s = struct(obj, varargin) if (nargin == 1) && isa(obj, "quantity.Discrete") - properties = fieldnames(obj); + tempProperties = fieldnames(obj); si = num2cell( size(obj) ); s(si{:}) = struct(); for l = 1:numel(obj) - - doNotCopyProperties = obj(l).doNotCopy; - - for k = 1:length(properties) - if ~any(strcmp(doNotCopyProperties, properties{k})) - s(l).(properties{k}) = obj(1).(properties{k}); - end + for k = 1:length(tempProperties) + s(l).(tempProperties{k}) = obj(1).(tempProperties{k}); end end else @@ -1392,8 +1392,11 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete ... gridNameNew = [gridNew.name]; gridNew = {gridNew.grid}; else - gridNameNew = misc.ensureString(gridNameNew); + gridNameNew = misc.ensureString(gridNameNew); gridNew = misc.ensureIsCell(gridNew); + for it = 1:numel(gridNew) + assert( isnumeric( [gridNew{it}] ), "The gridNew parameter must be a cell array of numeric arrays." ) + end end if obj(1).isConstant @@ -1424,7 +1427,55 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete ... for it = 1 : numel(obj) newObj(it).valueDiscrete = obj(it).on(newDomain); end - end + end % changeGrid() + + + function newObj = replaceGrid(obj, myNewDomain, optArgs) + % CHANGEGRID change the grid of the quantity. + % newObj = REPLACEGRID(obj, MYNEWDOMAIN, "gridName", NEWGRIDNAME) + % replace the grid of the obj quantity. The order of grid and + % gridName in the obj properties remains unchanged, only the + % grid points are exchanged. + % + % TODO: + % newObj = REPLACEGRID(obj, domain) changes the domain of the + % object specified by the name of DOMAIN into DOMAIN. + % + % newObj = REPLACEGRID(obj, domain, 'gridName', NEWNAME) changes the domain of the + % object specified by NEWNAME into DOMAIN. + + arguments + obj + myNewDomain quantity.Domain + optArgs.gridName = [myNewDomain.name]; + end + + if isempty(obj) + newObj = obj.copy(); + return; + end + + assert( intersect(obj(1).gridName, optArgs.gridName) == optArgs.gridName ); + + if obj(1).isConstant + error("Not yet implemented") + else + gridIndexNew = obj(1).domain.gridIndex(optArgs.gridName); + % initialization of the newDomain array as quantity.Domain + % array. This is required in order to handle also + % quantity.EquidistantDomains: + newDomain(1:obj(1).nargin) = quantity.Domain(); + newDomain(:) = obj(1).domain; + + for it = 1 : length(gridIndexNew) + newDomain(gridIndexNew(it)) = ... + quantity.Domain(myNewDomain(it).name, myNewDomain(it).grid); + end + end + + newObj = obj.copy(); + [newObj.domain] = deal(newDomain); + end % replaceGrid end %% math @@ -1556,26 +1607,50 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete ... error('sqrtm() is only implemented for quadratic matrices'); end end % sqrtm() + function P = power(obj, p) - % a.^p implemented by multiplication - % #todo unittest required - P = quantity.Discrete.zeros(size(obj), obj(1).domain); - for k = 1:numel(obj) - P(k) = obj(k)^p; - end - end + % a.^p elementwise power + P = quantity.Discrete(obj.on().^(p), obj(1).domain, ... + "name", obj(1).name + ".^{" + num2str(p) + "}"); + end % power() + function P = mpower(a, p) - % a^p implemented by multiplication - assert(p==floor(p) && p > 0); - P = a; - for k = 1:(p-1) - P = P * a; + % Matrix power a^p is matrix or scalar a to the power p. + if p == 0 + P = setName(eye(size(a)) + 0*a, "I"); + elseif p < 0 + if numel(a) > 1 + warning("mpower(a, p) implements a^p. " ... + + "For matrices a with negative exponent p, inv(a^(-p)) is returned. " ... + + "This represents a division from left, " ... + + "maybe division from right is needed in your case!") + end % this warning is not important in the scalar case. + P = inv(mpower(a, -p)); + else % p > 0 + assert(p==floor(p), "power p must be integer"); + P = a; + for k = 1:(p-1) + P = P * a; + end end end % mpower() + function s = num2str(obj) s = obj.name; end % num2str() + function P = times(a, b) + + assert( all( size(a) == size(b), 'all' ), 'the terms a and b must have the same size'); + + for i = 1:numel(a) + P(i) = a(i) * b(i); + end + + P = reshape(P, size(a)); + + end + function P = mtimes(a, b) % TODO rewrite the selection of the special cases! the % if-then-cosntruct is pretty ugly! @@ -1756,10 +1831,62 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete ... function y = exp(obj) % exp() is the exponential function using obj as the exponent. y = quantity.Discrete(exp(obj.on()), obj(1).domain, ... - 'name', "exp(" + obj(1).name + ")", ... - 'size', size(obj)); + 'name', "exp(" + obj(1).name + ")"); end % exp() + function [V, D, W] = eig(obj) + % eig Eigenvalues and eigenvectors pointwise over the domain + % E = eig(A) produces a column vector E containing the eigenvalues of + % a square matrix A. + % + % [V,D] = eig(A) produces a diagonal matrix D of eigenvalues and + % a full matrix V whose columns are the corresponding eigenvectors + % so that A*V = V*D. + % + % [V,D,W] = eig(A) also produces a full matrix W whose columns are the + % corresponding left eigenvectors so that W'*A = D*W'. + assert(numel(obj(1).domain)==1, "quantity.Discrete/eig is only implemented for one domain"); + assert((ndims(obj) <= 2) && (size(obj, 1) == size(obj, 2)), ... + "quantity.Discrete/eig is only implemented for quadratic quantities"); + + objDisc = permute(obj.on(), [2, 3, 1]); + if nargout == 1 + % E = eig(A) + VDisc = zeros([obj(1).domain.n, size(obj, 1)]); + for it = 1 : obj(1).domain.n + VDisc(it, :) = eig(objDisc(:, :, it)); + end % for it = 1 : obj(1).domain.n + else + % [V,D] = eig(A) or [V,D,W] = eig(A) + VDisc = zeros([obj(1).domain.n, size(obj)]); + DDisc = zeros([obj(1).domain.n, size(obj)]); + WDisc = zeros([obj(1).domain.n, size(obj)]); + for it = 1 : obj(1).domain.n + [VDisc(it, :, :), DDisc(it, :, :), WDisc(it, :, :)] = ... + eig(objDisc(:, :, it)); + end % for it = 1 : obj(1).domain.n + D = quantity.Discrete(DDisc, obj(1).domain, "name", "D"); + W = quantity.Discrete(WDisc, obj(1).domain, "name", "W"); + end + V = quantity.Discrete(VDisc, obj(1).domain, "name", "V"); + end % eig() + + function y = log(obj) + % log Natural logarithm. + % log(X) is the natural logarithm of the elements of X. + % Complex results are produced if X is not positive. + y = quantity.Discrete(log(obj.on()), obj(1).domain, ... + 'name', "log(" + obj(1).name + ")"); + end % log() + + function y = log10(obj) + % log10 Common (base 10) logarithm. + % log10(X) is the base 10 logarithm of the elements of X. + % Complex results are produced if X is not positive. + y = quantity.Discrete(log10(obj.on()), obj(1).domain, ... + 'name', "log10(" + obj(1).name + ")"); + end % log10() + function xNorm = l2norm(obj, integralGridName, optArg) % calculates the l2 norm, for instance % xNorm = sqrt(int_0^1 x.' * x dz). @@ -1791,6 +1918,24 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete ... end end % l2norm() + function xNorm = norm(obj, p) + % norm implements the vector norm, similar to builtin matlab function norm + % norm(V,P) returns the p-norm of V defined as SUM(ABS(V).^P)^(1/P). + % + % norm(X) is the same as norm(X,2). (euclidian norm) + + arguments + obj; + p (1, 1) double = 2; + end + + if p == 2 + xNorm = sqrt(sum(abs(obj).^p)); + else + xNorm = sum(abs(obj).^p).^(1/p); + end + end % norm(obj, p) + function xNorm = quadraticNorm(obj, varargin) % calculates the quadratic norm, i.e, % xNorm = sqrt(x.' * x). @@ -1856,7 +2001,7 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete ... p = a(1).gridSize(); q = p(2); p = p(1); - A = a.on(); + obj = a.on(); B = b.on(); % dimensions @@ -1869,7 +2014,7 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete ... for k = 1 : n % rows of P for l = 1 : m % columns of P for r = 1 : o % rows of B or columns of A - P(:, :, k, l) = P( :, :, k, l ) + A( :, :, k, r) .* B( :, :, r, l ); + P(:, :, k, l) = P( :, :, k, l ) + obj( :, :, k, r) .* B( :, :, r, l ); end end end @@ -1960,7 +2105,7 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete ... q = p(2); p = p(1); - A = a.on(); + obj = a.on(); B = repmat(b.on(), 1, 1, 1, q); B = permute(B, [1, 4, 2, 3]); @@ -1974,7 +2119,7 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete ... for k = 1 : n % rows of P for l = 1 : m % columns of P for r = 1 : o % rows of B or columns of A - P(:, :, k, l) = P( :, :, k, l ) + A( :, :, k, r) .* B( :, :, r, l ); + P(:, :, k, l) = P( :, :, k, l ) + obj( :, :, k, r) .* B( :, :, r, l ); end end end @@ -2187,7 +2332,11 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete ... end function [P, supremum] = relativeErrorSupremum(A, B) - + %% RELATIVEERRRORSUPREMUM compute the relative error of the supremum + % [P, SUPREMUM] = relativeErrorSupremum(A, B) computes the supremum of the absolute + % error of A and B and returns the relative error of A - B with respect to this + % supremum. The relative error is returned as P and the supremum is returned as + % SUPREMUM. assert(numel(A) == numel(B), 'Not implemented') P = A.copy(); @@ -2356,12 +2505,8 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete ... % obj2value(obj, myDomain) returns the valueDiscrete in the % form size(value) = [myDomain.gridLength objSize] - v = zeros([numel(obj(1).valueDiscrete), numel(obj)]); - for k = 1:numel(obj) - v(:,k) = obj(k).valueDiscrete(:); - end - - value = reshape(v, [obj(1).domain.gridLength(), size(obj)]); + value = reshape(cat(numel(obj(1).domain)+1, obj(:).valueDiscrete), ... + [obj(1).domain.gridLength(), size(obj)]); if nargin >= 2 && ~isequal( myDomain, obj(1).domain ) % if a new domain is specified for the evaluation of @@ -2369,10 +2514,10 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete ... if obj.isConstant % ... duplicate the constant value on the desired % domain - value = repmat(v, [cellfun(@(v) numel(v), {myDomain.grid}), ones(1, length(size(obj)))]); + value = repmat(value(:).', [myDomain.n, ones(1, ndims(obj))]); else %... do an interpolation based on the old data. - indexGrid = arrayfun(@(s) 1:s, size(obj), 'UniformOutput', false); + indexGrid = misc.indexGrid(size(obj)); tempInterpolant = numeric.interpolant(... [obj(1).grid, indexGrid{:}], value); tempGrid = {myDomain.grid}; @@ -2380,7 +2525,7 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete ... end end - end + end % obj2value() function result = diag2vec(obj) % This method creates a vector of quantities by selecting the @@ -2477,35 +2622,8 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete ... q = reshape(mat2cell(value, s{:}), valueSize); end % value2cell() - function [p, q, parameters, gridSize] = inputNormalizer(a, b) - - parameters = []; - - function s = innerInputNormalizer(A, B) - if isa(A, 'double') - s.name = num2str(A(:)'); - s.gridName = {}; - s.grid = {}; - s.value = A; - s.size = size(A); - s.nargin = 0; - elseif isa(A, 'quantity.Discrete') - s.name = A(1).name; - s.value = A.on(); - s.gridName = A(1).gridName; - s.grid = A(1).grid; - s.size = size(A); - s.nargin = A.nargin(); - parameters = struct(A); - gridSize = A.domain.gridLength(); - end - end - - q = innerInputNormalizer(b, a); - p = innerInputNormalizer(a, b); - - end end %% (Static) + methods(Access = protected) function [valDiscrete] = expandValueDiscrete(obj, newDomain) @@ -2702,13 +2820,7 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete ... [sprintf('%ix', obj(1).domain.gridLength, size(obj)) sprintf('\b')]; end end % getPropertyGroups + end % methods (Access = protected) - methods ( Static, Access = protected ) - function doNotCopy = doNotCopyPropertiesName() - % #DEPRECATED - doNotCopy = {'valueContinuous', 'valueDiscrete', 'derivatives'}; - end - end % methods ( Static, Access = protected ) - end % classdef diff --git a/+quantity/Domain.m b/+quantity/Domain.m index 1b3bb92cf0144a483c3708e697489eb642faef08..187f6eac0eee8cb217e9aca186d7f80adcb059bf 100644 --- a/+quantity/Domain.m +++ b/+quantity/Domain.m @@ -63,9 +63,27 @@ classdef (InferiorClasses = {?quantity.EquidistantDomain}) Domain < ... function s = string(obj) s = obj.name; - end + end + + function quan = Discrete(obj) + % DISCRETE casts the domain into a quantity.Discrete with the values of obj.grid. + arguments + obj (1, 1) quantity.Domain; + end + quan = quantity.Discrete(obj.grid, obj, 'name', obj.name); + end % quantity.Discrete() + + function quan = Symbolic(obj) + % SYMBOLIC casts the domain into a quantity.Symbolic with the grid name as symbolic + % value. + arguments + obj (1, 1) quantity.Domain; + end + quan = quantity.Symbolic(sym(obj.name), obj, 'name', obj.name); + end % Symbolic() + end - + methods (Access = public) function d = split(obj, splittingPoints, optArgs) @@ -140,15 +158,29 @@ classdef (InferiorClasses = {?quantity.EquidistantDomain}) Domain < ... end end - function d = copyAndRename(obj, newName) - d = obj.copy(); + function d = rename(obj, newName, oldName) + % RENAME renames the name of the domain OBJ. + % + % d = rename(obj, newName) replaces all names of obj with the names specified by the + % string array newName + % + % d = rename(obj, newName, oldName) replaces the names of obj specified by the string + % array oldName with the corresponding elements of the string array newName - newName = misc.ensureIsCell(newName); - assert(length(newName) == length(d), 'For the renaming of a copy of a domain, the numebr of new names must be equal the number of the domains'); - for k = 1:length(newName) - d(k).name = newName{k}; - end - end + arguments + obj quantity.Domain; + newName string; + oldName string = [obj.name]; + end % arguments + + assert(numel(newName) == numel(oldName), ... + "number of new names must be equal to number of old names."); + d = copy(obj); + for it = 1 : numel(newName) + d(obj.gridIndex(oldName(it))).name = newName{it}; + end % for it = 1 : numel(newName) + assert(misc.isunique([d.name]), "the resulting domain must contain unique names"); + end % rename function nd = ndims(obj) %NDIMS number of dimensions of the domain specified by the @@ -159,7 +191,7 @@ classdef (InferiorClasses = {?quantity.EquidistantDomain}) Domain < ... function n = numGridElements(obj) % NUMGRIDLEMENTS returns the number of the elements of the grid n = prod([obj.n]); - end + end % numGridElements() function s = gridLength(obj) %GRIDLENGTH number of discretization points for each grid in the @@ -191,6 +223,19 @@ classdef (InferiorClasses = {?quantity.EquidistantDomain}) Domain < ... d = obj( obj.gridIndex( searchName ) ); end + + function rest = remove(obj, toBeRemoved) + % REMOVE removes the domain specified by toBeRemoved from the quantity.Domain array obj. + % + % rest = remove(obj, toBeRemoved) removes the domain specified by toBeRemoved from the + % quantity.Domain array obj. toBeRemoved may be a string or a domain or a array of + % those. + + [~, logicalIdxToBeRemoved] = obj.gridIndex(toBeRemoved); + + rest = obj(~logicalIdxToBeRemoved); + end % remove() + function [idx, log] = gridIndex(obj, searchName, position) %% GRIDINDEX returns the index of the grid % [idx, log] = gridIndex(obj, names) searches in the name diff --git a/+quantity/Function.m b/+quantity/Function.m index 9cb0182d035e2221c16054aec3396b992c3c0579..a0b4c1d435e9681a08b1b7307b1a379edc776e85 100644 --- a/+quantity/Function.m +++ b/+quantity/Function.m @@ -66,7 +66,7 @@ classdef Function < quantity.Discrete myParser.addParameter('name', obj(1).name); myParser.parse(varargin{:}); Q = quantity.Discrete(obj.on(), myParser.Results.domain, 'name', myParser.Results.name); - end + end % quantity.Discrete() %----------------------------- % --- overloaded functions --- @@ -91,14 +91,29 @@ classdef Function < quantity.Discrete end value = reshape( cell2mat(value), [ gridLength(myDomain), size(obj)]); end - end + end % obj2value() + + function value = at(obj, point) + % at() evaluates the object at one point and returns it as array + % with the same size as size(obj). + value = zeros(size(obj)); + if ~iscell(point) + for it = 1 : numel(value) + value(it) = obj(it).valueContinuous(point); + end + else + for it = 1 : numel(value) + value(it) = obj(it).valueContinuous(point{:}); + end + end + end % at() function n = nargin(obj) % FIXME: check if all funtions in this object have the same % number of input values. n = numel(obj(1).grid); - end - end + end % nargin() + end % methods % ------------- % --- Mathe --- diff --git a/+quantity/Symbolic.m b/+quantity/Symbolic.m index 380276572c3ff07f8b47bbc1895551f814a12004..e5b4c95e1375008fe5f9f9480178231074130a94 100644 --- a/+quantity/Symbolic.m +++ b/+quantity/Symbolic.m @@ -4,9 +4,7 @@ classdef Symbolic < quantity.Function valueSymbolic sym; variable sym; end - properties (Constant) - defaultSymVar = sym('z', 'real'); - end + properties symbolicEvaluation = false; end @@ -30,7 +28,7 @@ classdef Symbolic < quantity.Function end variableParser = misc.Parser(); - variableParser.addParameter('variable', quantity.Symbolic.getVariable(valueContinuous)); + variableParser.addParameter('variable', []); variableParser.addParameter('symbolicEvaluation', false); variableParser.parse(varargin{:}); @@ -43,7 +41,6 @@ classdef Symbolic < quantity.Function fun = cell(S{:}); symb = cell(S{:}); - for k = 1:numel(valueContinuous) if iscell(valueContinuous) @@ -256,13 +253,13 @@ classdef Symbolic < quantity.Function c = cat@quantity.Discrete(dim, objCell{:}); end % cat() - function solution = subs(obj, gridName, values) - %% SUBS substitues gridName and values + function solution = subs(obj, gridName2Replace, values) + % SUBS substitues gridName and values % solution = subs(obj, DOMAIN) can be used to change the grid % of this quantity. The domains with the same domain name as % DOMAIN will be replaced with the corresponding grid in % DOMAIN. - % + %J % solution = subs(obj, DOMAINNAME, GRID) can be used to % change the grid of this quantity. The grid of the domains with the % DOMAINNAME will be replaced by the values specified in @@ -275,124 +272,82 @@ classdef Symbolic < quantity.Function % quantity.Domain objects. The NEWDOMAIN must be an % object-array of quantity.Domain. - if isa(gridName, 'quantity.Domain') + if isa(gridName2Replace, 'quantity.Domain') if nargin == 2 - gridName = {gridName.name}; - values = {gridName.grid}; + gridName2Replace = {gridName2Replace.name}; + values = {gridName2Replace.grid}; else - gridName = {gridName.name}; + gridName2Replace = {gridName2Replace.name}; end end - gridName = misc.ensureString(gridName); + gridName2Replace = misc.ensureString(gridName2Replace); if nargin == 3 && isa(values, 'quantity.Domain') % replacement of the grid AND the gridName % 1) replace the grid - solution = obj.changeGrid( {values.grid}, gridName ); + solution = obj.changeGrid( {values.grid}, gridName2Replace ); % 2) replace the name - solution = solution.subs( gridName, {values.name} ); + solution = solution.subs( gridName2Replace, {values.name} ); return - else - values = misc.ensureIsCell(values); - for it = 1 : numel(values) - % for later strcmp usage, values must be a cell-array with only - % numeric or char elements. - if isstring(values{it}) - values{it} = char(values{it}); - end - end % for it = 1 : numel(values) end - isNumericValue = cellfun(@isnumeric, values); - if any((cellfun(@(v) numel(v(:)), values)>1) & isNumericValue) - error('only implemented for one value per grid'); - end - numericValues = values(isNumericValue); - if ~isempty(numericValues) && numel(obj(1).gridName) == numel(numericValues) - % if all grids are evaluated, solution is a double array. - solution = reshape(obj.on(numericValues), size(obj)); - else - % evaluate numeric values - subsGrid = obj(1).grid; - selectRemainingGrid = false(1, numel(obj(1).grid)); - for currentGridName = gridName(isNumericValue) - selectGrid = strcmp(obj(1).gridName, currentGridName); - subsGrid{selectGrid} = values{strcmp(gridName, currentGridName)}; - selectRemainingGrid = selectRemainingGrid | selectGrid; - end - newGrid = obj(1).grid(~selectRemainingGrid); - newGridName = obj(1).gridName(~selectRemainingGrid); - for it = 1 : numel(values) - if ~isNumericValue(it) && ~isempty(obj(1).gridName(~selectRemainingGrid)) - % check if there is a symbolic value and if this value exists in the object - if isstring( values{it} ) || ischar( values{it} ) - newGridName( strcmp(obj(1).gridName(~selectRemainingGrid), gridName(it)) ) ... - = values{it}; - elseif isa(values{it}, 'sym') - gridNameTemp = symvar(values{it}); - assert(numel(gridNameTemp) == 1, 'replacing one gridName with 2 gridName is not supported'); - newGridName{strcmp(obj(1).gridName(~selectRemainingGrid), gridName(it))} ... - = char(gridNameTemp); - - else - error(['value{', num2str(it), '} is of unsupported type']); - end - end + values = misc.ensureIsCell(values); + isNumericValue = false(numel(values), 1); + for it = 1 : numel(values) + % check which values are substitutions with numerical values in the domain + if isnumeric(values{it}) + isNumericValue(it) = true; end - - symbolicSolution = subs(obj.sym(), cellstr(gridName), values); + end % for it = 1 : numel(values) + + % new domain is the old one without the domains that are evaluated at specific numeric + % values. Furthermore, the other domains are renamed. + newDomain = obj(1).domain.remove(gridName2Replace(isNumericValue)); + + if any(~isNumericValue) + % rename domains that are affected by symbolic substitution + gridName2ReplaceSymbolicOld = gridName2Replace(~isNumericValue); + gridName2ReplaceSymbolicNew = string(values(~isNumericValue)); - if isempty(symvar(symbolicSolution(:))) - % take the new values that are not numeric as the new - % variables: - newGridName = unique(newGridName, 'stable'); - newGrid = cell(1, numel(newGridName)); - charValues = cell(0); - for it = 1 : numel(values) - if isstring(values{it}) || ischar(values{it}) - charValues{end+1} = char(values{it}); - end - end - for it = 1 : numel(newGridName) - if ~isempty(charValues) && any(contains(charValues, newGridName{it})) - newGrid{it} = obj.gridOf(gridName( strcmp(newGridName{it}, values) )); + resultingName = [newDomain.name]; + for it = 1 : numel(gridName2ReplaceSymbolicOld) + resultingName(strcmp([newDomain.name], gridName2ReplaceSymbolicOld(it))) ... + = gridName2ReplaceSymbolicNew(it); + end % for it = 1 : numel(gridName2ReplaceSymbolicOld) + if misc.isunique(resultingName) + newDomain = newDomain.rename(gridName2ReplaceSymbolicNew, gridName2ReplaceSymbolicOld); + else % complicated case, for instance f(x, y).subs("x", "y") = f(y, y) + % for this, join is used. + for it = 1 : numel(gridName2ReplaceSymbolicNew) + if any(strcmp([newDomain.name], gridName2ReplaceSymbolicNew(it))) + % remove domain to be removed and join it with the single renamed domain. + newDomain = join(newDomain.remove(gridName2ReplaceSymbolicOld(it)), ... + newDomain.find(gridName2ReplaceSymbolicOld(it)).rename(... + gridName2ReplaceSymbolicNew(it))); else - newGrid{it} = obj.gridOf(newGridName{it}); - end - end - solution = quantity.Symbolic(double(symbolicSolution), ... - 'grid', newGrid, 'gridName', newGridName, 'name', obj(1).name); - else - % before creating a new quantity, it is checked that - % newGridName is unique. If there are non-unique - % gridName, multiple are removed and the finest grid - % from newGrid is taken. - uniqueGridName = unique(newGridName, 'stable'); - if numel(newGridName) == numel(uniqueGridName) - solution = quantity.Symbolic(symbolicSolution, ... - 'grid', newGrid, 'gridName', newGridName, 'name', obj(1).name); - else - uniqueGrid = cell(1, numel(uniqueGridName)); - for it = 1 : numel(uniqueGrid) - gridCandidatesTemp = newGrid(... - strcmp(uniqueGridName{it}, newGridName)); - for jt = 1 : numel(gridCandidatesTemp) - if ~isempty(uniqueGrid{it}) - assert(uniqueGrid{it}(1) == gridCandidatesTemp{jt}(1) ... - && uniqueGrid{it}(end) == gridCandidatesTemp{jt}(end), ... - 'grids must have same domain'); - end - if isempty(uniqueGrid{it}) || ... - numel(gridCandidatesTemp{jt}) > numel(uniqueGrid{it}) - uniqueGrid{it} = gridCandidatesTemp{jt}; - end - end + newDomain = newDomain.rename(... + gridName2ReplaceSymbolicNew(it), gridName2ReplaceSymbolicOld(it)); end - solution = quantity.Symbolic(symbolicSolution, ... - 'grid', uniqueGrid, 'gridName', uniqueGridName, 'name', obj(1).name); - end + end % for it = 1 : numel(gridName2ReplaceSymbolicNew) + end + + % convert elements of values to symbolic expressions + for it = 1 : numel(values) + if ~isnumeric(values{it}) + values{it} = sym(values{it}); + end + end % for it = 1 : numel(values) + end + + if numel(newDomain) > 0 + solution = quantity.Symbolic(... + subs(obj.sym(), sym(gridName2Replace), values), ... + newDomain, ... + 'name', obj(1).name); + else + solution = double(subs(obj.sym(), sym(gridName2Replace), values)); end end % subs() @@ -488,8 +443,8 @@ classdef Symbolic < quantity.Function obj(it).variable, varNew(s)), varNew(0)==ic); end solution = quantity.Symbolic(symbolicSolution, ... - 'gridName', {myParser.Results.newGridName, 'ic'}, ... - 'grid', {myParser.Results.variableGrid, myParser.Results.initialValueGrid}, ... + [quantity.Domain(myParser.Results.newGridName, myParser.Results.variableGrid), ... + quantity.Domain('ic', myParser.Results.initialValueGrid)], ... 'name', "solve(" + obj(1).name + ")"); end % solveDVariableEqualQuantity() @@ -540,6 +495,19 @@ classdef Symbolic < quantity.Function P = prod(quantity.Discrete(obj), dim); end end % prod() + + function P = power(obj, p) + % a.^p elementwise power + P = quantity.Symbolic(power(obj.sym(), p), obj(1).domain, ... + "name", obj(1).name + ".^{" + num2str(p) + "}"); + end % power() + + function P = mpower(obj, p) + % Matrix power a^p is matrix or scalar a to the power p. + P = quantity.Symbolic(mpower(obj.sym(), p), obj(1).domain, ... + "name", obj(1).name + "^{" + num2str(p) + "}"); + end % mpower() + function d = det(obj) % det(X) returns the the determinant of the squre matrix X if isempty(obj) @@ -710,6 +678,22 @@ classdef Symbolic < quantity.Function 'name', "expm(" + obj(1).name + ")"); end % expm() + function y = log(obj) + % log Natural logarithm. + % log(X) is the natural logarithm of the elements of X. + % Complex results are produced if X is not positive. + y = quantity.Symbolic(log(obj.sym()), obj(1).domain, ... + 'name', "log(" + obj(1).name + ")"); + end % log() + + function y = log10(obj) + % log10 Common (base 10) logarithm. + % log10(X) is the base 10 logarithm of the elements of X. + % Complex results are produced if X is not positive. + y = quantity.Symbolic(log10(obj.sym()), obj(1).domain, ... + 'name', "log10(" + obj(1).name + ")"); + end % log10() + function y = ctranspose(obj) % ctranspose() or ' is the complex conjugate tranpose y = quantity.Symbolic(conj(obj.sym().'), obj(1).domain, ... @@ -846,65 +830,31 @@ classdef Symbolic < quantity.Function P = quantity.Symbolic(zeros(valueSize), varargin{:}); end - function [p, q, parameters] = inputNormalizer(a, b) - - parameters = []; - - function s = innerInputNormalizer(a) - if isa(a, 'double') - s.name = num2str(a(:)'); - s.value = a; - % s.variable = []; - elseif isa(a, 'sym') - s.name = char(a); - s.value = a; - % s.variable = []; - elseif isa(a, 'quantity.Symbolic') - s.name = a(1).name; - s.value = a.sym(); - % s.variable = a.variable; - parameters = struct(a); - parameters = rmfield(parameters, 'gridName'); - end - end - - p = innerInputNormalizer(a); - q = innerInputNormalizer(b); - - end - end + end % methods (Static) methods (Static, Access = protected) function var = getVariable(symbolicFunction, nVar) + % nVar is needed for overloaded functionality in PolynomialOperator if misc.issym(symbolicFunction(:).') var = symvar(symbolicFunction(:).'); - else - var = []; - end - - if isempty(var) - % var = quantity.Symbolic.defaultSymVar; - else - - if var(1) ~= quantity.Symbolic.defaultSymVar + if ~isempty(var) && ~strcmp(string(var(1)), "z") % fast solution to order the spatial and temporal variable % in the common order: (z, t) var = flip(var); end + else + var = []; end - end - function doNotCopy = doNotCopyPropertiesName() - parentalProperties = quantity.Discrete.doNotCopyPropertiesName; - doNotCopy = {parentalProperties{:}, ... - 'valueSymbolic', 'isConstant'}; - end - function f = setValueContinuous(f, symVar) + end % getVariable() + + function [f, argList] = setValueContinuous(f, symVar) % converts symVar into a matlabFunction. + argList = string(symVar); if isa(f, 'sym') if isempty(symvar(f)) fDouble = double(f); - f = @(varargin) fDouble + quantity.Function.zero(varargin{:}); + f = eval("@(" + argList + ") fDouble + quantity.Function.zero(varargin{:})"); else if iscell( symVar ) symVar = [symVar{:}]; @@ -914,5 +864,5 @@ classdef Symbolic < quantity.Function end end end % setValueContinuous() - end -end + end % methods (Static, Access = protected) +end % classdef diff --git a/+signals/+faultmodels/TaylorPolynomial.m b/+signals/+faultmodels/TaylorPolynomial.m index d451e9b9e72eb5b671f1eb77a1955beb8431c005..a98619af394b66dd586fa76b9d344db04da496a4 100644 --- a/+signals/+faultmodels/TaylorPolynomial.m +++ b/+signals/+faultmodels/TaylorPolynomial.m @@ -79,8 +79,7 @@ classdef TaylorPolynomial < quantity.Discrete occurrence); obj@quantity.Discrete( val, ... - 'gridName', {'t'}, ... - 'grid', {myGlobalGrid}, ... + quantity.Domain("t", myGlobalGrid), ... 'name', ['f_' num2str(index)]); % put all the values to local variables @@ -128,12 +127,11 @@ classdef TaylorPolynomial < quantity.Discrete end function monomials = getMonomials(degree, t, tStar, myLocalGrid, T) monomials = quantity.Symbolic.zeros([degree, 1], ... - 'domain', quantity.Domain.empty(), ... + quantity.Domain.empty(), ... 'name', 'varphi_k'); for k = 1:degree monomials(k, 1) = quantity.Symbolic((t-tStar)^(k-1), ... - 'gridName', cellfun(@char, {t, tStar}, 'UniformOutput', false), ... - 'grid', {myLocalGrid, T}, ... + [quantity.Domain(char(t), myLocalGrid), quantity.Domain(char(tStar), T)], ... 'name', 'varphi_k'); end end diff --git a/+signals/BasicVariable.m b/+signals/BasicVariable.m index de37d3850c89f4013f76549accff985d302920b5..f259d2625d13e4c91fc2302db8ba635022b0f43d 100644 --- a/+signals/BasicVariable.m +++ b/+signals/BasicVariable.m @@ -1,39 +1,239 @@ classdef BasicVariable < handle - %BasicVariable Summary of this class goes here - % Detailed explanation goes here + %BasicVariable Class to handle quantities with a priori known derivatives + % This class is used to simplify the usage of quantities for which a lot of computations must be + % done on its derivatives. So a BasicVariable consists of a basic function and its derivatives, + % which are precomputed and safed. So computations can be performed without recalculation of the + % derivatives. properties ( SetAccess = protected ) - derivatives cell; - fun quantity.Discrete; + derivatives cell; + fun (1,1) quantity.Discrete; end - methods + properties ( Dependent ) + highestDerivative; % order of the highest computed derivative + T; % the transition time T + dt; % step size + end + + methods function obj = BasicVariable(fun, derivatives) obj.fun = fun; obj.derivatives = derivatives; end + function h = get.highestDerivative(obj) + h = numel(obj(1).derivatives); + end + function T = get.T(obj) + T = obj.domain.upper; + end + function dt = get.dt(obj) + if isa(obj.domain, "quantity.EquidistantDomain") + dt = obj.domain.stepSize; + else + delta_t = diff(obj.domain.grid); + assert( all( diff( delta_t ) < 1e-15 ), "Grid is not equidistant spaced" ); + dt = delta_t(1); + end + end end methods ( Access = public) - function D = diff(obj, k, ~) - % DIFF get the k-th derivative from saved derivatives + function D = diff(obj, domain, n) + % DIFF get the k-th derivative + % D = diff(obj, domain, n) tries to find the n-th derivative of obj.fun in the saved + % derivatives. If it does not exist, it tries to compute it with the evaluateFunction. + % If n is a non integer number, the Riemann-Liouville fractional derivative is + % computed. For this, a formula is used which requires the next integer + 1 derivative + % of the function to avoid the singularity under the integral. Actually, the domain is + % clear, but it is used hear to be consistent with the quantity.Discrete/diff. arguments obj - k double {mustBeInteger} = 0; - ~; + domain (1,1) quantity.Domain; + n (1,1) double = 0; + end + + for l = 1:numel(obj) + + assert( domain.isequal( obj(l).domain ),... + "The derivative wrt. this domain is not possible.") + + if n == 0 + D(l) = obj(l).fun; + + elseif ~numeric.near(round(n), n) + % fractional derivative: + D(l) = obj(l).fDiff(n); + + elseif n > length(obj(l).derivatives) + % For some functions their maybe a possibility to compute new derivatives on the + % fly. For this, the evaluateFunction(k) can be used, where k is the order of + % the derivative. + D(l) = obj(l).evaluateFunction(obj.domain, n); + obj(l).derivatives{end+1} = D(l); + else + % use the pre-computed derivatives + D(l) = obj(l).derivatives{n}; + end + end + D = reshape(D, size(obj)); + end + + function D = diffs(obj, k) + % DIFFS compute multiple derivatives + % D = diffs(obj, k) computes the k-th derivatives of OBJ. At this, k can be a vector + % of derivative orders. + arguments + obj, + k (:,1) double {mustBeInteger, mustBeNonnegative} = 0:numel(obj(1).derivatives); end + D_ = arrayfun( @(i) obj.diff(obj.domain, i), k, 'UniformOutput', false); - if k == 0 - D = obj.fun; - elseif k > length(obj.derivatives) - error(['Requested derivative ' num2str(k) ' is not available']); + for i = 1:numel(k) + D(i,:) = D_{i}; + end + end + + + + function h = productRule(obj, phi, n) + % LEIBNIZ compute the k-derivative of the product of the basic variable and phi + % h = productRule(obj, phi, n) computes the k-th derivative of obj.fun*phi, i.e., + % h = d_t^n ( obj.fun(t) * phi ) + % using the product rule. This is usefull if the derivatives of the basic variable obj + % are known exactly and the derivatives of phi, but not the multiplication + % obj.diff(0)*phi. + t = phi.domain; + h = quantity.Discrete.zeros( size(obj), t ); + + if numeric.near(round(n), n) + % for integer order derivatives + for k = 0:n + h = h + misc.binomial(n, k) * obj.diff(t, n-k) * phi.diff(t, k); + end else - D = obj.derivatives{k}; + % for fractional order derivatives: [see Podlubny] + % p \~~ oo / p \ (k) p-k + % D (phi(t) f(t)) = > | | | phi (t) D f(t) + % a t /__ k=0 \ k / a t + h = misc.binomial( n, 0 ) * phi * obj.fDiff(n); + h0 = h; + k = 1; + % do the iteration on the loop until the difference is very small. + while max( abs( h0 ) ) / max( abs( h ) ) > 1e-9 + h0 = misc.binomial(n,k) * phi.diff(t, k) * obj.fDiff( n - k); + h = h + h0; + k = k+1; + end end end + function d = domain(obj) - d = obj.fun(1).domain; + d = obj(1).fun(1).domain; end - end -end \ No newline at end of file + + function plot(obj, varargin) + F = quantity.Discrete(obj); + F.plot(varargin{:}); + end + + function F = quantity.Discrete(obj) + F (1,:) = [obj.fun]; + + for i = 1:max([obj.highestDerivative]) + + for j = 1:numel(obj) + F_i(j) = obj(j).derivatives{i}; + end + + F(i+1,:) = reshape(F_i, size(obj)); + end + end + + function c = mtimes(a, b) + arguments + a double; + b signals.BasicVariable + end + + assert( size(a,2) == size(b,1), "dimensions of the terms for multiplication do not match") + F = quantity.Discrete(b); + + % do the multiplication for each derivative: + for i = 1:max([b.highestDerivative])+1 + tmp(i,:) = a * shiftdim(F(i,:)); + end + + % restore the result as signals.BasicVariable + for k = 1:numel(b) + for l = 2:size(tmp,1) + derivs_{l-1} = tmp(l,k); + end + c(k) = signals.BasicVariable( tmp(1,k), derivs_); + end + c = reshape(c, size(b)); + end + + end % methods (Access = public) + + methods (Access = protected) + function v = evaluateFunction(obj, domain, k) + error('conI:signals:BasicVariable:derivative', ['Requested derivative ' num2str(k) ' is not available']); + end + + function D = fDiff(obj, p) + % FDIFF compute the p-fractional derivative + % D = fDiff(obj, p) computes the fractional derivative of order p. To be + % concrete the Riemann-Liouville fractional derivative with initial point t0 = lower + % end of the considered time domain is computed. + % Because the basisc variable must be C^infinity function, we use the form + % of the fractional derivative described in eq. (2.80) in the book [Podlubny: Fractional + % differential equations]. Then, integration by parts is applied to eliminate the + % singularity: + % + + n = ceil(p); + tDomain = obj(1).domain; + t0 = tDomain.lower; + t = Discrete( tDomain ); + tauDomain = tDomain.rename( tDomain.name + "_Hat" ); + tau = Discrete( tauDomain ); + if p > 0 + + % TODO Try to implement the polynomial part as function or as symbolic + D = quantity.Discrete.zeros(size(obj), tDomain); + for k = 0:n + f_dk = obj.diff(tDomain, k); + + if f_dk.at(t0) ~= 0 + D = D + f_dk.at(t0) * (t - t0).^(k-p) / gamma( k-p + 1); + end + + end + assert( ~ any( isinf( obj.diff(tDomain, n+1).on() ) ), "conI:BasicVariable:fDiff", ... + "The function has a singularity! Hence, the fractional derivative can not be computed") + D = D + cumInt( ( t - tau ).^(n-p) * subs(obj.diff(tDomain, n+1), tDomain.name, tauDomain.name), ... + tauDomain, tauDomain.lower, tDomain.name) / gamma( n+1-p ); + + elseif p < 0 && p > -1 + alpha = -p; + D = (t - t0).^(alpha) / gamma( alpha + 1) * obj.diff(tDomain, 0).at(t0) + ... + cumInt( (t-tau).^alpha * subs(obj.diff(tDomain, 1), tDomain.name, tauDomain.name), ... + tauDomain, tauDomain.lower, tDomain.name) / gamma( alpha + 1 ); + + elseif p <= -1 + alpha = -p; + D = cumInt( (t-tau).^(alpha-1) * subs( obj.diff(tDomain, 0), tDomain.name, tauDomain.name), ... + tauDomain, tauDomain.lower, tDomain.name) / gamma( alpha ); + + elseif p == 0 + D = [obj.fun()]; + else + error("This should not happen") + end + end + + end + +end diff --git a/+signals/GevreyFunction.m b/+signals/GevreyFunction.m index bc1b546485e4678d3e5941ff3c0679e8943b1bd0..49ae6323fd7f7d41f40fcd8537a727b79019d96e 100644 --- a/+signals/GevreyFunction.m +++ b/+signals/GevreyFunction.m @@ -1,47 +1,91 @@ -classdef GevreyFunction < quantity.BasicVariable - %UNTITLED Summary of this class goes here - % Detailed explanation goes here +classdef GevreyFunction < signals.BasicVariable + %GEVREYFUNCTION - + % Class to generate a gevrey function and its derivative. The general function has the form + % + % / 0 : t <= 0 + % | + % f(t) = | f0 + K * h(t) : 0 < t < T + % | + % \ 0 : t >= T + % + % with the additional property int_0^T h(t) = 1. properties % Gevrey-order - order double = -1; + order (1,1) double = 1.99; + % the constant gain + gain double = 1; + % the offset: + offset (1,1) double = 0; + % the number of derivatives to be considered + numDiff (1,1) double = 5; + % underlying gevery function + g; + % derivative shift + diffShift (1,1) double = 0; end properties (Dependent) + % the exponent of the exponential function sigma; end methods - function obj = GevreyFunction(varargin) + function obj = GevreyFunction(optArgs) + % GEVREYFUNCTION initialization of a gevrey function as signals.BasicVariable + % obj = GevreyFunction(optArgs) creates a gevrey function object. The properties can + % be set by name-value-pairs and are "order", "gain", "offset", "numDiff", + % "timeDomain", "g", "diffShift". + arguments + optArgs.order (1,1) double = 1.99; + optArgs.gain double = 1; + optArgs.offset (1,1) double = 0; + optArgs.numDiff (1,1) double = 5; + optArgs.timeDomain (1,1) quantity.Domain = quantity.Domain.defaultDomain(100, "t"); + optArgs.g = @signals.gevrey.bump.g; + optArgs.diffShift (1,1) double = 0; + end - if nargin == 0 - g = []; - else - g = @signals.gevrey.bump.g; + % generate the underlying gevrey functions: + f_derivatives = cell(optArgs.numDiff + 1, 1); + for k = 0:optArgs.numDiff + f_derivatives{k+1} = ... + signals.GevreyFunction.evaluateFunction_p(optArgs, optArgs.timeDomain, k); end - obj@quantity.BasicVariable(g, varargin{:}); - if nargin > 0 - prsr = misc.Parser(); - prsr.addParameter('order', 1.5 ); - prsr.parse(varargin{:}); - obj.order = prsr.Results.order; + + obj@signals.BasicVariable(f_derivatives{1}, f_derivatives(2:end)); + + % set the parameters to the object properties: + for k = fieldnames(rmfield(optArgs, "timeDomain"))' + obj.(k{:}) = optArgs.(k{:}); end + end - - function s = get.sigma(obj) s = 1 ./ ( obj.order - 1); end function set.sigma(obj, s) obj.order = 1 + 1/s; end - + end + + methods (Static, Access = private) + function v = evaluateFunction_p(optArgs, domain, k) + + derivativeOrder = k + optArgs.diffShift; + % The offset is only added to the zero-order derivative. + offset_k = optArgs.offset * 0^derivativeOrder; + + v = quantity.Function(@(t) offset_k + optArgs.gain * ... + optArgs.g(t, derivativeOrder, ... + domain.upper, 1 ./ ( optArgs.order - 1)), domain); + end end methods (Access = protected) - function v = evaluateFunction(obj, grid) - v = obj.K * obj.valueContinuous(grid, obj.diffShift, obj.T, obj.sigma); + function v = evaluateFunction(obj, domain, k) + v = signals.GevreyFunction.evaluateFunction_p(obj, domain, k); end end end diff --git a/+signals/PolyBump.m b/+signals/PolyBump.m index a7087598e4a1072348bd9401b90290040eb5eef6..e841e98328bb737c5950b04ed5f99faa92183c33 100644 --- a/+signals/PolyBump.m +++ b/+signals/PolyBump.m @@ -40,7 +40,7 @@ classdef PolyBump < quantity.Function fun = signals.PolyBump.polyBumpFunction(a, b, c, T0, T1); - obj@quantity.Function(fun, 'domain', t); + obj@quantity.Function(fun, t); obj.a = a; obj.b = b; diff --git a/+signals/PolynomialOperator.m b/+signals/PolynomialOperator.m index 43c45824587e28b3f5acaffb8015e013b800bbea..1558cb401cdee65e5d03ded4a173bc97c8b27d16 100644 --- a/+signals/PolynomialOperator.m +++ b/+signals/PolynomialOperator.m @@ -69,13 +69,13 @@ classdef PolynomialOperator < handle & matlab.mixin.Copyable for k = 1 : numel(A) if isnumeric(A{k}) obj(k).coefficient = ... - quantity.Discrete(A{k}, 'domain', prsr.Results.domain); + quantity.Discrete(A{k}, prsr.Results.domain); else obj(k).coefficient = A{k}; %#ok<AGROW> end end elseif isnumeric(A) - obj(1).coefficient = quantity.Discrete(A, 'domain', prsr.Results.domain); + obj(1).coefficient = quantity.Discrete(A, prsr.Results.domain); end [obj.s] = deal(s); @@ -200,7 +200,7 @@ classdef PolynomialOperator < handle & matlab.mixin.Copyable c = cell(1, size(C, 3)); for k = 1:size(C, 3) c{k} = quantity.Discrete(double(C(:,:,k)), ... - 'gridName', {}, 'grid', {}, 'name', "adj(" + obj(1).coefficient(1).name + ")"); + quantity.Domain.empty(), 'name', "adj(" + obj(1).coefficient(1).name + ")"); end C = signals.PolynomialOperator(c); @@ -214,7 +214,7 @@ classdef PolynomialOperator < handle & matlab.mixin.Copyable c = cell(1, size(C, 3)); for k = 1:size(C, 3) c{k} = quantity.Discrete(double(C(:,:,k)), ... - 'domain', quantity.Domain.empty(), ... + quantity.Domain.empty(), ... 'name', "det(" + A(1).coefficient(1).name + ")"); end @@ -363,10 +363,9 @@ classdef PolynomialOperator < handle & matlab.mixin.Copyable end f = quantity.Discrete(... misc.fundamentalMatrix.odeSolver_par( A_full, spatialDomain ), ... - 'grid', {spatialDomain, spatialDomain}, 'gridName', {'z', 'zeta'}); + [quantity.Domain("z", spatialDomain), quantity.Domain("zeta", spatialDomain)]); - b = quantity.Discrete(B_full, 'grid', spatialDomain, ... - 'gridName', 'zeta'); + b = quantity.Discrete(B_full, quantity.Domain("zeta", spatialDomain)); p = cumInt(f * b, 'zeta', spatialDomain(1), 'z'); z0 = A(1).grid{1}(1); @@ -489,6 +488,7 @@ classdef PolynomialOperator < handle & matlab.mixin.Copyable end function newOperator = changeGrid(obj, gridNew, gridNameNew) + warning("DEPRICATED: Use subs instead") newOperator = signals.PolynomialOperator(obj.M.changeGrid(gridNew, gridNameNew)); end diff --git a/+signals/RandomStairs.m b/+signals/RandomStairs.m index 9dab4c4b4601c8bde95133ac715258754830da49..2d593f8d0d3fee7e9a6bbd874499ef7f85de9b9d 100644 --- a/+signals/RandomStairs.m +++ b/+signals/RandomStairs.m @@ -98,11 +98,9 @@ classdef RandomStairs end if optArg.smooth - q = quantity.Discrete(obj.smoothSignal(timeDomain.grid), ... - 'domain', timeDomain, 'name', obj.name); + q = quantity.Discrete(obj.smoothSignal(timeDomain.grid), timeDomain, 'name', obj.name); else - q = quantity.Discrete(obj.signal(timeDomain.grid), ... - 'domain', timeDomain, 'name', obj.name); + q = quantity.Discrete(obj.signal(timeDomain.grid), timeDomain, 'name', obj.name); end end diff --git a/+signals/SignalModel.m b/+signals/SignalModel.m index b46488429ab24461989808b18e626cb8ba1ebe09..8e8916bac3e4ff454b74f9995c0a1e1aac03511c 100644 --- a/+signals/SignalModel.m +++ b/+signals/SignalModel.m @@ -128,8 +128,8 @@ classdef SignalModel result.v( t >= obj(i).occurrence, : ) = sol.v; end - r = [r; quantity.Discrete(result.r, 'domain', optArg.time, 'size', [obj(i).n_p, 1])]; - v = [v; quantity.Discrete(result.v, 'domain', optArg.time, 'size', [obj(i).n_v, 1])]; + r = [r; quantity.Discrete(result.r, optArg.time, 'size', [obj(i).n_p, 1])]; + v = [v; quantity.Discrete(result.v, optArg.time, 'size', [obj(i).n_v, 1])]; end diff --git a/+signals/TimeDelayOperator.m b/+signals/TimeDelayOperator.m index 449f5312cbf9e8d4e463e8ac2d56e4d8e30289d8..ac7a86bb9654a3e9eb4721a4cdf766ef4f1800ff 100644 --- a/+signals/TimeDelayOperator.m +++ b/+signals/TimeDelayOperator.m @@ -83,7 +83,7 @@ classdef TimeDelayOperator < handle & matlab.mixin.Copyable end function t = t(obj) - t = quantity.Symbolic(sym(obj.timeDomain.name), 'domain', obj.timeDomain); + t = quantity.Symbolic(sym(obj.timeDomain.name), obj.timeDomain); end function d = diag(obj) @@ -129,7 +129,7 @@ classdef TimeDelayOperator < handle & matlab.mixin.Copyable end qinf = quantity.Discrete(infValue, ... - 'domain', namedArgs.spatialDomain); + namedArgs.spatialDomain); o = signals.TimeDelayOperator(qinf, timeDomain, 'isZero', true); end end diff --git a/+signals/WhiteGaussianNoise.asv b/+signals/WhiteGaussianNoise.asv deleted file mode 100644 index 20d0bce1383cb17e84238449fe095dc29ae93a73..0000000000000000000000000000000000000000 --- a/+signals/WhiteGaussianNoise.asv +++ /dev/null @@ -1,78 +0,0 @@ -classdef WhiteGaussianNoise - %WhiteGaussianNoise - - properties - sigma; - mue = 0; - end - - properties (Dependent) - variance; - end - - - methods - function obj = WhiteGaussianNoise(sigma) - obj.sigma = sigma; - end - - function s = get.variance(obj) - s = obj.sigma^2; - end - - function o = noise(obj, t) - o = randn(size(t)) * sqrt(obj.variance); - end - - function plotProbabilityDistributionFunction(obj, noise) - histogram(noise); - end - - function p = probabilityDensityFunction(obj, varargin) - - prsr = misc.Parser(); - prsr.addParameter('grid', linspace(- 5* obj.sigma, 5 * obj.sigma)'); - prsr.parse(varargin{:}); - - x = sym('x', 'real'); - p = quantity.Symbolic( 1 / sqrt( 2 * pi ) / obj.sigma * ... - exp( - (x - obj.mue)^2 / 2 / obj.sigma^2 ), ... - 'grid', prsr.Results.grid, ... - 'variable', x); - end - - function P = cumulativeDistributionFunction(obj, varargin) - p = obj.probabilityDensityFunction(varargin{:}); - P = p.cumInt('x', p.grid{1}(1), 'x'); - end - - function p = probability(obj, z) - % computes the probability, that a value is inside the domain - % abs(x) < z - - P = obj.cumulativeDistributionFunction; - - p = 2 * P.at(abs(z - obj.mue)) - 1; - - end - function - %CALCULATETREHSHOLDFORPROBABILITY computes the threshold for fault -%detection for a given probability detection -% -% fB = calculateProbabilityforDetection(X, MUE, SIGMA), calculate the -% treshold fB for a correct detection of the fault given the probability for -% detection X. Based on a normal distributed random variable with probability -% function P for a mean value MUE and a standard deviation SIGMA. -% The probability X is computed by -% P(|f(t)| <= fB) = P(-fB < f(t) <= fB) - P( |f(t)| <= fB) -% = F(fB) - F(-fB) -% F(fB) is the cumulative distribution function for a normal distribution -% with mean mue and sigma X = F(fB) = 0.5(1+erf((fB-mu)/(sqrt(2)*sigma))) -% Solving for fB with the inverse erf function and erf(-x) = -erf(x) yields -% fB = sqrt(2) * sigma * erfinv(X) + mue - -fB = sqrt(2)*sigma*erfinv(X) + mue; - - end -end - diff --git a/+signals/WhiteGaussianNoise.m b/+signals/WhiteGaussianNoise.m index dce235f8fb6cff495ac0c5747d4afa0af21d72de..0c33d8c8691ad95689f8139abf8cd49c12c54a5f 100644 --- a/+signals/WhiteGaussianNoise.m +++ b/+signals/WhiteGaussianNoise.m @@ -89,16 +89,14 @@ classdef WhiteGaussianNoise prsr.parse(varargin{:}); x = sym('x', 'real'); + xDomain = quantity.Domain("x", prsr.Results.grid); - p = quantity.Symbolic(zeros(size(obj)), ... - 'grid', prsr.Results.grid, 'variable', x); + p = quantity.Symbolic(zeros(size(obj)), xDomain); for i = 1:numel(obj) p(i) = quantity.Symbolic( 1 / sqrt( 2 * pi ) / obj(i).sigma * ... - exp( - (x - obj(i).mue)^2 / 2 / obj(i).sigma^2 ), ... - 'grid', prsr.Results.grid, ... - 'variable', x); + exp( - (x - obj(i).mue)^2 / 2 / obj(i).sigma^2 ), xDomain); end end diff --git a/+signals/smoothStep.m b/+signals/smoothStep.m index f649d53a14f4358470664b80468f72d5cb795ed1..c2e7addd6bf0c766d2887649dc567850a131f008 100644 --- a/+signals/smoothStep.m +++ b/+signals/smoothStep.m @@ -10,6 +10,9 @@ arguments optArg.s0 = 0; optArg.s1 = 1; end +% + +warning("DEPRICATED: Use misc.ss.setPointChange instead"); assert( all( size(optArg.s0) == [deg, 1])) assert( all( size(optArg.s1) == [deg, 1])) @@ -19,6 +22,6 @@ o = zeros(1, deg); s = spline([optArg.domain.lower, optArg.domain.upper], ... [o optArg.s0.', optArg.s1.' o], optArg.domain.grid); -S = quantity.Discrete(s, 'domain', optArg.domain); +S = quantity.Discrete(s, optArg.domain); end \ No newline at end of file diff --git a/+unittests/+misc/+ss/testSs.m b/+unittests/+misc/+ss/testSs.m index 633f9f1c54c49613edd77aab148f58751c548f28..924a32d552143ac58021438e36d088370bb8cabc 100644 --- a/+unittests/+misc/+ss/testSs.m +++ b/+unittests/+misc/+ss/testSs.m @@ -4,12 +4,37 @@ function [tests] = testSs() tests = functiontests(localfunctions()); end +function testSetPointChange(testCase) + +time = quantity.Domain('t', linspace(log(2), 17 / 8)); +n = 5; + +phi0 = pi; +phi1 = exp(1); + +p = quantity.Symbolic( misc.ss.setPointChange(phi0, phi1, time.lower, time.upper, n), time); + +testCase.verifyEqual(p.at(time.lower), phi0, 'AbsTol', 1e-12); +testCase.verifyEqual(p.at(time.upper), phi1, 'AbsTol', 1e-10); + +for k = 1:n + p_k = diff(p, 't', k); + testCase.verifyEqual(p_k.at(time.lower), 0, 'AbsTol', 1e-7); + testCase.verifyEqual(p_k.at(time.upper), 0, 'AbsTol', 1e-7); +end + +p_np1 = diff(p, 't', n + 1); +testCase.verifyFalse( abs( p_np1.at(time.lower) ) < 1e-6); +testCase.verifyFalse( abs( p_np1.at(time.upper) ) < 1e-6); + +end % testSetPointChange + function testRelativeDegree(tc) mySs = ss([0, 1; 0, 0], [0, 1; 1, 0], [1, 0; 0, 1], []); tc.verifyEqual(misc.ss.relativeDegree(mySs), [2, 1; 1, NaN]); tc.verifyEqual(misc.ss.relativeDegree(mySs, "min"), 1); tc.verifyEqual(misc.ss.relativeDegree(mySs, "max"), 2); -end +end % testRelativeDegree function planPolynomialTrajectory(tc) %% @@ -139,19 +164,19 @@ end function testCombineInputSignals(tc) myStateSpace = ss(-1, [1, 2, 3], 1, [], 'InputName', {'control', 'disturbance', 'my.fault'}); t = quantity.Domain('t', linspace(0, 4, 201)); -disturbanceSignal = quantity.Symbolic(sin(sym('t')), 'domain', t, 'name', 'disturbance'); -faultSignal = quantity.Symbolic(sin(sym('t')), 'domain', t, 'name', 'my.fault'); +disturbanceSignal = quantity.Symbolic(sin(sym('t')), t, 'name', 'disturbance'); +faultSignal = quantity.Symbolic(sin(sym('t')), t, 'name', 'my.fault'); % case 1: only disturbance u = misc.ss.combineInputSignals(myStateSpace, t.grid, 'disturbance', disturbanceSignal); -y = quantity.Discrete(lsim(myStateSpace, u.on(), t.grid), 'domain', t, 'name', 'y'); +y = quantity.Discrete(lsim(myStateSpace, u.on(), t.grid), t, 'name', 'y'); odeResiduum = - y.diff("t") - y + 2*disturbanceSignal; tc.verifyEqual(odeResiduum.abs.median(), 0, 'AbsTol', 5e-4); % case 2: fault and disturbance u2 = misc.ss.combineInputSignals(myStateSpace, t.grid, 'disturbance', disturbanceSignal, ... 'my.fault', faultSignal); -y2 = quantity.Discrete(lsim(myStateSpace, u2.on(), t.grid), 'domain', t, 'name', 'y'); +y2 = quantity.Discrete(lsim(myStateSpace, u2.on(), t.grid), t, 'name', 'y'); odeResiduum = - y2.diff("t") - y2 + 2*disturbanceSignal + 3*faultSignal; tc.verifyEqual(odeResiduum.abs.median(), 0, 'AbsTol', 5e-4); end diff --git a/+unittests/+misc/testBackstepping.m b/+unittests/+misc/testBackstepping.m index 08033a9a0cf41af5f8d207288c7137e3be851773..5c549fb39e763cbbdbf976280ede8dd800f6e7f1 100644 --- a/+unittests/+misc/testBackstepping.m +++ b/+unittests/+misc/testBackstepping.m @@ -10,17 +10,17 @@ myGrid = linspace(0, 1, 31); myDomain = [quantity.Domain('z', myGrid), quantity.Domain('zeta', myGrid)]; kernelData = quantity.Symbolic(... [z^2/2, sin(z); 1/(2-z), -z] * magic(2) * [-zeta^2/2, sin(2*zeta); 1/(2-zeta), zeta], ... - 'domain', myDomain, 'name', 'K'); + myDomain, 'name', 'K'); % calculate derivatives symbolic on raw data domainSelector = quantity.Discrete(quantity.Symbolic(zeta<=z, ... - 'domain', myDomain, 'name', '')); + myDomain, 'name', '')); kernelData_dz = domainSelector * quantity.Symbolic(... diff([z^2/2, sin(z); 1/(2-z), -z] * magic(2) * [-zeta^2/2, sin(2*zeta); 1/(2-zeta), zeta], z), ... - 'domain', myDomain, 'name', 'K'); + myDomain, 'name', 'K'); kernelData_dzeta = domainSelector * quantity.Symbolic(... diff([z^2/2, sin(z); 1/(2-z), -z] * magic(2) * [-zeta^2/2, sin(2*zeta); 1/(2-zeta), zeta], zeta), ... - 'domain', myDomain, 'name', 'K'); + myDomain, 'name', 'K'); % set up kernel data in misc.Backstepping objects k = misc.Backstepping('kernelInverse', kernelData, ... @@ -40,7 +40,7 @@ myGrid = linspace(0, 1, 31); myDomain = [quantity.Domain('z', myGrid), quantity.Domain('zeta', myGrid)]; kernelData = quantity.Symbolic(... [z^2/2, sin(z); 1/(2-z), -z] * magic(2) * [-zeta^2/2, sin(2*zeta); 1/(2-zeta), zeta], ... - 'domain', myDomain, 'name', 'K'); + myDomain, 'name', 'K'); % set up kernel data in misc.Backstepping objects k(1) = misc.Backstepping('kernel', kernelData, ... @@ -68,10 +68,10 @@ for it = 1 : numel(k) end % compare to old implementation for int_0^z -myDomain = quantity.Discrete(quantity.Symbolic(zeta<z, 'domain', myDomain)); +myDomain = quantity.Discrete(quantity.Symbolic(zeta<z, myDomain)); Kref = quantity.Discrete(... misc.invertBacksteppingKernel(kernelData.on({myGrid, myGrid}, {'z', 'zeta'}), -1), ... - 'domain', [quantity.Domain('z', myGrid), quantity.Domain('zeta', myGrid)]); + [quantity.Domain('z', myGrid), quantity.Domain('zeta', myGrid)]); k1Diff = myDomain * (Kref - k(1).kernelInverse); k5Diff = myDomain * (Kref - k(5).kernel); testCase.verifyEqual(MAX(abs(k1Diff)), 0, 'AbsTol', 1e-5); diff --git a/+unittests/+misc/testMisc.m b/+unittests/+misc/testMisc.m index ab189e71d150885cf2d2aaf3f06ea57bbe778f75..6151c86686d862a980206705bab936e1a7797866 100644 --- a/+unittests/+misc/testMisc.m +++ b/+unittests/+misc/testMisc.m @@ -4,6 +4,35 @@ function [tests] = testMisc() tests = functiontests(localfunctions()); end +function testFunctionArguments(tc) + names = misc.functionArguments(@(z, zeta, t) z*zeta+t); + tc.verifyEqual(names, ["z"; "zeta"; "t"]); +end % testFunctionArguments() + +function testBinomial(testCase) + +for k = 1:3 + for n = k:k+3 + % test the integer order + b1 = nchoosek(n, k); + b2 = misc.binomial(n, k); + testCase.verifyEqual(b1, b2); + end +end + +% test the non integer case - example from wikipedia +testCase.verifyEqual( misc.binomial(2.5, 2), 1.875) +testCase.verifyEqual( misc.binomial(-1, 4), (-1)^4) + +for k = -1:3 + % test non integer over zero + testCase.verifyEqual( misc.binomial( k*0.3, 0), 1); + % test non integer over one + testCase.verifyEqual( misc.binomial( k*0.3, 1), k * 0.3, 'AbsTol', 1e-16); +end + +end + function testUnique4cells(tc) Moriginal = {zeros(2), 0, rand(3), [0, 1; 0, 0], "asdf", 0, 'asdf', [0, 1; 0, 0], eps, eps}; Mexpected = {zeros(2), 0, Moriginal{3}, [0, 1; 0, 0], "asdf", eps}; diff --git a/+unittests/+misc/testMultArray.m b/+unittests/+misc/testMultArray.m index 810df6bf86f8565408a42dd742d78e1fae7dc2ab..b896e3ffb25d1b41a6e12ad489d7cb2c1db73c6c 100644 --- a/+unittests/+misc/testMultArray.m +++ b/+unittests/+misc/testMultArray.m @@ -121,11 +121,11 @@ end function test3variables(testCase) -z = linspace(0, pi, 7); -t = linspace(0, pi, 5); -v = 0:pi/2:3*pi/2; -[z, t] = ndgrid(z, t); -[v, t2] = ndgrid(v, t(1,:)); +zDom = quantity.Domain("z", linspace(0, pi, 7)); +tDom = quantity.Domain("t", linspace(0, pi, 5)); +vDom = quantity.Domain("v", 0:pi/2:3*pi/2); +[z, t] = ndgrid(zDom.grid, tDom.grid); +[v, t2] = ndgrid(vDom.grid, t(1,:)); syms V T Z bb = [cos(Z), 0; 0, sin(T)]; @@ -154,12 +154,10 @@ bc = misc.multArray(b, c, 4, 3, 2); bc = permute(bc, [ 1 2 4 3 5 ]); testCase.verifyTrue(numeric.near(bc, bbcc)); - -B = quantity.Discrete(b, 'size', [ 2, 2 ], 'gridName', {'z', 't'}, 'grid', {z(:,1), t(1,:)}); -C = quantity.Discrete(c, 'size', [ 2, 3 ], 'gridName', {'v', 't'}, 'grid', {v(:,1), t2(1,:)}); +B = quantity.Discrete(b, [zDom, tDom]); +C = quantity.Discrete(c, [vDom, tDom]); BC = B*C; -bcon = BC.on(); testCase.verifyTrue(numeric.near(bc, BC.on())); diff --git a/+unittests/+misc/testSolveVolterraIntegroDifferentialIVP.m b/+unittests/+misc/testSolveVolterraIntegroDifferentialIVP.m index 7bde4c0b73965f8bb8ce38f083c7b1aedb5ae35d..dc29fa361353c1a9f4b164d72378571edf76d7ed 100644 --- a/+unittests/+misc/testSolveVolterraIntegroDifferentialIVP.m +++ b/+unittests/+misc/testSolveVolterraIntegroDifferentialIVP.m @@ -17,11 +17,11 @@ classdef testSolveVolterraIntegroDifferentialIVP < matlab.unittest.TestCase methods(TestClassSetup) function tc = initData(tc) tc.zeta = quantity.Domain("zeta", tc.z.grid); - tc.M = quantity.Symbolic([1+sym("z"), sin(sym("z")); 0, 2], 'domain', tc.z); - tc.A = quantity.Symbolic([1+sym("z"), 0; -sym("z")^2, 2], 'domain', tc.z); - tc.B = quantity.Symbolic([1+sym("z"), 0, 0.1; 0.2, 0, 2; -1, 0.5, 0.5], 'domain', tc.z); - tc.C = quantity.Symbolic([1+sym("z"), 0, -1; 0, 2, -1], 'domain', tc.z); - tc.D = quantity.Symbolic([1+sym("z"), sym("z")*sym("zeta"); sym("zeta"), 2], 'domain', [tc.z, tc.zeta]); + tc.M = quantity.Symbolic([1+sym("z"), sin(sym("z")); 0, 2], tc.z); + tc.A = quantity.Symbolic([1+sym("z"), 0; -sym("z")^2, 2], tc.z); + tc.B = quantity.Symbolic([1+sym("z"), 0, 0.1; 0.2, 0, 2; -1, 0.5, 0.5], tc.z); + tc.C = quantity.Symbolic([1+sym("z"), 0, -1; 0, 2, -1], tc.z); + tc.D = quantity.Symbolic([1+sym("z"), sym("z")*sym("zeta"); sym("zeta"), 2], [tc.z, tc.zeta]); tc.N = misc.solveVolterraIntegroDifferentialIVP(tc.z, tc.ic, ... 'M', tc.M, 'A', tc.A, 'B', tc.B, 'C', tc.C, 'D', tc.D); end diff --git a/+unittests/+numeric/femMatricesTest.m b/+unittests/+numeric/femMatricesTest.m index 7ac8fe8232f3f0c4c805a25ee5de2b4384f6bd77..447e93b279720b6f6eb608229aa0424fc220cff7 100644 --- a/+unittests/+numeric/femMatricesTest.m +++ b/+unittests/+numeric/femMatricesTest.m @@ -3,11 +3,46 @@ tests = functiontests(localfunctions); end function mTest(testCase) -[M] = numeric.femMatrices('grid', linspace(0,1,3)); -M_ = [1/6, 1/12 0; ... - 1/12, 1/3, 1/12; ... - 0, 1/12, 1/6]; +spatialDomain = quantity.EquidistantDomain("z", 0, 1, 'stepNumber', 3); +I = quantity.Discrete.ones(1, spatialDomain); +[M, D2, L, P] = numeric.femMatrices(spatialDomain, ... + 'alpha', quantity.Discrete.ones(1, spatialDomain), ... + 'b', I, 'c', I); + +[~, D1] = numeric.femMatrices(spatialDomain, ... + 'beta', quantity.Discrete.ones(1, spatialDomain)); + +[~, D0] = numeric.femMatrices(spatialDomain, ... + 'gamma', quantity.Discrete.ones(1, spatialDomain)); + + + +M_ = spatialDomain.stepSize / 6 * ... + [2, 1, 0; ... + 1, 4, 1; ... + 0, 1, 2]; + +D2_ = 1 / spatialDomain.stepSize * ... + [-1 1 0; ... + 1 -2 1; ... + 0 1 -1]; + +D1_ = 1 / 2 * ... + [-1 1 0; ... + -1 0 1; ... + 0 -1 1]; + +D0_ = spatialDomain.stepSize / 6 * ... + [2, 1, 0; ... + 1, 4, 1; ... + 0, 1, 2]; + testCase.verifyEqual( M, M_, 'AbsTol', 10*eps ); +testCase.verifyEqual( D2, D2_, 'AbsTol', 10*eps); +testCase.verifyEqual( D1, D1_, 'AbsTol', 10*eps); +testCase.verifyEqual( D0, D0_, 'AbsTol', 5e-5); +testCase.verifyEqual( L, M_ * I.on(), 'AbsTol', 10*eps); +testCase.verifyEqual( P, I.on()' * M_, 'AbsTol', 10*eps); end \ No newline at end of file diff --git a/+unittests/+quantity/testBasicVariable.m b/+unittests/+quantity/testBasicVariable.m deleted file mode 100644 index daeca9c876dfcac1170fd05d59ee63556dd164e7..0000000000000000000000000000000000000000 --- a/+unittests/+quantity/testBasicVariable.m +++ /dev/null @@ -1,25 +0,0 @@ -function [tests] = testGevrey() -%TESTGEVREY Summary of this function goes here -% Detailed explanation goes here -tests = functiontests(localfunctions); -end - -function testBasicVariableInit(testCase) - -%% -t = linspace(0, 1, 11)'; -g1 = signals.GevreyFunction('order', 1.8, 't', t); -g2 = signals.GevreyFunction('order', 1.5, 't', t); - -G = [g1; g2]; -N_diff = 2; -derivatives = G.diff(0:N_diff); - -GD = zeros(length(t), N_diff+1, 2); -for k = 1:N_diff+1 - GD(:, k, 1) = signals.gevrey.bump.g(t, k-1, t(end), g1.sigma); - GD(:, k, 2) = signals.gevrey.bump.g(t, k-1, t(end), g2.sigma); -end - -testCase.verifyEqual(derivatives.on(), GD); -end \ No newline at end of file diff --git a/+unittests/+quantity/testDiscrete.m b/+unittests/+quantity/testDiscrete.m index becc80275197e369379b46853205d52b5d5af274..29945255d0d0da590bf163f05230491119109922 100644 --- a/+unittests/+quantity/testDiscrete.m +++ b/+unittests/+quantity/testDiscrete.m @@ -4,6 +4,56 @@ function [tests] = testDiscrete() tests = functiontests(localfunctions); end +function testEigScalar(tc) +z = quantity.Domain("z", linspace(-1, 1, 11)); +[Vz, Dz, Wz] = z.Discrete.eig(); +tc.verifyEqual(z.grid, Dz.on()); +tc.verifyEqual(ones(z.n, 1), Vz.on()); +tc.verifyEqual(ones(z.n, 1), Wz.on()); +end % testEigScalar() + +function testEigMat(tc) +z = quantity.Domain("z", linspace(-1, 1, 11)); +zSym = sym("z"); +quan = quantity.Discrete(quantity.Symbolic([1+zSym^2, 2; -zSym, zSym], z)); +% all output arguments +[Vq, Dq, Wq] = quan.eig(); +tc.verifyEqual(diag(eig(quan.at(0.4))), Dq.at(0.4), "AbsTol", 1e-15); +tc.verifyEqual(on(quan * Vq), on(Vq * Dq), "AbsTol", 1e-15); +tc.verifyEqual(on(Wq' * quan), on(Dq * Wq'), "AbsTol", 1e-15); +% 2 output arguments +[Vq, Dq] = quan.eig(); +tc.verifyEqual(diag(eig(quan.at(0.4))), Dq.at(0.4), "AbsTol", 1e-15); +tc.verifyEqual(on(quan * Vq), on(Vq * Dq), "AbsTol", 1e-15); +% 1 output argument +Eq = quan.eig(); +tc.verifyEqual(eig(quan.at(0.4)), Eq.at(0.4), "AbsTol", 1e-15); +tc.verifyEqual(on(Eq), on(Dq.diag2vec()), "AbsTol", 1e-15); +end % testEigMat() + +function testPower(tc) +%% scalar case +z = quantity.Domain("z", linspace(0, 1, 11)); +A = quantity.Discrete(sin(z.grid * pi), z); +aa = sin(z.grid * pi) .* sin(z.grid * pi); +AA = A.^2; + +tc.verifyEqual( aa, AA.on()); +tc.verifyEqual( on(A.^0), ones(z.n, 1)); +tc.verifyEqual( on(A.^3), on(AA * A), 'AbsTol', 1e-15); + +%% matrix case +zeta = quantity.Domain("zeta", linspace(0, 1, 21)); +B = [0*z.Discrete + zeta.Discrete, A + 0*zeta.Discrete; ... + z.Discrete+zeta.Discrete, A * subs(A, "z", "zeta")]; +BBB = B.^3; +BBB_alt = B .* B .* B; +tc.verifyEqual(BBB.on(), BBB_alt.on(), 'AbsTol', 1e-14); +tc.verifyEqual(BBB.at({0.5, 0.6}), (B.at({0.5, 0.6})).^3, 'AbsTol', 1e-15); +tc.verifyEqual(on(B.^0), ones([B(1).domain(1).n, B(1).domain(2).n, 2, 2]), ... + 'AbsTol', 1e-15); +end % testPower() + function setupOnce(testCase) syms z zeta t sy testCase.TestData.sym.z = z; @@ -12,6 +62,64 @@ testCase.TestData.sym.t = t; testCase.TestData.sym.sy = sy; end +function testNorm(tc) +z = quantity.Domain("z", linspace(0, 1, 11)); +quan = Discrete(z) * [-1; 2; 3]; +tc.verifyEqual(quan.norm.on(), on(z.Discrete*sqrt(1+4+9)), 'AbsTol', 1e-15); +tc.verifyEqual(quan.norm.on(), on(quan.norm(2)), 'AbsTol', 1e-15); +end + +function testLog(tc) +z = quantity.Domain("z", linspace(0, 1, 11)); +quan = Discrete(z) * ones(2, 2) + [1, 1; 0, 0]+0.1*ones(2,2); + +tc.verifyEqual(log(quan.on()), on(log(quan)), 'AbsTol', 1e-15); +tc.verifyEqual(on(quan), on(log(exp(quan))), 'AbsTol', 1e-15); +end % testLog() + +function testLog10(tc) +z = quantity.Domain("z", linspace(0, 1, 11)); +quan = Discrete(z) * ones(2, 2) + [1, 1; 0, 0]+0.1*ones(2,2); + +tc.verifyEqual(log10(quan.on()), on(log10(quan)), 'AbsTol', 1e-15); +end % testLog10() + +function powerTest(testCase) +t = quantity.Domain("t", linspace(0,1)); + +p(1,1) = Discrete(t); +p(2,1) = Discrete(t)^2; + +p2 = p.^2; + +testCase.verifyEqual(p2(1).on(), t.grid.^2) +testCase.verifyEqual(p2(2).on(), t.grid.^4, 'AbsTol', 1e-15) + +p25 = p.^2.5; + +testCase.verifyEqual(p25(1).on(), t.grid.^2.5); +testCase.verifyEqual(p25(2).on(), t.grid.^5, 'AbsTol', 1e-15); + +end + +function timesTest(testCase) + + t = quantity.Domain.defaultDomain(13, "t"); + + a(1) = quantity.Discrete(t.grid, t); + a(2) = quantity.Discrete(t.grid.^2, t); + a(3) = quantity.Discrete(t.grid.^3, t); + + p = a .* a; + + p_(1) = quantity.Discrete(t.grid.^2, t); + p_(2) = quantity.Discrete(t.grid.^4, t); + p_(3) = quantity.Discrete(t.grid.^6, t); + + testCase.verifyEqual( p.on(), p_.on(),'AbsTol', 1e-15) + +end + function testFindZeros(testCase) x = quantity.Domain('x', linspace(0,1)); @@ -480,14 +588,14 @@ end function testSqrt(testCase) % 1 spatial variable quanScalar1d = quantity.Discrete((linspace(0,2).^2).', quantity.Domain("z", linspace(0, 1)), ... - 'size', [1, 1], 'name', "s1d"); + 'name', "s1d"); quanScalarRoot = quanScalar1d.sqrt(); testCase.verifyEqual(quanScalarRoot.on(), linspace(0, 2).'); % 2 spatial variables quanScalar2d = quantity.Discrete((linspace(0, 2, 21).' + linspace(0, 2, 41)).^2, ... [quantity.Domain("z", linspace(0, 1, 21)), quantity.Domain("zeta", linspace(0, 1, 41))], ... - 'size', [1, 1], 'name', 's2d'); + 'name', 's2d'); quanScalar2dRoot = quanScalar2d.sqrt(); testCase.verifyEqual(quanScalar2dRoot.on(), (linspace(0, 2, 21).' + linspace(0, 2, 41))); @@ -504,10 +612,10 @@ end function testSqrtm(testCase) quanScalar1d = quantity.Discrete((linspace(0,2).^2).', quantity.Domain("z", linspace(0, 1)), ... - 'size', [1, 1], 'name', 's1d'); + 'name', 's1d'); quanScalar2d = quantity.Discrete((linspace(0, 2, 21).' + linspace(0, 2, 41)).^2, ... [quantity.Domain("z", linspace(0, 1, 21)), quantity.Domain("zeta", linspace(0, 1, 41))], ... - 'size', [1, 1], 'name', 's2d'); + 'name', 's2d'); % diagonal matrix - 1 spatial variable quanDiag = [quanScalar1d, 0*quanScalar1d; 0*quanScalar1d, (2)*quanScalar1d]; quanDiagRoot = quanDiag.sqrt(); % only works for diagonal matrices @@ -593,11 +701,16 @@ end function testSolveDVariableEqualQuantityConstant(testCase) %% simple constant case -quan = quantity.Discrete(ones(3, 1), quantity.Domain("z", linspace(0, 1, 3)), ... - 'size', [1, 1], 'name', 'a'); +quan = quantity.Discrete(ones(3, 1), quantity.Domain("z", linspace(0, 1, 3))); solution = quan.solveDVariableEqualQuantity(); [referenceResult1, referenceResult2] = ndgrid(linspace(0, 1, 3), linspace(0, 1, 3)); testCase.verifyEqual(solution.on(), referenceResult1 + referenceResult2, 'AbsTol', 10*eps); +% %% 2x2 constant case +%Lambda = quantity.Discrete(quantity.Symbolic([1+sym("z")^2; sym("z")-2], quantity.Domain("z", linspace(0, 1, 101)))); +%Lambda = quantity.Discrete(quantity.Symbolic([2; -1], quantity.Domain("z", linspace(0, 1, 11)))); +%solution = Lambda.solveDVariableEqualQuantity(); +%[referenceResult1, referenceResult2] = ndgrid(linspace(0, 1, 3), linspace(0, 1, 3)); +%testCase.verifyEqual(solution.on(), referenceResult1 + referenceResult2, 'AbsTol', 10*eps); end function testSolveDVariableEqualQuantityNegative(testCase) @@ -624,8 +737,8 @@ function testSolveDVariableEqualQuantityComparedToSym(testCase) z = testCase.TestData.sym.z; Z = quantity.Domain("z", linspace(0, 1, 5)); assume(z>0 & z<1); -quanBSym = quantity.Symbolic(1+z, Z, 'name', 'bSym', 'gridName', {'z'}); -quanBDiscrete = quantity.Discrete(quanBSym.on(), Z, 'name', 'bDiscrete', 'size', size(quanBSym)); +quanBSym = quantity.Symbolic(1+z, Z); +quanBDiscrete = quantity.Discrete(quanBSym.on(), Z); solutionBSym = quanBSym.solveDVariableEqualQuantity(); solutionBDiscrete = quanBDiscrete.solveDVariableEqualQuantity(); %solutionBSym.plot(); solutionBDiscrete.plot(); @@ -655,9 +768,9 @@ end function testMtimesDifferentGridLength(testCase) %% a = quantity.Discrete(ones(11, 1), quantity.Domain("z", linspace(0, 1, 11)), ... - 'size', [1, 1], 'name', 'a'); + 'name', 'a'); b = quantity.Discrete(ones(5, 1), quantity.Domain("z", linspace(0, 1, 5)), ... - 'size', [1, 1], 'name', 'b'); + 'name', 'b'); ab = a*b; % z = testCase.TestData.sym.z; @@ -907,7 +1020,6 @@ vInv = v.inv(); %% matrix example t = quantity.Domain("t", linspace(0, pi)); - [T, ~] = ndgrid(t.grid, s.grid); K = quantity.Discrete(permute(repmat(2*eye(2), [1, 1, size(T)]), [3, 4, 1, 2]), [t, s], ... @@ -1116,7 +1228,7 @@ bc = permute(bc, [ 1 2 4 3 5 ]); B = quantity.Discrete(b, [Z, T], 'size', [ 2, 2 ]); C = quantity.Discrete(c, [V, T], 'size', [2, 3]); BC = B*C; -testCase.verifyTrue(numeric.near(bc, BC.on)); +testCase.verifyTrue(numeric.near(bc, BC.on())); %% z = linspace(0,1).'; @@ -1148,13 +1260,13 @@ function testOnConstant(testCase) A = [0 1; 2 3]; const = quantity.Discrete(A, quantity.Domain.empty); -C = const.on([1:5]); +C = const.on(1:1:5); for k = 1:numel(A) testCase.verifyTrue( all( A(k) == C(:,k) )); end -end +end % testOnConstant() function testIsConstant(testCase) @@ -1191,21 +1303,28 @@ verifyTrue(testCase, numeric.near(CA.on(), ca, 1e-12)); end -function testMPower(testCase) - -%% -z = linspace(0,1).'; -a = sin(z * pi); - -A = quantity.Discrete({a}, quantity.Domain("z", z)); - -aa = sin(z * pi) .* sin(z * pi); +function testMPower(tc) +%% scalar case +z = quantity.Domain("z", linspace(0, 1, 11)); +A = quantity.Discrete(sin(z.grid * pi), z); +aa = sin(z.grid * pi) .* sin(z.grid * pi); AA = A^2; -verifyTrue(testCase, numeric.near(aa, AA.on())); - - -end +tc.verifyEqual( aa, AA.on()); +tc.verifyEqual( on(A^0), ones(z.n, 1)); +tc.verifyEqual( on(A^3), on(AA * A)); + +%% matrix case +zeta = quantity.Domain("zeta", linspace(0, 1, 21)); +B = [0*z.Discrete + zeta.Discrete, A + 0*zeta.Discrete; ... + z.Discrete+zeta.Discrete, A * subs(A, "z", "zeta")]; +BBB = B^3; +BBB_alt = B * B * B; +tc.verifyEqual(BBB.on(), BBB_alt.on(), 'AbsTol', 1e-14); +tc.verifyEqual(BBB.at({0.5, 0.6}), (B.at({0.5, 0.6}))^3, 'AbsTol', 1e-15); +tc.verifyEqual(on(B^0), permute(repmat(eye(2), [1, 1, B(1).domain(1).n, B(1).domain(2).n]), [3, 4, 1, 2]), ... + 'AbsTol', 1e-15); +end % testMPower() function testPlus(testCase) %% @@ -1223,9 +1342,9 @@ bzZeta = cos(zGrid * pi) + cos(zetaGrid * pi); Z = quantity.Domain("z", z); Zeta = quantity.Domain("zeta", zeta); -AB = quantity.Discrete({a, b}, Z.copyAndRename("blub")); +AB = quantity.Discrete({a, b}, Z.rename("blub")); A = quantity.Discrete({a}, Z); -ALr = quantity.Discrete({aLr}, Zeta.copyAndRename("z")); +ALr = quantity.Discrete({aLr}, Zeta.rename("z")); B = quantity.Discrete({b}, Z); C = quantity.Discrete({C}, Zeta); AZZETA = quantity.Discrete({azZeta}, [Z, Zeta]); @@ -1292,7 +1411,7 @@ testCase.verifyEqual( on( TZX + XTZ ), on( 2 * TZX ), 'AbsTol', eps ) end -function testInit(testCase) +function testInit(tc) %% z = linspace(0,1).'; t = linspace(0,1,101); @@ -1306,20 +1425,18 @@ a = quantity.Discrete(v, [Z, T]); b = quantity.Discrete(V, [Z, T]); % -verifyTrue(testCase, misc.alln(a.on() == b.on())); +verifyTrue(tc, all(a.on() == b.on(), 'all')); %% -z = linspace(0,1,31).'; -zeta = linspace(0,1,51); - +zeta = T.grid; [zGrid, zetaGrid] = ndgrid(z, zeta); - Z = quantity.Domain("z", z); Zeta = quantity.Domain("zeta", zeta); -c = quantity.Discrete(zGrid, [Z, Zeta], 'name', 'a'); -d = quantity.Discrete(zetaGrid', [Z, Zeta], 'name', 'b'); -%TODO write test case, the last line should not work +c = quantity.Discrete(zGrid, [Z, Zeta], 'name', 'a'); % this should work +tc.verifyError(... + @() quantity.Discrete(zetaGrid.', [Z, Zeta], 'name', 'd'), ...% this should not work + ''); end @@ -1369,7 +1486,7 @@ v = rand([100, 42, 2, 3]); Z = quantity.Domain('z', linspace(0,1,100)); T = quantity.Domain('t', linspace(0,1,42)); V = quantity.Discrete( quantity.Discrete.value2cell(v, [100, 42], [2, 3]), [Z, T]); -verifyTrue(testCase, misc.alln(v == V.on())); +verifyTrue(testCase, all(v == V.on(), 'all')); end function testMtimesDifferentGrid(testCase) @@ -1508,6 +1625,13 @@ quan4dArbitrary = quan4d.subs({'z', 'zeta', 'eta', 'beta'}, {'zeta', 'beta', 'z' testCase.verifyEqual(reshape(quan4d.on({1, 1, 1, 1}), size(quan4d)), ... reshape(quan4dArbitrary.on({1, 1, 1}), size(quan4dAllEta))); +% test a substution where a quantity.EquidistantDomain is used: +e = quantity.EquidistantDomain("t", 0, 1, "stepSize", 0.1); +q = Discrete(e); +p = q.subs(e, "z"); +testCase.verifyEqual( p.domain.name, "z") +testCase.verifyEqual( on( q.subs(e, z.grid) ), q.on(z.grid) ) + end diff --git a/+unittests/+quantity/testDomain.m b/+unittests/+quantity/testDomain.m index 11ee1963e5db1e7dbf3a4d522e3f796a5a34a554..197e367c4f5bed581279357e15f2590ad1487bf7 100644 --- a/+unittests/+quantity/testDomain.m +++ b/+unittests/+quantity/testDomain.m @@ -18,9 +18,34 @@ tc.TestData.d = d; end -function testParser(testCase) - -%profile on +function testDiscrete(tc) +d = quantity.Domain("a", linspace(0, 1, 3)); +tc.verifyEqual(d.Discrete.on(), d.grid); +end % testDiscrete + +function testSymbolic(tc) +d = quantity.Domain("a", linspace(0, 1, 3)); +tc.verifyEqual(d.Symbolic.on(), d.grid); +end % testDiscrete + +function testRename(tc) +d = [quantity.Domain("a", linspace(0, 1, 3)), ... + quantity.Domain("b", linspace(0, 2, 5)), ... + quantity.Domain("ab", linspace(0, pi, 4))]; +xyz = d.rename(["x", "y", "z"]); +xyz2 = d.rename(["z", "y", "x"], ["ab", "b", "a"]); +tc.verifyEqual([xyz.name], ["x", "y", "z"]); +tc.verifyEqual([xyz.name], [xyz2.name]); + +cba = d.rename(["c", "b", "a"], ["a", "b", "ab"]); +tc.verifyEqual([cba.name], ["c", "b", "a"]); +tc.verifyEqual([cba.n], [d.n]); + +zZetaDomain = [quantity.Domain("z", linspace(0, 1, 3)), quantity.Domain("zeta", linspace(0, 1, 3))]; +tc.verifyEqual([zZetaDomain.rename("a", "z").name], ["a", "zeta"]) +end % testRename() + +function testParser(tc)%profile on d = quantity.Domain.parser('blub', 1, 'grid', 1:3, 'gridName', 'test'); a = quantity.Domain.parser('domain', d); b = quantity.Domain.parser('blabla', 0, 'domain', a); @@ -29,47 +54,46 @@ b = quantity.Domain.parser('blabla', 0, 'domain', a, 'blub', 'a'); %profile viewer -testCase.verifyEqual(d, a); -testCase.verifyEqual(b, a); +tc.verifyEqual(d, a); +tc.verifyEqual(b, a); end -function testSplit(testCase) +function testSplit(tc) a = quantity.Domain('a', -5:15 ); b = a.split([-1, 0, 4]); -testCase.verifyEqual(b(1).grid', -5:-1) -testCase.verifyEqual(b(2).grid', -1:0) -testCase.verifyEqual(b(3).grid', 0:4) -testCase.verifyEqual(b(4).grid', 4:15) +tc.verifyEqual(b(1).grid', -5:-1) +tc.verifyEqual(b(2).grid', -1:0) +tc.verifyEqual(b(3).grid', 0:4) +tc.verifyEqual(b(4).grid', 4:15) -testCase.verifyWarning(@() a.split([0.1, 2.6]), 'DOMAIN:split') -testCase.verifyWarningFree(@() a.split([0.1, 2.6], 'AbsTol', 0.4), 'DOMAIN:split') +tc.verifyWarning(@() a.split([0.1, 2.6]), 'DOMAIN:split') +tc.verifyWarningFree(@() a.split([0.1, 2.6], 'AbsTol', 0.4), 'DOMAIN:split') c = a.split([0.1, 2.6], 'warning', false); -testCase.verifyEqual(c(1).grid', -5:0) -testCase.verifyEqual(c(2).grid', 0:3) -testCase.verifyEqual(c(3).grid', 3:15) +tc.verifyEqual(c(1).grid', -5:0) +tc.verifyEqual(c(2).grid', 0:3) +tc.verifyEqual(c(3).grid', 3:15) end -function testContains(testCase) +function testContains(tc) a = quantity.Domain('a', 0:10); b = quantity.Domain('b', 3:5); -testCase.verifyTrue( a.contains(b) ) -testCase.verifyTrue( a.contains(1) ) -testCase.verifyFalse( a.contains(11) ) -testCase.verifyFalse( a.contains(1.5) ) +tc.verifyTrue( a.contains(b) ) +tc.verifyTrue( a.contains(1) ) +tc.verifyFalse( a.contains(11) ) +tc.verifyFalse( a.contains(1.5) ) A = [quantity.Domain('a1', 0:10), quantity.Domain('a2', 0:10)]; -testCase.verifyTrue( A.contains(5) ) - +tc.verifyTrue( A.contains(5) ) end @@ -98,6 +122,15 @@ tc.verifyEqual( d.find([a.name b.name]), [a b]) end +function testRemove(tc) +d = tc.TestData.d; +dWithoutA = d.remove("a"); +tc.verifyEqual([dWithoutA.name], ["b" "c"]); + +a = d.remove(dWithoutA); +tc.verifyEqual(a.name, "a"); +end + function testGet(tc) z = linspace(0, pi, 3); diff --git a/+unittests/+quantity/testEquidistantDomain.m b/+unittests/+quantity/testEquidistantDomain.m index cb0b05ee38a4aae9cd926b904aaf25938a8e84eb..981f819a43ce6a288a1b7aad998889d01d3afa6f 100644 --- a/+unittests/+quantity/testEquidistantDomain.m +++ b/+unittests/+quantity/testEquidistantDomain.m @@ -40,4 +40,17 @@ EE(1:2) = quantity.Domain(); EE(1) = e; EE(2) = d; +end + +function testSubs(testCase) + +f = quantity.Discrete((1:5)', quantity.EquidistantDomain("t", 0, 1, "stepNumber", 5)); +testCase.verifyEqual( f.subs("t", 1), f.on(1)) +testCase.verifyEqual( f.changeGrid(quantity.Domain("t", linspace(0,1))).on(), linspace(1,5)', 'AbsTol', 1e-15); + +f = quantity.Symbolic(sym("t"), quantity.EquidistantDomain("t", 0, 1, "stepNumber", 5)); +testCase.verifyEqual( f.subs("t", 1), f.on(1)) +testCase.verifyEqual( f.changeGrid(quantity.Domain("t", linspace(0,1))).on(), linspace(0,1)', 'AbsTol', 1e-15); + + end \ No newline at end of file diff --git a/+unittests/+quantity/testFunction.m b/+unittests/+quantity/testFunction.m index c476cf957282700ae272f3ea18e0c3b373f7d073..966ae75b9b5dfa0fdd9e2c8dbb716820002a1b4c 100644 --- a/+unittests/+quantity/testFunction.m +++ b/+unittests/+quantity/testFunction.m @@ -4,6 +4,17 @@ function [tests] = testFunction() tests = functiontests(localfunctions); end +function testAt(tc) +z = quantity.Domain("z", linspace(0, 1, 11)); +f = quantity.Function({@(z)sin(z), @(z)1+z; @(z)cos(z), @(z)z.^2}, z); +fZZeta = quantity.Function({@(z,zeta)sin(zeta)+z, @(z,zeta)1+zeta-z; @(z,zeta)cos(z), @(z,zeta)zeta.^2}, ... + [z, z.rename("zeta")]); +fDisc = quantity.Discrete(f); +tc.verifyLessThan(abs(f.at({0.2}) - fDisc.at({0.2})), 10*eps); +fZZetaDisc = quantity.Discrete(fZZeta); +tc.verifyLessThan(abs(fZZeta.at({0.2, 0.4}) - fZZetaDisc.at({0.2, 0.4})), 10*eps); +end % testAt() + function testInt(testCase) z = quantity.Domain("z", linspace(0,1)); diff --git a/+unittests/+quantity/testSymbolic.m b/+unittests/+quantity/testSymbolic.m index f5d7ca4f8f812e85c7381b4651d254931bc236b9..c378c5dc5bd39a892feddaa5d84d9ce75a3b2c02 100644 --- a/+unittests/+quantity/testSymbolic.m +++ b/+unittests/+quantity/testSymbolic.m @@ -4,6 +4,99 @@ function [tests ] = testSymbolic() tests = functiontests(localfunctions()); end +function testValueContinuousBug(tc) +z = quantity.Domain("z", linspace(0, 1, 7)); +zeta = quantity.Domain("zeta", linspace(0, 1, 5)); +A = quantity.Symbolic(sin(sym("z") * pi), z); +B1 = [0*z.Symbolic + zeta.Symbolic, A + 0*zeta.Symbolic; ... + z.Symbolic+zeta.Symbolic, A * subs(A, "z", "zeta")]; +B2 = [zeta.Symbolic + 0*z.Symbolic, A + 0*zeta.Symbolic; ... + z.Symbolic+zeta.Symbolic, A * subs(A, "z", "zeta")]; +tc.verifyEqual(B1.on([z, zeta]), B2.on([z, zeta])); + +C1 = [0*z.Symbolic + zeta.Symbolic, A + 0*zeta.Symbolic]; +c1 = zeta.Symbolic + 0*z.Symbolic; +c2 = A + 0*zeta.Symbolic; +C2 = [c1, c2]; +tc.verifyEqual(C1.on([z, zeta]), C2.on([z, zeta])); + +% TODO: fix this error! See issue #41 +% this problem is maybe caused as input arguments of B2(1).valueContinuous is somehow ordered +% differently compared to B2(2:4).valueContinuous or all B(:).valueContinuous, +% i.e. B2(1).valueContinuous is (zeta, z) instead of (z, zeta). +end % testValueContinuousBug() + +function testPower(tc) +%% scalar case +z = quantity.Domain("z", linspace(0, 1, 11)); +A = quantity.Symbolic(sin(sym("z") * pi), z); +aa = sin(z.grid * pi) .* sin(z.grid * pi); +AA = A.^2; + +tc.verifyEqual( aa, AA.on()); +tc.verifyEqual( on(A.^0), ones(z.n, 1)); +tc.verifyEqual( on(A.^3), on(AA * A), 'AbsTol', 1e-15); + +%% matrix case +zeta = quantity.Domain("zeta", linspace(0, 1, 21)); +B = [0*z.Symbolic + zeta.Symbolic, A + 0*zeta.Symbolic; ... + z.Symbolic+zeta.Symbolic, A * subs(A, "z", "zeta")]; +BBB = B.^3; +BBB_alt = B .* B .* B; +tc.verifyEqual(BBB.on(), BBB_alt.on(), 'AbsTol', 1e-14); +tc.verifyEqual(BBB.at({0.5, 0.6}), (B.at({0.5, 0.6})).^3, 'AbsTol', 1e-15); +tc.verifyEqual(on(B.^0), ones([B(1).domain(1).n, B(1).domain(2).n, 2, 2]), ... + 'AbsTol', 1e-15); +end % testPower() + +function testMPower(tc) +%% scalar case +z = quantity.Domain("z", linspace(0, 1, 11)); +A = quantity.Symbolic(sin(sym("z") * pi), z); +aa = sin(z.grid * pi) .* sin(z.grid * pi); +AA = A^2; + +tc.verifyEqual( aa, AA.on()); +tc.verifyEqual( on(A^0), ones(z.n, 1)); +tc.verifyEqual( on(A^3), on(AA * A)); + +%% matrix case +zeta = quantity.Domain("zeta", linspace(0, 1, 21)); +B = [0*z.Symbolic + zeta.Symbolic, A + 0*zeta.Symbolic; ... + z.Symbolic+zeta.Symbolic, A * subs(A, "z", "zeta")]; +B_discrete = quantity.Discrete(B); +BBB = B^3; +BBB_alt = B * B * B; +BBB_discrete = B_discrete^3; +tc.verifyEqual(BBB.on(), BBB_alt.on(), 'AbsTol', 1e-14); +tc.verifyEqual(BBB.at({0.5, 0.6}), (B.at({0.5, 0.6}))^3, 'AbsTol', 1e-15); +tc.verifyEqual(BBB_discrete.at({0.5, 0.6}), (B.at({0.5, 0.6}))^3, 'AbsTol', 1e-15); +tc.verifyEqual(on(B^0), permute(repmat(eye(2), [1, 1, B(1).domain(1).n, B(1).domain(2).n]), [3, 4, 1, 2]), ... + 'AbsTol', 1e-15); +end % testMPower() + +function testNorm(tc) +z = quantity.Domain("z", linspace(0, 1, 11)); +quan = Symbolic(z) * [-1; 2; 3]; +tc.verifyEqual(quan.norm.on(), on(z.Discrete*sqrt(1+4+9)), 'AbsTol', 1e-15); +tc.verifyEqual(quan.norm.on(), on(quan.norm(2)), 'AbsTol', 1e-15); +end % testNorm() + +function testLog(tc) +z = quantity.Domain("z", linspace(0, 1, 11)); +quan = Symbolic(z) * ones(2, 2) + [1, 1; 0, 0]+0.1*ones(2,2); + +tc.verifyEqual(log(quan.on()), on(log(quan)), 'AbsTol', 1e-15); +tc.verifyEqual(on(quan), on(log(exp(quan))), 'AbsTol', 1e-15); +end % testLog() + +function testLog10(tc) +z = quantity.Domain("z", linspace(0, 1, 11)); +quan = Symbolic(z) * ones(2, 2) + [1, 1; 0, 0]+0.1*ones(2,2); + +tc.verifyEqual(log10(quan.on()), on(log10(quan)), 'AbsTol', 1e-15); +end % testLog10() + function testDet(tc) z = quantity.Domain("z", linspace(0, 1, 11)); zSymbolic = quantity.Symbolic(sym("z"), z); @@ -230,10 +323,10 @@ syms z zeta eta e a myGrid = linspace(0, 1, 7); myDomain(1) = quantity.Domain("z", myGrid); -myDomain(2) = myDomain(1).copyAndRename("zeta"); -myDomain(3) = myDomain(1).copyAndRename("eta"); -myDomain(4) = myDomain(1).copyAndRename("e"); -myDomain(5) = myDomain(1).copyAndRename("a"); +myDomain(2) = myDomain(1).rename("zeta"); +myDomain(3) = myDomain(1).rename("eta"); +myDomain(4) = myDomain(1).rename("e"); +myDomain(5) = myDomain(1).rename("a"); obj = quantity.Symbolic([z*zeta, eta*z; e*a, a], myDomain); @@ -470,7 +563,7 @@ function testZero(testCase) x = quantity.Symbolic(0, quantity.Domain.empty()); verifyEqual(testCase, x.on(), 0); -verifyTrue(testCase, misc.alln(on(x) * magic(5) == zeros(5))); +verifyTrue(testCase, all(on(x) * magic(5) == zeros(5), 'all')); end @@ -494,7 +587,7 @@ d = [quantity.Domain('z', linspace(0, 1, 5)), ... x = quantity.Symbolic(sin(z * t * pi), d); o = x / x; -verifyTrue(testCase, misc.alln(o.on == 1)); +verifyTrue(testCase, all(o.on() == 1, 'all')); % test constant values invsin = 1 / ( x + 8); @@ -627,7 +720,7 @@ b = quantity.Symbolic(v, myDom2); c = quantity.Symbolic(v, myDom2); %% -verifyTrue(testCase, misc.alln(b.on() == c.on())); +verifyTrue(testCase, all(b.on() == c.on(), 'all')); end function testPlus(testCase) @@ -868,13 +961,23 @@ solution = f.solveAlgebraic([1, 1], f(1).gridName{1}); testCase.verifyEqual(solution, 0.5); end +function testSubs3(tc) + zZeta = [quantity.Domain("z", linspace(0, 1, 11)), quantity.Domain("zeta", linspace(0, 1, 11))]; + Id = quantity.Symbolic([sym("z"), 1; 2 sym("zeta")], zZeta); + Id_z1 = quantity.Symbolic([1, 1; 2 sym("zeta")], zZeta.find("zeta")); + Id_zetaZ = quantity.Symbolic( [sym("zeta"), 1; 2 sym("z")], zZeta([2 1])); + + tc.verifyEqual(Id.subs("z", 1).on(), Id_z1.on(), 'AbsTol', 10*eps); + tc.verifyEqual( Id.subs({"z", "zeta"}, {"zeta", "z"}).on(), Id_zetaZ.on()) +end % testSubs3() + function testSubs2(tc) z = quantity.Domain("z", linspace(0, 1, 11)); Id = quantity.Symbolic(eye(2), z); tc.verifyEqual(Id.on(), Id.subs("z", "zeta").on(), 'AbsTol', 10*eps); end % testSubs2() -function testSubs(testCase) +function testSubs(tc) %% % init syms x y z w @@ -901,9 +1004,9 @@ Z0 = quantity.Domain('z', 0); fOfz = f.subs([X, Y], [Z Z]); fOfyx = f.subs({'x', 'y'}, {'w', 'x'}); -testCase.verifyEqual(Ff1, fOf1); -testCase.verifyEqual(Ffz.sym(), fOfz.sym()); -testCase.verifyEqual(Fyx.sym(), fOfyx.sym()); +tc.verifyEqual(Ff1, fOf1); +tc.verifyEqual(Ffz.sym(), fOfz.sym()); +tc.verifyEqual(Fyx.sym(), fOfyx.sym()); %% syms z zeta @@ -911,28 +1014,15 @@ assume(z>0 & z<1); assume(zeta>0 & zeta<1); F = quantity.Symbolic(zeros(1), ... [quantity.Domain.defaultDomain(3, "z"), quantity.Domain.defaultDomain(3, "zeta")]); Feta = F.subs('z', 'eta'); -testCase.verifyEqual(Feta(1).gridName, ["eta", "zeta"]); +tc.verifyEqual(Feta(1).gridName, ["eta", "zeta"]); %% fMessy = quantity.Symbolic(x^1*y^2*z^4*w^5, [X, Y, Z, W]); fMessyA = fMessy.subs({'x', 'y', 'z', 'w'}, {'a', 'a', 'a', 'a'}); fMessyYY = fMessy.subs({'w', 'x', 'z'}, {'xi', 'y', 'y'}); -testCase.verifyEqual(fMessyA.gridName, "a"); -testCase.verifyEqual(fMessyA.grid, {linspace(0, 1, 11)'}); -testCase.verifyEqual(fMessyYY.gridName, ["y", "xi"]); -testCase.verifyEqual(fMessyYY.grid, {linspace(0, 1, 11)', linspace(0, 1, 3)'}); -% % sub multiple numerics -> not implemented yet -% f11 = f.subs('x', [1; 2]); -% f1 = f.subs('x', 1); -% f2 = f.subs('x', 2); -% testCase.verifyEqual(reshape(f11(1,:,:).sym(), size(f)), fOf1); -% testCase.verifyEqual(reshape(f11(2,:,:).sym(), size(f)), f2.sym()); -% -% % sub multiple multiple variables -% f1232 = f.subs({'x', 'y'}, {[1; 2], [3; 2]}); -% f13 = f.subs({'x', 'y'}, {1, 3}); -% f22 = f.subs({'x', 'y'}, {2, 2}); -% testCase.verifyEqual(reshape(f1232(1,:,:), size(f)), f22); -% testCase.verifyEqual(reshape(f1232(2,:,:), size(f)), f13); -end +tc.verifyEqual(fMessyA.gridName, "a"); +tc.verifyEqual(fMessyA.grid, {linspace(0, 1, 11)'}); +tc.verifyEqual(fMessyYY.gridName, ["y", "xi"]); +tc.verifyEqual(fMessyYY.grid, {linspace(0, 1, 11)', linspace(0, 1, 3)'}); +end % testSubs() diff --git a/+unittests/+signals/signalsTest.m b/+unittests/+signals/signalsTest.m index 7fbced81d578400c443fb413aa5808a168ee142b..6980acce85b50c7b60eafcf1d046b302ef2effab 100644 --- a/+unittests/+signals/signalsTest.m +++ b/+unittests/+signals/signalsTest.m @@ -21,10 +21,10 @@ d = 1; sig = 3 / d^2 * z^2 - 2 / d^3 * z^3; Z = quantity.Domain('z', linspace(0, 1, 101)); -SI = quantity.Symbolic(sig, 'domain', Z); +SI = quantity.Symbolic(sig, Z); % compute step by function -si = signals.smoothStep(1, 'domain', Z); +si = quantity.Symbolic( misc.ss.setPointChange(0, 1, 0, 1, 1, "var", Z.name), Z ); % testCase.verifyEqual(si.on(), SI.on(), 'AbsTol', 6e-16); @@ -36,14 +36,212 @@ function testGevreyBump(testCase) % signals. g = signals.GevreyFunction('diffShift', 1); -b = signals.gevrey.bump.dgdt_1(g.T, g.sigma, g.grid{1}) * 1 / signals.gevrey.bump.dgdt_0(g.T, g.sigma, g.T); +b = signals.gevrey.bump.dgdt_1(g.T, g.sigma, g.domain.grid) * 1 / signals.gevrey.bump.dgdt_0(g.T, g.sigma, g.T); %% Test scaling of Gevrey function -verifyEqual(testCase, b, g.on(), 'AbsTol', 1e-13); +verifyEqual(testCase, b, g.fun.on(), 'AbsTol', 1e-13); % test the derivatives of the bump g = signals.GevreyFunction('diffShift', 0); -g.diff(0:5); + +orders = 1:5; +derivatives = arrayfun(@(i) g.diff(g.domain, i), orders); +verifyEqual(testCase, [derivatives.at(0), derivatives.at(g.T)], zeros( 1, 2*length(orders)), 'AbsTol', 1e-13) + +end + +function testBasicVariableInit(testCase) + +%% +t = quantity.Domain("t", linspace(0, 1, 11)'); +g1 = signals.GevreyFunction('order', 1.8, 'timeDomain', t); +g2 = signals.GevreyFunction('order', 1.5, 'timeDomain', t); + +G = [g1; g2]; +N_diff = 2; +derivatives = G.diffs(0:N_diff); + +GD = zeros(t.n, N_diff+1, 2); +for k = 1:N_diff+1 + GD(:, k, 1) = signals.gevrey.bump.g(t.grid, k-1, t.grid(end), g1.sigma); + GD(:, k, 2) = signals.gevrey.bump.g(t.grid, k-1, t.grid(end), g2.sigma); +end + +testCase.verifyEqual(derivatives.on(), GD); +end + +function basicVariable_Diff_test(tc) + t = sym('t'); + T = quantity.Domain('t', linspace(0,1)); + fun = t^2 * (1-t)^2; + + F = quantity.Symbolic(fun, T); + d1F = F.diff("t", 1); + d2F = F.diff("t", 2); + + b(1,:) = signals.BasicVariable(F, {d1F, d2F}); + b(2,:) = signals.BasicVariable(F, {d1F, d2F}); + + tc.verifyEqual(b.diff(T, 0), [F; F]); + tc.verifyEqual(b.diff(T, 1).on(), on(d1F * ones(2,1))); + tc.verifyEqual(b.diff(T, 2).on(), on(d2F * ones(2,1))); + tc.verifyError(@() b.diff(T, 3), 'conI:signals:BasicVariable:derivative') +end + +function testGevreyFunction(testCase) + +N = 3; +tau = quantity.Domain.defaultDomain(42, "tau"); +g(1,1) = signals.GevreyFunction('order', 1.5, 'gain', -5, 'offset', 2.5, 'numDiff', N, ... + 'timeDomain', tau, 'diffShift', 0); + +g(2,1) = signals.GevreyFunction('order', 1.9, 'gain', 5, 'offset',- 2.5, 'numDiff', N, ... + 'timeDomain', tau, 'diffShift', 0); + +testCase.verifyEqual(g.diff(tau, 0).at(0), [2.5; -2.5]) +testCase.verifyEqual(g.diff(tau, 0).at(1), [-2.5; 2.5]) + +% test all the precomputed derivatives plus one higher derivative +for k = 1:N+1 + testCase.verifyEqual(g.diff(tau, k).at(0), zeros(2,1)) + testCase.verifyEqual(g.diff(tau, k).at(1), zeros(2,1)) +end + +end + +function testGevreyFunctionTimes(testCase) + +N = 3; +tau = quantity.Domain.defaultDomain(42, "tau"); +g(1,1) = signals.GevreyFunction('order', 1.5, 'gain', -5, 'offset', 2.5, 'numDiff', N, ... + 'timeDomain', tau, 'diffShift', 0); + +g(2,1) = signals.GevreyFunction('order', 1.9, 'gain', 5, 'offset',- 2.5, 'numDiff', N, ... + 'timeDomain', tau, 'diffShift', 0); + +A = [1 2; 3 4]; + +b = A*g; +testCase.verifyEqual(b.diff(tau, 0).at(0), A * [2.5; -2.5]) +testCase.verifyEqual(b(1).highestDerivative, g(1).highestDerivative) + +end + +function testBasicVariableFractionalDerivative(testCase) + + t = sym('t'); + t0 = 0; + timeDomain = quantity.Domain('t', linspace(t0, t0+1, 1e2)); + + %% verify singularity case + beta = 1.5; + F = quantity.Symbolic(( t - t0 )^beta, timeDomain); + b(1,:) = signals.BasicVariable(F, {F.diff("t", 1), F.diff("t", 2), F.diff("t", 3)}); + p = 0.3; + testCase.verifyError( @() b.diff(timeDomain, p), "conI:BasicVariable:fDiff") + + + %% verify regular case + for beta = [1, 2, 3] + F = quantity.Symbolic(( t - t0 )^beta, timeDomain); + b(1,:) = signals.BasicVariable(F, {F.diff("t", 1), F.diff("t", 2), F.diff("t", 3)}); + + alpha = 0.3; + for k = 1:5 + p = k*alpha; + % compute the fractional derivative with the formula for the polynomial (see Podlubny: 2.3.4) + f_diff(k,1) = quantity.Symbolic( fractional.monomialFractionalDerivative(t, t0, beta, p), timeDomain); + + % compute the fractional derivative numerically + b_diff(k,1) = b.diff(timeDomain, p); + end + % plot(f_diff - b_diff) + + testCase.verifyEqual( b_diff.on(), f_diff.on(), 'AbsTol', 2e-2) + end + + %% verify fractional integration + testCase.verifyEqual( b.diff(timeDomain, 0).on(), F.on() ) + + for k = 1:5 + alpha = 0.3 * k; + J = int( (t- sym("tau")).^(alpha -1) * subs(F.sym, "t", "tau"), "tau", t0, t) / gamma(alpha); + J_num = b.diff( timeDomain, -alpha ); + + J = quantity.Symbolic(J, timeDomain); + + testCase.verifyEqual( J.on() , J_num.on, 'AbsTol', 3e-3 ); + end + +end + +function testBasicVariableFractionalProductRule(testCase) + +t = quantity.Domain("t", linspace(0,1)); +% choose a polynomial basis +N = 5; + + +beta = 1; +h(1,1) = quantity.Symbolic(sym("t").^beta, t); +poly(1,1) = quantity.Symbolic( sym("t"), t); +for k = 2:N + h(k,1) = h(k-1).diff(); + poly(k,1) = quantity.Symbolic( sym("t")^(k-1) / factorial(k-1), t); +end + +b = signals.BasicVariable( h(1), num2cell(h(2:end)) ); + +for k = 1:3 + + f = sym(b.diff(t, 0)) * sym(poly(k)); + + alpha = 0.3 * k; + n = ceil(alpha); + + frac_f = quantity.Symbolic(fractional.derivative(f, alpha, t.lower, t.name), t); + frac_p = b.productRule(poly(k), alpha); + + testCase.verifyEqual(frac_f.on(), frac_p.on(), 'AbsTol', 4e-3) +end + +end + + +function test_BasicVariable_ProductRule(testCase) + +t = quantity.Domain("t", linspace(0,1,11)); +phi = quantity.Symbolic( sin( sym("t") * pi), t); +bfun = quantity.Symbolic( cos(sym("t") * pi), t); +b = signals.BasicVariable( bfun, { bfun.diff(t, 1), bfun.diff(t, 2), bfun.diff(t, 3) }); + +for k = 0:3 + h1 = b.productRule(phi, k); + h2 = diff( phi * bfun, t, k); + testCase.verifyEqual( h1.on, h2.on, 'AbsTol', 1e-13 ); +end + +end + + + + + + + + + + + + + + + + + + + + + -end \ No newline at end of file diff --git a/+unittests/+signals/testBasicVariable.m b/+unittests/+signals/testBasicVariable.m deleted file mode 100644 index 0efc4a94cf92f3dd7477404a33fd24d9bc656697..0000000000000000000000000000000000000000 --- a/+unittests/+signals/testBasicVariable.m +++ /dev/null @@ -1,26 +0,0 @@ -function [tests] = testBasicVariable() -%testQuantity Summary of this function goes here -% Detailed explanation goes here -tests = functiontests(localfunctions); -end - -function testDiff(tc) - t = sym('t'); - T = quantity.Domain('t', linspace(0,1)); - fun = t^2 * (1-t)^2; - - F = quantity.Symbolic([fun; fun], 'domain', T); - d1F = F.diff("t", 1); - d2F = F.diff("t", 2); - - - b = signals.BasicVariable(F, {d1F, d2F}); - - tc.verifyEqual(b.diff(0), F); - tc.verifyEqual(b.diff(1), d1F); - tc.verifyEqual(b.diff(2), d2F); -end - -function testApplyPolynomialOperator(testCase) - %op = -end \ No newline at end of file diff --git a/+unittests/+signals/testPolynomialOperator.m b/+unittests/+signals/testPolynomialOperator.m index 32dcc78f9da73f0de1da19ebc9c0c40e81dd5ae9..b83c50ce0f237e40201dfaf4178abf965b78ee5b 100644 --- a/+unittests/+signals/testPolynomialOperator.m +++ b/+unittests/+signals/testPolynomialOperator.m @@ -8,7 +8,7 @@ function setupOnce(testCase) %% z = sym('z', 'real'); s = sym('s'); -Z = linspace(0, 1, 11)'; +Z = quantity.Domain("z", linspace(0, 1, 11)); % init with symbolic values A = cat(3, [ 0, 0, 0, 0; ... @@ -27,7 +27,7 @@ A = cat(3, [ 0, 0, 0, 0; ... B = cat(3, [1, 0, 0, 0]', zeros(4, 1), zeros(4, 1)); for k = 1:3 - a{k} = quantity.Symbolic(A(:,:,k), 'grid', Z, 'gridName', 'z'); + a{k} = quantity.Symbolic(A(:,:,k), Z); end A = signals.PolynomialOperator(a, 's', s); @@ -49,7 +49,7 @@ function testApplyTo(testCase) t = sym('t', 'real'); sol.y = quantity.Symbolic( t.^3 .* (1 - t).^3, ... - 'grid', linspace(0, 1, 1e2), 'gridName', 't'); + quantity.Domain("t", linspace(0, 1, 1e2))); sol.u = detsIA.applyTo(sol.y); sol.x = adjB.applyTo(sol.y); @@ -71,9 +71,9 @@ function testFundamentalMatrixSpaceDependent(testCase) % a = 20; z = sym('z', 'real'); - Z = linspace(0, 1, 5)'; + Z = quantity.Domain("z", linspace(0, 1, 5)); A = signals.PolynomialOperator(... - {quantity.Symbolic([1, z; 0, a], 'gridName', 'z', 'grid', Z)}); + {quantity.Symbolic([1, z; 0, a], Z)}); F = A.stateTransitionMatrix(); @@ -84,7 +84,7 @@ function testFundamentalMatrixSpaceDependent(testCase) f = (exp(z) - exp(a * z) - (1 - a) * z * exp( a * z ) ) / (1 - a)^2; end - PhiExact = quantity.Symbolic([exp(z), f; 0, exp(a*z)], 'grid', Z, 'gridName', 'z', 'name', 'Phi_A'); + PhiExact = quantity.Symbolic([exp(z), f; 0, exp(a*z)], Z, 'name', 'Phi_A'); testCase.verifyEqual(double(F), PhiExact.on(), 'RelTol', 1e-6); @@ -92,11 +92,11 @@ end % function testStateTransitionMatrix(testCase) N = 2; -Z = linspace(0,1,11)'; -A0 = quantity.Symbolic([0 1; 1 0], 'grid', Z, 'gridName', 'z'); -A1 = quantity.Symbolic([0 1; 0 0], 'grid', Z, 'gridName', 'z'); +z = quantity.Domain("z", linspace(0,1,11)); +A0 = quantity.Symbolic([0 1; 1 0], z); +A1 = quantity.Symbolic([0 1; 0 0], z); A = signals.PolynomialOperator({A0, A1}); -B = signals.PolynomialOperator(quantity.Symbolic([-1 -1; 0 0], 'grid', Z, 'gridName', 'z')); +B = signals.PolynomialOperator(quantity.Symbolic([-1 -1; 0 0], z)); [Phi1, Psi1] = A.stateTransitionMatrix(N, B); [Phi2, Psi2] = A.stateTransitionMatrixByOdeSystem(N, B); @@ -108,9 +108,7 @@ end function testChangeGrid(testCase) A = testCase.TestData.A; a = testCase.TestData.a; - A0 = A.changeGrid({0}, {'z'}); - - testCase.verifyEqual(cellfun(@(v) numel(v), A0(1).grid), 1); + A0 = A.subs('z', 0); for k = 1:3 testCase.verifyEqual( ... @@ -123,11 +121,11 @@ function testMTimes(testCase) zGrid = linspace(0, pi, 3)'; z = quantity.Domain("z", zGrid); A0 = quantity.Discrete(reshape((repmat([1 2; 3 4], length(zGrid), 1)), length(zGrid), 2, 2),... - 'domain', z, 'name', 'A'); + z, 'name', 'A'); A1 = quantity.Discrete(reshape((repmat([5 6; 7 8], length(zGrid), 1)), length(zGrid), 2, 2), ... - 'domain', z, 'name', 'A'); + z, 'name', 'A'); A2 = quantity.Discrete(reshape((repmat([9 10; 11 12], length(zGrid), 1)), length(zGrid), 2, 2), ... - 'domain', z, 'name', 'A'); + z, 'name', 'A'); A = signals.PolynomialOperator({A0, A1, A2}); B = signals.PolynomialOperator({A0}); @@ -157,13 +155,13 @@ function testMTimes(testCase) end function testSum(testCase) -z = linspace(0, pi, 5)'; - A0 = quantity.Discrete(reshape((repmat([1 2; 3 4], length(z), 1)), length(z), 2, 2),... - 'gridName', 'z', 'grid', z, 'name', 'A'); - A1 = quantity.Discrete(reshape((repmat([5 6; 7 8], length(z), 1)), length(z), 2, 2), ... - 'gridName', 'z', 'grid', z, 'name', 'A'); - A2 = quantity.Discrete(reshape((repmat([9 10; 11 12], length(z), 1)), length(z), 2, 2), ... - 'gridName', 'z', 'grid', z, 'name', 'A'); + z = quantity.Domain("z", linspace(0, pi, 5)); + A0 = quantity.Discrete(reshape((repmat([1 2; 3 4], z.n, 1)), z.n, 2, 2),... + z, 'name', 'A'); + A1 = quantity.Discrete(reshape((repmat([5 6; 7 8], z.n, 1)), z.n, 2, 2), ... + z, 'name', 'A'); + A2 = quantity.Discrete(reshape((repmat([9 10; 11 12], z.n, 1)), z.n, 2, 2), ... + z, 'name', 'A'); A = signals.PolynomialOperator({A0, A1, A2}); B = signals.PolynomialOperator({A0}); @@ -215,24 +213,12 @@ function testUminus(testCase) %% syms z s Z = quantity.Domain('z', linspace(0,1,11)); -A0 = quantity.Symbolic( [z, z.^2; z.^3, z.^4], 'domain', Z); -A1 = quantity.Symbolic( [sin(z), cos(z); tan(z), sinh(z)], 'domain', Z); +A0 = quantity.Symbolic( [z, z.^2; z.^3, z.^4], Z); +A1 = quantity.Symbolic( [sin(z), cos(z); tan(z), sinh(z)], Z); o = signals.PolynomialOperator({A0, A1}); mo = -o; %% verifyEqual(testCase, o(1).coefficient.on , -mo(1).coefficient.on); -end - - - - - - - - - - - - \ No newline at end of file +end \ No newline at end of file diff --git a/+unittests/+signals/testTimeDelayOperator.m b/+unittests/+signals/testTimeDelayOperator.m index 63def61783f0e5bccd055cf631ed5909dbb6aed5..83026e6aeb6c4f2c6f0019dc9512ddd0ad0d9478 100644 --- a/+unittests/+signals/testTimeDelayOperator.m +++ b/+unittests/+signals/testTimeDelayOperator.m @@ -9,9 +9,9 @@ function setupOnce(testCase) z = quantity.Domain('z', linspace(0,1)); zeta = quantity.Domain('zeta', linspace(0,1)); t = quantity.Domain('t', linspace(0, 2, 31)); -v = quantity.Discrete( z.grid, 'domain', z ); -c_zeta = quantity.Discrete( zeta.grid * 0, 'domain', zeta ); -c = quantity.Discrete( 1, 'domain', quantity.Domain.empty); +v = quantity.Discrete( z.grid, z ); +c_zeta = quantity.Discrete( zeta.grid * 0, zeta ); +c = quantity.Discrete( 1, quantity.Domain.empty); testCase.TestData.z = z; testCase.TestData.t = t; @@ -70,9 +70,9 @@ function testApplyTo(testCase) tau = quantity.Domain('tau', linspace(-1, 3, 201)); v = testCase.TestData.delay.variable; - h = quantity.Discrete( sin(tau.grid * 2 * pi), 'domain', tau ); + h = quantity.Discrete( sin(tau.grid * 2 * pi), tau ); H = quantity.Discrete( sin(z.grid * 2 * pi + t.grid' * 2 * pi), ... - 'domain', [z, t]); + [z, t]); H_ = v.applyTo(h); %% test a scalar operator @@ -85,9 +85,9 @@ function testApplyTo(testCase) %% test a diagonal operator h = quantity.Discrete( {sin(tau.grid * 2 * pi); ... - sin(tau.grid * 2 * pi)}, 'domain', tau ); + sin(tau.grid * 2 * pi)}, tau ); H = quantity.Discrete( {sin(z.grid * 0 + 2 * pi + t.grid' * 2 * pi); ... - sin(z.grid * 2 * pi + t.grid' * 2 * pi)}, 'domain', [z, t]); + sin(z.grid * 2 * pi + t.grid' * 2 * pi)}, [z, t]); D = testCase.TestData.delay.diagonal; H_ = D.applyTo(h); @@ -99,11 +99,9 @@ function testApplyTo(testCase) % g(z) = z % D[h] = z + sin( ( t + z ) * 2 * pi ) - h = quantity.Discrete( z.grid + sin(tau.grid' * 2 * pi), ... - 'domain', [z, tau]); + h = quantity.Discrete( z.grid + sin(tau.grid' * 2 * pi), [z, tau]); - Dh = quantity.Discrete( z.grid + sin( ( z.grid + t.grid') * 2 * pi ), ... - 'domain', [z, t]); + Dh = quantity.Discrete( z.grid + sin( ( z.grid + t.grid') * 2 * pi ), [z, t]); D = testCase.TestData.delay.variable; diff --git a/+unittests/testFractional.m b/+unittests/testFractional.m new file mode 100644 index 0000000000000000000000000000000000000000..1ebc3e1da83040c3fa8d2aeeed35406fa52c0474 --- /dev/null +++ b/+unittests/testFractional.m @@ -0,0 +1,36 @@ +function [tests] = testFractional() +%UNTITLED Summary of this function goes here +% Detailed explanation goes here +tests = functiontests(localfunctions); +end + +function testDerivative(testCase) +t = sym("t"); +t0 = 0.5; + + for p = [0.1 0.4 0.7 0.9] + + % define the analytic solution for the fractional derivative + [y, m] = fractional.monomialFractionalDerivative( t, t0, 1, p); + + % compute the fractional derivative with the function + f = fractional.derivative(m, p, t0, t); + + % evaluate both and compare them + dt = 0.1; + % skip the first value because there is a singularity that can not be computed by the + % default symbolic evaluation. + % TODO@ferdinand: allow the quantity.Symbolic to use the limit function for the evaluation + % at certain points. + tDomain = quantity.EquidistantDomain("t", t0+dt, t0+1, "stepSize", dt); + + Y = quantity.Symbolic( y, tDomain); + F = quantity.Symbolic( f, tDomain); + + testCase.verifyEqual( Y.on(), F.on(), 'AbsTol', 1e-15); + + h = fractional.derivative(m, p, t0, t, 'type', 'C'); + H = quantity.Symbolic( h, tDomain); + testCase.verifyEqual( H.on(), Y.on(), 'AbsTol', 1e-15); + end +end \ No newline at end of file diff --git a/.gitignore b/.gitignore index 43f5a61208957257007dc077b3ccbdcf34925299..2809e14d921afd710c454a6fd6274640916379cb 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ ***.aux ***.log .PowerFolder/ +*.asv