Commit cb036bc9 authored by Jakob Gabriel's avatar Jakob Gabriel
Browse files

Merge branch '14-quantity-domain' into 'master'

Resolve "quantity.Domain"

Closes #14

See merge request !5
parents daeb57b6 1d3f5e37
......@@ -6,13 +6,15 @@ classdef Parser < inputParser
methods
function obj = Parser()
function obj = Parser(varargin)
obj = obj@inputParser();
% set default properties
obj.KeepUnmatched = true;
obj.CaseSensitive = true;
obj.PartialMatching = false;
end % Parser()
function i = isDefault(obj, name)
......
function value = ensureIsCell(value)
%ENSUREISCELL ensures that the value is a cell.
% c = ensureIsCell(value) checks if value is a cell. If it is not a cell,
% it is converted as a cell.
if ~iscell(value)
value = {value};
end
end
......@@ -135,10 +135,9 @@ function obj = BasicVariable(valueContinuous, varargin)
Di(j) = obj_j.derivatives(k+1);
end
end
Di = reshape(Di, [ 1 size(obj)]);
D(i,:) = Di;
end
D = reshape(D, [numel(K) size(obj)]);
end
%% PROPERTIES
......
This diff is collapsed.
classdef Domain < handle & matlab.mixin.CustomDisplay
%DOMAIN class to describes a range of values on which a function can be defined.
% todo:
% * EquidistantDomain
% * multi dimensional
properties (SetAccess = protected)
% The discrete points of the grid for the evaluation of a
% continuous quantity. For an example, the function f(x) should be
% considered on the domain x \in X = [0, 1]. Then, a grid can be
% generated by X_grid = linspace(0, 1).
grid double {mustBeReal, mustBeFinite, mustBe.ascending};
% TODO add mustBeVector
% a speaking name for this domain; Should be unique, so that the
% domain can be identified by the name.
name char;
end
properties (Dependent)
n; % number of discretization points
lower; % lower bound of the domain
upper; % upper bound of the domain
end
properties (Dependent, Hidden)
ones;
end
methods
function obj = Domain(name, grid)
%DOMAIN initialize the domain
%
if nargin >= 1
obj.grid = grid(:);
obj.name = name;
end
end
function i = get.ones(obj)
i = ones(size(obj.grid));
end
function n = get.n(obj)
if isempty(obj.grid)
n = [];
else
n = length(obj.grid);
end
end
function lower = get.lower(obj)
lower = min( obj.grid );
end
function upper = get.upper(obj)
upper = max( obj.grid );
end
function nd = ndims(obj)
%NDIMS number of dimensions of the domain specified by the
%object-array.
nd = size(obj(:), 1);
end
function n = numGridElements(obj)
% NUMGRIDLEMENTS returns the number of the elements of the grid
n = prod([obj.n]);
end
function s = gridLength(obj)
%GRIDLENGTH number of discretization points for each grid in the
%object-array.
s = [obj.n];
end
% function s = eq(obj, obj2)
%
% if isempty(obj) && isempty(obj2)
% % if both are empty -> they are equal
% s = true;
% elseif isempty(obj) || isempty(obj2)
% % if only one domain is empty -> they are not equal
% s = false;
% else
% % if both are not empty: check if the grids and the
% % gridNames are equal
% s = all(obj.gridLength == obj2.gridLength);
%
% for k = 1:numel(obj)
% s = s && all(obj(k).grid == obj2(k).grid);
% s = s && strcmp(obj(k).name, obj2(k).name);
% end
% end
% end
%
function l = ne(obj, obj2)
l = ~(obj == obj2);
end
function [joinedDomain, index] = join(domain1, domain2)
%% gridJoin combines the grid and gridName of two objects (obj1,
% obj2), such that every gridName only occurs once and that the
% finer grid of both is used.
arguments
domain1 quantity.Domain = quantity.Domain.empty
domain2 quantity.Domain = quantity.Domain.empty
end
if nargin == 1 && length(domain1) == 1
joinedDomain = domain1;
index = 1;
return;
end
[joinedGrid, index] = unique({domain1.name, domain2.name}, 'stable');
joinedDomain = quantity.Domain.empty;
% #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
%
% Check if a domain is in both domains:
% -> then take the finer one of both
if any(logicalIndex1) && any(logicalIndex2)
tempDomain1 = domain1(index1);
tempDomain2 = domain2(index2);
assert(tempDomain1.lower == tempDomain2.lower, 'Grids must have same domain for gridJoin')
assert(tempDomain1.upper == tempDomain2.upper, 'Grids must have same domain for gridJoin')
if tempDomain1.gridLength > tempDomain2.gridLength
joinedDomain(i) = tempDomain1;
else
joinedDomain(i) = tempDomain2;
end
% If it is not in both, -> just take the normal grid
elseif any(logicalIndex1)
joinedDomain(i) = domain1(index1);
elseif any(logicalIndex2)
joinedDomain(i) = domain2(index2);
end
end
end
function [joinedDomain, index] = gridJoin(obj1, obj2)
[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);
i = i || any( cellfun(@isempty, {obj.grid} ) );
end
function i = isSubDomainOf(obj, d)
% ISSUBDOMAIN
% i = isSubDomainOf(OBJ, D) checks if the domain OBJ is a
% sub-domain of D.
i = obj.lower >= d.lower && ...
obj.upper <= d.upper;
end % isSubDomainOf
function matGrid = ndgrid(obj)
% ndgrid calles ndgrid for the default grid, if no other grid
% is specified. Empty grid as input returns empty cell as
% result.
if isempty(obj)
matGrid = {};
else
grids = {obj.grid};
[matGrid{1:obj.ndims}] = ndgrid(grids{:});
end
end % ndgrid()
function [idx, newDomain] = getPermutationIdx(obj, order)
if isa(order, 'quantity.Domain')
names = {order.name};
elseif ischar([order{:}])
names = order;
else
error('the input parameter order must be a array of quantity.Domain objects or a cell-array with string')
end
idx = cellfun(@(v) obj.gridIndex(v), names);
if isa(order, 'quantity.Domain')
newDomain = order;
else
newDomain = obj(idx);
end
end
function [newObj, I] = 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.
if nargin == 2 && strcmp(varargin{1}, 'descend')
descend = 1;
elseif nargin == 1 || strcmp(varargin{1}, 'ascend')
descend = 0;
else
error(['Unknown sorting order' char( varargin{1} )])
end
% only sort the grids if there is something to sort
if obj.ndims > 1
gridNames = {obj.name};
% this is the default case for ascending alphabetical
% order
[sortedNames, I] = sort(gridNames);
% if descending: flip the order of the entries
if descend
sortedNames = flip(sortedNames);
I = flip(I);
end
% sort the grid entries
myGrids = {obj.grid};
myGrids = myGrids(I);
newObj(1:length(myGrids)) = quantity.Domain();
for k = 1:length(myGrids)
newObj(k) = quantity.Domain(sortedNames{k}, myGrids{k});
end
else
newObj = obj;
end
end% sort()
end
methods (Access = protected)
function s = getPropertyGroups(obj)
% Function to display the correct values
if isempty(obj)
s = getPropertyGroups@matlab.mixin.CustomDisplay(obj);
return;
else
s = getPropertyGroups@matlab.mixin.CustomDisplay(obj(1));
end
if numel(obj) ~= 1
grids = {obj.grid};
sizes = cellfun(@(g)size(g), grids, 'UniformOutput', false);
s.PropertyList.grid = ...
[sprintf('%ix%i, ', cell2mat(sizes)) sprintf('\b\b\b')];
s.PropertyList.name = ...
[sprintf('%s, ', obj.name) sprintf('\b\b\b')];
s.PropertyList.lower = ...
[sprintf('%d, ', obj.lower) sprintf('\b\b\b')];
s.PropertyList.upper = ...
[sprintf('%.3f, ', obj.upper) sprintf('\b\b\b')];
s.PropertyList.n = ...
[sprintf('%i, ', obj.n) sprintf('\b\b\b')];
end
end % getPropertyGroups
end % Access = protected
methods (Static)
function d = gridCells2domain(grids, gridNames)
grids = misc.ensureIsCell(grids);
gridNames = misc.ensureIsCell(gridNames);
assert(length( grids ) == length(gridNames))
d = quantity.Domain.empty();
for k = 1:length(grids)
d = [d quantity.Domain(gridNames{k}, grids{k})];
end
end
function g = defaultGrid(gridSize, name)
if nargin == 1
% If no names are specified, chose x_1, x_2, ... as default
% names.
name = cell(1, length(gridSize));
for k = 1:length(gridSize)
name{k} = ['x_' num2str(k)];
end
end
% generate a default gird with given sizes
g = quantity.Domain.empty();
for k = 1:length(gridSize)
o = ones(1, length(gridSize) + 1); % + 1 is required to deal with one dimensional grids
o(k) = gridSize(k);
O = ones(o);
O(:) = linspace(0, 1, gridSize(k));
g(k) = quantity.Domain(name{k}, O);
end
end
function [myDomain, unmatched] = parser(varargin)
%% domain parser
domainParser = misc.Parser();
domainParser.addParameter('domain', {}, @(g) isa(g, 'quantity.Domain'));
domainParser.addParameter('gridName', '', @(g) ischar(g) || iscell(g));
domainParser.addParameter('grid', [], @(g) isnumeric(g) || iscell(g));
domainParser.parse(varargin{:});
if domainParser.isDefault('domain') && ...
( domainParser.isDefault('grid') || ...
domainParser.isDefault('gridName') )
% case 1: nothing about the grid is defined
error('No domain is specified! A domain is obligatory for the initialization of a quantity.')
elseif domainParser.isDefault('domain')
% case 3: the gridNames and the gridValues are defined:
% -> initialize quantity.Domain objects with the
% specified values
myGridName = misc.ensureIsCell(domainParser.Results.gridName);
myGrid = misc.ensureIsCell(domainParser.Results.grid);
assert(isequal(numel(myGrid), numel(myGridName)), ...
'number of grids and gridNames must be equal');
% initialize the domain objects
myDomain = quantity.Domain.empty();
for k = 1:numel(myGrid)
myDomain(k) = quantity.Domain(myGridName{k}, myGrid{k});
end
else
% else case: the domains are specified as domain
% objects.
myDomain = domainParser.Results.domain;
end
assert(numel(myDomain) == numel(unique({myDomain.name})), ...
'The names of the domain must be unique');
unmatched = domainParser.UnmatchedNameValuePair;
end
end
end
classdef EquidistantDomain < quantity.Domain
%EQUIDISTANTDOMAIN class to handle the discretization of the range of
%definition of a function. The discretization points are equally
%distributed over the domain.
properties
Property1
end
methods
function obj = untitled(inputArg1,inputArg2)
%UNTITLED Construct an instance of this class
% Detailed explanation goes here
obj.Property1 = inputArg1 + inputArg2;
end
function outputArg = method1(obj,inputArg)
%METHOD1 Summary of this method goes here
% Detailed explanation goes here
outputArg = obj.Property1 + inputArg;
end
end
end
......@@ -74,57 +74,26 @@ classdef Function < quantity.Discrete
%-----------------------------
% --- overloaded functions ---
%-----------------------------
function value = on(obj, myGrid, myGridName)
% evaluates obj.valueContinuous function and returns an array
% with the dimensions (n, m, ..., z_disc). n, m, ... are the
% array dimensions of obj.valueContinuous and z_disc is the
% dimensions of the grid.
if nargin == 1
gridSize = obj(1).gridSize;
if gridSize > 2
value = cat(length(gridSize),obj.valueDiscrete);
else % important to include this case for empty grids!
value = [obj.valueDiscrete]; % = cat(2,obj.valueDiscrete)
end
value = reshape(value, [gridSize, size(obj)]);
return
elseif nargin >= 2 && ~iscell(myGrid)
myGrid = {myGrid};
end
gridPermuteIdx = 1:obj(1).nargin;
if isempty(gridPermuteIdx)
gridPermuteIdx = 1;
end
if nargin == 3
if ~iscell(myGridName)
myGridName = {myGridName};
end
assert(numel(myGrid) == numel(myGridName), ...
['If on() is called by using gridNames as third input', ...
', then the cell-array of grid and gridName must have ', ...
'equal number of elements.']);
assert(numel(myGridName) == obj(1).nargin, ...
'All (or none) gridName must be specified');
gridPermuteIdx = cellfun(@(v) obj(1).gridIndex(v), myGridName);
myGrid = myGrid(gridPermuteIdx);
end
% at first the value has to be initialized by different
% dimensions, to simplify the evaluation of each entry:
gridSize = cellfun('length', myGrid);
if isempty(gridSize), gridSize = 1; end
function value = obj2value(obj, myDomain, recalculate)
value = nan([numel(obj), gridSize]);
for k = 1:numel(obj)
ndGrd = obj.ndgrid( myGrid );
tmp = obj(k).evaluateFunction( ndGrd{:} );
% Replaced
% double(subs(obj(k).valueSymbolic, obj.variable, grid));
% because this is very slow!
value(k,:) = tmp(:);
% check if the domain for the evaluation has changed. If not
% we can use the stored values in valueDiscrete:
if nargin < 3
recalculate = false;
end
if ~recalculate && isequal(myDomain, obj(1).domain)
value = obj2value@quantity.Discrete(obj);
else
% otherwise the function has to be evaluated on the new
% domain
value = cell(size(obj));
for k = 1:numel(obj)
ndGrd = myDomain.ndgrid;
tmp = obj(k).evaluateFunction( ndGrd{:} );
value{k} = tmp(:);
end
value = reshape( cell2mat(value), [ gridLength(myDomain), size(obj)]);
end
value = reshape(permute(value, [1+(gridPermuteIdx), 1]), ...
[gridSize(gridPermuteIdx), size(obj)]);
end
function n = nargin(obj)
......@@ -134,8 +103,6 @@ classdef Function < quantity.Discrete
end
end
% -------------
% --- Mathe ---
%--------------
......
......@@ -19,28 +19,63 @@ classdef Operator < handle & matlab.mixin.Copyable
end
methods
function obj = Operator(A, varargin)
% OPERATOR initialization of an operator object
% obj = Operator(A, varargin) initializes an operator of the
% form
% A[x(t)](z) = A0(z) x(z,t) + A1(z) dz x(z,t) + ...
% A2(z) dz^2 x(z,t) + Ak dz^k x(z,t)
% The input parameter A can be:
% cell-array of doubles
% this is good to initialize operators with constant
% coefficients
% A[x](t) = a_0 x(t) + a_1 dt x(t) + ...
%
% cell-array of quantities
% this helps to initialize operators with variable
% coefficients:
% A[x(t)](z) = A0(z) x(z,t) + A1(z) dz x(z,t) + ...
% A2(z) dz^2 x(z,t) + Ak dz^k x(z,t)
%
%
%
if nargin > 0
prsr = misc.Parser();
prsr.addOptional('s', sym('s'));
prsr.addOptional('name', '');
prsr.addOptional('domain', quantity.Domain.empty());
prsr.parse(varargin{:});
s = prsr.Results.s;
%TODO assert that A has entries with the same size
if isa(A, 'sym') && symvar(A) == prsr.Results.s
% if the matrix A is a symbolic expression which
% contains the algebraic representation of the
% operator, then the object can be generated if the
% symbolic expression is converted into the right form
% TODO: this works only for constant coefficients:
% catch the case if the coefficients are variable:
assert(length(symvar(A)) == 1, 'Not yet implemented')
A = num2cell( double( polyMatrix.polynomial2coefficients(A, prsr.Results.s) ), ...
[1, 2]);
end
if iscell(A)
for k = 1 : numel(A)
if isnumeric(A{k})
obj(k).coefficient = ...