Commit cca9aa78 authored by Ferdinand Fischer's avatar Ferdinand Fischer
Browse files

Changed implementation of integration for "Discretes". The input arguments are...

Changed implementation of integration for "Discretes". The input arguments are now consitent with the matlab "int" function.
parent 606ec16c
......@@ -47,7 +47,7 @@ classdef (InferiorClasses = {?quantity.Symbolic, ?quantity.Operator}) Discrete
% size(valueOriginal{it}) == gridSize
% OR
% 2) adouble-array with
% size(valueOriginal) == [gridSize, size(quantity)]
% size(valueOriginal) == [gridSize, size(quantity)]
% Furthermore, 'gridName' must be part of the name-value-pairs
% in varargin. Additional parameters can be specified using
% name-value-pair-syntax in varargin.
......@@ -155,10 +155,10 @@ classdef (InferiorClasses = {?quantity.Symbolic, ?quantity.Operator}) Discrete
obj = reshape(obj, objSize);
end
end% Discrete() constructor
%---------------------------
% --- getter and setters ---
%---------------------------
%---------------------------
function i = isConstant(obj)
% the quantity is interpreted as constant if it has no grid or
% it has a grid that is only one point.
......@@ -190,7 +190,7 @@ classdef (InferiorClasses = {?quantity.Symbolic, ?quantity.Operator}) Discrete
end
obj.gridName = name;
end
function set.grid(obj, grid)
if ~iscell(grid)
grid = {grid};
......@@ -285,7 +285,7 @@ classdef (InferiorClasses = {?quantity.Symbolic, ?quantity.Operator}) Discrete
if isempty(a)
if nargin > 1
varargin{1}.assertSameGrid(varargin{2:end});
end
end
return;
else
referenceGridName = a(1).gridName;
......@@ -328,9 +328,9 @@ classdef (InferiorClasses = {?quantity.Symbolic, ?quantity.Operator}) Discrete
else
referenceGridName = a(1).gridName;
referenceGrid = a(1).grid;
referenceGridSize = a(1).gridSize(referenceGridName);
referenceGridSize = a(1).gridSize(referenceGridName);
end
for it = 1 : numel(varargin)
if isempty(varargin{it})
continue;
......@@ -407,7 +407,7 @@ classdef (InferiorClasses = {?quantity.Symbolic, ?quantity.Operator}) Discrete
% X3 ...]' when any of X1, X2, X3, etc. is an object.
%
% See also HORZCAT, CAT.
c = cat(2, a, varargin{:});
c = cat(2, a, varargin{:});
end
function c = vertcat(a, varargin)
%VERTCAT Vertical concatenation.
......@@ -448,27 +448,27 @@ classdef (InferiorClasses = {?quantity.Symbolic, ?quantity.Operator}) Discrete
% B = CAT(DIM,A1,A2,A3,A4,...) concatenates the input
% arrays A1, A2, etc. along the dimension DIM.
%
% When used with comma separated list syntax, CAT(DIM,C{:}) or
% When used with comma separated list syntax, CAT(DIM,C{:}) or
% CAT(DIM,C.FIELD) is a convenient way to concatenate a cell or
% structure array containing numeric matrices into a single matrix.
%
% Examples:
% a = magic(3); b = pascal(3);
% a = magic(3); b = pascal(3);
% c = cat(4,a,b)
% produces a 3-by-3-by-1-by-2 result and
% s = {a b};
% for i=1:length(s),
% for i=1:length(s),
% siz{i} = size(s{i});
% end
% sizes = cat(1,siz{:})
% produces a 2-by-2 array of size vectors.
%
%
% See also NUM2CELL.
% Copyright 1984-2005 The MathWorks, Inc.
% Built-in function.
if nargin == 1
objCell = {a};
objCell = {a};
else
objCell = [{a}, varargin(:)'];
......@@ -490,7 +490,7 @@ classdef (InferiorClasses = {?quantity.Symbolic, ?quantity.Operator}) Discrete
c = quantity.Discrete.empty(S);
return
else
obj = objCell{objIdx};
obj = objCell{objIdx};
end
for k = 1:numel(objCell(~isEmpty))
......@@ -505,7 +505,7 @@ classdef (InferiorClasses = {?quantity.Symbolic, ?quantity.Operator}) Discrete
if isempty(value)
M = zeros([prod(obj(1).gridSize), size(value(l))]);
end
M = reshape(M, [obj(1).gridSize, size(value)]);
M = reshape(M, [obj(1).gridSize, size(value)]);
o = quantity.Discrete( M, ...
'size', size(value), ...
'gridName', obj(1).gridName, ...
......@@ -520,7 +520,7 @@ classdef (InferiorClasses = {?quantity.Symbolic, ?quantity.Operator}) Discrete
[fineGrid, fineGridName] = getFinestGrid(objCell{~isEmpty});
for it = 1 : (numel(varargin) + 1) % +1 because the first entry is a
objCell{it} = objCell{it}.changeGrid(fineGrid, fineGridName);
end
end
assertSameGrid(objCell{:});
argin = [{dim}, objCell(:)'];
c = builtin('cat', argin{:});
......@@ -569,13 +569,13 @@ classdef (InferiorClasses = {?quantity.Symbolic, ?quantity.Operator}) Discrete
end
solution = objInverseTemp.on(rhs);
% solution = zeros(numel(obj), 1);
% for it = 1 : numel(obj)
% objInverseTemp = obj(it).invert(gridName);
% solution(it) = objInverseTemp.on(rhs(it));
% end
% solution = reshape(solution, size(obj));
% solution = zeros(numel(obj), 1);
% for it = 1 : numel(obj)
% objInverseTemp = obj(it).invert(gridName);
% solution(it) = objInverseTemp.on(rhs(it));
% end
% solution = reshape(solution, size(obj));
end
function inverse = invert(obj, gridName)
......@@ -611,7 +611,7 @@ classdef (InferiorClasses = {?quantity.Symbolic, ?quantity.Operator}) Discrete
myParser.parse(varargin{:});
variableGrid = myParser.Results.variableGrid;
myGridSize = [numel(variableGrid), ...
myGridSize = [numel(variableGrid), ...
numel(myParser.Results.initialValueGrid)];
% the time (s) vector has to start at 0, to ensure the IC.
......@@ -632,14 +632,14 @@ classdef (InferiorClasses = {?quantity.Symbolic, ?quantity.Operator}) Discrete
if numel(positiveVariableGrid) > 1
[resultGridPositive, odeSolutionPositive] = ...
ode45(@(y, z) obj(it).on(z), ...
positiveVariableGrid, ...
myParser.Results.initialValueGrid(icIdx), options);
positiveVariableGrid, ...
myParser.Results.initialValueGrid(icIdx), options);
end
if numel(negativeVariableGrid) >1
[resultGridNegative, odeSolutionNegative] = ...
ode45(@(y, z) obj(it).on(z), ...
negativeVariableGrid, ...
myParser.Results.initialValueGrid(icIdx), options);
negativeVariableGrid, ...
myParser.Results.initialValueGrid(icIdx), options);
end
if any(variableGrid == 0)
resultGrid = [flip(resultGridNegative(2:end)); 0 ; resultGridPositive(2:end)];
......@@ -682,8 +682,8 @@ classdef (InferiorClasses = {?quantity.Symbolic, ?quantity.Operator}) Discrete
assert(numel(values) == numel(gridName2Replace), ...
'gridName2Replace and values must be of same size');
% here substitution starts:
% The first (gridName2Replace{1}, values{1})-pair is
% here substitution starts:
% The first (gridName2Replace{1}, values{1})-pair is
% replaced. If there are more cell-elements in those inputs
% then subs() is called again for the remaining pairs
% (gridName2Replace{2:end}, values{2:end}).
......@@ -725,10 +725,10 @@ classdef (InferiorClasses = {?quantity.Symbolic, ?quantity.Operator}) Discrete
newValue = misc.diagNd(obj.on(newGridForOn), gridIndices);
newGrid = {newGridForOn{gridIndices(1)}, ...
newGridForOn{1:1:numel(newGridForOn) ~= gridIndices(1) ...
& 1:1:numel(newGridForOn) ~= gridIndices(2)}};
& 1:1:numel(newGridForOn) ~= gridIndices(2)}};
newGridName = {values{1}, ...
obj(1).gridName{1:1:numel(obj(1).gridName) ~= gridIndices(1) ...
& 1:1:numel(obj(1).gridName) ~= gridIndices(2)}};
& 1:1:numel(obj(1).gridName) ~= gridIndices(2)}};
else
% this is the default case. just grid name is
......@@ -790,7 +790,12 @@ classdef (InferiorClasses = {?quantity.Symbolic, ?quantity.Operator}) Discrete
% sich ungewöhnlich verhält, wenn man sie ohne idx argument
% aufruft.
if nargin == 1
value = 1:obj.gridSize;
if numel(obj.gridSize) == 1
value = zeros(obj.gridSize, 1);
else
value = zeros(obj.gridSize, 1);
end
if isempty(value)
value = 0;
end
......@@ -798,12 +803,15 @@ classdef (InferiorClasses = {?quantity.Symbolic, ?quantity.Operator}) Discrete
if ~iscell(varargin)
varargin = {varargin};
end
value = cellfun(@(v) v(varargin{:}), {obj.valueDiscrete});
value = reshape(value, size(obj));
value = cellfun(@(v) v(varargin{:}), {obj.valueDiscrete}, ...
'UniformOutpu', false);
valueSize = size(value{1});
value = reshape([value{:}], [valueSize(1:obj(1).nargin), size(obj)]);
end
end
function n = nargin(obj)
% FIXME: check if all funtions in this object have the same
% number of input values.
......@@ -910,7 +918,7 @@ classdef (InferiorClasses = {?quantity.Symbolic, ?quantity.Operator}) Discrete
end
end
function myLabel = labelHelper(gridNumber)
myLabel = ['$$', greek2tex(obj(rowIdx, columnIdx, figureIdx).gridName{gridNumber}), '$$'];
end
......@@ -999,6 +1007,26 @@ classdef (InferiorClasses = {?quantity.Symbolic, ?quantity.Operator}) Discrete
[newObj.grid] = deal(myGrid);
end
function [lowerBounds, upperBounds] = getBoundaryValues(obj, varargin)
% GETBOUNDARYVALUES returns the boundary values of the grid
% [lowerBounds, upperBounds] = getBoundaryValues(obj, gridName)
% returns the boundary values of the grid, specified by
% gridName
%
% [lowerBounds, upperBounds] = getBoundaryValues(obj) returns
% the boudary values of all grids defined for the object.
grids = obj(1).grid(obj.gridIndex(varargin{:}));
lowerBounds = zeros(1, numel(grids));
upperBounds = zeros(1, numel(grids));
for k = 1:numel(grids)
lowerBounds(k) = grids{k}(1);
upperBounds(k) = grids{k}(end);
end
end
end
%% math
......@@ -1103,7 +1131,7 @@ classdef (InferiorClasses = {?quantity.Symbolic, ?quantity.Operator}) Discrete
% misc.multArray is very efficient, but requires that the
% multiple dimensions of the input are correctly arranged.
[idx, permuteGrid] = computePermutationVectors(a, b);
parameters = struct(a(1));
parameters.name = [a(1).name, ' ', b(1).name];
parameters.grid = [...
......@@ -1130,7 +1158,7 @@ classdef (InferiorClasses = {?quantity.Symbolic, ?quantity.Operator}) Discrete
if b.gridIndex(parameters.gridName{it})
bGrid{b.gridIndex(parameters.gridName{it})} = ...
parameters.grid{it};
end
end
end
parameters = misc.struct2namevaluepair(parameters);
......@@ -1164,11 +1192,11 @@ classdef (InferiorClasses = {?quantity.Symbolic, ?quantity.Operator}) Discrete
objDiscreteReshaped = permute(reshape(objDiscreteOriginal, ...
[prod(obj(1).gridSize), size(obj)]), [2, 3, 1]);
invDiscrete = zeros([prod(obj(1).gridSize), size(obj)]);
parfor it = 1 : size(invDiscrete, 1)
invDiscrete(it, :, :) = inv(objDiscreteReshaped(:, :, it));
end
y = quantity.Discrete(reshape(invDiscrete, size(objDiscreteOriginal)),...
'size', size(obj), ...
'name', ['(', obj(1).name, ')^{-1}'], ...
......@@ -1208,11 +1236,11 @@ classdef (InferiorClasses = {?quantity.Symbolic, ?quantity.Operator}) Discrete
function x = mldivide(A, B)
% mldivide see doc mldivide of matlab:
% "x = A\B is the solution to the equation Ax = B. Matrices
% "x = A\B is the solution to the equation Ax = B. Matrices
% A and B must have the same number of rows."
x = inv(A)*B;
end
function x = mrdivide(B, A)
% mRdivide see doc mrdivide of matlab:
% "x = B/A solves the system of linear equations x*A = B for x.
......@@ -1319,6 +1347,10 @@ classdef (InferiorClasses = {?quantity.Symbolic, ?quantity.Operator}) Discrete
% gridName properties of obj for the "names" and returns its
% index as "idx" and its logical index as "log"
if nargin == 1
names = obj(1).gridName();
end
if ~iscell(names)
names = {names};
end
......@@ -1349,9 +1381,9 @@ classdef (InferiorClasses = {?quantity.Symbolic, ?quantity.Operator}) Discrete
if obj.isConstant && isempty(obj(1).gridName)
result = quantity.Discrete(zeros(size(obj)), ...
'size', size(obj), 'grid', obj(1).grid, ...
'gridName', obj(1).gridName, ...
'name', ['(d_{.}', obj(1).name, ')']);
'size', size(obj), 'grid', obj(1).grid, ...
'gridName', obj(1).gridName, ...
'name', ['(d_{.}', obj(1).name, ')']);
return
end
......@@ -1393,45 +1425,72 @@ classdef (InferiorClasses = {?quantity.Symbolic, ?quantity.Operator}) Discrete
'name', ['(d_{', gridName, '}', obj(1).name, ')']);
end
function I = int(obj, intGridName)
function I = int(obj, varargin)
% INT integrate
% INT(obj) is the definite integral of obj with respect to
% all of its independent variables over the grid obj(1).grid.
% INT(obj, intGridName) is the definite integral of obj with
% respect to the spatial coordinate intGridName, that has to
% INT(obj, gridName) is the definite integral of obj with
% respect to the spatial coordinate grid name, that has to
% be one of obj(1).gridName. The names must be a string or a
% array of strings.
% INT(obj, a, b)
intGridName = obj(1).gridName;
[a, b] = obj.getBoundaryValues;
if nargin == 1
intGridName = obj(1).gridName;
% see default settings.
elseif nargin == 2 || nargin == 4
% (obj, z) OR (obj, z, a, b)
intGridName = varargin{1};
elseif nargin == 3
% (obj, a, b)
a = varargin{1};
b = varargin{2};
end
if nargin > 2
error('Not yet implemented');
if numel(intGridName) > 1
obj = obj.int(intGridName{1}, a(1), b(1));
I = obj.int(intGridName{2:end}, a(2:end), b(2:end));
return
end
%% do the integration over one dimension
I = obj.copy();
[idxName, isGrid] = obj.gridIndex(intGridName); % get index of the variables that should be considered for integration
% get index of the variables that should be considered for
% integration
[idxGrid, isGrid] = obj.gridIndex(intGridName);
[domainIdx{1:obj(1).nargin}] = deal(':');
currentGrid = obj(1).grid{ idxGrid };
domainIdx{idxGrid} = find(currentGrid >= a(idxGrid) & currentGrid <= b(idxGrid));
myGrid = currentGrid(domainIdx{idxGrid});
for kObj = 1:numel(obj)
J = obj(kObj).on(); % get numerical values
for kName = 1:length(idxName) % iterate over all the integration dimensions
currentGrid = obj(kObj).grid{idxName(kName)};% currentGrid that should be used for the integration.
J = numeric.trapz_fast_nDim(currentGrid, J, idxName(kName));
J = numeric.trapz_fast_nDim(myGrid, obj(kObj).atIndex(domainIdx{:}), idxGrid);
% If the quantity is integrated, so that the first
% dimension collapses, this dimension has to be shifted
% away, so that the valueDiscrete can be set correctly.
if size(J,1) == 1
J = shiftdim(J);
end
if all(isGrid)
grdName = '';
grd = {};
newGrid = {};
else
grdName = obj(kObj).gridName{~isGrid};
grd = obj(kObj).grid{~isGrid};
newGrid = obj(kObj).grid{~isGrid};
end
% create result:
I(kObj).valueDiscrete = J;
I(kObj).gridName = grdName;
I(kObj).grid = grd;
I(kObj).grid = newGrid;
end
end
function result = cumInt(obj, varargin)
......@@ -1442,7 +1501,7 @@ classdef (InferiorClasses = {?quantity.Symbolic, ?quantity.Operator}) Discrete
% result = grid{1}^boundaryGridName ...
% f(..., zeta, ... ) dintGrid
% two input parser since some default values depend on
% two input parser since some default values depend on
% intGridName.
myParser1 = misc.Parser;
myParser1.addParameter('intGridName', obj(1).gridName{1});
......@@ -1464,7 +1523,7 @@ classdef (InferiorClasses = {?quantity.Symbolic, ?quantity.Operator}) Discrete
result = quantity.Discrete(F, 'size', size(obj), 'grid', myGrid, ...
'gridName', obj(1).gridName, 'name', ['int(', obj(1).name, ')']);
% substitute gridName from original gridName (= intGridName) to
% substitute gridName from original gridName (= intGridName) to
% boundaryGridName
if ~iscell(boundaryGridName)
boundaryGridName = {boundaryGridName};
......@@ -1557,7 +1616,7 @@ classdef (InferiorClasses = {?quantity.Symbolic, ?quantity.Operator}) Discrete
function maxValue = max(obj)
% max returns the maximal value of all elements of A over all
% variables as a double array.
maxValue = reshape(max(obj.on(), [], 1:obj(1).nargin), [size(obj)]);
maxValue = reshape(max(obj.on(), [], 1:obj(1).nargin), [size(obj)]);
end
function maxValue = MAX(obj)
......@@ -1568,20 +1627,20 @@ classdef (InferiorClasses = {?quantity.Symbolic, ?quantity.Operator}) Discrete
function minValue = min(obj)
% max returns the minimum value of all elements of A over all
% variables as a double array.
minValue = reshape(min(obj.on(), [], 1:obj(1).nargin), [size(obj)]);
minValue = reshape(min(obj.on(), [], 1:obj(1).nargin), [size(obj)]);
end
function absQuantity = abs(obj)
% abs returns the absolut value of the quantity as a quantity
absQuantity = quantity.Discrete(abs(obj.on()), ...
'grid', obj(1).grid, 'gridName', obj(1).gridName, ...
'size', size(obj), 'name', ['|', obj(1).name, '|']);
'size', size(obj), 'name', ['|', obj(1).name, '|']);
end
function meanValue = mean(obj)
% max returns the mean value of all elements of A over all
% variables as a double array.
meanValue = reshape(mean(obj.on(), 1:obj(1).nargin), [size(obj)]);
meanValue = reshape(mean(obj.on(), 1:obj(1).nargin), [size(obj)]);
end
function f = volterra(K, x, varargin)
......@@ -1610,13 +1669,13 @@ classdef (InferiorClasses = {?quantity.Symbolic, ?quantity.Operator}) Discrete
ip.parse2ws(varargin{:});
if ip.isDefault('variable') && x.nargin > 1
error(['For x quantities wrt. two independent' ...
error(['For x quantities wrt. two independent' ...
' variables, the name of the variable has to be' ...
'parametrized explicitly']);
end
%% prepare numerical data
%% prepare numerical data
% do the multiplication under the integration and safe the
% information for the grid:
v = K*x;
......@@ -1626,7 +1685,7 @@ classdef (InferiorClasses = {?quantity.Symbolic, ?quantity.Operator}) Discrete
t.log = ~s.log;
t.grid = v(1).grid{t.log};
t.name = v(1).gridName{t.log};
V = v.on();
V = v.on();
% check if s is the second independent variable in V
if s.idx == 1 % permutation needed in this case!
......@@ -1659,17 +1718,17 @@ classdef (InferiorClasses = {?quantity.Symbolic, ?quantity.Operator}) Discrete
- numeric.trapz_fast_nDim(s.grid(1:tIdx), V_t(1:tIdx, :, :)));
end
end
if fixedLowerBound
f = quantity.Discrete(F, 'size', size(v), 'grid', {t.grid}, ...
'gridName', {t.name}, 'name', ['volterra(', K(1).name, x(1).name, ')']);
'gridName', {t.name}, 'name', ['volterra(', K(1).name, x(1).name, ')']);
else
f = quantity.Discrete(F, 'size', size(v), 'grid', {t.grid, s.grid}, ...
'gridName', {t.name, s.name}, 'name', ['volterra(', K(1).name, x(1).name, ')']);
end
end
function q = obj2value(obj)
% for a case with a 3-dimensional grid, the common cell2mat
% with reshape did not work. So it has to be done manually:
......@@ -1812,7 +1871,7 @@ classdef (InferiorClasses = {?quantity.Symbolic, ?quantity.Operator}) Discrete
function [idx, permuteGrid] = computePermutationVectors(a, b)
% Computes the required permutation vectors to use
% misc.multArray for multiplication of matrices
uA = unique(a(1).gridName);
assert(numel(uA) == numel(a(1).gridName), 'Gridnames have to be unique!');
uB = unique(b(1).gridName);
......
......@@ -493,10 +493,15 @@ classdef Symbolic < quantity.Function
'name', ['sqrtm(', x(1).name, ')']);
end
function D = diff(obj, k)
function D = diff(obj, k, gridName)
if nargin == 1
k = 1;
end
if nargin == 3
error('Not yet implemented')
end
% TODO: specify for which variable it should be differentiated
D = obj.copy();
[D.name] = deal(['(d_{' char(obj(1).variable) '} ' D(1).name, ')']);
......@@ -656,45 +661,12 @@ classdef Symbolic < quantity.Function
'variable', obj(1).variable);
end
% function result = cumInt(obj, varargin)
% % cumInt performes the integral
% % result = int_boundaryGridName(1)^boundaryGridName(2) ...
% % f(..., zeta, ... ) dintGrid
% % or if numel(boundaryGridName) == 1
% % result = grid{1}^boundaryGridName ...
% % f(..., zeta, ... ) dintGrid
%
% % two input parser since some default values depend on
% % intGridName.
% myParser1 = misc.Parser;
% myParser1.addParameter('intGridName', obj(1).gridName{1});
% myParser1.parse(varargin{:});
% intGridName = myParser1.Results.intGridName;
% intGridIdx = obj.gridIndex(intGridName);
% intVariable = obj(1).variable(intGridIdx);
%
% myParser2 = misc.Parser;
% myParser2.addParameter('boundaryGridName', {intGridName});
% myParser2.parse(varargin{:});
% boundaryGridName = myParser2.Results.boundaryGridName;
% if ~iscell(boundaryGridName)
% boundaryGridName = {0, boundaryGridName};
% end
% if numel(boundaryGridName) == 1
% boundaryGridName = {0, boundaryGridName{1}};
% end
% for it = 1 : numel(boundaryGridName)
% if ischar(boundaryGridName{it})
% boundaryValue{it} = obj(1).variable(obj.gridIndex(boundaryGridName{it}));
% else
% boundaryValue{it} = boundaryGridName{it};
% end
% end
%
% % integrate
% resultSymbolic = int(obj.sym(), intVariable, boundaryValue{1}, boundaryValue{it});
% result = quantity.Symbolic(resultSymbolic, 'name', ['int(', obj(1).name, ')'], ...
% 'grid', obj(1).grid, 'variabe', obj(1).variable);
%% TODO das verhalten von int und cumint an das von quantity.Discrete anpassen!
% function C = cumint(obj, z, a, b)
% z = obj(1).grid{1};
% C = obj.int(z, z(1), obj.variable);
% end
function C = int(obj, z, a, b)
......
......@@ -582,6 +582,20 @@ testCase.verifyEqual(a(end), A.atIndex(end));
testCase.verifyEqual(a(1), A.atIndex(1));
testCase.verifyEqual(a(23), A.atIndex(23));
y = linspace(0,2,51);
b1 = sin(z * pi * y);
b2 = cos(z * pi * y);
B = quantity.Discrete({b1; b2}, 'grid', {z, y}, 'gridName', {'z', 'y'});
B_1_y = B.atIndex(1,1);
b_1_y = [sin(0); cos(0)];
testCase.verifyTrue(numeric.near(squeeze(B_1_y(1,1,:)), b_1_y));
B_z_1 = B.atIndex(':',1);
testCase.verifyTrue(all(B_z_1(:,1,1) == 0));
testCase.verifyTrue(all(B_z_1(:,:,2) == 1));
end
function testGridJoin(testCase)
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment