basisChebyshev class

Defines a class to represent a univariate Chebyshev basis.

Objects of this class are of type 'handle', useful for passing the basis by reference.

Objects of class basisChebyshev have the following properties:

Object of class basisChebyshev have the following methods:

Last updated: October 4, 2014.

Copyright (C) 2013-2014 Randall Romero-Aguilar

Licensed under the MIT license, see LICENSE.txt

Contents

classdef basisChebyshev < handle
    properties (SetAccess = protected)
        nodes       % nodes for own dimension
        D           % operators to compute derivative
        I           % operators to compute integrals
    end

    properties (SetAccess = public)
        n  =  3     % number of nodes
        a  = -inf     % lower limits
        b  =  inf     % upper limits
        nodetype = 'gaussian'   % type of nodes
        varname = 'V0'             % optional name for variable
        WarnOutOfBounds = true    % warns if interpolating outsite [a,b] interval
    end



    methods

basisChebyshev

        function B = basisChebyshev(...
                n,...           scalar, number of nodes
                a,...           scalar, lower bound
                b,...           scalar, upper bound
                nodetype,...    string, 'gaussian','endpoint', or 'lobatto'
                varname)%       string, name for variable

Constructor for basisChebyshev

B = basisChebyshev(n,a,b,nodetype,varname)

The following inputs are required:

Optional inputs

If no inputs are provided (needed to preallocate array of basis)

            if nargin ==0
                B.n = 3;
                B.a = -1;
                B.b = 1;
                return
            end

SET PROPERTIES

            B.n = n;
            B.a = a;
            B.b = b;

Set nodetype

            if nargin > 3 && ~isempty(nodetype)
                B.nodetype = lower(nodetype);
            end

Set variable name

            if nargin==5
                B.varname = varname;
            end

Check validity of inputs and set nodes

            B.CheckValidity;
            B.SetNodes;

        end %basisChebyshev1

copy

        function Bcopy = copy(B)

Bcopy = B.copy

Makes a (value) copy of basis B. Since class basisChebyshev is a handle, in this example

B2 = B;
B3 = B.copy;
B2.a = 0;
B3.n = 9;

the line B2.a = 0 sets the lower limit a to zero in both B2 and B (B2 is just a pointer to B), whereas the line B3.n = 9 does not modify B (on assignment, B3 is simply a copy of B, not a pointer to B).

            Bcopy = basisChebyshev(B.n,B.a,B.b,B.nodetype, B.varname);
        end

checkValidity

        function CheckValidity(B)

B.CheckValidity

Checks the consistency of some basis B parameters. Called by constructor to ensure the following conditions :

            if ~isscalar(B.a); error('Parameter ''a'' must be scalar'); end
            if ~isscalar(B.b); error('Parameter ''b'' must be scalar'); end
            if ~isscalar(B.n); error('Parameter ''n'' must be scalar'); end

            if B.a > B.b;  error('Lower bound must be less than upper bound'), end
            if B.n < 2;    error('n must be greater than one'), end

            if ~ismember(lower(B.nodetype),{'gaussian' 'endpoint' 'lobatto'})
                warning(strcat('Unknown nodetype ',B.nodetype,': using gaussian instead'))
                B.nodetype = 'gaussian';
            end

        end %CheckValidity

SetNodes

        function SetNodes(B)

B.SetNodes

Computes the nodes of the basis B, depending on B.nodetype, which can be 'gaussian', 'endpoint', or 'lobatto'. This method is called by the constructor.

            n = B.n;
            a = B.a;
            b = B.b;

            s=(b-a)/2;
            m=(b+a)/2;

            switch B.nodetype
                case 'gaussian'   % Gaussian nodes
                    k = pi*(0.5:(n-0.5))';
                    x = m-cos(k/n)*s;
                case 'endpoint'     % Extend nodes to endpoints
                    k = pi*(0.5:(n-0.5))';
                    x = m-cos(k/n)*s;
                    aa = x(1);
                    bb = x(end);
                    x = (bb*a-aa*b)/(bb-aa)+(b-a)/(bb-aa)*x;
                case 'lobatto'     % Lobatto nodes
                    k = pi*(0:(n-1))';
                    x = m - cos(k/(n-1))*s;
            end

            x(abs(x)<eps) = 0;
            B.nodes = x;
        end %SetNodes

DiffOperator

        function DiffOperator(B,...
                orderD,...            order of derivative
                integrate...          integral if true
                )

B.DiffOperator(orderD,integrate)

Compute operators to differentiate and integrate. This method is needed by Interpolation when input order is not zero; it should not be called directly by the user.

