From 9cdda192d7e779f6535e1d71b00660f2dfd19a9d Mon Sep 17 00:00:00 2001 From: Ferdinand Fischer <ferdinand.fischer@fau.de> Date: Tue, 10 Mar 2020 19:58:45 +0100 Subject: [PATCH] Change type of quantity.Domain.name from char to string. Change the parameter order of quantity.*/diff Now, the domain name wrt which the derivative should be taken is the second argument and the order of the derivative is the third. speed up of the unittest utilizing setupOnce --- +misc/ensureString.m | 16 ++++ +mustBe/gridName.m | 8 ++ +quantity/Discrete.m | 108 ++++++++------------- +quantity/Domain.m | 144 ++++++++++++---------------- +quantity/Piecewise.m | 8 +- +quantity/Symbolic.m | 37 +++---- +unittests/+misc/testMisc.m | 12 +++ +unittests/+quantity/testDiscrete.m | 90 ++++++++++------- +unittests/+quantity/testDomain.m | 24 +++-- +unittests/+quantity/testSymbolic.m | 26 ++--- 10 files changed, 242 insertions(+), 231 deletions(-) create mode 100644 +misc/ensureString.m create mode 100644 +mustBe/gridName.m diff --git a/+misc/ensureString.m b/+misc/ensureString.m new file mode 100644 index 0000000..52d80b0 --- /dev/null +++ b/+misc/ensureString.m @@ -0,0 +1,16 @@ +function [str] = ensureString(chr) + +if iscell( chr ) + if ~all( cellfun( @ischar, chr ) ) + chr = [chr{:}]; + end +end + +if isstring( chr ) + str = chr; +else + str = convertCharsToStrings( chr ); +end + +end + diff --git a/+mustBe/gridName.m b/+mustBe/gridName.m new file mode 100644 index 0000000..d2c4147 --- /dev/null +++ b/+mustBe/gridName.m @@ -0,0 +1,8 @@ +function gridName( name ) + +name = misc.ensureString( name ); + +assert( isstring( name ), 'A grid name should be a string-array') + +end + diff --git a/+quantity/Discrete.m b/+quantity/Discrete.m index b435a3c..63841fc 100644 --- a/+quantity/Discrete.m +++ b/+quantity/Discrete.m @@ -1,4 +1,5 @@ -classdef (InferiorClasses = {?quantity.Symbolic}) Discrete < handle & matlab.mixin.Copyable & matlab.mixin.CustomDisplay +classdef (InferiorClasses = {?quantity.Symbolic}) Discrete ... + < handle & matlab.mixin.Copyable & matlab.mixin.CustomDisplay properties (SetAccess = protected) % Discrete evaluation of the continuous quantity @@ -79,7 +80,7 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete < handle & matlab.mi %% input parser myParser = misc.Parser(); - myParser.addParameter('name', string(), @isstr); + myParser.addParameter('name', "", @mustBe.gridName); myParser.addParameter('figureID', 1, @isnumeric); myParser.parse(varargin{:}); @@ -143,9 +144,9 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete < handle & matlab.mi %--------------------------- function gridName = get.gridName(obj) if isempty(obj.domain) - gridName = {}; + gridName = []; else - gridName = {obj.domain.name}; + gridName = [obj.domain.name]; end end @@ -316,21 +317,18 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete < handle & matlab.mi % for this, find the index of the common domain in list of % temporary combined domain - intersectDomain = intersect( {originalDomain( ~logOfDomain ).name}, ... - {g(1).domain.name} ); + intersectDomain = intersect( originalDomain( ~logOfDomain ), ... + g(1).domain ); if ~isempty(intersectDomain) - idx = 1:length(tmpDomain); - logCommon = strcmp({tmpDomain.name}, intersectDomain); + idx = tmpDomain.gridIndex( intersectDomain ); % take the diagonal values of the common domain, i.e., z = zeta % use the diag_nd function because it seems to be faster % then the diagNd function, although the values must be % sorted. - % #TODO: Rewrite the diagNd function, using for loops, in order to be as fast as diag_nd - newValues = permute( newValues, [idx(logCommon), idx(~logCommon)]); - newValues = misc.diag_nd(newValues); + newValues = misc.diagNd(newValues, idx); end % *) build a new valueDiscrete on the correct grid. @@ -398,14 +396,10 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete < handle & matlab.mi % Since the order of the domains is not neccessarily equal to the % order in obj(1).domain, this is more involved: myDomain = misc.ensureIsCell(myDomain); - gridNames = misc.ensureIsCell(gridNames); + gridNames = misc.ensureString(gridNames); assert(all(cellfun(@(v)isvector(v), myDomain)), ... 'The cell entries for a new grid have to be vectors') - assert(iscell(gridNames), ... - 'The gridNames parameter must be cell array') - assert(all(cellfun(@ischar, gridNames)), ... - 'The gridNames must be strings') newGrid = myDomain; myDomain = quantity.Domain.empty(); @@ -1015,7 +1009,7 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete < handle & matlab.mi function [idx, logicalIdx] = gridIndex(obj, varargin) warning('DEPRICATED: use quantity.Domain.gridIndex method instead') - [idx, logicalIdx] = obj(1).domain.gridIndex(varargin{:}); + [~, idx, logicalIdx] = obj(1).domain.find(varargin{:}); end function value = at(obj, point) @@ -1305,19 +1299,20 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete < handle & matlab.mi % corresponding domain from DOMAIN. % % example: - % q.changeGrid( linspace(0,1)', 't') - % will change the grid with the name 't' to the new grid linspace(0,1)' + % q.changeGrid( linspace(0,1)', 't') + % will change the grid with the name 't' to the new grid + % linspace(0,1)' if isempty(obj) newObj = obj.copy(); return; end if isa(gridNew, 'quantity.Domain') - gridNameNew = {gridNew.name}; + gridNameNew = [gridNew.name]; gridNew = {gridNew.grid}; else + gridNameNew = misc.ensureString(gridNameNew); gridNew = misc.ensureIsCell(gridNew); - gridNameNew = misc.ensureIsCell(gridNameNew); end if obj(1).isConstant @@ -1325,7 +1320,7 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete < handle & matlab.mi newDomain(1:length( gridNew )) = quantity.Domain(); for it = 1 : length(gridNew) newDomain(it) = ... - quantity.Domain(gridNameNew{it}, gridNew{it}); + quantity.Domain(gridNameNew(it), gridNew{it}); end else gridIndexNew = obj(1).domain.gridIndex(gridNameNew); @@ -1333,9 +1328,9 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete < handle & matlab.mi for it = 1 : length(gridIndexNew) newDomain(gridIndexNew(it)) = ... - quantity.Domain(gridNameNew{it}, gridNew{it}); + quantity.Domain(gridNameNew(it), gridNew{it}); end - assert(isequal({newDomain.name}, obj(1).gridName), ... + assert(isequal([newDomain.name], obj(1).gridName), ... 'rearranging grids failed'); end @@ -1661,17 +1656,13 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete < handle & matlab.mi % Optionally, a weight can be defined, such that instead % xNorm = sqrt(int_0^1 x.' * weight * x dz). % The integral domain is specified by integralGrid. - arguments obj; - integralGridName = {obj(1).domain.name}; + integralGridName {mustBe.gridName} = [obj(1).domain.name]; optArg.weight = eye(size(obj, 1)); end - integralGridName = misc.ensureIsCell(integralGridName); - - assert(all(ischar([integralGridName{:}])), ... - 'integralGrid must specify a gridName as a char'); + integralGridName = misc.ensureString(integralGridName); if obj.nargin == 1 && all(strcmp(obj(1).gridName, integralGridName)) xNorm = sqrtm(on(int(obj.' * optArg.weight * obj), integralGridName)); @@ -1844,16 +1835,16 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete < handle & matlab.mi end end - function result = diff(obj, k, diffGridName) + function result = diff(obj, diffGridName, k) % DIFF computation of the derivative % result = DIFF(obj, k, diffGridName) applies the % 'k'th-derivative for the variable specified with the input % 'diffGridName' to the obj. If no 'diffGridName' is specified, % then diff applies the derivative w.r.t. all gridNames. - if nargin == 1 || isempty(k) - k = 1; % by default, only one derivatve per diffGridName is applied - else - assert(isnumeric(k) && (round(k) == k)) + arguments + obj; + diffGridName (1,1) string = obj(1).gridName; + k uint64 = 1; end if obj.isConstant && isempty(obj(1).gridName) @@ -1864,11 +1855,6 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete < handle & matlab.mi return end - if nargin < 3 % if no diffGridName is specified, then the derivative - % w.r.t. all gridNames is applied - diffGridName = obj(1).gridName; - end - if isa(diffGridName, 'quantity.Domain') % a quantity.Domain is used instead of a grid name % -> get the grid name from the domain object @@ -1880,22 +1866,7 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete < handle & matlab.mi diffGridName = {diffGridName.name}; end - % diff for each element of diffGridName (this is rather - % inefficient, but an easy implementation of the specification) - if iscell(diffGridName) || isempty(diffGridName) - if numel(diffGridName) == 0 || isempty(diffGridName) - result = copy(obj); - else - result = obj.diff(k, diffGridName{1}); % init result - for it = 2 : numel(diffGridName) - result = result.diff(k, diffGridName{it}); - end - end - elseif k == 0 - result = obj; - else - result = obj.diff_inner(k, diffGridName); - end + result = obj.diff_inner(k, diffGridName); end function I = int(obj, varargin) @@ -2323,8 +2294,8 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete < handle & matlab.mi end function result = diff_inner(obj, k, diffGridName) - gridSelector = strcmp(obj(1).gridName, diffGridName); - gridSelectionIndex = find(gridSelector); + + gridSelectionIndex = obj(1).domain.gridIndex(diffGridName); spacing = gradient(obj(1).grid{gridSelectionIndex}, 1); assert(numeric.near(spacing, spacing(1)), ... @@ -2350,9 +2321,9 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete < handle & matlab.mi 'name', ['(d_{', diffGridName, '}', obj(1).name, ')']); if k > 1 - % % if a higher order derivative is requested, call the function - % % recursivly until the first-order derivative is reached - result = result.diff(k-1, diffGridName); + % if a higher order derivative is requested, call the function + % recursivly until the first-order derivative is reached + result = result.diff(diffGridName, k-1); end end @@ -2360,13 +2331,18 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Discrete < handle & matlab.mi % Computes the required permutation vectors to use % misc.multArray for multiplication of matrices - uA = unique(a(1).gridName, 'stable'); - assert(numel(uA) == numel(a(1).gridName), 'Gridnames have to be unique!'); - uB = unique(b(1).gridName, 'stable'); - assert(numel(uB) == numel(b(1).gridName), 'Gridnames have to be unique!'); + % #todo this assertion should not be required! +% uA = unique(a(1).gridName, 'stable'); +% assert(numel(uA) == numel(a(1).gridName), 'Gridnames have to be unique!'); +% uB = unique(b(1).gridName, 'stable'); +% assert(numel(uB) == numel(b(1).gridName), 'Gridnames have to be unique!'); % 1) find common entries - common = intersect(a(1).gridName, b(1).gridName); + if isempty( b(1).gridName ) || isempty( a(1).gridName ) + common = []; + else + common = intersect(a(1).gridName, b(1).gridName); + end commonA = false(1, a.nargin()); commonB = false(1, b.nargin()); diff --git a/+quantity/Domain.m b/+quantity/Domain.m index 5717025..1e10a1a 100644 --- a/+quantity/Domain.m +++ b/+quantity/Domain.m @@ -11,7 +11,7 @@ classdef (InferiorClasses = {?quantity.EquidistantDomain}) Domain < ... % a speaking name for this domain; Should be unique, so that the % domain can be identified by the name. - name char; + name string = ""; n (1,1) double {mustBeInteger}; % number of discretization points == gridLength end @@ -96,7 +96,7 @@ classdef (InferiorClasses = {?quantity.EquidistantDomain}) Domain < ... s = [obj.n]; end - function d = find(obj, domain, varargin) + function d = find(obj, searchName) % FIND a domain in object-array of quantity.Domain % % d = find(OBJ, NAME) returns the domain with the name NAME @@ -111,30 +111,56 @@ classdef (InferiorClasses = {?quantity.EquidistantDomain}) Domain < ... % % d = find(OBJ, { NAME or DOMAIN}) the NAME or the DOMAIN to % be found can also be defined by cell-array. - - names = {obj.name}; - if isa(domain, 'quantity.Domain') - searchName = {domain.name}; - elseif ischar(domain) - searchName = {domain}; - elseif iscell(domain) - searchName = domain; - else - error('Not implemented yet') + arguments + obj quantity.Domain; + end + arguments (Repeating) + searchName string; + end + + d = obj( obj.gridIndex( searchName ) ); + end + function [idx, log] = gridIndex(obj, searchName, position) + %% GRIDINDEX returns the index of the grid + % [idx, log] = gridIndex(obj, names) searches in the name + % properties of obj for the "names" and returns its index as + % "idx" and its logical index as "log" + arguments + obj + searchName = [obj.name]; + position string = "all"; end - if numel(searchName) > 1 - d = obj.find(searchName{:}); - return + if isa(searchName, 'quantity.Domain') + searchName = [searchName.name]; end - d = obj(strcmp(names, searchName)); + objNames = [obj.name]; + + if isempty(objNames) + idx = 0; + log = []; + else + searchName = misc.ensureString( searchName ); + + log = false(size(objNames)); + counter = 1:numel(objNames); + idx = []; + for k = 1 : numel(searchName) + log_ = objNames == searchName(k); + log = log | log_; + idx = [idx counter( log_ )]; + end + if isempty(idx) + idx = 0; + end + end - for it = 1 : numel(varargin) - d = [d, find(obj, varargin{:})]; + if position == "first" + idx = idx(1); end - end - + + end % gridIndex function l = ne(obj, obj2) % ~= Not equal. l = ~(obj == obj2); @@ -155,27 +181,17 @@ classdef (InferiorClasses = {?quantity.EquidistantDomain}) Domain < ... return; end - [joinedGrid, index] = unique({domain1.name, domain2.name}, 'stable'); + [joinedGrid, index] = unique([domain1.name, domain2.name], 'stable'); joinedDomain(1 : numel(joinedGrid)) = quantity.Domain(); - % #todo@domain: the gird comparison seems to be very - % complicated - % check for each grid if it is in the domain of obj1 or obj2 or % both for i = 1 : numel(joinedGrid) - currentGridName = joinedGrid{i}; - [index1, logicalIndex1] = domain1.gridIndex(currentGridName); - [index2, logicalIndex2] = domain2.gridIndex(currentGridName); - - % - % if ~any(logicalIndex1) - % joinedDomain(i) = obj2(index2); - % elseif ~any(logicalIndex2) - % joinedDomain(i) = obj1(index1); - % else - % + currentGridName = joinedGrid(i); + [index1, logicalIndex1] = domain1.gridIndex(currentGridName, "first"); + [index2, logicalIndex2] = domain2.gridIndex(currentGridName, "first"); + % Check if a domain is in both domains: % -> then take the finer one of both if any(logicalIndex1) && any(logicalIndex2) @@ -192,7 +208,7 @@ classdef (InferiorClasses = {?quantity.EquidistantDomain}) Domain < ... end % If it is not in both, -> just take the normal grid elseif any(logicalIndex1) - joinedDomain(i) = domain1(index1); + joinedDomain(i) = domain1(index1(1)); elseif any(logicalIndex2) joinedDomain(i) = domain2(index2); end @@ -204,41 +220,7 @@ classdef (InferiorClasses = {?quantity.EquidistantDomain}) Domain < ... [joinedDomain, index] = obj1.join(obj2); end % gridJoin() - function [idx, logicalIdx] = gridIndex(obj, names) - %% GRIDINDEX returns the index of the grid - % [idx, log] = gridIndex(obj, names) searches in the name - % properties of obj for the "names" and returns its index as - % "idx" and its logical index as "log" - arguments - obj - names = {obj.name}; - end - - if isa(names, 'quantity.Domain') - names = {names.name}; - end - - names = misc.ensureIsCell(names); - - idx = zeros(1, length(names)); - nArgIdx = 1:obj.ndims(); - - logicalIdx = false(1, obj.ndims()); - - for k = 1:length(names) - log = strcmp({obj.name}, names{k}); - logicalIdx = logicalIdx | log; - if any(log) - % #todo@domain: if the index for a grid name is searched in a - % list with multiple grids but same names, only the first - % occurrence is returned. this should be changed... - tmpFirst = nArgIdx(log); - idx(k) = tmpFirst(1); - else - idx(k) = 0; - end - end - end % gridIndex + function i = isempty(obj) i = any(size(obj) == 0); @@ -269,14 +251,14 @@ classdef (InferiorClasses = {?quantity.EquidistantDomain}) Domain < ... function [idx, newDomain] = getPermutationIdx(obj, order) if isa(order, 'quantity.Domain') - names = {order.name}; - elseif ischar([order{:}]) - names = order; + names = [order.name]; + elseif iscell(order) || ischar(order) || isstring(order) + names = misc.ensureString( order ); else - error('the input parameter order must be a array of quantity.Domain objects or a cell-array with string') + error('the input parameter order must be a array of quantity.Domain objects or a string-array') end - idx = cellfun(@(v) obj.gridIndex(v), names); + idx = cellfun(@(v) obj.gridIndex(v), names); % #todo@domainNameString if isa(order, 'quantity.Domain') newDomain = order; @@ -302,7 +284,7 @@ classdef (InferiorClasses = {?quantity.EquidistantDomain}) Domain < ... % only sort the grids if there is something to sort if obj.ndims > 1 - gridNames = {obj.name}; + gridNames = [obj.name]; % this is the default case for ascending alphabetical % order @@ -404,7 +386,7 @@ classdef (InferiorClasses = {?quantity.EquidistantDomain}) Domain < ... %% domain parser domainParser = misc.Parser(); domainParser.addParameter('domain', {}, @(g) isa(g, 'quantity.Domain')); - domainParser.addParameter('gridName', '', @(g) ischar(g) || iscell(g)); + domainParser.addParameter('gridName', ''); domainParser.addParameter('grid', [], @(g) isnumeric(g) || iscell(g)); domainParser.parse(varargin{:}); @@ -418,7 +400,7 @@ classdef (InferiorClasses = {?quantity.EquidistantDomain}) Domain < ... % -> initialize quantity.Domain objects with the % specified values - myGridName = misc.ensureIsCell(domainParser.Results.gridName); + myGridName = misc.ensureString(domainParser.Results.gridName); myGrid = misc.ensureIsCell(domainParser.Results.grid); assert(isequal(numel(myGrid), numel(myGridName)), ... @@ -427,7 +409,7 @@ classdef (InferiorClasses = {?quantity.EquidistantDomain}) Domain < ... % initialize the domain objects myDomain = quantity.Domain.empty(); for k = 1:numel(myGrid) - myDomain(k) = quantity.Domain(myGridName{k}, myGrid{k}); + myDomain(k) = quantity.Domain(myGridName(k), myGrid{k}); end else % else case: the domains are specified as domain @@ -435,7 +417,7 @@ classdef (InferiorClasses = {?quantity.EquidistantDomain}) Domain < ... myDomain = domainParser.Results.domain; end - assert(numel(myDomain) == numel(unique({myDomain.name})), ... + assert(misc.isunique([myDomain.name]), ... 'The names of the domain must be unique'); unmatched = domainParser.UnmatchedNameValuePair; diff --git a/+quantity/Piecewise.m b/+quantity/Piecewise.m index 4f2844d..adfb612 100644 --- a/+quantity/Piecewise.m +++ b/+quantity/Piecewise.m @@ -31,21 +31,21 @@ classdef (InferiorClasses = {?quantity.Symbolic}) Piecewise < quantity.Discrete % ensure all have the same domain d = quantities{1}(1).domain; - assert( all( cellfun(@(q) all( strcmp( {d.name}, {q(1).domain.name} ) ), quantities) ) , ... + assert( all( cellfun(@(q) all( strcmp( [d.name], [q(1).domain.name] ) ), quantities) ) , ... 'The quantities for the piecewise combination must have the same domain names' ); assert(length(quantities) >= 2, 'Only one quantity is given for the piecewise combination. At least 2 are required.'); assert( quantities{1}(1).domain.gridIndex(domainToJoin) == 1, ... 'The domain to join must be the first domain!'); - joinedGrid = quantities{1}(1).domain.find(domainToJoin).grid; + joinedGrid = quantities{1}(1).domain.find(domainToJoin.name).grid; joinedValues = quantities{1}.on(); % ensure the domains fit to each other and then concatenate % them for k = 2:length(quantities) - dkm1 = quantities{k-1}(1).domain.find(domainToJoin); - dk = quantities{k}(1).domain.find(domainToJoin); + dkm1 = quantities{k-1}(1).domain.find(domainToJoin.name); + dk = quantities{k}(1).domain.find(domainToJoin.name); assert(dkm1.upper == dk.lower, 'Domains do not connect to each other'); diff --git a/+quantity/Symbolic.m b/+quantity/Symbolic.m index db27771..ae435a7 100644 --- a/+quantity/Symbolic.m +++ b/+quantity/Symbolic.m @@ -70,10 +70,10 @@ classdef Symbolic < quantity.Function % variable if isa(fun{k}, 'sym') symb{k} = fun{k}; - fun{k} = quantity.Symbolic.setValueContinuous(symb{k}, {myDomain.name}); + fun{k} = quantity.Symbolic.setValueContinuous(symb{k}, [myDomain.name]); elseif isa(fun{k}, 'double') symb{k} = sym(fun{k}); - fun{k} = quantity.Symbolic.setValueContinuous(symb{k}, {myDomain.name}); + fun{k} = quantity.Symbolic.setValueContinuous(symb{k}, [myDomain.name]); elseif isa(fun{k}, 'function_handle') symb{k} = sym(fun{k}); else @@ -299,10 +299,10 @@ classdef Symbolic < quantity.Function else gridName = {gridName.name}; end - else - gridName = misc.ensureIsCell(gridName); end + gridName = misc.ensureString(gridName); + if nargin == 3 && isa(values, 'quantity.Domain') % replacement of the grid AND the gridName % 1) replace the grid @@ -314,14 +314,6 @@ classdef Symbolic < quantity.Function values = misc.ensureIsCell(values); end -% % ensure that domains which should be replaced are known by the -% % object -% for k = 1:length(values) -% if ischar(values{k}) -% assert( any( strcmp( obj(1).gridName, values{k} ) ), 'The domain name to be replaced must be a domain name of the object' ); -% end -% end - isNumericValue = cellfun(@isnumeric, values); if any((cellfun(@(v) numel(v(:)), values)>1) & isNumericValue) error('only implemented for one value per grid'); @@ -344,13 +336,13 @@ classdef Symbolic < quantity.Function 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 ischar(values{it}) - newGridName{strcmp(obj(1).gridName(~selectRemainingGrid), gridName{it})} ... + 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})} ... + newGridName{strcmp(obj(1).gridName(~selectRemainingGrid), gridName(it))} ... = char(gridNameTemp); else @@ -359,7 +351,7 @@ classdef Symbolic < quantity.Function end end - symbolicSolution = subs(obj.sym(), gridName, values); + symbolicSolution = subs(obj.sym(), cellstr(gridName), values); if isempty(symvar(symbolicSolution(:))) % take the new values that are not numeric as the new @@ -374,7 +366,7 @@ classdef Symbolic < quantity.Function end for it = 1 : numel(newGridName) if ~isempty(charValues) && any(contains(charValues, newGridName{it})) - newGrid{it} = obj.gridOf(gridName{strcmp(values, newGridName{it})}); + newGrid{it} = obj.gridOf(gridName( strcmp(values, newGridName{it}) )); else newGrid{it} = obj.gridOf(newGridName{it}); end @@ -709,11 +701,8 @@ classdef Symbolic < quantity.Function % from a to b." if nargin == 2 - if ~iscell(z) - z = {z}; - end - grdIdx = strcmp(obj(1).gridName, z); + grdIdx = obj(1).domain.gridIndex(z); a = obj(1).grid{grdIdx}(1); b = obj(1).grid{grdIdx}(end); end @@ -949,7 +938,11 @@ classdef Symbolic < quantity.Function fDouble = double(f); f = @(varargin) fDouble + quantity.Function.zero(varargin{:}); else - f = matlabFunction(f, 'Vars', symVar ); + if iscell( symVar ) + symVar = [symVar{:}]; + end + + f = matlabFunction(f, 'Vars', convertStringsToChars( symVar ) ); end end end % setValueContinuous() diff --git a/+unittests/+misc/testMisc.m b/+unittests/+misc/testMisc.m index 009f05b..c8677b5 100644 --- a/+unittests/+misc/testMisc.m +++ b/+unittests/+misc/testMisc.m @@ -4,6 +4,18 @@ function [tests] = testMisc() tests = functiontests(localfunctions()); end +function testEnsureString(testCase) + +a = "a"; +b = 'b'; + +testCase.verifyEqual( misc.ensureString(a), a) +testCase.verifyEqual( misc.ensureString(b), string(b)) +testCase.verifyEqual( misc.ensureString({a, b}), [a, string(b)]) +testCase.verifyEqual( misc.ensureString({b, b}), [string(b), string(b)]) + +end + function testEyeNd(tc) n = 2; tc.verifyEqual(misc.eyeNd([n, n]), eye(n)); diff --git a/+unittests/+quantity/testDiscrete.m b/+unittests/+quantity/testDiscrete.m index 2a45b97..7cbe9f6 100644 --- a/+unittests/+quantity/testDiscrete.m +++ b/+unittests/+quantity/testDiscrete.m @@ -1,14 +1,27 @@ - function [tests] = testDiscrete() %testQuantity Summary of this function goes here % Detailed explanation goes here tests = functiontests(localfunctions); end + +function setupOnce(testCase) +syms z zeta t sy +testCase.TestData.sym.z = z; +testCase.TestData.sym.zeta = zeta; +testCase.TestData.sym.t = t; +testCase.TestData.sym.sy = sy; +end + function testL2Norm(tc) + +Z = tc.TestData.sym.z; +T = tc.TestData.sym.t; +ZETA = tc.TestData.sym.zeta; + t = quantity.Domain('t', linspace(0, 2, 101)); z = quantity.Domain('z', linspace(0, 1, 51)); -x = quantity.Symbolic([sym('z') * sym('t'); sym('t')], 'domain', [z, t]); +x = quantity.Symbolic([Z * T; T], 'domain', [z, t]); xNorm = sqrt(int(x.' * x, 'z')); tc.verifyEqual(MAX(abs(xNorm - x.l2norm('z'))), 0, 'AbsTol', 1e-12); @@ -105,9 +118,6 @@ tc.verifyEqual(q1V , q2V') end % testSort function testBlkdiag(tc) -% init some data -syms z zeta - z = quantity.Domain('z', linspace(0, 1, 21)); zeta = quantity.Domain('zeta', linspace(0, 1, 41)); @@ -149,7 +159,9 @@ tc.verifyEqual(blub(2).name, 'asdf'); end function testCtranspose(tc) -syms z zeta +z = tc.TestData.sym.z; +zeta = tc.TestData.sym.zeta; + qSymbolic = quantity.Symbolic(... [1+z*zeta, -zeta; -z, z^2], 'grid', {linspace(0, 1, 21), linspace(0, 1, 41)},... 'gridName', {'z', 'zeta'}, 'name', 'q'); @@ -168,8 +180,9 @@ tc.verifyEqual(qDiscrete2.imag.on(), -qDiscrete2Ctransp.imag.on()); end % testCtranspose function testTranspose(tc) +z = tc.TestData.sym.z; +zeta = tc.TestData.sym.zeta; -syms z zeta qSymbolic = quantity.Symbolic(... [1+z*zeta, -zeta; -z, z^2], 'grid', {linspace(0, 1, 21), linspace(0, 1, 41)},... 'gridName', {'z', 'zeta'}, 'name', 'q'); @@ -183,7 +196,9 @@ tc.verifyEqual(qDiscrete(2,1).on(), qDiscreteTransp(1,2).on()); end % testTranspose function testFlipGrid(tc) -syms z zeta +z = tc.TestData.sym.z; +zeta = tc.TestData.sym.zeta; + myGrid = linspace(0, 1, 11); f = quantity.Discrete(quantity.Symbolic([1+z+zeta; 2*zeta+sin(z)] + zeros(2, 1)*z*zeta, ... 'gridName', {'z', 'zeta'}, 'grid', {myGrid, myGrid})); @@ -398,7 +413,8 @@ end function testDiag2Vec(testCase) % quantity.Symbolic -syms z +z = testCase.TestData.sym.z; + do = quantity.Domain('z', linspace(0,1,3)); myMatrixSymbolic = quantity.Symbolic([sin(0.5*z*pi)+1, 0; 0, 0.9-z/2], 'domain', do); myVectorSymbolic = diag2vec(myMatrixSymbolic); @@ -463,7 +479,9 @@ testCase.verifyEqual(solution.on(), referenceResult1 + referenceResult2, 'AbsTol end function testSolveDVariableEqualQuantityNegative(testCase) -syms z zeta +z = testCase.TestData.sym.z; +zeta = testCase.TestData.sym.zeta; + assume(z>0 & z<1); assume(zeta>0 & zeta<1); myParameterGrid = linspace(0, 0.1, 5); Lambda = quantity.Symbolic(-0.1-z^2, ...%, -1.2+z^2]),...1+z*sin(z) @@ -482,7 +500,8 @@ end function testSolveDVariableEqualQuantityComparedToSym(testCase) %% compare with symbolic implementation -syms z +z = testCase.TestData.sym.z; + assume(z>0 & z<1); quanBSym = quantity.Symbolic(1+z, 'grid', {linspace(0, 1, 5)}, ... 'gridName', 'z', 'name', 'bSym', 'gridName', {'z'}); @@ -496,7 +515,8 @@ end function testSolveDVariableEqualQuantityAbsolut(testCase) %% compare with symbolic implementation -syms z +z = testCase.TestData.sym.z; + assume(z>0 & z<1); quanBSym = quantity.Symbolic(1+z, 'grid', {linspace(0, 1, 11)}, ... 'gridName', 'z', 'name', 'bSym', 'gridName', {'z'}); @@ -505,7 +525,7 @@ quanBDiscrete = quantity.Discrete(quanBSym.on(), 'grid', {linspace(0, 1, 11)}, . solutionBDiscrete = quanBDiscrete.solveDVariableEqualQuantity(); myGrid = solutionBDiscrete.grid{1}; -solutionBDiscreteDiff = solutionBDiscrete.diff(1, 's'); +solutionBDiscreteDiff = solutionBDiscrete.diff('s'); quanOfSolutionOfS = zeros(length(myGrid), length(myGrid), 1); for icIdx = 1 : length(myGrid) quanOfSolutionOfS(:, icIdx, :) = quanBSym.on(solutionBDiscrete.on({myGrid, myGrid(icIdx)})); @@ -523,7 +543,8 @@ b = quantity.Discrete(ones(5, 1), 'grid', linspace(0, 1, 5), 'gridName', 'z', .. 'size', [1, 1], 'name', 'b'); ab = a*b; % -syms z +z = testCase.TestData.sym.z; + c = quantity.Symbolic(1, 'grid', linspace(0, 1, 21), 'gridName', 'z', 'name', 'c'); ac = a*c; @@ -547,10 +568,8 @@ sinfun = quantity.Discrete(sin(z), 'grid', z, 'gridName', 'z'); % very bad a the boundarys of the domain. Z = linspace(0.1, pi-0.1)'; testCase.verifyTrue(numeric.near(sinfun.diff().on(Z), cos(Z), 1e-3)); -testCase.verifyTrue(numeric.near(sinfun.diff(2).on(Z), -sin(Z), 1e-3)); -testCase.verifyTrue(numeric.near(sinfun.diff(3).on(Z), -cos(Z), 1e-3)); - - +testCase.verifyTrue(numeric.near(sinfun.diff('z', 2).on(Z), -sin(Z), 1e-3)); +testCase.verifyTrue(numeric.near(sinfun.diff('z', 3).on(Z), -cos(Z), 1e-3)); end @@ -560,27 +579,26 @@ function testDiffConstant2d(testCase) myQuantity = quantity.Discrete(cat(3, 2*ones(11, 21), zNdgrid, zetaNdgrid), ... 'grid', {linspace(0, 1, 11), linspace(0, 1, 21)}, ... 'gridName', {'z', 'zeta'}, 'name', 'constant', 'size', [3, 1]); -myQuantityDz = diff(myQuantity, 1, 'z'); -myQuantityDzeta = diff(myQuantity, 1, 'zeta'); -myQuantityDZzeta = diff(myQuantity, 1); -myQuantityDZzeta2 = diff(myQuantity, 1, {'z', 'zeta'}); +myQuantityDz = diff(myQuantity, 'z', 1); +myQuantityDzeta = diff(myQuantity, 'zeta', 1); -testCase.verifyEqual(myQuantityDZzeta.on(), myQuantityDZzeta2.on()); +testCase.verifyError( @() diff(myQuantity), 'MATLAB:functionValidation:NotScalar' ) +testCase.verifyError( @() diff(myQuantity, 1, {'z', 'zeta'}), 'MATLAB:functionValidation:ClassConversionError' ) +testCase.verifyError( @() diff(myQuantity, {'z', 'zeta'}), 'MATLAB:functionValidation:NotScalar' ) % constant testCase.verifyEqual(myQuantityDz(1).on(), zeros(11, 21)); testCase.verifyEqual(myQuantityDzeta(1).on(), zeros(11, 21)); -testCase.verifyEqual(myQuantityDZzeta(1).on(), zeros(11, 21)); + % zNdgrid testCase.verifyEqual(myQuantityDz(2).on(), ones(11, 21), 'AbsTol', 10*eps); testCase.verifyEqual(myQuantityDzeta(2).on(), zeros(11, 21), 'AbsTol', 10*eps); -testCase.verifyEqual(myQuantityDZzeta(2).on(), zeros(11, 21), 'AbsTol', 10*eps); % zetaNdgrid testCase.verifyEqual(myQuantityDz(3).on(), zeros(11, 21), 'AbsTol', 10*eps); testCase.verifyEqual(myQuantityDzeta(3).on(), ones(11, 21), 'AbsTol', 10*eps); -testCase.verifyEqual(myQuantityDZzeta(3).on(), zeros(11, 21), 'AbsTol', 10*eps); + end function testOn(testCase) @@ -669,7 +687,7 @@ zeta11 = quantity.Domain('zeta', z11_); a = quantity.Discrete(ones([z11.n, z11.n, 2, 2]), 'size', [2, 2], ... 'domain', [z11, zeta11], 'name', 'A'); -syms zeta +zeta = testCase.TestData.sym.zeta; z10_ = linspace(0, 1, 10); b = quantity.Symbolic((eye(2, 2)), 'gridName', 'zeta', ... @@ -683,7 +701,9 @@ end function testMTimesPointWise(tc) -syms z zeta +z = tc.TestData.sym.z; +zeta = tc.TestData.sym.zeta; + Z = linspace(0, pi, 51)'; ZETA = linspace(0, pi / 2, 71)'; @@ -785,7 +805,8 @@ tGrid = linspace(pi, 1.1*pi, 51)'; s = tGrid; [T, S] = ndgrid(tGrid, tGrid); -syms sy t +t = testCase.TestData.sym.t; +sy = testCase.TestData.sym.sy; a = [ 1, sy; t, 1]; b = [ sy; 2*sy]; @@ -842,7 +863,10 @@ testCase.verifyEqual( f.on(f(1).grid, f(1).gridName), V.on(f(1).grid, f(1).gridN tGrid = linspace(pi, 1.1*pi, 51)'; s = tGrid; [T, S] = ndgrid(tGrid, tGrid); -syms sy tt + +sy = testCase.TestData.sym.sy; +t = testCase.TestData.sym.t; + a = [ 1, sy; t, 1]; A = zeros([size(T), size(a)]); @@ -1287,12 +1311,12 @@ testCase.verifyEqual(quan10, shiftdim(quan.on({1, 0}))); testCase.verifyEqual(quan01, shiftdim(quan.on({0, 1}))); testCase.verifyEqual(quanZetaZ.on(), quan.on()); testCase.verifyEqual(quant1.on(), squeeze(quan.on({quan(1).grid{1}, 1}))); -testCase.verifyEqual(quanZetaZ(1).gridName, {'zeta', 'z'}) -testCase.verifyEqual(quant1(1).gridName, {'t'}) +testCase.verifyEqual(quanZetaZ(1).gridName, ["zeta", "z"]) +testCase.verifyEqual(quant1(1).gridName, ["t"]) quanZetaZetaReference = misc.diagNd(quan.on({linspace(0, 1, 41), linspace(0, 1, 41)}), [1, 2]); testCase.verifyEqual(quanZetaZeta.on(), quanZetaZetaReference); -testCase.verifyEqual(quanEta(1).gridName, {'eta', 'zeta'}) +testCase.verifyEqual(quanEta(1).gridName, ["eta", "zeta"]) testCase.verifyEqual(quanPt.on(), shiftdim(quan.on({1, quan(1).grid{2}}))); myQuantity = quanZetaZeta.subs(quantity.Domain('zeta', linspace(0,1,3))); diff --git a/+unittests/+quantity/testDomain.m b/+unittests/+quantity/testDomain.m index 7fff801..444efef 100644 --- a/+unittests/+quantity/testDomain.m +++ b/+unittests/+quantity/testDomain.m @@ -29,9 +29,9 @@ d = testCase.TestData.d; testCase.verifyEqual( d.find('a'), a) testCase.verifyEqual( d.find('b', 'c'), [b c]) testCase.verifyEqual( d.find({'b', 'c'}), [b c]) -testCase.verifyEqual( d.find(b), b); -testCase.verifyEqual( d.find({a c}), [a c]) -testCase.verifyEqual( d.find([a b]), [a b]) +testCase.verifyEqual( d.find(b.name), b); +testCase.verifyEqual( d.find({a.name c.name}), [a c]) +testCase.verifyEqual( d.find([a.name b.name]), [a b]) end @@ -45,7 +45,7 @@ c = quantity.Domain('c', z); d = [a b c]; testCase.verifyEqual(d.find('a'), a); -testCase.verifyEqual(d.find(b), b); +testCase.verifyEqual(d.find(b.name), b); testCase.verifyEqual(d.find('c'), c); end @@ -88,7 +88,7 @@ d = [a b c]; [d__, I__] = d.sort('ascend'); d___ = d.sort(); -testCase.verifyEqual({d_.name}, {'c', 'b', 'a'}); +testCase.verifyEqual([d_.name], ["c", "b", "a"]); testCase.verifyEqual({d__.name}, {d.name}); testCase.verifyEqual({d__.name}, {d___.name}); testCase.verifyEqual( I_, flip(I__) ); @@ -168,16 +168,22 @@ joinedDomainCC = sort( gridJoin(c, c) ); joinedDomainBC = sort( gridJoin(b, c) ); joinedDomain = sort( gridJoin(Z, [Z, T]) ); -testCase.verifyEqual({joinedDomainAB.name}, {'p', 's', 't', 'z'}); +testCase.verifyEqual([joinedDomainAB.name], ["p", "s", "t", "z"]); testCase.verifyEqual({joinedDomainAB.grid}, {s(:), s(:), t(:), z(:)}); -testCase.verifyEqual({joinedDomainCC.name}, {'p'}); +testCase.verifyEqual(joinedDomainCC.name, "p"); testCase.verifyEqual({joinedDomainCC.grid}, {t(:)}); -testCase.verifyEqual({joinedDomainBC.name}, {'p', 's'}); +testCase.verifyEqual([joinedDomainBC.name], ["p", "s"]); testCase.verifyEqual({joinedDomainBC.grid}, {s(:), t(:)}); testCase.verifyEqual({joinedDomain.grid}, {t(:), z(:)}); + +d1 = [Z Z T]; +d2 = quantity.Domain.empty; + +testCase.verifyEqual( d1.join( d2 ), [Z T] ) + end -function testGridIndex(testCase) +function testFind_Index(testCase) z = linspace(0, 2*pi, 71)'; t = linspace(0, 3*pi, 51); s = linspace(0, 1); diff --git a/+unittests/+quantity/testSymbolic.m b/+unittests/+quantity/testSymbolic.m index 9631375..8a0e2ae 100644 --- a/+unittests/+quantity/testSymbolic.m +++ b/+unittests/+quantity/testSymbolic.m @@ -140,13 +140,8 @@ fdz = quantity.Symbolic([zeta, 1; 0, 0], ... fdzeta = quantity.Symbolic([z, 0; 1, 0], ... 'gridName', {'z', 'zeta'}, 'grid', {myGrid, myGrid}); -fRefTotal = 0*f.on(); -fRefTotal(:,:,1,1) = 1; -testCase.verifyEqual(on(diff(f)), fRefTotal); -testCase.verifyEqual(on(diff(f, 1, {'z', 'zeta'})), fRefTotal); -testCase.verifyEqual(on(diff(f, 2, {'z', 'zeta'})), 0*fRefTotal); -testCase.verifyEqual(on(diff(f, 1, 'z')), fdz.on()); -testCase.verifyEqual(on(diff(f, 1, 'zeta')), fdzeta.on()); +testCase.verifyEqual(on(diff(f, 'z')), fdz.on()); +testCase.verifyEqual(on(diff(f, 'zeta')), fdzeta.on()); end @@ -483,11 +478,10 @@ f2 = cosh(z * pi); f = quantity.Symbolic([f1 ; f2 ], 'name', 'f', 'domain', ... quantity.Domain('z', linspace(0,1,11))); -d2f = f.diff(2); -d1f = f.diff(1); +d2f = f.diff("z", 2); +d1f = f.diff("z", 1); %% - F1 = symfun([f1; f2], z); D1F = symfun([diff(f1,1); diff(f2,1)], z); D2F = symfun([diff(f1,2); diff(f2,2)], z); @@ -500,8 +494,8 @@ R3 = D2F(z); %% verifyTrue(testCase, numeric.near(double([R1{:}]), f.on(), 1e-12)); -verifyTrue(testCase, numeric.near(double([R2{:}]), f.diff(1).on(), 1e-12)); -verifyTrue(testCase, numeric.near(double([R3{:}]), f.diff(2).on(), 1e-12)); +verifyTrue(testCase, numeric.near(double([R2{:}]), f.diff('z', 1).on(), 1e-12)); +verifyTrue(testCase, numeric.near(double([R3{:}]), f.diff('z', 2).on(), 1e-12)); end @@ -683,7 +677,7 @@ Q_var = quantity.Symbolic(z, 'domain', quantity.Domain('z', 1:2)); testCase.verifyError(... @() quantity.Symbolic(z, 'domain', quantity.Domain.empty()), ... - 'quantity:Symbolic:Initialisation' ); + 'symbolic:sym:matlabFunction:InvalidVars' ); testCase.verifyTrue(Q_constant.isConstant); testCase.verifyFalse(Q_var.isConstant); @@ -809,16 +803,16 @@ assume(z>0 & z<1); assume(zeta>0 & zeta<1); F = quantity.Symbolic(zeros(1), 'grid', ... {linspace(0, 1, 3), linspace(0, 1, 3)}, 'gridName', {'z', 'zeta'}); Feta = F.subs('z', 'eta'); -testCase.verifyEqual(Feta(1).gridName, {'eta', 'zeta'}); +testCase.verifyEqual(Feta(1).gridName, ["eta", "zeta"]); %% fMessy = quantity.Symbolic(x^1*y^2*z^4*w^5, 'domain', [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.gridName, "a"); testCase.verifyEqual(fMessyA.grid, {linspace(0, 1, 11)'}); -testCase.verifyEqual(fMessyYY.gridName, {'y', 'xi'}); +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]); -- GitLab