classdef edgefx
    %   EDGEFX: Edgefunction class
    %   Constructs edgefunctions that are based on numerous geomteric and
    %   topographic criteria that guide the spatially variability of elemental
    %   resolution when building a mesh.
    %   Copyright (C) 2018  Keith Roberts & William Pringle
    %
    %   The following properties are available for input to the edgefx method:
    %      Keyword     Default Val	Variable type
    %      'dis'        defval;     scalar
    %      'fs'         defval;     scalar
    %      'wl'         defval;     scalar/array
    %      'slp'        defval;     scalar/array
    %      'ch'         defval;     scalar
    %      'min_el_ch'  100;     	scalar
    %      'AngOfRe'    60;        	scalar
    %      'max_el'     inf;        scalar/array
    %      'max_el_ns'  inf;     	scalar
    %      'g'          0.20;       scalar/array
    %      'geodata'    defval;     geodata class
    %      'lmsl'       defval;     geodata class
    %      'dt'         -1;         scalar
    %      'fl'         defval;     scalar
    %      'Channels'   defval;   	cell array
    %      'h0'         defval;     scalar
    %      [defval = 0; % placeholder value if arg is not passed.]
    %
    %      The 'wl','slp','max_el','g' options inputs can be scalars to apply
    %      the parameter globally or they can be arrays to bound each parameter
    %      value within topographic elevation bounds, using the following stucture:
    %      [parameter_value1 z_min1 z_max1;
    %       parameter_value2 z_min2 z_max2;
    %       ...
    %       parameter_valuen z_minn z_maxn];
    %
    %       where, z_min is the minimum elevation bound and z_max is the maximum elevation bound.
    %        - similar to axis bounds on matlab plots such as caxis, xlim, ylim, ...
    %        - z is oriented same as the DEM, i.e., +ve value of z is above datum (overland)
    %
    %   This program is free software: you can redistribute it and/or modify
    %   it under the terms of the GNU General Public License as published by
    %   the Free Software Foundation, either version 3 of the License, or
    %   (at your option) any later version.
    %
    %   This program is distributed in the hope that it will be useful,
    %   but WITHOUT ANY WARRANTY; without even the implied warranty of
    %   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    %   GNU General Public License for more details.
    %
    %   You should have received a copy of the GNU General Public License
    %   along with this program.  If not, see <http://www.gnu.org/licenses/>.
    properties
        nx % number of x coords
        ny % number of y coords
        x0y0 % 2-tuple of bot. left of domain

        lmsl % lmsl boundary as a geodata class

        bbox
        boubox
        used % edge function keywords

        dis  % percent the edgelength changes in space
        fd   % distance function
        hhd  % matrix containing distance fx values.
        fs   % number of elements to resolve a feature
        fsd  % matrix containing feature size fx values.
        wl   % wavelength
        wld  % matrix containing wavelength fx values.
        slp  % slope edge function
        slpd % matrix containing filtered slope fx values.
        fl   % slope filter parameters
        ch   % channels
        chd  % matrix containing channel fx values.
        AngOfRe % Angle of reslope
        g    % max allowable grade of mesh.
        dt   % theoretical simulateable timestep

        F    % edge function in gridded interpolant format.

        h0    % min. resolution of edgelength function in meters
        gridspace % edgelength function resolution in WGS84 degrees.
        max_el % max. resolution in domain
        max_el_ns % max. resolution +-<0.01 from shoreline
        boudist % distance in WGS84 degrees to boundary of mesh

        min_el_ch % min. element size along the channel
        Channels % cell array of streams.

    end

    methods

        %% Class constructor/pass it a shape class and edgefx params.
        function obj = edgefx(varargin)
            % Check for m_map dir
            M_MAP_EXISTS=0 ;
            if exist('m_proj','file')==2
              M_MAP_EXISTS=1 ;
            end
            if M_MAP_EXISTS~=1
              error('Where''s m_map? Chief, you need to read the user guide')
            end

            % Check for utilties dir
            UTIL_DIR_EXISTS=0 ;
            if exist('inpoly.m','file')
              UTIL_DIR_EXISTS=1 ;
            end
            if UTIL_DIR_EXISTS~=1
              error('Where''s the utilities directory? Chief, you need to read the user guide')
            end

            p = inputParser;

            defval = 0; % placeholder value if arg is not passed.
            % add name/value pairs
            addOptional(p,'dis',defval);
            addOptional(p,'fs',defval);
            addOptional(p,'wl',defval);
            addOptional(p,'slp',defval);
            addOptional(p,'ch',defval);
            addOptional(p,'min_el_ch',100);
            addOptional(p,'AngOfRe',60);
            addOptional(p,'max_el',inf);
            addOptional(p,'max_el_ns',inf);
            addOptional(p,'g',0.20);
            addOptional(p,'geodata',defval)
            addOptional(p,'lmsl',defval)
            addOptional(p,'dt',-1);
            addOptional(p,'fl',defval);
            addOptional(p,'Channels',defval);
            addOptional(p,'h0',defval);

            % parse the inputs
            parse(p,varargin{:});
            % store the inputs as a struct
            inp = p.Results;
            % get the fieldnames of the edge functions
            inp = orderfields(inp,{'max_el','min_el_ch','AngOfRe',...
                'geodata','lmsl','Channels',...
                'dis','fs','fl','g','max_el_ns',...
                'wl','slp','ch','dt','h0'});
            fields = fieldnames(inp);
            % loop through and determine which args were passed.
            % also, assign reasonable default values if some options were

            % not assigned.
            for i = 1 : numel(fields)
                type = fields{i};
                switch type
                    % parse aux options first
                    case('max_el')
                        obj.max_el = inp.(fields{i});
                        if ~isempty(obj.max_el)
                            obj.max_el = inp.(fields{i});
                        end
                    case('max_el_ns')
                        obj.max_el_ns= inp.(fields{i});
                        if obj.max_el_ns ~=0
                            obj.max_el_ns = inp.(fields{i});
                        end
                        % obj.max_el_ns = obj.max_el_ns/111e3;
                    case('g')
                        obj.g= inp.(fields{i});
                        if obj.g ~=0
                            obj.g = inp.(fields{i});
                        end
                    case('fl')
                        obj.fl = inp.(fields{i});
                        if obj.fl ~=0
                            obj.fl = inp.(fields{i});
                        end
                    case('geodata')
                        if isa(inp.(fields{i}),'geodata')
                            feat = inp.(fields{i});
                        end
                    case('lmsl')
                        if isa(inp.(fields{i}),'geodata')
                            obj.lmsl = inp.(fields{i});
                        end
                    case('dt')
                        obj.dt=inp.(fields{i});
                        if obj.dt >= 0
                            obj.dt = inp.(fields{i});
                        end
                    case('h0')
                        obj.h0=inp.(fields{i});
                        if obj.h0 ~= 0
                            obj.h0 = inp.(fields{i});
                        else
                            obj.h0 = feat.h0;
                        end
                    case('min_el_ch')
                        obj.min_el_ch=inp.(fields{i});
                        if obj.min_el_ch ~= 100
                            obj.min_el_ch = inp.(fields{i});
                        end
                    case('AngOfRe')
                        obj.AngOfRe=inp.(fields{i});
                        if obj.AngOfRe ==60
                            obj.AngOfRe = inp.(fields{i});
                        end
                    case('Channels')
                        obj.Channels=inp.(fields{i});
                        if ~isempty(obj.Channels) ~=0
                            obj.Channels = inp.(fields{i});
                        end
                end
            end

            % kjr april 28, 2018-form mesh size grid on-the-fly
            obj.fd       = @dpoly;
            obj.x0y0     = feat.x0y0 + sqrt(eps);
            obj.gridspace = obj.h0/111e3;
            obj.nx       = ceil(abs(feat.x0y0(1)-feat.bbox(1,2))/obj.gridspace);
            obj.ny       = ceil(abs(feat.x0y0(2)-feat.bbox(2,2))/obj.gridspace);
            obj.bbox     = feat.bbox;
            obj.boubox   = feat.boubox;

            % WJP: Do Mercator projection for calculating
            % distances from shoreline
            if feat.bbox(1,2) > 180
                m_proj('mercator','lat', [-88 90], 'long',[0 360]);
            else
                m_proj('mercator','lat', [-88 90],'long',[-180 180]);
            end
            % now turn on the edge functions
            for i = 1 : numel(fields)
                type = fields{i};
                switch type
                    case('dis')
                        obj.dis = inp.(fields{i});
                        if obj.dis ~= 0
                            disp('Building distance function...');
                            obj = distfx(obj,feat);
                            obj.used{end+1} = 'dis';
                        end
                    case('fs')
                        obj.fs  = inp.(fields{i});
                        if obj.fs ~= 0
                            disp('Building feature size function...');
                            try
                                obj = featfx(obj,feat);
                                obj.used{end+1} = 'fs';
                            catch
                                disp('man, no medial points!')
                            end
                        end
                    case('wl')
                        obj.wl  = inp.(fields{i});
                        if obj.wl(1)~=0 && ~isempty(feat.Fb)
                            disp('Building wavelength function...');
                            obj = wlfx(obj,feat);
                            obj.used{end+1} = 'wl';
                        end
                    case('slp')
                        obj.slp  = inp.(fields{i});
                        if obj.slp(1) ~= 0 && ~isempty(feat.Fb)
                            disp('Building slope function...');
                            obj = slpfx(obj,feat);
                            obj.used{end+1} = 'slp';
                        end
                    case('ch')
                        obj.ch  = inp.(fields{i});
                        if obj.ch ~= 0
                            disp('Building channel function...');
                            obj = chfx(obj,feat);
                            obj.used{end+1} = 'ch';
                        end
                    case{'g','geodata','lmsl','max_el','min_el_ch',...
                            'Channels','max_el_ns','h0','dt','fl','AngOfRe'}
                        % dummy to avoid warning
                    otherwise
                        warning('Unexpected edge function name/value pairs.')
                end
                if i==numel(fields)
                    obj = finalize(obj,feat);
                    disp('Finalized edge function!');
                end
            end
        end

        %% Traditional distance function, linear distance from coastline.
        function obj = distfx(obj,feat)
            [d,obj] = get_dis(obj,feat);
            obj.boudist = d;
            obj.hhd = obj.h0 + obj.dis*abs(d);
        end
        %% Feature size function, approximates width of nearshore geo.
        function obj = featfx(obj,feat)
            if isempty(feat.mainland) && isempty(feat.inner)
                % No medial points so use distance function using grade
                warning('No mainland or inner')
                xg = CreateStructGrid(obj);
                obj.fsd = xg*NaN;
                clearvars xg
                return
            end
            % Make sure we don't create a singularity on the coast in the
            % distance function!
            [d,obj] = get_dis(obj,feat);
            obj.boudist = d ;

            [xg,yg] = CreateStructGrid(obj);
            % use a harvestine assumption
            dx = obj.h0*cosd(yg(1,:)); % for gradient function
            dy = obj.h0;               % for gradient function
            % Calculate the gradient of the distance function.
            [ddy,ddx] = EarthGradient(d,dy,dx);
            d_fs = sqrt(ddx.^2 + ddy.^2);
            clearvars ddx ddy
            % WJP: This fix is to put a medial point in narrow channels
            rf = []; cf = [];
            for ii = 2:size(d,1)-1
                for jj = 2:size(d,2)-1
                    if d(ii,jj) < 0
                        if (d(ii-1,jj) >= 0 && ...
                                d(ii+1,jj) >= 0) || ...
                                (d(ii,jj-1) >= 0 && ...
                                d(ii,jj+1) >= 0)
                            rf(end+1) = ii;
                            cf(end+1) = jj;
                        end
                    end
                end
            end
            % Find singularties in the distance function that are
            % within the poly to get the medial axis.
            % Lets only create medial points in places that are
            % sufficiently far away from the coastline to capture the
            % feature and thus aren't spurious.
            d_fs = reshape(d_fs,[],1); d = reshape(d,[],1);
            lg = d_fs < 0.90  & d < -0.5*obj.h0 ;
            [r,c] = ind2sub([obj.nx obj.ny],find(lg));
            % create the coordinates using x0y0 + r*h0
            r = [r; rf']; c = [c; cf']; %WJP: add on "fixed points"
            x_kp = obj.x0y0(1) + (r-1)*obj.gridspace;
            y_kp = obj.x0y0(2) + (c-1)*obj.gridspace;
            clearvars lg r c cf rf
            % Ensure there are enough medial points nearby
            % to avoid surpurious medial points.
            % WJP: We want a line of points larger than around 7. This
            % corresponds to having three points back or forward up the
            % line. Let's check for those three closest points and ensure
            % they are within about co*h0 distance from each other.
            % co is the cutoff distance = 2*sqrt(2) (two diagonal dist)
            co = 2*sqrt(2);
            if length(x_kp) > 12
                [~, dmed] = WrapperForKsearch([x_kp,y_kp],[x_kp,y_kp],4);
                prune = dmed(:,2) > co*obj.h0 | dmed(:,3) > 2*co*obj.h0 | ...
                    dmed(:,4) > 3*co*obj.h0;
                x_kp( prune ) = [];
                y_kp( prune ) = [];
            end

            % Now get the feature size along the coastline
            if length(x_kp) > 12
                % Use KD-tree
                dPOS = 0*d;
                noblks = ceil(length(dPOS)*2*8*1e-9);
                blklen = floor(length(dPOS)/noblks);
                ns = 1;
                for blks = 1:noblks
                    if blks == noblks
                        ne = length(dPOS);
                    else
                        ne = ns + blklen - 1;
                    end
                    [~,dPOS(ns:ne)] = WrapperForKsearch([x_kp,y_kp],...
                        [xg(ns:ne)',yg(ns:ne)'],1);
                    ns = ne + 1;
                end
            else
                % No medial points so use distance function using grade
                warning('No medial points, resorting to distance function')
                d = reshape(d,obj.nx,[]);
                obj.fsd = obj.h0 + obj.g*abs(d);
                clear x_kp y_kp d d_fs dPOS xg yg
                return
            end
            % reshape back
            d = reshape(d,obj.nx,[]); dPOS = reshape(dPOS,obj.nx,[]);
            % Feature_size is distance from medial axis plus distance to
            % coastline. min_el is then feature_size*2/R where R is
            % number of elements to model the feature
            % WJP: d needs to be the absolute value because when we
            % include the floodplain since dPOS is not signed but d is,
            % they will cancel out producing really fine resolution
            W = dPOS+abs(d);
            if obj.fs < 0
                % automatic feature size
                fs_auto = min(-obj.fs,ceil(W/obj.h0));
                obj.fsd = 2*W./fs_auto;
            else
                obj.fsd = 2*W/obj.fs;
            end
            clear x_kp y_kp d d_fs dPOS xg yg
        end

        function [d,obj] = get_dis(obj,feat)
            % Function used by distfx and featfx to return distance
            % and make the Fdis interpolant
            d = feval(obj.fd,obj,feat);
            d = reshape(d,obj.nx,[]);
        end

        %% Wavelength edge function.
        function obj = wlfx(obj,feat)

            % interpolate DEM's bathy linearly onto our edgefunction grid.
            [xg,yg] = CreateStructGrid(obj);
            tmpz    = feat.Fb(xg,yg);
            if ~isempty(feat.Fb2)
               tmpz2 = feat.Fb2(xg,yg);
               tmpz(isnan(tmpz)) = tmpz2(isnan(tmpz));
               clear tmpz2
            end
            % Initialise wld
            obj.wld = NaN([obj.nx,obj.ny]);
            grav    = 9.807;
            period  = 12.42*3600; % M2 period in seconds

            for param = obj.wl'
                if numel(param)==1
                    % no bounds specified.
                    wlp = param(1);
                    z_min = -inf;
                    z_max = +inf;
                else
                    wlp = param(1);
                    z_min = param(2);
                    z_max = param(3);
                end
                % limit min. depth to 1 m
                twld = period*sqrt(grav*max(abs(tmpz),1))/wlp;
                limidx = (tmpz >= z_min & tmpz < z_max);
                % Set wld with depth mask applied
                obj.wld(limidx) = twld(limidx);
                clearvars twld
            end
            clearvars tmpz xg yg;
        end

      %% Topographic length scale/slope edge function.
        function obj = slpfx(obj,feat)

            [xg,yg] = CreateStructGrid(obj);

            tmpz    = feat.Fb(xg,yg);
            if ~isempty(feat.Fb2)
               tmpz2 = feat.Fb2(xg,yg);
               tmpz(isnan(tmpz)) = tmpz2(isnan(tmpz));
               clear tmpz2
            end
            tmpz(tmpz > 50) = 50; % ensure no larger than 50 m above land
            % use a Harvestine assumption
            dx = obj.h0*cosd(min(yg(1,:),85)); % for gradient function
            dy = obj.h0;               % for gradient function
            % lets filter the bathy to get only relevant features
            % loop over each set of bandpass filter lengths
            tmpz_f = zeros(size(tmpz));
            if obj.fl(1) < 0 && obj.fl(1) ~= -999
                disp('INFO: Rossby radius of deformation filter on.') ;
                rbfilt = abs(obj.fl(1));
                barot  = 1;
                if length(obj.fl) > 1 && obj.fl(2) < 0
                    disp('INFO: Using 1st-mode baroclinic Rossby radius.');
                    barot = 0;
                else
                    disp('INFO: Using barotropic Rossby radius.') ;
                end
                obj.fl = [];
                filtit = 1;
            elseif obj.fl(1) == 0
                disp('INFO: Slope filter is off.');
                obj.fl = [];
                tmpz_f = tmpz;
                filtit = 0 ;
            elseif obj.fl(1) == -999
                disp('INFO: Slope filter is LEGACY.');
                obj.fl = [];
                tmpz_f = tmpz;
                filtit = -999;
            else
                filtit = 0;
                for lambda = obj.fl'
                    if length(lambda) == 1 || lambda(2) == 0
                        % do a low pass filter
                        tmpz_ft  = filt2(tmpz,dy,lambda(1),'lp') ;
                    elseif all(lambda ~= 0)
                        % do a bandpass filter
                        tmpz_ft  = filt2(tmpz,dy,lambda,'bp') ;
                    else
                        % highpass filter not recommended
                        warning(['Highpass filter on bathymetry in slope' ...
                            'edgelength function is not recommended'])
                        tmpz_ft  = filt2(tmpz,dy,lambda(2),'hp') ;
                    end
                    tmpz_f = tmpz_f + tmpz_ft;
                end
            end

            % Rossby radius of deformation filter
            if filtit == 1
                tic
                bs = NaN([obj.nx,obj.ny]);
                % break into 10 deg latitude chunks, or less if higher res
                div = ceil(min(1e7/obj.nx,10*obj.ny/(max(yg(:))-min(yg(:)))));
                grav = 9.807;
                nb = ceil(obj.ny/div);
                n2s = 1;
                for jj = 1:nb
                    n2e = min(obj.ny,n2s + div - 1);
                    % Rossby radius of deformation filter
                    % See Shelton, D. B., et al. (1998): Geographical variability of the first-baroclinic Rossby radius of deformation. J. Phys. Oceanogr., 28, 433-460.
                    ygg = yg(:,n2s:n2e);
                    dxx = mean(dx(n2s:n2e));
                    f = 2*7.29e-5*abs(sind(ygg));
                    if barot
                        % barotropic case
                        c = sqrt(grav*max(1,-tmpz(:,n2s:n2e)));
                    else
                        % baroclinic case (estimate Nm to be 2.5e-3)
                        Nm = 2.5e-3;
                        c = Nm*max(1,-tmpz(:,n2s:n2e))/pi;
                    end
                    rosb = c./f;
                    clear f;
                    % update for equatorial regions
                    I = abs(ygg) < 5; Re = 6371e3;
                    twobeta = 4*7.29e-5*cosd(ygg(I))/Re;
                    rosb(I) = sqrt(c(I)./twobeta);
                    clear twobeta
                    % limit rossby radius to 10,000 km for practical purposes
                    rosb(rosb > 1e7) = 1e7;
                    % Keep lengthscales rbfilt * barotropic
                    % radius of deformation
                    rosb = min(10,max(0,floor(log2(rosb/dy/rbfilt))));
                    edges = double(unique(rosb(:)));
                    bst = rosb*0;
                    for i = 1:length(edges)
                        if edges(i) > 0
                            mult = 2^edges(i);
                            xl = 1; xu = obj.nx;
                            if (max(xg(:)) > 179 && min(xg(:)) < -179) || ...
                                    (max(xg(:)) > 359 && min(xg(:)) < 1)
                                % wraps around
                                xr = [obj.nx-mult/2+1:1:obj.nx xl:xu 1:mult/2];
                            else
                                xr = xl:xu;
                            end
                            yl = max(1,n2s-mult/2);
                            yu = min(obj.ny,n2e+mult/2);
                            if max(yg(:)) > 89 && yu == obj.ny
                                % create mirror around pole
                                yr = [yl:yu yu-1:-1:2*obj.ny-n2e-mult/2];
                            else
                                yr = yl:yu;
                            end
                            if mult == 2
                                tmpz_ft = filt2(tmpz(xr,yr),...
                                           [dxx dy],dy*2.01,'lp');
                            else
                                tmpz_ft = filt2(tmpz(xr,yr),...
                                           [dxx dy],dy*mult,'lp');
                            end
                            % delete the padded region
                            tmpz_ft(1:find(xr == 1,1,'first')-1,:) = [];
                            tmpz_ft(obj.nx+1:end,:) = [];
                            tmpz_ft(:,1:find(yr == n2s,1,'first')-1) = [];
                            tmpz_ft(:,n2e-n2s+2:end) = [];
                        else
                            tmpz_ft = tmpz(:,n2s:n2e);
                        end
                        [by,bx] = EarthGradient(tmpz_ft,dy,dx(n2s:n2e)); % get slope in x and y directions
                        tempbs  = sqrt(bx.^2 + by.^2);          % get overall slope
                        bst(rosb == edges(i)) = tempbs(rosb == edges(i));
                    end
                    bs(:,n2s:n2e) = bst;
                    n2s = n2e + 1;
                end
                toc
                % legacy filter
            elseif filtit==-999
                bs = NaN([obj.nx,obj.ny]);
                % Rossby radius of deformation filter
                f = 2*7.29e-5*abs(sind(yg));
                % limit to 1000 km
                rosb = min(1000e3,sqrt(9.81*abs(tmpz))./f);
                % autmatically divide into discrete bins
                [~,edges] = histcounts(rosb);
                tmpz_ft  = tmpz; dyb = dy;
                % get slope from filtered bathy for the segment only
                [by,bx] = EarthGradient(tmpz_ft,dy,dx); % get slope in x and y directions
                tempbs  = sqrt(bx.^2 + by.^2); % get overall slope
                for i = 1:length(edges)-1
                    sel = rosb >= edges(i) & rosb <= edges(i+1);
                    rosbylb = mean(edges(i:i+1));
                    if rosbylb > 2*dyb
                        disp(['i = ',num2str(i), ' rl/dx = ',num2str(rosbylb/dyb)])
                        tmpz_ft  = filt2(tmpz_ft,dyb,rosbylb,'lp');
                        dyb = rosbylb;
                        % get slope from filtered bathy for the segment only
                        [by,bx] = EarthGradient(tmpz_ft,dy,dx); % get slope in x and y directions
                        tempbs  = sqrt(bx.^2 + by.^2); % get overall slope
                    else
                        % otherwise just use the same tempbs from before
                    end
                    % put in the full one
                    bs(sel) = tempbs(sel);
                end
            else
                % get slope from (possibly filtered) bathy
                [by,bx] = EarthGradient(tmpz_f,dy,dx); % get slope in x and y directions
                bs      = sqrt(bx.^2 + by.^2); % get overall slope
            end
            clear bx by tmpz_f tmpz_ft

            % Allow user to specify depth ranges for slope parameter.
            obj.slpd = NaN([obj.nx,obj.ny]);
            for param = obj.slp'
                if numel(param)==1
                    % no bounds specified. valid in this range.
                    slpp = param(1);
                    z_min = -inf;
                    z_max = +inf;
                else
                    slpp = param(1);
                    z_min = param(2);
                    z_max = param(3);
                end
                % Calculating the slope function
                dp = max(1,-tmpz);
                tslpd = (2*pi/slpp)*dp./(bs+eps);
                % apply slope with mask
                limidx = (tmpz >= z_min & tmpz < z_max);
                obj.slpd(limidx) = tslpd(limidx);
                clearvars tslpd
            end
            clearvars tmpz xg yg
        end

        %% Channel edgefunction
        function obj = chfx(obj,feat)

            ang_of_reslope=obj.AngOfRe ;      % Estimate width of channel using tangent of this angle times depth.

            % STEP 1: Calculate the width of each channel using a v-shape approx to
            % channel's cross sectional area.
            for jj = 1:length(obj.Channels)
                if (isempty(obj.Channels{jj})); continue ;end
                pts{jj} = obj.Channels{jj};
                dp{jj} = feat.Fb(pts{jj});                               % depth at x,y channel locations
                radii{jj}=(tand(ang_of_reslope)*max(1,-dp{jj}));         % estimate of channel's width in degrees at x,y locations ASSUMING angle of reslope
                tempbb{jj} = feat.boubox;
            end

            [xg,yg] = CreateStructGrid(obj);

            % STEP 2: For each channel point, set the resolution around a
            % neighborhood of points as |h|/ch where ch is non-dim # between 0.5-1.5
            obj.chd = NaN([obj.nx,obj.ny]);
            % prune the potentially large channel network to only ones
            % partially enclosed in boubox
            isin = cellfun(@inpolysum,pts,tempbb);
            pts(isin==0) = []; radii(isin==0)= [];

            %figure; plot(xg,yg,'k.');
            tic
            tidx = [];
            for jj =1:length(pts)                                           % For each channel jj
                sel = pts{jj};
                in  = inpoly(sel,feat.boubox(1:end-1,:));
                sel(~in,:) = [];
                for jjj = 1 : size(sel,1)                                   % For each channel point jjj on channel jj.
                    % will the stencil be too large (nidx^2)?
                    nidx = ceil(radii{jj}(jjj)/obj.h0);                       % stencil of points around channel point.
                    % find linear index of channel point in 2-d matrix.
                    [lidx] = FindLinearIdx(sel(jjj,1),sel(jjj,2),xg,yg);
                    % convert to r,c or grid indices.
                    [r,c]     = ind2sub(size(xg),lidx);
                    [cid,rid] = ndgrid(c-nidx:c+nidx,r-nidx:r+nidx);    % grid of nearby points
                    % ensure that all these points are within the domain.
                    rid = min(max(rid(:),1),size(xg,1));
                    cid = min(max(cid(:),1),size(xg,2));
                    % convert back to linear indices.
                    temp = sub2ind(size(xg),rid,cid);
                    tidx = [tidx; temp];
                end
            end
            toc
            tmpz = feat.Fb(xg(tidx),yg(tidx));
            if ~isempty(feat.Fb2)
               tmpz2 = feat.Fb2(xg(tidx),yg(tidx));
               tmpz(isnan(tmpz)) = tmpz2(isnan(tmpz));
               clear tmpz2
            end
            dp = max(1,-tmpz);
            obj.chd(tidx) = dp/obj.ch;
            obj.chd(obj.chd < obj.min_el_ch) = obj.min_el_ch;
            clearvars Fb pts dp radii tempbb xg yg
        end

        %% General function to plot
        function plot(obj,type)
            % form grid here.
            % working out plottable size
            [xgrid,ygrid] = CreateStructGrid(obj);

            if nargin == 2
                switch type
                    case('dis')
                        val = obj.hhd;
                        tt = 'Distance EdgeLength Function';
                    case('fs')
                        val = obj.fsd;
                        tt = 'Feature Size EdgeLength Function';
                    case('wl')
                        val = obj.wld;
                        tt = 'Wavelength EdgeLength Function';
                    case('ch')
                        val = obj.chd;
                        tt = 'Channel EdgeLength Function';
                    case('slp')
                        val = obj.slpd;
                        tt = 'Slope EdgeLength Function';
                    otherwise
                        warning('Unexpected plot type. No plot created.')
                end
            else
                val = obj.F.Values;
                tt = 'Total EdgeLength Function';
            end

            % working out stride
            mem = inf; stride = obj.gridspace;
            while mem > 1
                xx = obj.x0y0(1):stride:obj.bbox(1,2);
                yy = obj.x0y0(2):stride:obj.bbox(2,2);
                xs = whos('xx'); ys = whos('yy');
                mem = xs.bytes*ys.bytes/1e9;
                stride = stride*2;
            end
            stride = ceil(stride/obj.gridspace);
            I = [1:stride:size(xgrid,1)];
            J = [1:stride:size(xgrid,2)];

            figure;
            m_proj('merc',...
                'long',[obj.bbox(1,1) obj.bbox(1,2)],...
                'lat',[obj.bbox(2,1) obj.bbox(2,2)])
            hold on; m_fastscatter(xgrid(I,J),ygrid(I,J),val(I,J));
            cb = colorbar; ylabel(cb,'edgelength in meters');
            colormap(lansey)
            m_grid() %'xtick',10,'tickdir','out',...
            % 'yaxislocation','left','fontsize',7);
            title(tt);
        end

        %% Finalize edge function
        % Add grading, cfl limiting and bound enforcement.
        function obj = finalize(obj,feat)
            % package the edge functions into a known order.
            counter = 0;
            hh = zeros([obj.nx,obj.ny]);
            for i = 1 : numel(obj.used)
                type = obj.used{i};
                switch type
                    case('dis')
                        counter = counter + 1;
                        hh(:,:,counter) = obj.hhd;
                        obj.hhd = single(obj.hhd);
                    case('fs')
                        counter = counter + 1;
                        hh(:,:,counter) = obj.fsd;
                        obj.fsd = single(obj.fsd);
                    case('wl')
                        counter = counter + 1;
                        hh(:,:,counter) = obj.wld;
                        obj.wld = single(obj.wld);
                    case('slp')
                        counter = counter + 1;
                        hh(:,:,counter) = obj.slpd;
                        obj.slpd = single(obj.slpd);
                    case('ch')
                        counter = counter + 1;
                        hh(:,:,counter) = obj.chd;
                        obj.ch = single(obj.chd);
                    otherwise
                        error('FATAL:  Could not finalize edge function');
                end
            end

            [hh_m] = min(hh,[],3);
            clearvars hh

            [xg,yg] = CreateStructGrid(obj);

            % Before grading, ensure edgefx reflects the spacing in
            % proximity to constraints.
            if ~isempty(feat.weirs)
                % form temporary mesh size function interpolat
                % if spacing is negative -999 then it will use the mesh
                % size function to determine the spacing.
                %Ftmp = griddedInterpolant(xg,yg,hh_m,'linear','nearest');

                for i =1:length(feat.weirs)
                    if iscell(feat.weirs)
                        % get spacing in meters along the face of the weir
                        weir_spacing = feat.weirs{i}(1,4);
                        % get crestline points in wgs84
                        txy = feat.weirs{i}(:,1:2) ;
                    else
                        % get spacing in meters along the face of the weir
                        weir_spacing = feat.weirs(i).min_ele;
                        % get crestline points in wgs84
                        txy = [feat.weirs(i).X feat.weirs(i).Y];
                    end
                    xy = [];
                    [xy(:,2),xy(:,1)] = my_interpm(txy(:,2),txy(:,1),...
                        (obj.h0/2)/111e3);
                    % edit the mesh size function in proximity to the weir
                    for j = 1 : length(xy)
                        nidx = 10 ;
                        % return the indices into the mesh size function
                        [lidx] = FindLinearIdx(xy(j,1),xy(j,2),xg,yg);
                        [r,c]  = ind2sub(size(xg),lidx);
                        [cid,rid]=ndgrid((c-nidx:c+nidx)',(r-nidx:r+nidx)');    % grid of nearby points
                        rid = [rid(:);r]; cid=[cid(:);c];
                        hh_m(rid,cid) = weir_spacing ;
                    end
                end
            end

            % enforce all mesh resolution bounds,grade and enforce the CFL in planar metres
            if ~isinf(obj.max_el_ns) && ~isempty(obj.boudist)
                nearshore = abs(obj.boudist) < 2/3*obj.h0;
                hh_m(nearshore & hh_m > obj.max_el_ns) = obj.max_el_ns;
            end
            hh_m(hh_m < obj.h0 ) = obj.h0;

            % Compute tmpz if necessary
            if length(obj.max_el) > 1 || length(obj.g) > 1 || obj.dt(1) >= 0
                tmpz    = feat.Fb(xg,yg);
                if ~isempty(feat.Fb2)
                   tmpz2 = feat.Fb2(xg,yg);
                   tmpz(isnan(tmpz)) = tmpz2(isnan(tmpz));
                   clear tmpz2
                end
            end

            for param = obj.max_el'
                if numel(param)==1 && param~=0
                    mx   = obj.max_el(1);
                    limidx = hh_m > mx | isnan(hh_m);

                    hh_m( limidx ) = mx;
                else
                    mx  = param(1);
                    z_min = param(2);
                    z_max = param(3);

                    limidx = (tmpz >= z_min & tmpz < z_max) & ...
                             (hh_m > mx | isnan(hh_m));

                    hh_m( limidx ) = mx;
                end
            end

            % Make sure this is called before releasing memory...
            if obj.dt(1) == 0
                % Find min allowable dt based on dis or fs function
                if any(~cellfun('isempty',strfind(obj.used,'dis')))
                    hh_d = obj.hhd;
                elseif any(~cellfun('isempty',strfind(obj.used,'fs')))
                    hh_d = obj.fsd;
                else
                    error(['FATAL: cannot use automatic timestep ' ...
                           'limiter without specifying dis or fs option']);
                end
            end

            obj = release_memory(obj) ;

            disp('Relaxing the mesh size gradient');
            if all(abs(obj.bbox(1,:)) == 180)
                % for global mesh make it cyclical
                hh_m = [hh_m(end,:); hh_m; hh_m(1,:)];
            end
            hfun = reshape(hh_m',[numel(hh_m),1]);

            dx = obj.h0*cosd(min(yg(1,:),85)); % for gradient function
            dy = obj.h0;                       % for gradient function

            % make g a function of space
            dmy     = xg*0 ;
            for param = obj.g'
                if numel(param)==1 && param~=0
                    lim   = obj.g(1);
                    dmy  = dmy + lim ;
                else
                    lim  = param(1);
                    z_min  = param(2);
                    z_max  = param(3);

                    limidx = (tmpz >= z_min & tmpz < z_max) ;

                    dmy( limidx ) = lim;
                end
            end
            if all(abs(obj.bbox(1,:)) == 180)
                % for global mesh make it cyclical
                dmy = [dmy(end,:); dmy; dmy(1,:)];
            end
            fdfdx = reshape(dmy',[numel(dmy),1]);
            clearvars dmy;

            [hfun,flag] = limgradStruct(obj.ny,dx,dy,hfun,...
                fdfdx,sqrt(length(hfun)));
            if flag == 1
                disp('Gradient relaxing converged!');
            else
                error(['FATAL: Gradient relaxing did not converge, '
                    'please check your edge functions']);
            end
            % reshape it back
            hh_m = reshape(hfun,obj.ny,[])';
            clearvars hfun fdfdx
            if all(abs(obj.bbox(1,:)) == 180)
                % for global mesh make it cyclical
                hh_m = hh_m(2:end-1,:);
                hh_m(end,:) = hh_m(1,:);
            end

            % Limit CFL if dt >= 0, dt = 0 finds dt automatically.
            if obj.dt(1) >= 0
                if isempty(feat.Fb)
                    error('No DEM supplied Can''t CFL limit.')
                end
                % Estimate wavespeed in ocean (second term represents orbital
                % velocity at 0 degree phase for 1-m amp. wave).
                grav = 9.807;
                % Limit the minimum depth to 1 m (ignore overland)
                tmpz(tmpz > - 1) = -1;
                u = sqrt(grav*abs(tmpz)) + sqrt(grav./abs(tmpz));

                % Desired timestep to bound edgefx
                desDt = obj.dt(1);
                if isnan(desDt)
                    disp('No timestep enforcement due to no distance function')
                end

                % Determine Courant min. and max. bounds
                if length(obj.dt) < 2
                    % default behavior if no bounds are passed
                    % for explicit type schemes we ensure the maxCr is
                    % bounded
                    minCr = eps; % Cannot be zero!!!
                    maxCr = 0.5;
                elseif length(obj.dt) == 2
                    if obj.dt(1)==0.0
                        error('dt = 0 is only intended for restricting the upper bound of Cr')
                    end
                    % minimum Courant number bound
                    minCr = obj.dt(2);
                    maxCr = inf;
                elseif length(obj.dt) == 3
                    % minimum and maximum Courant bound
                    minCr = obj.dt(2);
                    maxCr = obj.dt(3);
                else
                    error('Length of dt name-value pair should be <=3')
                end

                if minCr > maxCr
                    error('MinCr is > MaxCr, please switch order in dt name value call')
                end

                % Automatic timestep selection option (for ADCIRC models)
                if desDt == 0
                    hh_d(hh_d < obj.h0) = obj.h0;
                    obj.dt = min(min(maxCr*hh_d./u));
                    desDt  = obj.dt;
                end

                % Enforcement of Courant bounds in mesh size function
                fprintf(1, [ ...
                    'Enforcing timestep of ',num2str(desDt),' seconds ', ...
                    'with Courant number bounds of ',num2str(minCr),' to ',num2str(maxCr),'\n', ...
                    ] ) ;

                Cr = (desDt*u)./hh_m; % Courant number for given desDt for mesh size variations

                % resolution that meets max. Cr. criteria for desDt
                dxn_min = u*desDt/maxCr;
                % resolution that meets min. Cr. criteria for desDt
                dxn_max = u*desDt/minCr;

                % resolve min. Cr violations
                hh_m( Cr <= minCr) = dxn_max( Cr <= minCr); %--in planar metres

                % resolve max. Cr violations
                hh_m( Cr > maxCr) = dxn_min( Cr > maxCr);   %--in planar metres

                clear dxn_min dxn_max Cr
                clear u hh_d tmpz
            end

            obj.F = griddedInterpolant(xg,yg,hh_m,'linear','nearest');

            clearvars xg yg
        end

        function [xg,yg] = CreateStructGrid(obj)
            [xg,yg] = ndgrid(obj.x0y0(1) + (0:obj.nx-1)'*obj.gridspace, ...
                obj.x0y0(2) + (0:obj.ny-1)'*obj.gridspace);
        end

        function obj = release_memory(obj)
            % releases heavy components from data structures
            if isa(obj,'edgefx')
                disp('--------------------------------------------------');
                disp('Releasing individual edge functions from memory...');
                disp('--------------------------------------------------');
                if ~isempty(obj.fsd)
                    obj.fsd = [];
                end

                if ~isempty(obj.wld)
                    obj.wld = [];
                end

                if ~isempty(obj.slpd)
                    obj.slpd = [];
                end

                if ~isempty(obj.chd)
                    obj.chd = [];
                end

                if ~isempty(obj.ch)
                    obj.ch = [];
                end

                if ~isempty(obj.hhd)
                    obj.hhd = [];
                end

                if ~isempty(obj.boudist)
                    obj.boudist = [];
                end

                if ~isempty(obj.Channels)
                    obj.Channels = [];
                end

                if ~isempty(obj.ch)
                   obj.ch = [];
                end

                if ~isempty(obj.boudist)
                   obj.boudist = [];
                end
            end
        end

    end

end


function [Hhat_LMMSE,C_LMMSE,tau_p,R,H] = functionChannelEstimates_impairments(R,channelGaindB,nbrOfRealizations,M,K,L,p,f,kappatUE,kapparBS)
%Generate the channel realizations and estimates of these channels for all
%UEs in the entire network. The channels are assumed to be correlated
%Rayleigh fading. The LMMSE estimator that takes transceiver hardware
%impairments into account is used.
%
%INPUT:
%R                 = M x M x K x L x L matrix with spatial correlation
%                    matrices for all UEs in the network. R(:,:,k,j,l) is
%                    the correlation matrix for the channel between UE k
%                    in cell j and the BS in cell l. This such matrix can
%                    either include the average channel gain or can be
%                    normalized arbitrarily.
%channelGaindB     = K x L x L matrix containing the average channel gains
%                    in dB of all the channels, if these are not already
%                    included in the spatial channel correlation matrices.
%                    The product R(:,:,k,j,l)*10^(channelGaindB(k,j,l)/10)
%                    is the full spatial channel correlation matrix.
%nbrOfRealizations = Number of channel realizations
%M                 = Number of antennas per BS
%K                 = Number of UEs per cell
%L                 = Number of BSs and cells
%p                 = Uplink transmit power per UE (same for everyone)
%f                 = Pilot reuse factor
%kappatUE          = Hardware quality of the UEs' transmitters
%kapparBS          = Hardware quality of the BSs' receivers
%
%OUTPUT:
%Hhat_LMMSE   = M x nbrOfRealizations x K x L x L matrix with the LMMSE
%               channel estimates. The matrix Hhat_LMMSE(:,n,k,j,l) is the
%               n:th channel estimate of the channel between UE k in cell j
%               and the BS in cell l.
%C_LMMSE      = M x M x K x L x L matrix with estimation error correlation
%               matrices when using LMMSE estimation. The matrix is
%               organized in the same way as R.
%tau_p        = Length of pilot sequences
%R            = Scaled version of the input spatial correlation matrices R,
%               where the channel gains from channelGaindB are included
%H            = M x nbrOfRealizations x K x L x L matrix with the true
%               channel realizations. The matrix is organized in the same
%               way as Hhat_LMMSE.
%
%
%This Matlab function was developed to generate simulation results to:
%
%Emil Bjornson, Jakob Hoydis and Luca Sanguinetti (2017),
%"Massive MIMO Networks: Spectral, Energy, and Hardware Efficiency",
%Foundations and Trends in Signal Processing: Vol. 11, No. 3-4,
%pp. 154-655. DOI: 10.1561/2000000093.
%
%For further information, visit: https://www.massivemimobook.com
%
%This is version 1.0 (Last edited: 2017-11-04)
%
%License: This code is licensed under the GPLv2 license. If you in any way
%use this code for research that results in publications, please cite our
%monograph as described above.

%% Generate channel realizations

%Generate uncorrelated Rayleigh fading channel realizations
H = (randn(M,nbrOfRealizations,K,L,L)+1i*randn(M,nbrOfRealizations,K,L,L));

%Prepare a matrix to save the channel gains per UE
betas = zeros(K,L,L);

%Go through all channels and apply the channel gains to the spatial
%correlation matrices
for j = 1:L

    for l = 1:L

        for k = 1:K

            %Extract channel gain in linear scale
            betas(k,j,l) = 10^(channelGaindB(k,j,l)/10);

            %Apply channel gain to correlation matrix
            R(:,:,k,j,l) = betas(k,j,l) * R(:,:,k,j,l);

            %Apply correlation to the uncorrelated channel realizations
            Rsqrt = sqrtm(R(:,:,k,j,l));
            H(:,:,k,j,l) = sqrt(0.5)*Rsqrt*H(:,:,k,j,l);

        end

    end

end

%% Perform channel estimation

%Length of pilot sequences
tau_p = f*K;

%Generate pilot pattern
if f == 1

    pilotPattern = ones(L,1);

elseif f == 2 %Only works in the running example with its 16 BSs

    pilotPattern = kron(ones(2,1),[1; 2; 1; 2; 2; 1; 2; 1]);

elseif f == 4 %Only works in the running example with its 16 BSs

    pilotPattern = kron(ones(2,1),[1; 2; 1; 2; 3; 4; 3; 4]);

elseif f == 16 %Only works in the running example with its 16 BSs

    pilotPattern = (1:L)';

end

%Store identity matrix of size M x M
eyeM = eye(M);

%Generate realizations of normalized noise
Np = sqrt(0.5)*(randn(M,nbrOfRealizations,K,L,f) + 1i*randn(M,nbrOfRealizations,K,L,f));

%Prepare to store LMMSE channel estimates
Hhat_LMMSE = zeros(M,nbrOfRealizations,K,L,L);

%Prepare to store estimation error correlation matrices
C_LMMSE = zeros(M,M,K,L,L);

%% Go through all f pilot groups
for g = 1:f

    %Create transmit distortion term
    etaUE = sqrt(0.5*p*tau_p*kapparBS*(1-kappatUE))*(randn(1,nbrOfRealizations,K,L,K) + 1i*randn(1,nbrOfRealizations,K,L,K));

    %Go through all cells
    for j = 1:L

        %Extract the cells that belong to pilot group g
        groupMembers = find(g==pilotPattern)';

        %Go through all UEs
        for k = 1:K

            %Create receive distortion term
            etabarBS = sqrt(0.5*p*tau_p*(1-kapparBS))*(randn(M,nbrOfRealizations,K,L) + 1i*randn(M,nbrOfRealizations,K,L));

            %Sum up transmit and receive distortion terms
            etaSum = repmat(etaUE(:,:,:,:,k),[M 1 1 1]) + etabarBS;

            %Compute processed pilot signal for all UEs that use these pilots, according to (6.22)
            ypk = sqrt(p*kappatUE*kapparBS)*tau_p*sum(H(:,:,k,g==pilotPattern,j),4) + squeeze(sum(sum(H(:,:,:,:,j).*etaSum,4),3)) + sqrt(tau_p)*Np(:,:,k,j,g);

            %Compute sum of all UEs' correlation matrices to BS j
            RjAll = sum(sum(R(:,:,:,:,j),3),4);

            %Compute the matrix that is inverted in the LMMSE estimator
            PsiInv = (p*kappatUE*kapparBS*tau_p*sum(R(:,:,k,g==pilotPattern,j),4) + p*(1-kappatUE)*kapparBS*RjAll + p*(1-kapparBS)*diag(diag(RjAll)) + eyeM);

            %Go through the cells in pilot group g
            for l = groupMembers

                %Compute LMMSE estimate of channel between BS l and UE k in
                %cell j using (6.23) in Theorem 6.1
                RPsi = R(:,:,k,l,j) / PsiInv;
                Hhat_LMMSE(:,:,k,l,j) = sqrt(p*kappatUE*kapparBS)*RPsi*ypk;

                %Compute corresponding estimation error correlation matrix, using (6.26)
                C_LMMSE(:,:,k,l,j) = R(:,:,k,l,j) - p*kappatUE*kapparBS*tau_p*RPsi*R(:,:,k,l,j);

            end

        end

    end

end


function z = min( x, y, dim )

%   Disciplined convex/geometric programming information:
%       MAX is convex, log-log-concave, and nondecreasing in its first
%       two arguments. Thus when used in disciplined convex programs,
%       both arguments must be concave (or affine). In disciplined
%       geometric programs, both arguments must be log-concave/affine.

persistent remap remap_1 remap_2 remap_3
error( nargchk( 1, 3, nargin ) );
if nargin == 2,

    %
    % min( X, Y )
    %

    sx = size( x );
    sy = size( y );
    xs = all( sx == 1 );
    ys = all( sy == 1 );
    if xs,
        sz = sy;
    elseif ys || isequal( sx, sy ),
        sz = sx;
    else
        error( 'Array dimensions must match.' );
    end

    %
    % Determine the computation methods
    %

    if isempty( remap ),
        remap1 = cvx_remap( 'real' );
        remap1 = remap1' * remap1;
        remap2 = cvx_remap( 'log-concave' )' * cvx_remap( 'nonpositive' );
        remap3 = remap2';
        remap4 = cvx_remap( 'log-concave', 'real' );
        remap4 = remap4' * remap4;
        remap5 = cvx_remap( 'concave' );
        remap5 = remap5' * remap5;
        remap   = remap1 + ~remap1 .* ...
            ( 2 * remap2 + 3 * remap3 + ~( remap2 | remap3 ) .* ...
            ( 4 * remap4 + ~remap4 .* ( 5 * remap5 ) ) );
    end
    vx = cvx_classify( x );
    vy = cvx_classify( y );
    vr = remap( vx + size( remap, 1 ) * ( vy - 1 ) );
    vu = sort( vr(:) );
    vu = vu([true;diff(vu)~=0]);
    nv = length( vu );

    %
    % The cvx multi-objective problem
    %

    xt = x;
    yt = y;
    if nv ~= 1,
        z = cvx( sz, [] );
    end
    for k = 1 : nv,

        %
        % Select the category of expression to compute
        %

        if nv ~= 1,
            t = vr == vu( k );
            if ~xs,
                xt = cvx_subsref( x, t );
                sz = size( xt ); %#ok
            end
            if ~ys,
                yt = cvx_subsref( y, t );
                sz = size( yt ); %#ok
            end
        end

        %
        % Apply the appropriate computation
        %

        switch vu( k ),
        case 0,
            % Invalid
            error( 'Disciplined convex programming error:\n    Cannot perform the operation min( {%s}, {%s} )', cvx_class( xt, false, true ), cvx_class( yt, false, true ) );
        case 1,
            % constant
            cvx_optval = cvx( min( cvx_constant( xt ), cvx_constant( yt ) ) );
        case 2,
            % min( log-concave, nonpositive ) (no-op)
            cvx_optval = yt;
        case 3,
            % min( nonpositive, log-concave ) (no-op)
            cvx_optval = xt;
        case 4,
            % posy
            cvx_begin gp
                 hypograph variable zt( sz )
                xt >= zt; %#ok
                yt >= zt; %#ok
            cvx_end
        case 5,
            % non-posy
            cvx_begin
                hypograph variable zt( sz )
                xt >= zt; %#ok
                yt >= zt; %#ok
            cvx_end
        otherwise,
            error( 'Shouldn''t be here.' );
        end

        %
        % Store the results
        %

        if nv == 1,
            z = cvx_optval;
        else
            z = cvx_subsasgn( z, t, cvx_optval );
        end

    end

else

    %
    % min( X, [], dim )
    %

    if nargin > 1 && ~isempty( y ),
        error( 'min with two matrices to compare and a working dimension is not supported.' );
    end
    sx = size( x );
    if nargin < 2,
        dim = cvx_default_dimension( sx );
    elseif ~cvx_check_dimension( dim ),
        error( 'Third argument must be a positive integer.' );
    end

    %
    % Determine sizes, quick exit for empty arrays
    %

    sx = [ sx, ones( 1, dim - length( sx ) ) ];
    nx = sx( dim );
    if any( sx == 0 ),
        sx( dim ) = min( sx( dim ), 1 );
        z = zeros( sx );
        return
    end
    sy = sx;
    sy( dim ) = 1;

    %
    % Type check
    %

    if isempty( remap_3 ),
        remap_1 = cvx_remap( 'real' );
        remap_2 = cvx_remap( 'log-concave', 'real' );
        remap_3 = cvx_remap( 'concave' );
    end
    vx = cvx_reshape( cvx_classify( x ), sx );
    t1 = all( reshape( remap_1( vx ), sx ), dim );
    t2 = all( reshape( remap_2( vx ), sx ), dim );
    t3 = all( reshape( remap_3( vx ), sx ), dim );
    t3 = t3 & ~( t1 | t2 );
    t2 = t2 & ~t1;
    ta = t1 + ( 2 * t2 + 3 * t3 ) .* ~t1;
    nu = sort( ta(:) );
    nu = nu([true;diff(nu)~=0]);
    nk = length( nu );

    %
    % Quick exit for size 1
    %

    if nx == 1 && all( nu ),
        z = x;
        return
    end

    %
    % Permute and reshape, if needed
    %

    if dim > 1 && any( sx( 1 : dim - 1 ) > 1 ),
        perm = [ dim, 1 : dim - 1, dim + 1 : length( sx ) ];
        x   = permute( x,  perm );
        ta  = permute( ta, perm );
        sx  = sx(perm);
        sy  = sy(perm);
        dim = 1;
    else
        perm = [];
    end
    nv = prod( sx ) / nx;
    x  = reshape( x, nx, nv );

    %
    % Perform the computations
    %

    if nk ~= 1,
        z = cvx( [ 1, nv ], [] );
    end
    for k = 1 : nk,

        if nk == 1,
            xt = x;
        else
            tt = ta == nu( k );
            xt = cvx_subsref( x, ':', tt );
            nv = nnz( tt ); %#ok
        end

        switch nu( k ),
            case 0,
                error( 'Disciplined convex programming error:\n   Invalid computation: min( {%s} )', cvx_class( xt, false, true ) );
            case 1,
                cvx_optval = min( cvx_constant( xt ), [], dim );
            case 2,
                cvx_begin gp
                    hypograph variable zt( 1, nv )
                    xt >= ones(nx,1) * zt; %#ok
                cvx_end
            case 3,
                cvx_begin
                    hypograph variable zt( 1, nv )
                    xt >= ones(nx,1) * zt; %#ok
                cvx_end
            otherwise,
                error( 'Shouldn''t be here.' );
        end

        if nk == 1,
            z = cvx_optval;
        else
            z = cvx_subsasgn( z, tt, cvx_optval );
        end

    end

    %
    % Reverse the reshaping and permutation steps
    %

    z = reshape( z, sy );
    if ~isempty( perm ),
        z = ipermute( z, perm );
    end

end

% Copyright 2010 Michael C. Grant and Stephen P. Boyd.
% See the file COPYING.txt for full copyright information.
% The command 'cvx_where' will show where this file is located.


%%*******************************************************************
%% HSDHKMrhsfun: compute the right-hand side vector of the
%%               Schur complement equation for the  HKM direction.
%%
%% SDPT3: version 3.1
%% Copyright (c) 1997 by
%% K.C. Toh, M.J. Todd, R.H. Tutuncu
%% Last Modified: 16 Sep 2004
%%*******************************************************************

function [rhs,EinvRc,hRd] = HSDHKMrhsfun(blk,At,par,X,Z,rp,Rd,sigmu,hRd,dX,dZ)

m = par.m;
if (nargin > 8)
    corrector = 1;
else
    corrector = 0;
    hRd = zeros(m+2,1);
end
hEinvRc = zeros(m+2,1);
EinvRc = cell(size(blk,1),1);
if length(sigmu)==1; sigmu = sigmu*ones(1,size(blk,1)); end
%%
for p = 1:size(blk,1)
    pblk = blk(p,:);
    n = sum(pblk{2});
    if strcmp(pblk{1},'l')
        if (corrector)
            Rq = dX{p}.*dZ{p};
        else
            Rq = sparse(n,1);
            tmp  = par.dd{p}.*Rd{p};
            tmp2 = mexMatvec(At{p},tmp,1);
            hRd = hRd + tmp2;
        end
        EinvRc{p} = sigmu(p)./Z{p}-X{p} -Rq./Z{p};
        tmp2 = mexMatvec(At{p,1},EinvRc{p},1);
        hEinvRc = hEinvRc + tmp2;
    elseif strcmp(pblk{1},'q')
        if (corrector)
            ff{p} = qops(pblk,1./par.gamz{p},Z{p},3); %#ok
            hdx = qops(pblk,par.gamz{p},ff{p},5,dX{p});
            hdz = qops(pblk,par.gamz{p},ff{p},6,dZ{p});
            hdxdz = Arrow(pblk,hdx,hdz);
            Rq = qops(pblk,par.gamz{p},ff{p},6,hdxdz);
        else
            Rq  = sparse(n,1);
            tmp = par.dd{p}.*Rd{p} ...
                + qops(pblk,qops(pblk,Rd{p},par.Zinv{p},1),X{p},3) ...
                + qops(pblk,qops(pblk,Rd{p},X{p},1),par.Zinv{p},3);
            tmp2 = mexMatvec(At{p,1},tmp,1);
            hRd = hRd + tmp2;
        end
        EinvRc{p} = sigmu(p)*par.Zinv{p}-X{p} -Rq;
        tmp2 = mexMatvec(At{p,1},EinvRc{p},1);
        hEinvRc = hEinvRc + tmp2;
    elseif strcmp(pblk{1},'s')
        if (corrector)
            Rq = Prod3(pblk,dX{p},dZ{p},par.Zinv{p},0);
            Rq = 0.5*(Rq+Rq');
        else
            Rq = sparse(n,n);
            tmp = Prod3(pblk,X{p},Rd{p},par.Zinv{p},0,par.nzlistAy{p});
            EinvRc{p} = 0.5*(tmp+tmp');
            tmp2 = AXfun(pblk,At(p,:),par.permA(p,:),EinvRc(p));
            hRd = hRd + tmp2;
        end
        EinvRc{p} = sigmu(p)*par.Zinv{p}-X{p}- Rq;
        tmp2 = AXfun(pblk,At(p,:),par.permA(p,:),EinvRc(p));
        hEinvRc = hEinvRc + tmp2;
    end
end
%%
rhs = rp + hRd - hEinvRc;
rhs(m+1) = rhs(m+1) + (par.mu/par.tau - par.kap);
if (corrector)
    rhs(m+1) = rhs(m+1) - par.dtau*par.dkap/par.tau;
end
%%*******************************************************************


function obj = t1_pnp_func_new(in1,in2,in3)
coefs_tq1_1 = in2(1);
coefs_tq1_2 = in2(4);
coefs_tq2_1 = in2(2);
coefs_tq1_3 = in2(7);
coefs_tq2_2 = in2(5);
coefs_tq3_1 = in2(3);
coefs_tq1_4 = in2(10);
coefs_tq2_3 = in2(8);
coefs_tq3_2 = in2(6);
coefs_tq1_5 = in2(13);
coefs_tq2_4 = in2(11);
coefs_tq3_3 = in2(9);
coefs_tq1_6 = in2(16);
coefs_tq2_5 = in2(14);
coefs_tq3_4 = in2(12);
coefs_tq1_7 = in2(19);
coefs_tq2_6 = in2(17);
coefs_tq3_5 = in2(15);
coefs_tq1_8 = in2(22);
coefs_tq2_7 = in2(20);
coefs_tq3_6 = in2(18);
coefs_tq1_9 = in2(25);
coefs_tq2_8 = in2(23);
coefs_tq3_7 = in2(21);
coefs_tq2_9 = in2(26);
coefs_tq3_8 = in2(24);
coefs_tq3_9 = in2(27);
coefs_tq1_10 = in2(28);
coefs_tq2_10 = in2(29);
coefs_tq3_10 = in2(30);
pinvG1_1 = in1(1);
pinvG1_2 = in1(4);
pinvG1_3 = in1(7);
q0 = in3(1,:);
q1 = in3(2,:);
q2 = in3(3,:);
q3 = in3(4,:);
obj = q0.^2.*(coefs_tq1_1.*pinvG1_1+coefs_tq2_1.*pinvG1_2+coefs_tq3_1.*pinvG1_3)+q1.^2.*(coefs_tq1_5.*pinvG1_1+coefs_tq2_5.*pinvG1_2+coefs_tq3_5.*pinvG1_3)+q2.^2.*(coefs_tq1_8.*pinvG1_1+coefs_tq2_8.*pinvG1_2+coefs_tq3_8.*pinvG1_3)+q3.^2.*(coefs_tq1_10.*pinvG1_1+coefs_tq2_10.*pinvG1_2+coefs_tq3_10.*pinvG1_3)+q0.*q1.*(coefs_tq1_2.*pinvG1_1+coefs_tq2_2.*pinvG1_2+coefs_tq3_2.*pinvG1_3)+q0.*q2.*(coefs_tq1_3.*pinvG1_1+coefs_tq2_3.*pinvG1_2+coefs_tq3_3.*pinvG1_3)+q0.*q3.*(coefs_tq1_4.*pinvG1_1+coefs_tq2_4.*pinvG1_2+coefs_tq3_4.*pinvG1_3)+q1.*q2.*(coefs_tq1_6.*pinvG1_1+coefs_tq2_6.*pinvG1_2+coefs_tq3_6.*pinvG1_3)+q1.*q3.*(coefs_tq1_7.*pinvG1_1+coefs_tq2_7.*pinvG1_2+coefs_tq3_7.*pinvG1_3)+q2.*q3.*(coefs_tq1_9.*pinvG1_1+coefs_tq2_9.*pinvG1_2+coefs_tq3_9.*pinvG1_3);


function [net, info] = cnn_train_adagrad(net, imdb, getBatch, varargin)
% CNN_TRAIN   Demonstrates training a CNN
%    CNN_TRAIN() is an example learner implementing stochastic gradient
%    descent with momentum to train a CNN for image classification.
%    It can be used with different datasets by providing a suitable
%    getBatch function.

opts.train = [] ;
opts.val = [] ;
opts.numEpochs = 300 ;
opts.batchSize = 256 ;
opts.useGpu = false ;
opts.learningRate = 0.001 ;
opts.continue = false ;
opts.expDir = fullfile('data','exp') ;
opts.conserveMemory = false ;
opts.sync = true ;
opts.prefetch = false ;
opts.weightDecay = 0.0005 ;
opts.errorType = 'multiclass' ;
opts.plotDiagnostics = false ;
opts.delta = 1e-8;
opts.display = 1;
opts.snapshot = 1;
opts.test_interval = 1;
opts = vl_argparse(opts, varargin) ;

if ~exist(opts.expDir, 'dir'), mkdir(opts.expDir) ; end
if isempty(opts.train), opts.train = find(imdb.images.set==1) ; end
if isempty(opts.val), opts.val = find(imdb.images.set==2) ; end
if isnan(opts.train), opts.train = [] ; end

% -------------------------------------------------------------------------
%                                                    Network initialization
% -------------------------------------------------------------------------

for i=1:numel(net.layers)
  if ~strcmp(net.layers{i}.type,'conv'), continue; end
  net.layers{i}.filtersMomentum = zeros(size(net.layers{i}.filters), ...
    class(net.layers{i}.filters)) ;
  net.layers{i}.biasesMomentum = zeros(size(net.layers{i}.biases), ...
    class(net.layers{i}.biases)) ; %#ok<*ZEROLIKE>
  if ~isfield(net.layers{i}, 'filtersLearningRate')
    net.layers{i}.filtersLearningRate = 1 ;
  end
  if ~isfield(net.layers{i}, 'biasesLearningRate')
    net.layers{i}.biasesLearningRate = 1 ;
  end
  if ~isfield(net.layers{i}, 'filtersWeightDecay')
    net.layers{i}.filtersWeightDecay = 1 ;
  end
  if ~isfield(net.layers{i}, 'biasesWeightDecay')
    net.layers{i}.biasesWeightDecay = 1 ;
  end
end

if opts.useGpu
  net = vl_simplenn_move(net, 'gpu') ;
  for i=1:numel(net.layers)
    if ~strcmp(net.layers{i}.type,'conv'), continue; end
    net.layers{i}.filtersMomentum = gpuArray(net.layers{i}.filtersMomentum) ;
    net.layers{i}.biasesMomentum = gpuArray(net.layers{i}.biasesMomentum) ;
  end
end

G_f = cell(numel(net.layers), 1);
G_b = cell(numel(net.layers), 1);

for l=1:numel(net.layers)

  if ~strcmp(net.layers{l}.type, 'conv'), continue ; end

  G_f{l} = zeros(size(net.layers{l}.filters), 'single');
  G_b{l} = zeros(size(net.layers{l}.biases), 'single');

end

% -------------------------------------------------------------------------
%                                                        Train and validate
% -------------------------------------------------------------------------

rng(0) ;

if opts.useGpu
  one = gpuArray(single(1)) ;
else
  one = single(1) ;
end

info.train.objective = [] ;
info.train.error = [] ;
info.train.topFiveError = [] ;
info.train.speed = [] ;
info.val.objective = [] ;
info.val.error = [] ;
info.val.topFiveError = [] ;
info.val.speed = [] ;

lr = opts.learningRate ;
res = [] ;
for epoch=1:opts.numEpochs

  % fast-forward to where we stopped
  modelPath = @(ep) fullfile(opts.expDir, sprintf('net-epoch-%d.mat', ep));
  modelFigPath = fullfile(opts.expDir, 'net-train.pdf') ;
  if opts.continue
    if exist(modelPath(epoch),'file')
      if epoch == opts.numEpochs
        load(modelPath(epoch), 'net', 'info') ;
      end
      continue ;
    end
    if epoch > 1
      fprintf('resuming by loading epoch %d\n', epoch-1) ;
      load(modelPath(epoch-1), 'net', 'info') ;
    end
  end

  train = opts.train(randperm(numel(opts.train))) ;
  val = opts.val ;

  info.train.objective(end+1) = 0 ;
  info.train.error(end+1) = 0 ;
  info.train.topFiveError(end+1) = 0 ;
  info.train.speed(end+1) = 0 ;
  info.val.objective(end+1) = 0 ;
  info.val.error(end+1) = 0 ;
  info.val.topFiveError(end+1) = 0 ;
  info.val.speed(end+1) = 0 ;

  for t=1:opts.batchSize:numel(train)
    % get next image batch and labels
    batch = train(t:min(t+opts.batchSize-1, numel(train))) ;
    batch_time = tic ;
    fprintf('training: epoch %02d: processing batch %3d of %3d ...', epoch, ...
            fix(t/opts.batchSize)+1, ceil(numel(train)/opts.batchSize)) ;
    [im, labels] = getBatch(imdb, batch) ;
    if opts.prefetch
      nextBatch = train(t+opts.batchSize:min(t+2*opts.batchSize-1, numel(train))) ;
      getBatch(imdb, nextBatch) ;
    end
    if opts.useGpu
      im = gpuArray(im) ;
    end

    % backprop
    net.layers{end}.class = labels ;
    res = vl_simplenn(net, im, one, res, ...
      'conserveMemory', opts.conserveMemory, ...
      'sync', opts.sync) ;

    % gradient step
    for l=1:numel(net.layers)
      if ~strcmp(net.layers{l}.type, 'conv'), continue ; end

      g_f = (net.layers{l}.filtersLearningRate) * ...
            (opts.weightDecay * net.layers{l}.filtersWeightDecay) * net.layers{l}.filters + ...
            (net.layers{l}.filtersLearningRate) / numel(batch) * res(l).dzdw{1};
      g_b = (net.layers{l}.biasesLearningRate) * ...
            (opts.weightDecay * net.layers{l}.biasesWeightDecay) * net.layers{l}.biases + ...
            (net.layers{l}.biasesLearningRate) / numel(batch) * res(l).dzdw{2};

      G_f{l} = G_f{l} + g_f .^ 2;
      G_b{l} = G_b{l} + g_b .^ 2;

      net.layers{l}.filters = net.layers{l}.filters - lr ./ (opts.delta + sqrt(G_f{l})) .* g_f;
      net.layers{l}.biases  = net.layers{l}.biases - lr ./ (opts.delta + sqrt(G_b{l})) .* g_b;
    end

    % print information
    batch_time = toc(batch_time) ;
    speed = numel(batch)/batch_time ;
    info.train = updateError(opts, info.train, net, res, batch_time) ;

    fprintf(' %.2f s (%.1f images/s)', batch_time, speed) ;
    n = t + numel(batch) - 1 ;
    switch opts.errorType
      case 'multiclass'
        fprintf(' err %.1f err5 %.1f', ...
          info.train.error(end)/n*100, info.train.topFiveError(end)/n*100) ;
        fprintf('\n') ;
      case 'binary'
        fprintf(' err %.1f', ...
          info.train.error(end)/n*100) ;
        fprintf('\n') ;
      case 'euclideanloss'
        fprintf(' err %.1f', info.train.error(end) / n);
        fprintf('\n') ;
    end

    % debug info
    if opts.plotDiagnostics
      figure(2) ; vl_simplenn_diagnose(net,res) ; drawnow ;
    end
  end % next batch

  % evaluation on validation set
  if epoch == 1 || rem(epoch, opts.test_interval) == 0 || epoch == opts.numEpochs
  for t=1:opts.batchSize:numel(val)
    batch_time = tic ;
    batch = val(t:min(t+opts.batchSize-1, numel(val))) ;
    fprintf('validation: epoch %02d: processing batch %3d of %3d ...', epoch, ...
            fix(t/opts.batchSize)+1, ceil(numel(val)/opts.batchSize)) ;
    [im, labels] = getBatch(imdb, batch) ;
    if opts.prefetch
      nextBatch = val(t+opts.batchSize:min(t+2*opts.batchSize-1, numel(val))) ;
      getBatch(imdb, nextBatch) ;
    end
    if opts.useGpu
      im = gpuArray(im) ;
    end

    net.layers{end}.class = labels ;
    res = vl_simplenn(net, im, [], res, ...
      'disableDropout', true, ...
      'conserveMemory', opts.conserveMemory, ...
      'sync', opts.sync) ;

    % print information
    batch_time = toc(batch_time) ;
    speed = numel(batch)/batch_time ;
    info.val = updateError(opts, info.val, net, res, batch_time) ;

    fprintf(' %.2f s (%.1f images/s)', batch_time, speed) ;
    n = t + numel(batch) - 1 ;
    switch opts.errorType
      case 'multiclass'
        fprintf(' err %.1f err5 %.1f', ...
          info.val.error(end)/n*100, info.val.topFiveError(end)/n*100) ;
        fprintf('\n') ;
      case 'binary'
        fprintf(' err %.1f', ...
          info.val.error(end)/n*100) ;
        fprintf('\n') ;
      case 'euclideanloss'
        fprintf(' err %.1f', info.val.error(end) / n);
        fprintf('\n') ;
    end
  end
  end

  % save
  info.train.objective(end) = info.train.objective(end) / numel(train) ;
  info.train.error(end) = info.train.error(end) / numel(train)  ;
  info.train.topFiveError(end) = info.train.topFiveError(end) / numel(train) ;
  info.train.speed(end) = numel(train) / info.train.speed(end) ;
  info.val.objective(end) = info.val.objective(end) / numel(val) ;
  info.val.error(end) = info.val.error(end) / numel(val) ;
  info.val.topFiveError(end) = info.val.topFiveError(end) / numel(val) ;
  info.val.speed(end) = numel(val) / info.val.speed(end) ;
  if epoch == 1 || rem(epoch, opts.snapshot) == 0 || epoch == opts.numEpochs
  save(modelPath(epoch), 'net', 'info') ;
  end

  if epoch == 1 || rem(epoch, opts.display) == 0 || epoch == opts.numEpochs
  figure(1) ; clf ;
  subplot(1,2,1) ;
  semilogy(1:epoch, info.train.objective, 'k') ; hold on ;
  semilogy([1 opts.test_interval : opts.test_interval : epoch epoch], info.val.objective([1 opts.test_interval : opts.test_interval : epoch epoch]), 'b') ;
  xlabel('training epoch') ; ylabel('energy') ;
  grid on ;
  h=legend('train', 'val') ;
  set(h,'color','none');
  title('objective') ;
  subplot(1,2,2) ;
  switch opts.errorType
    case 'multiclass'
      plot(1:epoch, info.train.error, 'k') ; hold on ;
      plot(1:epoch, info.train.topFiveError, 'k--') ;
      plot([1 opts.test_interval : opts.test_interval : epoch epoch], info.val.error([1 opts.test_interval : opts.test_interval : epoch epoch]), 'b') ;
      plot([1 opts.test_interval : opts.test_interval : epoch epoch], info.val.topFiveError([1 opts.test_interval : opts.test_interval : epoch epoch]), 'b--') ;
      h=legend('train','train-5','val','val-5') ;
    case 'binary'
      plot(1:epoch, info.train.error, 'k') ; hold on ;
      plot([1 opts.test_interval : opts.test_interval : epoch epoch], info.val.error([1 opts.test_interval : opts.test_interval : epoch epoch]), 'b') ;
      h=legend('train','val') ;
    case 'euclideanloss'
      plot(1 : epoch, info.train.error, 'k'); hold on;
      plot([1 opts.test_interval : opts.test_interval : epoch epoch], info.val.error([1 opts.test_interval : opts.test_interval : epoch epoch]), 'b') ;
      h = legend('train', 'val') ;
  end
  grid on ;
  xlabel('training epoch') ; ylabel('error') ;
  set(h,'color','none') ;
  title('error') ;
  drawnow ;
  print(1, modelFigPath, '-dpdf') ;
  end
end

% -------------------------------------------------------------------------
function info = updateError(opts, info, net, res, speed)
% -------------------------------------------------------------------------
predictions = gather(res(end-1).x) ;
sz = size(predictions) ;
n = prod(sz(1:2)) ;

labels = net.layers{end}.class ;
info.objective(end) = info.objective(end) + sum(double(gather(res(end).x))) ;
info.speed(end) = info.speed(end) + speed ;
switch opts.errorType
  case 'multiclass'
    [~,predictions] = sort(predictions, 3, 'descend') ;
    error = ~bsxfun(@eq, predictions, reshape(labels, 1, 1, 1, [])) ;
    info.error(end) = info.error(end) +....
      sum(sum(sum(error(:,:,1,:))))/n ;
    info.topFiveError(end) = info.topFiveError(end) + ...
      sum(sum(sum(min(error(:,:,1:5,:),[],3))))/n ;
  case 'binary'
    error = bsxfun(@times, predictions, labels) < 0 ;
    info.error(end) = info.error(end) + sum(error(:))/n ;
  case 'euclideanloss'
    error = euclideanloss(sigmoid(predictions), labels);
    info.error(end) = info.error(end) + error;
end


%SEQUENTIAL Sequential mapping
%
%   V = SEQUENTIAL(W1,W2)
%   B = SEQUENTIAL(A,W)
%
% INPUT
%   W,W1,W2  Mappings
%   A        Dataset
%
% OUTPUT
%   V        Sequentially combined mapping
%   B        Dataset
%
% DESCRIPTION
% The two mappings W1 and W2 are combined into a single mapping V. Note
% that SEQUENTIAL(W1,W2) is equivalent to W1*W2. If W2 is a mapping of
% a type 'combiner', it is called to make a combination attempt.
%	SEQUENTIAL(A,W) maps the dataset A by the sequential mapping W.
%
% This routine is automatically called to execute W = W1*W2 or B = A*W2 in
% case W2 is a sequential mapping. It should not be directly called by users.
%
% SEE ALSO (PRTools Guide)
% MAPPINGS, DATASETS

% Copyright: R.P.W. Duin, r.p.w.duin@37steps.com

function [w,varargout] = sequential(w1,w2,w3)

	% Just necessary to inform PRMAP.
	if (nargin == 0)
		w = prmapping(mfilename,'combiner');
		return;
	end
	[m1,k1] = size(w1);
	[m2,k2] = size(w2);
	if (~isa(w2,'prmapping'))
		error('Second argument should be a mapping.')
	end

	if isa(w1,'prmapping')
		% Definition
    if isempty(w1) % treat empty mapping as unity mapping
      w = w2;
    elseif (iscombiner(w2))
			% Execute the mapping W2.
      varargout = repmat({[]},[1, max((nargout-1),0)]);
			map = getmapping_file(w2);
			pars = getdata(w2);
			[w,varargout{:}] = feval(map,w1,pars{:});
		else
			% W2 is just a general mapping.
			if (k1 > 0) && (m2 > 0) && (k1 ~= m2)
				error('Inner mapping/data sizes do not agree.')
			end

			% Define the mapping type after combining W1 and W2.
			if (isuntrained(w1)) || (isuntrained(w2))
				mappingtype = 'untrained';
      elseif (isgenerator(w2))
        mappingtype = 'generator';
      %elseif (isfixed(w2)) || (isfixed_cell(w2))
      %  mappingtype = 'fixed';
			elseif (istrained(w1)) || (istrained(w2))
				mappingtype = 'trained';
			else
				mappingtype = 'fixed';
			end

			if strcmp(mappingtype,'untrained')
				labels = [];
				size_in = 0;
				size_out = 0;
			elseif (m2 == 0 || k2 == 0) && (m1 ~= 0) && (k1 ~= 0)
				% E.G. TRAINED * FIXED
				labels   = getlabels(w1);
				size_in  = getsize_in(w1);
				size_out = getsize_out(w1);

			elseif (m2 ~= 0) && (k2 ~= 0) && (m1 == 0 || k1 == 0)
				% FIXED * TRAINED
				labels   = getlabels(w2);
				size_in  = getsize_in(w2);
				size_out = getsize_out(w2);

			elseif ~istrained(w2)
				% TRAINED * FIXED
				labels   = getlabels(w1);
				size_in  = getsize_in(w1);
				size_out = getsize_out(w2);

			else
				% TRAINED * TRAINED
				labels = getlabels(w2);
				size_in = getsize_in(w1);
				size_out = getsize_out(w2);
			end
			w = prmapping(mfilename,mappingtype,{w1,w2},labels,size_in,size_out);
    end
    if ismapping(w) && (getbatch(w1) || getbatch(w2))
      [n1,b1,o1] = getbatch(w1);
      [n2,b2,o2] = getbatch(w2);
      if ~n1
        b12 = b2; o12 = o2;
      elseif ~n2
        b12 = b1; o12 = o1;
      else
        b12 = min(b1,b2); o12 = min(o1,o2);
      end
      w = setbatch(w,true,b12,o12);
    end

  elseif isempty(w2) % treat empty mapping as unity mapping
    w = w1;

  else
		% Execution. We are here, when SEQUENTIAL(A,V) is called.
		if nargin == 3 % needed as MAP breaks down sequential mappings
			w2 = w2*w3;  % restore them!
		end
		a = w1;
		if (~isa(a,'double')) && (~isa(a,'prdataset'))
			error('Just datasets or doubles can be mapped.')
		end
		% V can be a more complex mapping.
		% v = +w2; v1 = v{1}; v2 = v{2};
    [v1,v2] = getdata(w2);
		if (isuntrained(v1))
			if (isuntrained(v2))
				u = a*v1;
				w = u*(a*u*v2);
			else
				w = a*v1*v2;
			end
		else
			if (isuntrained(v2)) && (~isgenerator(v1))
				w = v1*(a*v1*v2);
				% may be v1 changed the dimensionality, reset it:
				w = setsize_in(w,size(a,2));
      else
        w1 = a*v1;
        w = w1*v2;
				%w = a*v1*v2;
				featlabels = getlabels(w2);
				if (isdataset(w)) && ~isempty(featlabels) && size(w,2) == size(featlabels,1)
					w = setfeatlab(w,featlabels);
				end
			end
		end
		if ismapping(w)
			w = setbatch(w,getbatch(w2));
		end
	end
return;


% timecrossf() - compute the time frequency content of an array rows and
%                cross-coherence between the rows for. If the rows depict
%                different components activation, these results can then
%                be used to analyse time relation between these
%                components  (using the brainmovie function). You may
%                also input electrode activities into this function to
%                analyse the synchronisation and phase relashionship
%                between the electrodes.
%
% Usage:
%    >> [ allersps, allitcs, allcrossfs, allcrossfphis, times, freqs] ...
%        = timecrossf(data, frames, tlimits, srate, cycles, ...);
%
% Inputs:
%   data        - multi-row data (nb_input,frames*nepochs) for the first
%                 condition. Each row is a different input. The function
%                 will conpute the time frequency content of each row and
%                 the cross-coherence between the rows. If data is a cell
%                 array { data1 data2 }, the function will compute the
%                 time-frequency decomposition for these 2 arrays and the
%                 difference between them.
%   frames      - frames per epoch                        {768}
%   tlimits     - epoch time limits (ms) [mintime maxtime]{[-1000 2000]}
%   srate       - data sampling rate (Hz)                 {256}
%   ...         - see timef or crossf command for details about other
%                 parameters
%
% Outputs:
%   allersps      - cell array (size nb_row,1) of ERSP (event-related
%                   power spectrum content of each row of the data array)
%   allitcs       - cell array (size nb_row,1) of ITC (event-related
%                   inter-trial coherence content of each row of the data
%                   array)
%   allcrossfs    - cell array (size nb_row, nb_row) of cross-coherence
%                   between the row of the data array (only the upper
%                   diagonal part of the cell array is used)
%   allcrossfphis - cell array (size nb_row, nb_row) of cross-coherence
%                   phase between the row of the data array (only the
%                   upper diagonal part of the cell array is used)
%   times         - time array returned by the crossf or timef functions
%   freqs         - frequency array returned by the crossf or timef
%                   functions
%
% Example1: generate a movie, 1 condition, below 10Hz dynamics
%
%   % ICA decomposition, activity in array actICA (31 x (176 x n)),
%   % graph for 6 components, 176 points from -100 ms to 600 ms after
%   % stimulus onset, 250 Hz sampling rate
%   [ allersps, allitcs, allcrossfs, allcrossfphis, times, freqs] ...
%        = timecrossf( actICA(1:6,:), 176, [-100 600], 250, 1, 32, 100);
%   brainmovie( ersps, itcs, crossfs_amp, crossfs_phase, times, [1:2] )
%   %[1:2] indicates the frequency rows to take into account (freqs(1:2))
%
% Example2: generate a movie, 2 condition, below 10Hz dynamics
%
%   % Same as above with actICA2 (31 x (176 x n2)) the activity of the
%   % components for a different condition
%   [ allersps, allitcs, allcrossfs, allcrossfphis, times, freqs] ...
%        = timecrossf( actICA(1:6,:), 176, [-100 600], 250, 1, 32, 100);
%   [ allersps(:,2), allitcs(:,2), allcrossfs(:,:,2), ...
%	                          allcrossfphis(:,:,2)] ...
%        = timecrossf( actICA2(1:6,:), 176, [-100 600], 250, 1, 32, 100);
%   brainmovie( allersps, allitcs, allcrossfs, allcrossfphis, times, ...
%                [1:2] )
%
% See also timef(), crossf(), brainmovie()

% arno@salk.edu, Arnaud Delorme, CNL / Salk Institute, 2001

% This program is free software; you can redistribute it and/or
% modify it.
% This program is distributed in the hope that it will be useful,
% but WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

function [ ALLERSP, ALLITC, ALLCROSSF, ALLCROSSFANGLE, times, freqs ] = timecrossf(data, frames, tlimits, srate, cycle, varargin);

if nargin < 3
	help timecrossf;
	return;
end;

SAVE    = 0;	% save graphs in jpeg format
%XDISP   = 'cole:0.0';
nbcompo = size( data, 1);
if iscell(data)
    data1 = data{1};
    data2 = data{2};
    nbcompo = size( data1, 1);
end;

% compute all time frequency maps
% -------------------------------
compter = 1;
for numcompo = 1:nbcompo
	if iscell(data)
        [ersp,itc,powbase,times,freqs,erspboot,itcboot] = newtimef({ data1(numcompo,:) data2(numcompo,:) }, ...
                                                          frames, tlimits, srate, cycle, varargin{:});
        size(ersp{1})
        ALLERSP{numcompo,1}     = applyboot( ersp{1}, erspboot{1});
        ALLERSP{numcompo,2}     = applyboot( ersp{2}, erspboot{2});
        ALLERSP{numcompo,3}     = applyboot( ersp{3}, erspboot{3});
        ALLITC {numcompo,1}     = applyboot( abs(itc{1}) , itcboot{1});
        ALLITC {numcompo,2}     = applyboot( abs(itc{2}) , itcboot{2});
        if ~isreal(itc{3}), itc{3} = abs(itc{3}); end;
        ALLITC {numcompo,3}     = applyboot( itc{3}, itcboot{3});
    else
        [ersp,itc,powbase,times,freqs,erspboot,itcboot] = newtimef( data(numcompo,:), ...
                                                          frames, tlimits, srate, cycle, varargin{:});
        ALLERSP{numcompo,1}    = applyboot( ersp, erspboot);
        ALLITC{numcompo,1}     = applyboot( itc,  itcboot);
    end;
	if SAVE
		%command = sprintf('print -djpeg timef%2.2d', numcompo); eval(command); close;
		command = sprintf('hgsave(''timef%2.2d'');', numcompo); eval(command); close;
	end;
	compter = compter + 1 ;
end;

% compute all cross coherence maps
% --------------------------------
ALLCROSSF = cell(nbcompo, nbcompo);
ALLCROSSFANGLE = cell(nbcompo, nbcompo);
for index1 = 1:nbcompo
	for index2 = 1:nbcompo
		if index2 > index1
            if iscell(data)
                [coh,mcoh,timesout,freqsout,cohboot,cohangles] = newcrossf({ data1(index1,:) data2(index1,:)}, ...
                                                                  { data1(index2,:) data2(index2,:)}, frames,  ...
                                                                  tlimits, srate, cycle, varargin{:});
                size(coh{1})
                ALLCROSSF      { index1, index2, 1 } = applyboot(coh{1}, cohboot{1});
                ALLCROSSF      { index1, index2, 2 } = applyboot(coh{2}, cohboot{2});
                ALLCROSSF      { index1, index2, 3 } = applyboot(coh{3}, cohboot{3});
                ALLCROSSFANGLE { index1, index2, 1 } = cohangles{1};
                ALLCROSSFANGLE { index1, index2, 2 } = cohangles{2};
                ALLCROSSFANGLE { index1, index2, 3 } = cohangles{3};
            else
                [coh,mcoh,timesout,freqsout,cohboot,cohangles] = newcrossf(data(index1,:), data(index2,:), frames,  ...
                                                                  tlimits, srate, cycle, varargin{:});
                ALLCROSSF      { index1, index2 } = applyboot(coh, cohboot);
                ALLCROSSFANGLE { index1, index2 } = cohangles;
                if SAVE
                    %command = sprintf('print -djpeg crossf%2.2d-%2.2d%', index1, index2); eval(command); close;
                    command = sprintf('hgsave(''crossf%2.2d-%2.2d%'');', index1, index2); eval(command); close;
                end;
            end;
		end;
	end;
end;

return;

function array = applyboot(array, arrayboot)
    if isempty(arrayboot), return; end;
    if size(arrayboot,3) == 2
        array(find((array > arrayboot(:,:,1)) & (array < arrayboot(:,:,2)))) = 0;
    elseif size(arrayboot,2) > 2
        array(find(array < arrayboot(:,:))) = 0;
    elseif size(arrayboot,2) == 2
        array(find((array > repmat(arrayboot(:,1),[1 size(array,2)])) & ...
                   (array < repmat(arrayboot(:,2),[1 size(array,2)])))) = 0;
    else
        array(find(array < repmat(arrayboot(:,1),[1 size(array,2)]))) = 0;
    end;


classdef prtClassKnn < prtClass
    % prtClassKnn  K-nearest neighbors classifier
    %
    %    CLASSIFIER = prtClassKnn returns a K-nearest neighbors classifier
    %
    %    CLASSIFIER = prtClassKnn(PROPERTY1, VALUE1, ...) constructs a
    %    prtClassKnn object CLASSIFIER with properties as specified by
    %    PROPERTY/VALUE pairs.
    %
    %    A prtClassKnn object inherits all properties from the abstract class
    %    prtClass. In addition is has the following properties:
    %
    %    k                  - The number of neigbors to be considered
    %    distanceFunction   - The function to be used to compute the
    %                         distance from samples to cluster centers.
    %                         It must be a function handle of the form:
    %                         @(x1,x2)distFun(x1,x2). Most prtDistance*
    %                         functions will work.
    %
    %    For information on the  K-nearest neighbors classifier algorithm, please
    %    refer to the following URL:
    %
    %    http://en.wikipedia.org/wiki/K-nearest_neighbor_algorithm
    %
    %    A prtClassKnn object inherits the TRAIN, RUN, CROSSVALIDATE and
    %    KFOLDS methods from prtAction. It also inherits the PLOT method
    %    from prtClass.
    %
    %    Example:
    %
    %     TestDataSet = prtDataGenUnimodal;      % Create some test and
    %     TrainingDataSet = prtDataGenUnimodal;  % training data
    %     classifier = prtClassKnn;           % Create a classifier
    %     classifier = classifier.train(TrainingDataSet);    % Train
    %     classified = run(classifier, TestDataSet);         % Test
    %     classifier.plot;
    %
    %    See also prtClass, prtClassLogisticDiscriminant, prtClassBagging,
    %    prtClassMap, prtClassCap, prtClassBinaryToMaryOneVsAll, prtClassDlrt,
    %    prtClassPlsda, prtClassFld, prtClassRvm, prtClassGlrt,  prtClass

    properties (SetAccess=private)

        name = 'K-Nearest Neighbor'   % K-Nearest Neighbor
        nameAbbreviation = 'KNN'      % KNN
        isNativeMary = true;          % true

    end

    properties

        k = 3;   % The number of neighbors to consider in the voting

        distanceFunction = @(x1,x2)prtDistanceEuclidean(x1,x2);   % Function handle to compute distance
    end

    methods
        function Obj = prtClassKnn(varargin)
            Obj = prtUtilAssignStringValuePairs(Obj,varargin{:});
            Obj.verboseStorage = true;
        end
        function Obj = set.k(Obj,val)
            if ~prtUtilIsPositiveScalarInteger(val)
                error('prt:prtClassKnn:k','k must be a positive scalar integer');
            end
            Obj.k = val;
        end

        function Obj = set.distanceFunction(Obj,val)
            if ~isa(val,'function_handle')
                error('prt:prtClassKnn:distanceFunction','distanceFunction must be a function handle');
            end
            Obj.distanceFunction = val;
        end
    end

    methods (Access=protected, Hidden = true)
        function Obj = preTrainProcessing(Obj,DataSet)
            if ~Obj.verboseStorage
                warning('prtClassKnn:verboseStorage:false','prtClassKnn requires verboseStorage to be true; overriding manual settings');
            end
            Obj.verboseStorage = true;
            Obj = preTrainProcessing@prtClass(Obj,DataSet);
        end
        function Obj = trainAction(Obj,twiddle)
            %Do nothing; we've already specified "verboseStorage = true",
            %so the ".dataSet" field will be set when it comes time to test
        end

        function ClassifierResults = runAction(Obj,PrtDataSet)

            x = getObservations(PrtDataSet);
            n = PrtDataSet.nObservations;

            nClasses = Obj.dataSet.nClasses;
            uClasses = Obj.dataSet.uniqueClasses;
            labels = getTargets(Obj.dataSet);
            y = zeros(n,nClasses);

            xTrain = getObservations(Obj.dataSet);

            largestMatrixSize = prtOptionsGet('prtOptionsComputation','largestMatrixSize');
            memBlock = max(floor(largestMatrixSize/size(xTrain,1)),1);

            if n > memBlock
                for start = 1:memBlock:n
                    indices = start:min(start+memBlock-1,n);

                    distanceMat = feval(Obj.distanceFunction,xTrain,x(indices,:));

                    [twiddle,I] = sort(distanceMat,1,'ascend');
                    I = I(1:Obj.k,:);
                    L = labels(I);

                    % MATLAB indexing is inexplicably different if I is
                    % 1xN or (k>1)xN
                    if Obj.k ~= 1
                        L = L';
                    end

                    for class = 1:nClasses
                        y(indices,class) = sum(L == uClasses(class),2);
                    end
                end
            else
                distanceMat = feval(Obj.distanceFunction,xTrain,x);

                [twiddle,I] = sort(distanceMat,1,'ascend');
                I = I(1:Obj.k,:);
                L = labels(I);

                % MATLAB indexing is inexplicably different if I is
                % 1xN or (k>1)xN
                if Obj.k ~= 1
                    L = L';
                end

                for class = 1:nClasses
                    y(:,class) = sum(L == uClasses(class),2);
                end
            end

            [Etc.nVotes,Etc.MapGuessInd] = max(y,[],2);
            Etc.MapGuess = uClasses(Etc.MapGuessInd);
            ClassifierResults = PrtDataSet;
            ClassifierResults.X = y;

        end

    end
end