It updates the operators in B.D (if integrate is false) or in B.I (otherwise). Input orderD is the maximum order to compute.

            if nargin < 2, orderD = 1; end
            if nargin < 3, integrate = false; else integrate = logical(integrate); end
            if orderD < 0, error('In DiffOperator: order must be nonnegative'), end

            derivative = ~integrate;


            if orderD==0, return, end


            % Use previously stored values for orders
            if (derivative && length(B.D) >= orderD), return, end
            if (integrate  && length(B.I) >= orderD), return, end


            n = B.n;
            a = B.a;
            b = B.b;

            if(n-orderD < 2 && derivative)
                warnmsg = strcat('Insufficient nodes: truncating order to = ',num2str(n-2));
                warning(warnmsg)
                orderD = n-2;
            end

            if derivative
                %===============TAKE DERIVATIVE=========
                if isempty(B.D)
                    [j,i] = meshgrid(1:n,1:n);
                    rc = mod(i+j,2) & (j>i);
                    d = zeros(n,n);
                    d(rc) = (4/(b-a))*(j(rc)-1);
                    d(1,:) = d(1,:)/2;
                    d = sparse(d(1:end-1,:));
                    B.D{1} = d;
                else
                    d = B.D{1};
                end

                h = length(B.D) + 1;  % index of 1st missing element in B.D
                for ii = h:orderD
                    B.D{ii} = d(1:n-ii,1:n-ii+1) * B.D{ii-1};
                end
            else
                %===============TAKE INTEGRAL=========
                nn = n + orderD;
                i = (0.25*(b-a))./(1:nn);
                d = sparse(diag(i) + diag(-i(1:nn-2),2));  %good
                d(1,1) = 2*d(1,1);

                d0 = ((-1).^(0:nn-1)).*sum(d);

                if isempty(B.I)
                    B.I{1} = [d0(1:n);d(1:n,1:n)];
                end
                h = length(B.I)+1;    % index of 1st missing element in B.I

                for ii = h:orderD
                    B.I{ii}=[d0(1:n+ii-1);d(1:n+ii-1,1:n+ii-1)] * B.I{ii-1};
                end

            end
        end

Interpolation

        function Phi = Interpolation(B,... ##CompEcon:  chebbas.m
                x,...                      k-vector of the evaluation points
                order,...                  order of derivative
                integrate)%                integrate if true
Phi = B.Interpolation(x,order,integrate)

Computes interpolation matrices Phi for basis B.

Its inputs are

Output Phi returns the interpolation k.n.h array, where n is number of basis functions.

See also basis.Evaluate.

            if nargin<2 || isempty(x)
                x = B.nodes;  % use basis nodes if x is missing
                hasArg_x = false;
                nx = B.n;
            else
                x = x(:);   % make sure x is column vector;
                hasArg_x = true;
                nx = numel(x);
            end

            if nargin<3, order = 0; end
            if nargin<4, integrate = false; end

            maxorder = max(order);
            nn = B.n + maxorder * integrate;

            % check if out of bounds
            if hasArg_x && B.WarnOutOfBounds
                isBelow =  x < B.a - eps(B.a);
                isAbove =  x > B.b + eps(B.b);
                isOut = isBelow | isAbove;

                if any(isOut)
                    xout = x(isOut);
                    error(['OutOfBounds!: Interpolating %s = %f failed because basis defined only in [%4.2f, %4.2f]\n',...
                        'To allow interpolation outside this interval, set basis field ''WarnOutOfBounds'' to false'],...
                        B.varname,xout(1),B.a,B.b)
                end


            end


            % Compute order 0 interpolation matrix
            if hasArg_x || ~strcmp(B.nodetype,'gaussian') % evaluate at arbitrary nodes
                m = length(x);
                z = (2/(B.b - B.a))* (x-(B.a + B.b)/2);
                bas = zeros(m,nn);
                bas(:,1) = 1;
                bas(:,2) = z;
                z = 2*z;
                for i = 3:nn
                    bas(:,i) = z.*bas(:,i-1) - bas(:,i-2);
                end
            else  % evaluate at usual Gaussian nodes
                temp=((B.n-0.5):-1:0.5)';
                bas  = cos((pi/B.n) * temp *(0:nn-1));
            end


            % Compute Phi ////////////////////////
            %Phi = cell(length(order),1);
            Phi = zeros(nx,B.n,length(order));

            done = nan(length(order),1);

            for ii = 1:length(order)
                if any(order(ii)==done)  %done it already
                    %Phi{ii} = Phi{find(order(ii)==done,1)};
                    Phi(:,:,ii) = Phi(:,:,find(order(ii)==done,1));
                else

The operators I and D are saved into the basis when created. The try/catch sequence avoids calling the DiffOperator method (to speed up execution)

                    if order(ii)==0
                        %Phi{ii} = bas;
                        Phi(:,:,ii) = bas;
                    elseif integrate
                        %Phi{ii} = bas(:,1:B.n+order(ii)) * B.I{order(ii)};
                        try
                            Phi(:,:,ii) = bas(:,1:B.n+order(ii)) * B.I{order(ii)};
                        catch
                            DiffOperator(B,maxorder,integrate);
                            Phi(:,:,ii) = bas(:,1:B.n+order(ii)) * B.I{order(ii)};
                        end
                    else % derivative
                        %Phi{ii} = bas(:,1:B.n-order(ii)) * B.D{order(ii)};
                        try
                            Phi(:,:,ii) = bas(:,1:B.n-order(ii)) * B.D{order(ii)};
                        catch
                            DiffOperator(B,maxorder,integrate);
                            Phi(:,:,ii) = bas(:,1:B.n-order(ii)) * B.D{order(ii)};
                        end
                    end
                    Phi(abs(Phi)<eps(10)) = 0;

                end
                done(ii) = order(ii);

            end


        end %Interpolation

Evaluate

        function y = Interpolate(B,...      ##CompEcon:  chebbas.m
                coef,...             coefficient matrix
                varargin...          inputs for Interpolation method
                )
y = B.Evaluate(coef,x,order,integrate)

Evaluates the interpolated functions defined by basis B and coefficients coef. Inputs x, order and integrate are as required by Interpolation method, while coef is a n.m matrix (n basis functions and m interpolated functions)

Output y returns the interpolated functions as a k.m.h array, where k is the number of evaluation points, m the number of functions (number of columns in coef), and h is number of order derivatives.

            if nargin<2, error('Missing ''coef'' input');end

            Phi = B.Interpolation(varargin{:});

            nx = size(Phi,1);  % number of evaluation points
            ny = size(coef,2); % number of evaluated functions
            no = size(Phi,3);  % number of order evaluations

            y = zeros(nx,ny,no);

            for h = 1:no
                y(:,:,h) = Phi(:,:,h) * coef;
            end

            if ny==1  % only one function
                y = squeeze(y);
            end

        end %Evaluate

plot

         function plot(B,ii)
B.plot(ii)

Plots the basis functions. Input ii is optional, to plot only the specified basis functions.

            if nargin<2
                ii = 1:B.n;
            else
                ii(ii>B.n) = [];
            end
            x = linspace(B.a,B.b,1+min(120,10*B.n))';
            Phi = B.Interpolation(x);
            Phi = Phi{1};
            hold off
            plot(x,0*x,'k'); hold on
            plot(x,Phi(:,ii),'LineWidth',1.75)
            plot(B.nodes,0*B.nodes,'ro')
            title(B.varname)
            hold off

        end

Nearest node

        function [xnode,idx] = nearestNode(B,x)
[xnode,idx] = nearestNode(B,x)

For a given value x, it finds the nearest node in basis B. This method is useful in simulations, when an initial guess solution at x can be given by xnode. idx is the associated index, e.g.

xnode = B.nodes(idx,:)'
            gap = abs(B.nodes - x);
            [~, idx] = min(gap);
            xnode = B.nodes(idx);

        end

display

        function display(B)

Prints a summary of the basis

            for i=1:numel(B)
                fprintf('\nChebyshev Basis of one dimension with %s nodes\n\n',B(i).nodetype)

                fprintf('\t%-22s %-12s %-16s\n','    Variable','# of nodes','    Interval')
                fprintf('\t%-22s     %-8.0f [%6.2f, %6.2f]\n',B(i).varname,B(i).n,B(i).a,B(i).b)
                fprintf('\n\n')
            end
        end

Reset

        function Reset(B)

Following a change in the basis definition, the nodes must be recomputed and the fields D and I are no longer valid. This method is called by the setter methods, so the user does not need to call it explicitly.

            B.CheckValidity;
            B.SetNodes;
            B.I = [];
            B.D = [];
        end

Setter methods

The following setter methods allow changing the basis definition (parameters n,a,b,nodetype). Changes to these parameters require recomputing the nodes and deleting the operators D and I.

Adjusting the lower bound

        function set.a(B,value)
            B.a = value;
            B.Reset;
        end

Adjusting the upper bound

        function set.b(B,value)
            B.b = value;
            B.Reset;
        end

Adjusting the number of nodes

        function set.n(B,value)
            B.n = value;
            B.Reset;
        end

Adjusting the type of nodes

        function set.nodetype(B,value)
            B.nodetype = value;
            B.Reset;
        end
    end %methods


end %classdef