function [rv,rvDebiased,rvSS,rvDebiasedSS,diagnostics] = realized_variance_optimal_sampling(price,time,timeType,samplingType,samplingInterval,subsamples,options)
% Estimates realized variance using Bandi-Russell optimal sampling selection
%
% USAGE:
%   [RV,RVD,RVSS,RVSSD,DIAGNOSTICS] = realized_variance_optimal_sampling(PRICE,TIME,TIMETYPE,SAMPLINGTYPE,SAMPLINGINVERVAL)
%   [RV,RVD,RVSS,RVSSD,DIAGNOSTICS] = realized_variance_optimal_sampling(PRICE,TIME,TIMETYPE,SAMPLINGTYPE,SAMPLINGINVERVAL,SUBSAMPLES,OPTIONS)
%
% INPUTS:
%   PRICE            - m by 1 vector of high frequency prices
%   TIME             - m by 1 vector of times where TIME(i) corresponds to PRICE(i)
%   TIMETYPE         - String describing the way times are measured
%                       'wall'    24-hour clock of the form HHMMSS, e.g. 101543 or 153217
%                       'seconds' Time measured in seconds past midnight on the first day.
%                       'unit'  Unit normalized date format, e.g. .1, .234, .9
%                         Unit normalized times are more general than the other types and can be
%                         applied to data from more than one calendar day
%   SAMPLINGTYPE     - String describing the type of sampling to use when
%                        filtering PRICE
%                        'CalendarTime' - Sample in calendar time using observations separated by
%                          SAMPLINGINTERVAL seconds
%                        'CalendarUniform' - Sample in calendar time using SAMPLINGINTERVAL
%                          observations spread uniformly between TIME(1) and TIME(m)
%                        'BusinessTime' - Sample in business (tick) time using observation separated
%                          by SAMPLINGINTERVAL ticks
%                        'BusinessUniform' - Sample in business (tick) time using observations
%                          uniformly spaced in business time.
%                        'Fixed' - Sample at specific points in time. When using fixed,
%                          SAMPLINGINTERVAL must be a n by 1 vector of times with the same TIMETYPE
%                          as TIME (i.e. seconds if TIME is in seconds)
%   SAMPLINGINTERVAL  - Scalar integer or n by 1 vector whose meaning depends on SAMPLINGTYPE
%   SUBSAMPLES        - [OPTIONAL] Scalar integer indicating the number of subsample realized
%                         variance estimators to average with the original realized variance.
%                         Subsample realized variances are based on prices uniformly spaced between
%                         the times (Calendar sampling) or ticks (Business sampling).  SUBSAMPLES=1
%                         will compute a subsample realized variance using the mid-point of the
%                         price sample points, 2 will use 1/3 and 2/3, and so on. In general this
%                         number should be small so the subsample estimators will be "sparse". If
%                         the computed optimal sampling frequency is smaller than SUBSAMPLES, then
%                         SUBSAMPLES is set to the compute optimal sampling frequency.
%   OPTIONS           - [OPTIONAL] Option structure initialized by realized_options.
%                         See help realized_options for a description of fields.
%
% OUTPUTS:
%   RV                - Realized variance estimated using the Bandi-Russell estimator of the optimal
%                         sampling frequency
%   RVD               - Debiased realized variance estimated using the Bandi-Russell estimator of
%                         the optimal sampling frequency for a debiased estimator
%   RVSS              - Subsample RV using the computed optimal samplig frequency
%   RVSSD             - Debiased version of subsample RV using the computed optimal samplig frequency
%   DIAGNOSTICS       - Structure with fields
%                         OPTIMALSAMPLES          - Optimal number of samples estimated using the
%                                                   Bandi-Russell methodology
%                         OPTIMALSAMPLESDEBIASED  - Optimal number of samples estimated using the
%                                                   Bandi-Russell methodology for use with a
%                                                   debiased estimator.
%                         SAMPLES                 - Number of sampled used.  Should be equal to
%                                                   OPTIMALNUMBEROFSAMPLES unless larger than the
%                                                   number of prices or smaller than 2
%                         SAMPLESDEBIASED         - Number of sampled used in debiased estimator.
%                                                   Should be equal to
%                                                   OPTIMALNUMBEROFSAMPLESDEBIASED unless larger
%                                                   than the number of prices or smaller than 2
%                         NOISEVARIANCE           - Estimated noise variance
%                         DEBIASEDNOISEVARIANCE   - Estimated noise variance bias adjusted
%                         IQESTIMATE              - Estimated IQ
%
% COMMENTS:
%   This function estimates the optimal number of samples to use when computing realized variance
%   using the method of Bandi & Russell (2008).  The role of SAMPLINGTYPE and SAMPLINGINTERVAL are
%   to set the MAXIMUM frequency of returns.  For example, if the maximum frequency was to be 15
%   seconds, set SAMPLINGTYPE='CalendarTime' and SAMPLINGINTERVAL=15.  To use all of the data,
%   set SAMPLINGTYPE='BusinessTime' and SAMPLINGINTERVAL=1.
%
% EXAMPLE:
%  % Optimal sampling in using all data
%  rvos = realized_variance_optimal_sampling(PRICE)
%  % Optimal sampling in business time with subsampling
%  [rvos,rvosSS] = realized_variance_optimal_sampling(PRICE,TIME,'Wall','BusinessTime',1,10)
%  % Optimal sampling in business time, using every 15th trade
%  rvos = realized_variance_optimal_sampling(PRICE,TIME,'Wall','BusinessTime',15)
%  % Optimal sampling where the maximum sampling frequency is 5 seonds
%  rvos = realized_variance_optimal_sampling(PRICE,TIME,'Wall','CalendarTime',5)
%
%  See also REALIZED_VARIANCE, REALIZED_KERNEL, REALIZED_QUANTILE_VARIANCE, REALIZED_RANGE,
%  REALIZED_PRICE_FILTER, REALIZED_THRESHOLD_VARIANCE

% Copyright: Kevin Sheppard
% kevin.sheppard@economics.ox.ac.uk
% Revision: 1    Date: 5/1/2008

%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Input Checking
%%%%%%%%%%%%%%%%%%%%%%%%%%%%

switch nargin
    case 1
        time = linspace(wall2seconds(93000),wall2seconds(160000),length(price));
        timeType = 'seconds';
        samplingType = 'businessuniform';
        samplingInterval = 1;
        subsamples = 1;
        options = realized_options('optimal sampling');
    case 5
        subsamples = 1;
        options = realized_options('optimal sampling');
    case 6
        options = realized_options('optimal sampling');
    case 7
        if isempty(subsamples)
            subsamples = 1;
        end
        % Nothing
    otherwise
        error('5 to 7 inputs required.')
end

errorMessage = realized_variance_optimal_sampling_parameter_check(price,time,timeType,samplingType,samplingInterval,subsamples,options);
if ~isempty(errorMessage )
    error(errorMessage)
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Input Checking
%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Inserted to protect against inputing integer times
time = double(time);
% Filter the price
[filteredPrice,filteredTime] = realized_price_filter(price,time,timeType,samplingType,samplingInterval);

% Compute the number of prices
m = size(filteredPrice,1);

% Next need to estimate IQ using RV squared
[noiseVariance, debiasedNoiseVariance, IQEstimate] = realized_noise_estimate(filteredPrice, filteredTime, timeType, options);
% Need to double these since these are the variance of the error to returns, not to prices which is
% what is returns from realized_noise_esitmate
noiseVariance = 2*noiseVariance;
debiasedNoiseVariance = 2*debiasedNoiseVariance;

% Need to estimate the IQ,  Variance of Noise, and 4th moment of noise (Eq. (16))
optimalNumberOfSamples = round((IQEstimate/(noiseVariance)^2)^(1/3));
if optimalNumberOfSamples>m
    warning('oxfordRealized:excessiveLags','WARNING')
    SamplesUsed = m;
elseif optimalNumberOfSamples<2
    warning('oxfordRealized:tooFewLags','WARNING')
    SamplesUsed = 2;
else
    SamplesUsed = optimalNumberOfSamples;
end
% Compute the realized variance using BT since the prices have been
% filtered
actualSubsamples = min(subsamples,SamplesUsed);
[rv,rvSS] = realized_variance(filteredPrice, filteredTime, timeType,'BusinessTime',SamplesUsed,actualSubsamples);

filteredPriceNoise = realized_price_filter(filteredPrice, filteredTime, timeType, options.noiseVarianceSamplingType, options.noiseVarianceSamplingInterval);
returns = diff(log(filteredPriceNoise));
if options.useAdjustedNoiseCount
    n = sum(returns~=0);
else
    n = length(returns);
end
noise4thPower = sum(returns.^4)/n;
noiseVariance = sum(returns.^2)/n;
% FIXME: Need to change way denominator is estimated
% Eq. 18
optimalNumberOfSamplesDebiased = round((2*IQEstimate / (2*noise4thPower - 3*noiseVariance^2))^(1/2));
if optimalNumberOfSamplesDebiased >m
    warning('oxfordRealized:excessiveLags','WARNING')
    samplesDebiased = m;
elseif optimalNumberOfSamplesDebiased <2
    warning('oxfordRealized:tooFewLags','WARNING')
    samplesDebiased = 2;
else
    samplesDebiased = optimalNumberOfSamplesDebiased;
end

% TODO See if it is possible to do a debiased using 1-m*/m type change
actualSubsamples = min(subsamples,samplesDebiased);
[rvDebiased,rvDebiasedSS] = realized_variance(filteredPrice,filteredTime,timeType,samplingType,samplesDebiased,actualSubsamples);
rvDebiased = rvDebiased - optimalNumberOfSamplesDebiased * noiseVariance;
rvDebiasedSS = rvDebiasedSS - optimalNumberOfSamplesDebiased * noiseVariance;
rvDebiased = rvDebiased/(1-optimalNumberOfSamplesDebiased/m);
rvDebiasedSS = rvDebiasedSS/(1-optimalNumberOfSamplesDebiased/m);

diagnostics.m = length(filteredPrice) - 1;
diagnostics.optimalSamples = optimalNumberOfSamples;
diagnostics.samples = SamplesUsed;
diagnostics.optimalNumberOfSamplesDebiased = optimalNumberOfSamplesDebiased;
diagnostics.samplesDebiased = samplesDebiased;
diagnostics.noiseVariance = noiseVariance;
diagnostics.debiasedNoiseVariance = debiasedNoiseVariance;
diagnostics.IQEstimate = IQEstimate;

function errorMessage = realized_variance_optimal_sampling_parameter_check(price,time,timeType,samplingType,samplingInterval,subsamples,options)
% Support function for realized_variance_optimal_sampling that does input validation
%
% USAGE:
%   [ERRORMESSAGE,OPTIONS] = realized_variance_optimal_sampling_parameter_check(PRICE,TIME,TIMETYPE,SAMPLINGTYPE,SAMPLINGINVERVAL,OPTIONS)
%
% INPUTS:
%   See realized_variance_optimal_sampling
%
% OUTPUT:
%   ERRORMESSAGE - String containing a description of the error if one is detected.  Empty if no error.
%
% COMMENTS:
%   See realized_options for a description of the other fields in OPTIONS
%
%  See also REALIZED_VARIANCE_OPTIMAL_SAMPLING

% Copyright: Kevin Sheppard
% kevin.sheppard@economics.ox.ac.uk
% Revision: 1    Date: 5/1/2008
errorMessage = [];

if size(price,2)>size(price,1)
    price=price';
end
if size(price,2)>1
    errorMessage = 'PRICE must be a m by 1 vector.';
    return
end
if size(time,2)>size(time,1)
    time=time';
end
if any(diff(time)<0)
    errorMessage = 'TIME must be sorted and increasing';
    return
end
if size(time,2)>1 || length(time)~=length(price)
    errorMessage = 'TIME must be a m by 1 vector.';
    return
end

timeType=lower(timeType);
if ~ismember(timeType,{'wall','seconds','unit'})
    errorMessage = 'TIMETYPE must be one of ''wall'', ''seconds'' or ''unit''.';
    return;
end

samplingType=lower(samplingType);
if ~ismember(samplingType,{'calendartime','calendaruniform','businesstime','businessuniform','fixed'})
    errorMessage = ('SAMPLINGTYPE must be one of ''CalendarTime'', ''CalendarUniform'', ''BusinessTime'', ''BusinessUniform'' or ''Fixed''.');
    return;
end

if ~isempty(subsamples)
    if ~isscalar(subsamples) || subsamples<0 || floor(subsamples)~=subsamples
        errorMessage = 'SUBSAMPLES must be a non-negative scalar.';
        return
    end
end

% SAMPLINGINTERVAL
m=size(price,1);
t0=time(1);
tT=time(m);
if ismember(samplingType,{'calendartime','calendaruniform','businesstime','businessuniform'})
    % Must be a scalar integer if timeType is seconds or wall
    if ismember(timeType,{'wall','seconds'})
        if ~isscalar(samplingInterval) || floor(samplingInterval)~=samplingInterval || samplingInterval<1
            error('SAMPLINGINTERVAL must be a positive integer for the SAMPLINGTYPE selected when using ''wall'' or ''seconds'' as TIMETYPE.')
        end
    else
        if ~isscalar(samplingInterval) || samplingInterval<0
            error('SAMPLINGINTERVAL must be a positive value for the SAMPLINGTYPE selected when using ''unit'' as TIMETYPE.')
        end
    end
else
    if size(samplingInterval,2)>size(samplingInterval,1)
        samplingInterval=samplingInterval';
    end
    if ~(any(samplingInterval>=t0) && any(samplingInterval<=tT))
        error('At least one sampling interval must be between min(TIME) and max(TIME) when using ''Fixed'' as SAMPLINGTYPE.')
    end
    if any(diff(samplingInterval)<=0)
        error('When using ''Fixed'' as SAMPLINGTYPE the vector of sampling times in SAMPLINGINTERVAL must be sorted and strictly increasing.')
    end
end

% Options

% List of flat top kernels
flatTopKernelList = {'bartlett','twoscale','2ndorder','epanechnikov',...
    'cubic','multiscale','5thorder','6thorder','7thorder','8thorder','parzen',...
    'th1','th2','th5','th16'};

% List of non flat top kernels
nonFlatTopKernelList = {'nonflatparzen','qs','fejer','thinf','bnhls'};

% Combined kernel list
kernelList = [flatTopKernelList nonFlatTopKernelList];

% Check fields for valid values
optionsFieldNames = fieldnames(options);
% Insert any missing fiedls
defaultOptions = realized_options('Optimal Sampling');
defaultFieldNames = fieldnames(defaultOptions);
missingFields = setdiff(defaultFieldNames,optionsFieldNames);
if ~isempty(missingFields)
    for i = 1:length(missingFields)
        options.(missingFields{i}) = defaultOptions.(missingFields{i});
    end
end

for i=1:length(optionsFieldNames)
    fieldName = optionsFieldNames{i};
    fieldValue = options.(fieldName);
    if ischar(fieldValue)
        fieldValue = lower(fieldValue);
        options.(fieldName) = fieldValue;
    end

    switch fieldName
        case {'medFrequencyKernel'}
            % Member of kernelList
            if ~ismember(fieldValue,kernelList)
                errorMessage = ['OPTIONS.' fieldName ' must be one of the listed types.'];
                return
            end
        case {'medFrequencyBandwidth'}
            % Non-negative scalar
            if ~isempty(fieldValue) && ~isnonnegativescalar(fieldValue)
                errorMessage = ['OPTIONS.' fieldName ' must a non-negative scalar.'];
                return
            end
        case {'useDebiasedNoise','useAdjustedNoiseCount'}
            % Logical or scalar
            if ~islogical(fieldValue) && ~ismember(fieldValue,[0 1])
                errorMessage = 'OPTIONS.useDebiasedNoise must be a logical value.';
                return
            end
        case {'IQEstimationSamplingType','medFrequencySamplingType','noiseVarianceSamplingType'}
            % One of the sampling types
            if ~ismember(fieldValue,{'calendartime','calendaruniform','businesstime','businessuniform','fixed'})
                errorMessage = ['OPTIONS.' fieldName ' must be one of ''CalendarTime'', ''CalendarUniform'', ''BusinessTime'', ''BusinessUniform'' or ''Fixed''.'];
                return
            end
        case {'medFrequencySamplingInterval','noiseVarianceSamplingInterval','IQEstimationSamplingInterval'}
            % Non-negative scalar, less that 1 if timeType is unit
            if isempty(fieldValue) || ~isnonnegativescalar(fieldValue)
                errorMessage = ['OPTIONS.' fieldName ' must be a non-negative scalar between 0 and 1.'];
                return
            end
            if strcmp(timeType,'unit') && fieldValue>1
                errorMessage = ['OPTIONS.' fieldName ' must be less than 1 if TIMETYPE when ''unit''.'];
                return
            end
    end
end

function condition = isnonnegativescalar(x)
% Function that returns logical true if that input is a non-empty scalar >=0
condition = ~isempty(x) && isscalar(x) && x>=0;


% Calculate the 16-bit CRC used on the payload for DroneID
%
% This code is a MATLAB port of
% https://github.com/dji-sdk/Guidance-SDK/blob/master/examples/uart_example/crc16.cpp
%
% This function can be used to validate a CRC by running with all bytes including the CRC.  If the purpose is to
% calculate what the CRC should be, then do not include the additional two bytes of zeros and instead just remove the
% bytes that will be the CRC
%
% @param byte_vector Row/Column vector of uint8 values
% @return checksum uint16 CRC output value
function [checksum] = calculate_crc(byte_vector)

    crc_table = [
        0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf, ...
        0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7, ...
        0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e, ...
        0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876, ...
        0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd, ...
        0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5, ...
        0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c, ...
        0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974, ...
        0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb, ...
        0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3, ...
        0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a, ...
        0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72, ...
        0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9, ...
        0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1, ...
        0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738, ...
        0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70, ...
        0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7, ...
        0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff, ...
        0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036, ...
        0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e, ...
        0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5, ...
        0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd, ...
        0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134, ...
        0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c, ...
        0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3, ...
        0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb, ...
        0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232, ...
        0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a, ...
        0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1, ...
        0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9, ...
        0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330, ...
        0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78 ...
    ];

    assert(~isempty(byte_vector), "Input vector was empty");
    assert(isrow(byte_vector) || iscolumn(byte_vector), "Input vector must be row/col");

    checksum = uint16(0x3692);

    for idx=1:length(byte_vector)
        % Very verbose due to the amount of strange things that need to be done to make MATLAB play nice with bit ops
        new_byte = uint8(byte_vector(idx));
        table_idx = bitxor(checksum, uint16(new_byte));
        table_idx = bitand(table_idx, uint16(0x00ff));
        val = crc_table(table_idx + 1);
        temp_checksum = bitshift(checksum, -8);
        checksum = bitxor(temp_checksum, val);
    end
end


function pointsOut = applyHomography(pointsIn, H)
%% Apply homography transformation to a set of points.

%% License
% ACADEMIC OR NON-PROFIT ORGANIZATION NONCOMMERCIAL RESEARCH USE ONLY
% Copyright (c) 2018 Bingyao Huang
% All rights reserved.

% Redistribution and use in source and binary forms, with or without
% modification, are permitted provided that the following conditions are met:

% The above copyright notice and this permission notice shall be included in all
% copies or substantial portions of the Software.

% If you publish results obtained using this software, please cite our paper.

% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
% SOFTWARE.

%%

% convert to homogenious coord and transpose
pointsInHomogen = [pointsIn, ones(size(pointsIn,1),1)]';

% apply homography
pointsOutHomogen = H * pointsInHomogen;

% divide by w and convert back to non-homogenious, then transpose
pointsOut = [pointsOutHomogen(1,:)./pointsOutHomogen(3,:); ...
    pointsOutHomogen(2,:)./pointsOutHomogen(3,:)]';
end


%   TTeMPS Toolbox.
%   Michael Steinlechner, 2013-2016
%   Questions and contact: michael.steinlechner@epfl.ch
%   BSD 2-clause license, see LICENSE.txt

function [X, residual, cost, times] = amen_fast( L, F, X, opts )

t_start = tic();
% set default opts
if ~exist( 'opts', 'var');        opts = struct();     end
if ~isfield( opts, 'nSweeps');    opts.nSweeps = 4;    end
if ~isfield( opts, 'maxrank');    opts.maxrank = 20;   end
if ~isfield( opts, 'maxrankRes'); opts.maxrankRes = 4; end
if ~isfield( opts, 'tolRes');     opts.tolRes = 1e-13;  end
if ~isfield( opts, 'tol');        opts.tol = 1e-13;     end
if ~isfield( opts, 'solver');     opts.solver = 'direct';   end
if ~isfield( opts, 'prec');       opts.prec = true;   end

d = X.order;
n = X.size;

normF = norm(F);
cost = cost_function( L, X, F );
residual = norm( apply(L, X) - F ) / normF;
times = toc(t_start);

for sweep = 1:opts.nSweeps
    X = orthogonalize(X, 1);
    for mu = 1:d-1
        disp( ['Current core: ', num2str(mu)] )

        % STEP 1: Solve mu-th core opimization
        F_mu = contract( X, F, mu );
        sz = [X.rank(mu), X.size(mu), X.rank(mu+1)];

        if strcmpi( opts.solver, 'direct' )
            % if system very small
            L_mu = contract( L, X, mu );
            U_mu = L_mu \ F_mu(:);
            X.U{mu} = reshape( U_mu, sz );
        elseif strcmpi( opts.solver, 'pcg' )
            [left, right] = Afun_prepare( L, X, mu );
            [B2, V, E] =  prepare_precond( L.L0, X, mu );

            U_mu = pcg( @(y) Afun( L, y, mu, sz, left, right), ...
                     F_mu(:), ...
                     1e-10, 1000, ...
                     @(y) apply_precond( B2, V, E, y, sz ), [],...
                     X.U{mu}(:) );
            X.U{mu} = reshape( U_mu, sz );
        else
            error( 'Unknown opts.solver type. Use either ''direct'' (default) or ''pcg''.' )
        end

        % STEP 2: Calculate current residual and cost function
        res =  F - apply(L, X);
        residual = [residual; norm( res ) / normF];
        cost = [cost; cost_function( L, X, F )];
        disp(['Rel. residual: ' num2str(residual(end)) ', Current rank: ' num2str(X.rank) ]);

        % STEP 3: Augment mu-th and (mu+1)-th core with (truncated) residual
        R = contract( X, res, [mu, mu+1] );
        R_combined = unfold(R{1},'left') * unfold(R{2},'right');
        if opts.prec
            R_combined = precond_residual( L.L0, X, R_combined, mu );
        end
        [uu,ss,~] = svd( R_combined, 'econ');
        s = find( diag(ss) > opts.tolRes*norm(diag(ss)), 1, 'last' );
		if opts.maxrankRes ~= 0
			s = min( s, opts.maxrankRes );
		end
        R{1} = reshape( uu(:,1:s)*ss(1:s,1:s), [X.rank(mu), n(mu), s]);
        %R{2} = reshape( vv(:,1:s)', [s, n(mu+1), X.rank(mu+2)]);

        left = cat(3, X.U{mu}, R{1});
        %right = cat(1, X.U{mu+1}, R{2});

        % STEP 4: Move orthogonality to (mu+1)-th core while performing rank truncation
        [U,S,~] = svd( unfold(left,'left'), 'econ' );
        t = find( diag(S) > opts.tol*norm(diag(S)), 1, 'last' );
        t = min( t, opts.maxrank );
        X.U{mu} = reshape( U(:,1:t), [X.rank(mu), n(mu), t] );
        X.U{mu+1} = rand( t, n(mu+1), X.rank(mu+2));

        times = [times; toc(t_start)];
    end
    for mu = d:-1:2
        disp( ['Current core: ', num2str(mu)] )

        % STEP 1: Solve mu-th core opimization
        F_mu = contract( X, F, mu );
        sz = [X.rank(mu), X.size(mu), X.rank(mu+1)];

        if strcmpi( opts.solver, 'direct' )
            L_mu = contract( L, X, mu );
            U_mu = L_mu \ F_mu(:);
            X.U{mu} = reshape( U_mu, size(X.U{mu}) );
        elseif strcmpi( opts.solver, 'pcg' )
            [left, right] = Afun_prepare( L, X, mu );
            [B2, V, E] =  prepare_precond( L.L0, X, mu );

            U_mu = pcg( @(y) Afun( L, y, mu, sz, left, right), ...
                     F_mu(:), ...
                     1e-10, 1000, ...
                     @(y) apply_precond( B2, V, E, y, sz ), [],...
                     X.U{mu}(:) );
            X.U{mu} = reshape( U_mu, sz );
        else
            error( 'Unknown opts.solver type. Use either ''direct'' (default) or ''diag''.' )
        end

        % STEP 2: Calculate current residual and cost function
        res =  F - apply(L, X);
        residual = [residual; norm( res ) / normF];
        disp(['Rel. residual: ' num2str(residual(end)) ', Current rank: ' num2str(X.rank) ]);
        cost = [cost; cost_function( L, X, F )];

        % STEP 3: Augment mu-th and (mu+1)-th core with (truncated) residual
        R = contract( X, res, [mu-1, mu] );
        R_combined = unfold(R{1},'left') * unfold(R{2},'right');
        if opts.prec
            R_combined = precond_residual( L.L0, X, R_combined, mu-1 );
        end
        [~,ss,vv] = svd( R_combined, 'econ');
        s = find( diag(ss) > opts.tolRes*norm(diag(ss)), 1, 'last' );
		if opts.maxrankRes ~= 0
			s = min( s, opts.maxrankRes );
		end
        R{2} = reshape( ss(1:s,1:s)*vv(:,1:s)', [s, n(mu), X.rank(mu+1)]);

        right = cat(1, X.U{mu}, R{2});

        % STEP 4: Move orthogonality to (mu+1)-th core while performing rank truncation
        [~,S,V] = svd( unfold(right,'right'), 'econ' );
        t = find( diag(S) > opts.tol*norm(diag(S)), 1, 'last' );
        t = min( t, opts.maxrank );
        X.U{mu} = reshape( V(:,1:t)', [t, n(mu), X.rank(mu+1)] );
        X.U{mu-1} = rand( X.rank(mu-1), n(mu-1), t);

        times = [times; toc(t_start)];
    end

end

end

function res = cost_function( L, X, F )
res = 0.5*innerprod( X, apply(L, X) ) - innerprod( X, F );
end

function res = euclid_grad( L, X, F )
res = apply(L, X) - F;
end

function res = precond_residual( L0, X, R_combined, idx )
    n = size(L0, 1);
    rl = X.rank(idx);
    rr = X.rank(idx+2);

    B1 = zeros( rl );
    % calculate B1 part:
    for i = 1:idx-1
        % apply L to the i'th core
        tmp = X;
        tmp.U{i} = tensorprod_ttemps( tmp.U{i}, L0, 2 );
        B1 = B1 + innerprod( X, tmp, 'LR', idx-1);
    end

    % calculate B2 part:
    B2 = kron( L0, speye(n) ) + kron( speye(n), L0 );

    B3 = zeros( rr );
    % calculate B3 part:
    for i = idx+2:X.order
        tmp = X;
        tmp.U{i} = tensorprod_ttemps( tmp.U{i}, L0, 2 );
        B3 = B3 + innerprod( X, tmp, 'RL', idx+2);
    end

    [V,E] = eig( kron( eye(rr), B1 ) + kron( B3, eye(rl) ) );
    E = diag(E);

    R_combined = reshape( R_combined, [rl, n*n, rr] );
    rhs = matricize( R_combined, 2 ) * V;
    Y = zeros(size(rhs));
    for i=1:length(E)
        Y(:,i) = (B2 + E(i)*speye(n*n)) \ rhs(:,i);
    end
    res = tensorize( Y*V', 2, [rl, n*n, rr] );
    res = reshape( res, [rl*n, n*rr] );
end

function [left, right] = Afun_prepare( A, x, idx )
    y = A.apply(x);
    if idx == 1
        right = innerprod( x, y, 'RL', idx+1 );
        left = [];
    elseif idx == x.order
        left = innerprod( x, y, 'LR', idx-1 );
        right = [];
    else
        left = innerprod( x, y, 'LR', idx-1 );
        right = innerprod( x, y, 'RL', idx+1 );
    end
end

function res = Afun( A, U, idx, sz, left, right )

    V = reshape( U, sz );
    V = A.apply( V, idx );

    if idx == 1
        tmp = tensorprod_ttemps( V, right, 3 );
    elseif idx == A.order
        tmp = tensorprod_ttemps( V, left, 1 );
    else
        tmp = tensorprod_ttemps( V, right, 3);
        tmp = tensorprod_ttemps( tmp, left, 1);
    end

    res = tmp(:);
end
function [B2, V, E] = prepare_precond( L0, X, idx )
    n = size(L0, 1);
    rl = X.rank(idx);
    rr = X.rank(idx+1);

    B1 = zeros( rl );
    % calculate B1 part:
    for i = 1:idx-1
        % apply L to the i'th core
        tmp = X;
        tmp.U{i} = tensorprod_ttemps( tmp.U{i}, L0, 2 );
        B1 = B1 + innerprod( X, tmp, 'LR', idx-1);
    end

    % calculate B2 part:
    B2 = L0;

    B3 = zeros( rr );
    % calculate B3 part:
    for i = idx+1:X.order
        tmp = X;
        tmp.U{i} = tensorprod_ttemps( tmp.U{i}, L0, 2 );
        B3 = B3 + innerprod( X, tmp, 'RL', idx+1);
    end

    [V,E] = eig( kron( eye(rr), B1 ) + kron( B3, eye(rl) ) );
    E = diag(E);
end
function res = apply_precond( B2, V, E, rhs, sz )
    n = size(B2, 1);
    rhs = reshape( rhs, sz );
    rhs = matricize( rhs, 2 ) * V;
    Y = zeros(size(rhs));
    for i=1:length(E)
        Y(:,i) = (B2 + E(i)*speye(n)) \ rhs(:,i);
    end
    res = tensorize( Y*V', 2, sz );
    res = res(:);
end


% Outputs:  (1) r: steady state interest rate
%           (2) w: steady state wage
%           (3) K: capital stock
%           (4) A: matrix for computing value function
%           (5) u: utility over grid
%           (5) c: consumption over grid
%           (6) V: value function over grid
%			(7) g: distribution
%           (8) dV_Upwind: derivative of value function by upwind scheme
%           (9) dVf: derivative of value function by forward difference
%           (10) dVb: derivative of value function by backward difference
%           (11) If: indicator for forward drift in savings
%           (12) Ib: indicator for backward drift in savings
%           (13) I0: indicator for no drift in savings

function [r,w,K,A,u,c,V,g,dV_Upwind,dVf,dVb,If,Ib,I0] = compute_steady_state()

%----------------------------------------------------------------
% Housekeeping
%----------------------------------------------------------------

% Declare global variables
global ggamma rrho ddelta aalpha ssigmaTFP rrhoTFP z lla mmu ttau I amin amax a da aa ...
	zz Aswitch rmin rmax r0 maxit crit Delta Ir crit_S zAvg A

% Initialze variables for iteration
dVf = zeros(I,2);
dvB = zeros(I,2);
c = zeros(I,2);
KS = zeros(Ir,1);
r = r0;
KD = (((aalpha) / (r + ddelta)) ^ (1 / (1 - aalpha))) * zAvg;
w = (1 - aalpha) * (KD ^ aalpha) * ((zAvg) ^ (-aalpha));
v0(:,1) = (w*mmu*(1-z(1)) + r.*a).^(1-ggamma)/(1-ggamma)/rrho;
v0(:,2) = (w*(1-ttau)*z(2) + r.*a).^(1-ggamma)/(1-ggamma)/rrho;

%----------------------------------------------------------------
% Iterate to find steady state interest rate
%----------------------------------------------------------------

for ir=1:Ir

    r_r(ir)=r;
    rmin_r(ir)=rmin;
    rmax_r(ir)=rmax;

    KD(ir,1) = (((aalpha) / (r + ddelta)) ^ (1 / (1 - aalpha))) * zAvg;
	w = (1 - aalpha) * (KD(ir) ^ aalpha) * ((zAvg) ^ (-aalpha));

    if ir>1

        v0 = V_r(:,:,ir-1);

    end

    v = v0;

    %%%%
    % Solve for value function given r
    %%%%

    for n=1:maxit

        V = v;
        V_n(:,:,n)=V;

        % Compute forward difference
        dVf(1:I-1,:) = (V(2:I,:)-V(1:I-1,:))/da;
        dVf(I,:) = (w*((1 - ttau) * z + mmu * (1 - z)) + r.*amax).^(-ggamma); %will never be used, but impose state constraint a<=amax just in case

        % Compute backward difference
        dVb(2:I,:) = (V(2:I,:)-V(1:I-1,:))/da;
        dVb(1,:) = (w*((1 - ttau) * z + mmu * (1 - z)) + r.*amin).^(-ggamma); %state constraint boundary condition

        % Compute consumption and savings with forward difference
        cf = dVf.^(-1/ggamma);
        ssf = w*((1 - ttau) * zz + mmu * (1 - zz)) + r.*aa - cf;

        % Compute consumption and savings with backward difference
        cb = dVb.^(-1/ggamma);
        ssb = w*((1 - ttau) * zz + mmu * (1 - zz)) + r.*aa - cb;

        % Compute consumption and derivative of value function for no drift
        c0 = w*((1 - ttau) * zz + mmu * (1 - zz)) + r.*aa;
        dV0 = c0.^(-ggamma);

        % Compute upwind differences
        If = ssf > 0;       %positive drift --> forward difference
        Ib = ssb < 0;       %negative drift --> backward difference
        I0 = (1-If-Ib);     %no drift
        dV_Upwind = dVf.*If + dVb.*Ib + dV0.*I0;
        c = dV_Upwind.^(-1/ggamma);
        u = c.^(1-ggamma)/(1-ggamma);
        savingsSS = w*((1 - ttau) * zz + mmu * (1 - zz)) + r.*aa - c;

        % Construct matrix for updating implicit scheme
        X = -min(ssb,0)/da;
        Y = -max(ssf,0)/da + min(ssb,0)/da;
        Z = max(ssf,0)/da;

        A1=spdiags(Y(:,1),0,I,I)+spdiags(X(2:I,1),-1,I,I)+spdiags([0;Z(1:I-1,1)],1,I,I);
        A2=spdiags(Y(:,2),0,I,I)+spdiags(X(2:I,2),-1,I,I)+spdiags([0;Z(1:I-1,2)],1,I,I);
        A = [A1,sparse(I,I);sparse(I,I),A2] + Aswitch;

        B = (1/Delta + rrho)*speye(2*I) - A;

        u_stacked = [u(:,1);u(:,2)];
        V_stacked = [V(:,1);V(:,2)];
        b = u_stacked + V_stacked/Delta;

        % Solve system of equations for updating implicit scheme
        V_stacked = B\b;

        V = [V_stacked(1:I),V_stacked(I+1:2*I)];

        % Update value function and check convergence
        Vchange = V - v;
        v = V;

        dist(n) = max(max(abs(Vchange)));
        if dist(n)<crit

            %disp('Value Function Converged, Iteration = ')
            %disp(n)
            break

        end

    end

    %%%%
    % Solve for stationary distribution
    %%%%

    % Preallocate matrices for solving linear system
    AT = A';
    b = zeros(2*I,1);

    % Normalization so pdf integrates to 1
    i_fix = 1;
    b(i_fix)=.1;
    row = [zeros(1,i_fix-1),1,zeros(1,2*I-i_fix)];
    AT(i_fix,:) = row;

    %Solve linear system for distribution
    gg = AT\b;
    g_sum = gg'*ones(2*I,1)*da;
    gg = gg./g_sum;
    g = [gg(1:I),gg(I+1:2*I)];

    % Compute objects from this iteration
    g_r(:,:,ir) = g;
    adot(:,:,ir) = w * ((1 - ttau) * zz + mmu * (1 - zz))+ r.*aa - c;
    V_r(:,:,ir) = V;

    KS(ir,1) = g(:,1)'*a*da + g(:,2)'*a*da;
    S(ir,1) = KS(ir,1) - KD(ir,1);

    % Update interest rate
    if S(ir)>crit_S

        %disp('Excess Supply')
        rmax = r;
        r = 0.5*(r+rmin);

    elseif S(ir)<-crit_S;

        %disp('Excess Demand')
        rmin = r;
        r = 0.5*(r+rmax);

    elseif abs(S(ir))<crit_S;

        display('Steady State Found, Interest rate =')
        disp(r)
        break

    end

end

% Save steady state aggregate capital stock
K = KS(ir,1);
% investment
savings = w*((1 - ttau) * zz + mmu * (1 - zz)) + r.*aa - c;
invest = sum((reshape(savings,I*2,1) .* gg)'*da);


function [params] = pt_calc(params)
    % Calculation of the probabilistic forecast test.
    %
    % [params] = pt_calc(params)
    %
    % Input parameters:
    %   params.mCatalog           Earthquake catalog
    %   params.mPolygon           Polygon (defined by ex_selectgrid)
    %   params.vX                 X-vector (defined by ex_selectgrid)
    %   params.vY                 Y-vector (defined by ex_selectgrid)
    %   params.vUsedNodes         Used nodes vX * vY defining the mPolygon (defined by ex_selectgrid)
    %   params.bRandom            Perform random simulation (true) or real calculation (false)
    %   params.nCalculation       Number of random simulations
    %   params.bMap               Calculate a map (true) or a cross-section (false)
    %   params.bNumber            Use constant number (true) or constant radius (false)
    %   params.nNumberEvents      Number of earthquakes if bNumber is true
    %   params.fRadius            Radius of gridnode if bNumber is false
    %   params.nMinimumNumber     Minimum number of earthquakes per node for determining a b-value
    %   params.fForecastPeriod    Forecasting period in years
    %   params.bLearningPeriod    Use different learning period than the rest of the catalog
    %   params.fLearningPeriod    Learning period in years
    %   params.bSignificance      Calculate significance during random simulation
    %                             using params.fRealProbability
    %   params.fRealProbability   Probability of calculation with real data
    %   params.nCalculateMC       Method to calculate magnitude of completeness (see also: help calc_Mc)
    %   params.nTestMethod        Method to calculate the Kagan & Jackson test (see also: help kj_poissonian)
    %   params.bMinMagMc          Use magnitude of completeness as lower limit of magnitude range for testing (true)
    %                             Use params.fMinMag as lower limit (false)
    %   params.fMinMag            Lower limit of magnitude range for testing
    %   params.fMaxMag            Upper limit of magnitude range for testing
    %
    % Output parameters:
    %   Same as input parameters including
    %   params.mValueGrid         Matrix of calculated Kagan & Jackson test values
    %   params.vRandomMeans       Vector of means of probability differences per simulation run
    %   params.vSignificanceLevel Vector of significance levels per simulation run
    %   params.fBValueOverall     Calculated overall b-value
    %   params.fStdDevOverall     Calculated standard deviation
    %   params.fMcOverall         Calculated magnitude of completeness
    %
    % Danijel Schorlemmer
    % July 10, 2002

    report_this_filefun();

    % Perform the calculation
    % -----------------------
    % Compute the b-values for both models
    [mValueGridH, vcsGridNamesH] = pt_calcinput(params.mLearningCatalog, params.mObservedCatalog, params.mPolygon, params.rOptions(1));
    [mValueGridN, vcsGridNamesN] = pt_calcinput(params.mLearningCatalog, params.mObservedCatalog, params.mPolygon, params.rOptions(2));
    % Apply test moethod settings
    if params.nTestMethod == 2    % Use overall b-value for test hypothesis if it's impossible to compute a b-value
        vSel = isnan(mValueGridH(:,1));
        mValueGridH(vSel,:) = mValueGridN(vSel,:);
    end
    % Init result matrix
    mValueGrid_ = [];
    % Loop over all grid nodes
    for nNode_ = 1:length(params.mPolygon(:,1))
        % Create node catalogs
        mLearningNodeCatalog_ = params.mLearningCatalog(params.rOptions(3).caLearningNodeIndices{nNode_}, :);
        mObservedNodeCatalog_ = params.mObservedCatalog(params.rOptions(3).caObservedNodeIndices{nNode_}, :);
        % Define magnitude range for testing
        fMinMag_ = max([mValueGridH(nNode_,3) mValueGridN(nNode_,3)]);
        if ~(params.bMinMagMc)
            fMinMag_ = max(params.fMinMag, fMinMag_);
        end
        % Calculate the probability-ratio
        [fDeltaProbability, fProbabilityN, fProbabilityH, vPredictionFMD, vObservedFMD, vMagnitudeBins] = pt_poissonian(mLearningNodeCatalog_, params.fLearningPeriodUsed, ...
            mObservedNodeCatalog_, params.fObservedPeriodUsed, params.rOptions(3).nMinimumNumber, mValueGridH(nNode_,1), mValueGridN(nNode_,1), ...
            mValueGridH(nNode_,3), mValueGridN(nNode_,3), fMinMag_, params.fMaxMag);
        if (((params.bRandomNode) | (params.bSaveRates)) & (~isnan(fProbabilityH)))
            nLen_ = length(vObservedFMD(:,1));
            vXMin_ = ones(nLen_, 1) * params.mPolygon(nNode_,1) - (params.rOptions(3).fSizeRectX/2);
            vXMax_ = ones(nLen_, 1) * params.mPolygon(nNode_,1) + (params.rOptions(3).fSizeRectX/2);
            vYMin_ = ones(nLen_, 1) * params.mPolygon(nNode_,2) - (params.rOptions(3).fSizeRectY/2);
            vYMax_ = ones(nLen_, 1) * params.mPolygon(nNode_,2) + (params.rOptions(3).fSizeRectY/2);
            vZMin_ = zeros(nLen_, 1);
            vZMax_ = zeros(nLen_, 1);
            vWeight_ = ones(nLen_, 1);
            vMagMin_ = vMagnitudeBins - 0.05;
            vMagMax_ = vMagnitudeBins + 0.05;
            vRatesH = [vXMin_ vXMax_ vYMin_ vYMax_ vZMin_ vZMax_ vMagMin_ vMagMax_ vPredictionFMD(:,1) vWeight_ vObservedFMD];
            vRatesN = [vXMin_ vXMax_ vYMin_ vYMax_ vZMin_ vZMax_ vMagMin_ vMagMax_ vPredictionFMD(:,2) vWeight_ vObservedFMD];
            if params.bSaveRates
                params.vRatesH = [params.vRatesH; vRatesH];
                params.vRatesN = [params.vRatesN; vRatesN];
            end
            if params.bRandomNode
                [rRelmTest] = relm_RTest4(vRatesH, vRatesN, params.nNumberCalculationNode, vMagnitudeBins(1), 1, 1, 0);
                fAlpha = rRelmTest.fAlpha;
                fBeta = rRelmTest.fBeta;
            else
                fAlpha = nan;
                fBeta = nan;
            end
        else
            fAlpha = nan;
            fBeta = nan;
        end
        mValueGrid_= [mValueGrid_; fDeltaProbability fProbabilityN fProbabilityH ...
            fAlpha fBeta];
    end % for nNode
    params.vcsGridNames = cellstr(char('Probability difference', ...
        'Null hypothesis', 'Test hypothesis', 'Alpha', 'Beta'));
    % Add the b-values of the hypothesis to the overall value grid
    params.mValueGrid = [mValueGrid_ mValueGridH mValueGridN];
    params.vcsGridNames = [params.vcsGridNames; vcsGridNamesH; vcsGridNamesN];
end


function [mMcBdepth] = calc_McBwdepth(mCatalog, fBinning, nEvents, nOverlap, nBootSample,nMinNumberevents )
% function [mMcBdepth] = calc_McBwdepth(mCatalog, fBinning, nEvents, nOverlap, nBootSample,nMinNumberevents)
% ------------------------------------------------------------------------
% Calculate Mc and b_value with depth; Mc is EMR-medianMc determined by bootstrapping
%
% Incoming variables:
% mCatalog : Earthquake catalog
% fBinnig  : Binning interval
% nEvents  : Number of events per window
% nOverlap : Number of events that overlap in windows
% nBootSample : Number of bootstrap samples
% nMinNumberevents : Minimum number of events
%
% Outgoing variables:
% mMcBdepth(:,1) : Depth
% mMcBdepth(:,2) : EMR Median Mc
% mMcBdepth(:,3) : 16-percentile
% mMcBdepth(:,4) : 84-percentile
% mMcBdepth(:,5) : b-value
% mMcBdepth(:,6) : Standard deviation of b (Shi & Bolt, 1982)
% mMcBdepth(:,7) :a-value
%
% Author: J. Woessner
% last update: 04.04.03

% Initialze
mMcBdepth = [];

% Set fix values
fMinMag = min(mCatalog(:,6));
fMaxMag = max(mCatalog(:,6));
fMinDepth = min(mCatalog(:,7));
fMaxDepth = max(mCatalog(:,7));

% Sorting by depth
[vSortDepth,vIndiceSort] = sort(mCatalog(:,7));
mCatDep = mCatalog(vIndiceSort(:,1),:);
for nStep = 1:nEvents/nOverlap:length(mCatDep(:,6))-nEvents
    mCat = mCatDep(nStep:nStep+nEvents,:);
    % Determine Mc by bootstrapping
    [vMc, vMls, fMc_org, fStdMc_org, fSkew, vPerc, fMedianMc, fMeanMc, fStdMc, v1Sigma] = calc_BstMc(mCat,fBinning,nBootSample);
    % Mean depth
    fMeanDepth = (mCatDep(nStep,7)+mCatDep(round(nStep+nEvents),7))/2;
    % B-value determination
    % Select magnitude range to calculate b-value for EMR median Mc
    vSel = mCatDep(:,6) >= fMedianMc-0.05;
    mCat2 = mCatDep(vSel,:);
    % Check for minimum number of events
    if length(mCat(:,1)) > nMinNumberevents
        try
            [fMeanMag, fBValue, fStdDev, fAValue] =  calc_bmemag(mCat2, fBinning);
            vBvalue = [fBValue fStdDev fAValue];
        catch
            vBvalue = [NaN NaN NaN];
        end
    else
        vBvalue = [NaN NaN NaN];
    end; % END of IF
    % Result matrix
    mMcBdepth = [mMcBdepth; fMeanDepth fMedianMc v1Sigma vBvalue];
end; % End of FOR nStep


function P = cpf_p(parameterization, step, z, V, lam, Vprv, lamprv, pv, pq)
%CPF_P Computes the value of the CPF parameterization function.
%   P = CPF_P(PARAMETERIZATION, STEP, Z, V, LAM, VPRV, LAMPRV, PV, PQ)
%
%   Computes the value of the parameterization function at the current
%   solution point.
%
%   Inputs:
%       PARAMETERIZATION : Value of cpf.parameterization option
%       STEP : continuation step size
%       Z : normalized tangent prediction vector from previous step
%       V : complex bus voltage vector at current solution
%       LAM : scalar lambda value at current solution
%       VPRV : complex bus voltage vector at previous solution
%       LAMPRV : scalar lambda value at previous solution
%       PV : vector of indices of PV buses
%       PQ : vector of indices of PQ buses
%
%   Outputs:
%       P : value of the parameterization function at the current point
%
%   See also CPF_PREDICTOR, CPF_CORRECTOR.

%   MATPOWER
%   Copyright (c) 1996-2016, Power Systems Engineering Research Center (PSERC)
%   by Shrirang Abhyankar, Argonne National Laboratory
%   and Ray Zimmerman, PSERC Cornell
%
%   This file is part of MATPOWER.
%   Covered by the 3-clause BSD License (see LICENSE file for details).
%   See https://matpower.org for more info.

%% evaluate P(x0, lambda0)
if parameterization == 1        %% natural
    if lam >= lamprv
        P = lam - lamprv - step;
    else
        P = lamprv - lam - step;
    end
elseif parameterization == 2    %% arc length
    Va = angle(V);
    Vm = abs(V);
    Vaprv = angle(Vprv);
    Vmprv = abs(Vprv);
    P = sum(([Va([pv; pq]); Vm(pq); lam] - [Vaprv([pv; pq]); Vmprv(pq); lamprv]).^2) - step^2;
elseif parameterization == 3    %% pseudo arc length
    nb = length(V);
    Va = angle(V);
    Vm = abs(V);
    Vaprv = angle(Vprv);
    Vmprv = abs(Vprv);
    P = z([pv; pq; nb+pq; 2*nb+1])' * ...
        ( [Va([pv; pq]); Vm(pq); lam] - [Vaprv([pv; pq]); Vmprv(pq); lamprv] )...
        - step;
end


%%*************************************************************************
%% symqmr: symmetric QMR with left (symmetric) preconditioner.
%%         The preconditioner used is based on the analytical
%%         expression of inv(A).
%%
%% [x,resnrm,solve_ok] = symqmr(A,b,L,tol,maxit)
%%
%% child function: linsysolvefun.m
%%
%% A = [mat11 mat12; mat12' mat22].
%% b = rhs vector.
%% if matfct_options = 'chol' or 'spchol'
%%    L = Cholesky factorization of (1,1) block.
%%    M = Cholesky factorization of
%%        Schur complement of A ( = mat12'*inv(mat11)*mat12-mat22).
%% else
%%    L = triangular factors of A.
%%    M = not relevant.
%% end
%% resnrm = norm of qmr-generated residual vector b-Ax.
%%
%% SDPT3: version 3.1
%% Copyright (c) 1997 by
%% K.C. Toh, M.J. Todd, R.H. Tutuncu
%% Last Modified: 16 Sep 2004
%%*************************************************************************

   function  [xx,resnrm,solve_ok] = symqmr(A,b,L,tol,maxit,printlevel)

   N = length(b);
   if (nargin < 6); printlevel = 1; end
   if (nargin < 5) | isempty(maxit); maxit = max(30,length(A.mat22)); end;
   if (nargin < 4) | isempty(tol); tol = 1e-10; end;
   tolb = min(1e-4,tol*norm(b));

   solve_ok = 1;
   x = zeros(N,1);
   if (norm(x))
      if isstruct(A); Aq = matvec(A,x); else; Aq=A*x; end;
      r = b-Aq;
   else
      r = b;
   end
   err = norm(r); resnrm(1) = err; minres = err; xx = x;
   if (err < 1e-3*tolb); return; end

   q = precond(A,L,r);
   tau_old   = norm(q);
   rho_old   = r'*q;
   theta_old = 0;
   d = zeros(N,1);
   res = r; Ad = zeros(N,1);
%%
%% main loop
%%
   tiny = 1e-30;
   for iter = 1:maxit

       if isstruct(A); Aq = matvec(A,q); else; Aq=A*q; end;
       sigma = q'*Aq;
       if (abs(sigma) < tiny)
          solve_ok = 2;
          if (printlevel); fprintf('*'); end;
          break;
       else
          alpha = rho_old/sigma;
          r = r - alpha*Aq;
       end
       u = precond(A,L,r);

       theta = norm(u)/tau_old; c = 1/sqrt(1+theta^2);
       tau = tau_old*theta*c;
       gam = (c^2*theta_old^2); eta = (c^2*alpha);
       d = gam*d + eta*q;
       x = x + d;
%%
       Ad = gam*Ad + eta*Aq;
       res = res - Ad;
       err = norm(res); resnrm(iter+1) = err;
       if (err < minres); xx = x; minres = err; end
       if (err < tolb); break; end
       if (iter > 10)
          if (err > 0.98*mean(resnrm(iter-10:iter)))
             solve_ok = 0.5; break;
          end
       end
%%
       if (abs(rho_old) < tiny)
          solve_ok = 2;
          if (printlevel); fprintf('*'); end;
          break;
       else
          rho  = r'*u;
          beta = rho/rho_old;
          q = u + beta*q;
       end
       rho_old = rho;
       tau_old = tau;
       theta_old = theta;
   end
   if (iter == maxit); solve_ok = 0.3; end;
%%
%%*************************************************************************
%% precond:
%%*************************************************************************

   function Mx = precond(A,L,x)

   m = length(L.perm); m2 = length(x)-m;
   Mx = zeros(length(x),1);

   for iter = 1:1
      if norm(Mx); r = full(x - matvec(A,Mx)); else; r = full(x); end
      r1 = r(1:m);
      if (m2 > 0)
         r2 = r(m+[1:m2]);
         w = linsysolvefun(L,r1);
         z = mexMatvec(A.mat12,w,1) - r2;
         z = L.Mu \ (L.Ml \ (L.Mp*z));
         r1 = r1 - mexMatvec(A.mat12,z);
      end
      d = linsysolvefun(L,r1);
      if (m2 > 0)
         d = [d; z];
      end
      Mx = Mx + d;
   end
%%*************************************************************************
%% matvec: matrix-vector multiply.
%% matrix = [A.mat11 A.mat12; A.mat12' A.mat22]
%%*************************************************************************

   function Ax = matvec(A,x);

   m = length(A.mat11); m2 = length(x)-m;

   if issparse(x); x = full(x); end
   if (m2 > 0)
      x1 = x(1:m);
   else
      x1 = x;
   end
   Ax = mexMatvec(A.mat11,x1);
   if (m2 > 0)
      x2 = x(m+[1:m2]);
      Ax = Ax + mexMatvec(A.mat12,x2);
      Ax2 = mexMatvec(A.mat12,x1,1) + mexMatvec(A.mat22,x2);
      Ax = [full(Ax); full(Ax2)];
   end
%%*************************************************************************


classdef mlcnn
% Convolutional Multilayer Neural Network
%------------------------------------------------------------------------------
% Initialize, train, and test a multilayer convolutional neural network.
%
% Supports multiple activation functions including linear, sigmoid, tanh, and
% soft rectification (softplus).
%
% Also supports mean squared error (mse), binary (xent), and multi-class
% (mcxent) cross-entropy cost functions.
%
% Supports early stopping regularization.
%------------------------------------------------------------------------------
% DES
% stan_s_bury@berkeley.edu

properties
	class = 'mlcnn';
	nLayers;				% # OF UNIT LAYERS
	layers ;				% LAYER STRUCTS

	netOut = [];			% CURRENT OUTPUT OF THE NETWORK
	netError;				% CURRENT NETWORK OUTPUT ERROR

	costFun = 'mse';		% COST FUNCTION
	J = [];					% CURRENT COST FUNCTION VALUE

	nEpoch = 10;			% TOTAL NUMBER OF TRAINING EPOCHS
	epoch = 1;				% CURRENT EPOCH

	batchSize = 20;			% SIZE OF MINIBATCHES
	trainBatches = [];		% BATCH INDICES
	xValBatches = [];		% XVAL. DATA INDICES

	trainCost = [];			% TRAINING ERROR HISTORY (ALL DATA)
	xValCost = [];			% XVALIDATION EROR HISTORY
	bestxValCost = Inf		% TRACK BEST NETWORK ERROR

	nXVal = 0;				% PORTION OF DATA HELD OUT FOR XVALIDATION
	stopEarly = 5;			% # OF EARLY STOP EPOCHS (XVAL ERROR INCREASES)
	stopEarlyCnt = 0;		% EARLY STOPPING CRITERION COUNTER
	bestNet = [];			% STORAGE FOR BEST NETWORK

	denoise = 0;			% PROPORTION OF VISIBLE UNIT DROPOUT (SIGMOID ONLY)
	dropout = 'not used';	% (POTENTIAL) PROP. OF HID. UNIT DROPOUT (SIGMOID ONLY)

	wPenalty = 0;			% WEIGHT DECAY TERM
	beginWeightDecay=1;
	momentum = 'not used';	% (POTENTIAL) MOMENTUM

	saveEvery = realmax;	% SAVE PROGRESS EVERY # OF EPOCHS
	saveDir;
	visFun;					% VISUALIZATION FUNCTION HANDLE
	trainTime = Inf;		% TRAINING DURATION
	verbose = 500;			% DISPLAY THIS # OF WEIGHT UPDATES
	auxVars = [];			% AUXILIARY VARIABLES (VISUALIZATION, ETC)
	useGPU = 0;				% USE THE GPU, IF AVAILABLE
	gpuDevice = [];			% STORAGE FOR GPU DEVICE
	checkGradients = 0;		% NUMERICAL GRADIENT CHECKING
end

methods
	function self = mlcnn(arch)
	% net = mlnn(arch)
	%--------------------------------------------------------------------------
	%mlnn constructor method. Initilizes a mlnn object, <net> given a user-
	%provided architecture, <arch>.
	%--------------------------------------------------------------------------
		self = self.init(arch);
	end

	function print(self)
		properties(self)
		methods(self)
	end

	function self = train(self, data, targets)
	%net = train(data, targets)
	%--------------------------------------------------------------------------
	% Train a convolutional neural net using stochastic gradient descent.
	% INPUT:
	%     <data>:  - [#PixelsY x #PixelsX x #Channels x #Obs];
	%
	%  <targets>:  - [#Output x #Obs]
	%--------------------------------------------------------------------------

		% DISTRIBUTE VALUES TO THE GPU
		if self.useGPU
			self = gpuDistribute(self);
		end

		self = self.makeBatches(data);
		self.trainCost = zeros(self.nEpoch,1);
		self.xValCost = zeros(self.nEpoch,1);
		nBatches = numel(self.trainBatches);
		tic; cnt = 1;

		if self.checkGradients,	checkNNGradients(self,data,targets); end

		% MAIN
	    while 1
			if self.verbose, self.printProgress('epoch'); end
			batchCost = zeros(nBatches,1);
			wPenalty = self.wPenalty;

			if self.epoch >= self.beginWeightDecay
				self.wPenalty = wPenalty;
			else
				self.wPenalty = 0;
			end

			for iB = 1:nBatches
				% GET BATCH DATA
				batchIdx = self.trainBatches{iB};
				netInput = data(:,:,:,batchIdx);
				netTargets = (targets(:,batchIdx));

				% ADD NOISE TO INPUT (DENOISING CAE)
				if self.denoise > 0
				    netInput = netInput.*(rand(size(netInput))>self.denoise);
				end

				% BACKPROP MAIN
				self = self.fProp(netInput, netTargets);
				self = self.bProp;
				self = self.updateParams;

				% ASSESS BATCH COST
				batchCost(iB) = self.J;
				cnt = cnt + 1;
			end

	        % AVERAGE COST OVER ALL TRAINING POINTS
			self.trainCost(self.epoch) = mean(batchCost);

	        % CROSS-VALIDATION
			if ~isempty(self.xValBatches)
				self = self.crossValidate(data,targets);
				self = self.assessNet;
				if self.verbose, self.printProgress('xValCost');end
			end

			% EARLY STOPPING
			if (self.epoch >= self.nEpoch) || ...
				(self.stopEarlyCnt >= self.stopEarly),
				self.trainCost = self.trainCost(1:self.epoch);
				self.xValCost = self.xValCost(1:self.epoch);
				break;
			end

			% SAVE BEST NETWORK
			if ~mod(self.epoch,self.saveEvery) & ~isempty(self.saveDir)
				self.save;
			end

			% DISPLAY
			if self.verbose
				self.printProgress('trainCost');
				if ~mod(cnt,self.verbose);
					self.visLearning;
				end
			end
			self.epoch = self.epoch + 1;
	    end
		self.trainTime = toc;

		% RETURN VALUES FROM THE GPU
		if self.useGPU
			self = gpuGather(self);
			reset(self.gpuDevice);
			self.gpuDevice = [];
		end
	end

	function self = fProp(self,netInput, targets)
	%[net,out] = fProp(netInput, targets)
	%--------------------------------------------------------------------------
	%Forward propagation of input signals, <netInput>. Also updates state of
	%network cost, if provided with <targets>. Also returns network output
	%<out>, if requested.
	%--------------------------------------------------------------------------
		if notDefined('targets'), targets = []; end
		nObs = size(netInput, 1);
		self.layers{1}.fm = netInput;

		for lL = 2:self.nLayers
			switch self.layers{lL}.type
			case 'conv'
			% LOOP OVER LAYER FEATURE MAPS
			for jM = 1:self.layers{lL}.nFM

				[nInY,nInX,nInFM,nObs] = size(self.layers{lL-1}.fm);

				% INITIALIZE LAYER MAP -- [nY,nX,nM,nObs]
				featMap = zeros([self.layers{lL}.fmSize,1,nObs]);

				% POOL OVER INPUT FEATURE MAPS,
				% CALC LAYER PRE-ACTIVATION
				for iM = 1:self.layers{lL-1}.nFM
					featMap = featMap + convn(self.layers{lL-1}.fm(:,:,iM,:), ...
					self.layers{lL}.filter(:,:,iM,jM),'valid');
				end

				if any(isnan(self.layers{lL}.b(jM))), keyboard, end

				% ADD LAYER BIAS
				featMap = featMap + self.layers{lL}.b(jM);

				% COMPLETE FEATURE MAP
				self.layers{lL}.fm(:,:,jM,:) = self.calcAct(featMap,self.layers{lL}.actFun);

			end

			case 'subsample'
				stride = self.layers{lL}.stride;
				% DOWNSAMPLE THE FEATURE MAPS FROM LAYER (l-1)
				for jM = 1:self.layers{lL-1}.nFM
					layerIn = self.layers{lL-1}.fm(:,:,jM,:);
					self.layers{lL}.fm(:,:,jM,:) = self.DOWN(layerIn,stride);
				end

			case 'output'
				% UNPACK OUTPUT FEATURES & CALCULATE NETWORK OUTPUT
				self = self.calcOutput;
			case 'rect'
			case 'lcn'
			case 'pool'
			end
		end
		% COST FUNCTION & OUTPUT ERROR SIGNAL
		if ~isempty(targets)
			[self.J, self.netError] = self.cost(targets,self.costFun);
		end
		if nargout > 1
			out = self.layers{end}.act;
		end
	end

	function self = calcOutput(self);
	% net = calcOutput();
	%--------------------------------------------------------------------------
	% Calculate the network ouput given the current parameters
	%--------------------------------------------------------------------------

		[nY,nX,nM,nObs]= size(self.layers{end-1}.fm);

		% # OF ENTRIES IN EACH FEATURE MAP
		nMap = prod([nY,nX]);

		% INITIALIZE OUTPUT FEATURES
		self.layers{end}.features = zeros(nMap*nM,nObs);

		% UNPACK MAPS INTO A MATRIX FOR CALCULATING OUTPUT
		for jM = 1:self.layers{end-1}.nFM
			map = self.layers{end-1}.fm(:,:,jM,:);
			self.layers{end}.features((jM-1)*nMap+1:jM*nMap,:) = reshape(map,nMap,nObs);
		end

		% CALC NET OUTPUTS
		preAct = bsxfun(@plus,self.layers{end}.W* ...
						self.layers{end}.features, ...
		                self.layers{end}.b);

		self.netOut = self.calcAct(preAct,self.layers{end}.actFun);

		if any(isnan(self.netOut)), keyboard; end
	end

	function [J, dJ] = cost(self,targets,costFun)
	% [J, dJ] = cost(self,targets,costFun)
	%--------------------------------------------------------------------------
	% Calculate the cost function <J> and derivative thereof for a set of targets
	% and the current state of the network.
	%--------------------------------------------------------------------------

		netOut = self.netOut;

		[nTargets,nObs] = size(netOut);
		switch costFun
		case 'mse' % REGRESSION
			delta = targets - netOut;
			J = 0.5*sum(sum(delta.^2))/nObs;
			dJ = -delta;

		case 'xent' % BINARY CLASSIFICATION
			J = -sum(sum(targets.*log(netOut) + (1-targets).*log(1-netOut)))/nObs;
			dJ = (netOut - targets)./(netOut.*(1-netOut));

		case 'mcxent' % MULTI-CLASS CLASSIFICATION (UNDER DEVO)
			class = softMax(netOut);
			J = -sum(sum(targets.*log(class)))/nObs;
			dJ = sum(labels - targets);

		case {'class','classerr'}  % CLASSIFICATION ERROR (WINNER TAKE ALL)

			[~, class] = max(netOut,[],1);
			[~, t] = max(targets,[],1);
			J = sum((class ~= t))/nObs;
			dJ = 'no gradient';
		case {'correlation','cc'}
			J = corr2(netOut,targets);
			dJ = 'no gradient';
		end
		if any(isnan(self.J)), keyboard, end
	end

	function self = bProp(self)
	%net = bProp()
	%--------------------------------------------------------------------------
	%Perform gradient descent no the loss w.r.t. each of the model parameters
	%using the backpropagation algorithm. Returns updated network object, <net>
	%--------------------------------------------------------------------------

		% DERIVATIVE OF OUTPUT ACTIVATION FUNCTION
		dAct = self.calcActDeriv(self.netOut,self.layers{end}.actFun);

		% OUTPUT ERROR SIGNAL -- [#Out x #Obs]
		outES = self.netError.*dAct;

		% ERROR SIGNAL  -- [#Features x #Obs]
		es = self.layers{end}.W'*outES;

		% IN CASE LAST FEATURE LAYER IS CONV.
		if strcmp(self.layers{end-1}.type,'conv')
			dAct = self.calcActDeriv(self.layers{end}.features,self.layers{end-1}.actFun);
			es = es.*dAct;
		end

		% REPACK ERROR SIGNAL INTO 2-D FEATURE MAP REPRESENTATION
		[nY,nX,nM,nObs] = size(self.layers{end-1}.fm);
		self.layers{end-1}.es = zeros([nY,nX,nM,nObs]);

		nMap = prod([nY*nX]); % NUMBER OF ENTRIES PER 2D MAP

		for jM = 1:self.layers{end-1}.nFM
			self.layers{end-1}.es(:,:,jM,:) = ...
			reshape(es((jM-1)*nMap+1:jM*nMap,:),[nY,nX,1,nObs]);
		end

		% BACKPROPATE ERROR SIGNAL
		for lL = self.nLayers-2:-1:2
			switch self.layers{lL}.type
			case 'conv'
				stride = self.layers{lL + 1}.stride;
				mapSz = size(self.layers{lL}.fm);
				self.layers{lL}.es = zeros(mapSz);

				for jM = 1:self.layers{lL}.nFM
					switch self.layers{lL+1}.type
					case 'subsample'
						% UPSAMPLE ES FROM ABOVE SUBSAMPLE LAYER
						propES = self.UP(self.layers{lL+1}.es(:,:,jM,:), ...
						                 [stride(1), stride(2),1,1])/prod(stride);
					case 'rect'
					case 'lcn'
					end

                   % DERIVATIVE OF ACTIVATION FUNCTION
                   dAct = self.calcActDeriv(self.layers{lL}.fm(:,:,jM,:), ...
											self.layers{lL}.actFun);

                   % CALCULATE LAYER ERROR SIGNAL
					self.layers{lL}.es(:,:,jM,:) = propES.*dAct;
				end
			case 'rect'
			case 'lcn'
			case 'pool'

			case 'subsample'
				[nY,nX,nM,nObs] = size(self.layers{lL}.fm);
				self.layers{lL}.es = zeros([nY,nX,nM,nObs]);
				for jM = 1:self.layers{lL}.nFM
					% FORM FEATURE MAP ERROR SIGNAL
					propES = zeros(nY,nX,1,nObs);
					for kM = 1:self.layers{lL+1}.nFM
						rotFilt = self.ROT(self.layers{lL+1}.filter(:,:,jM,kM));
						es = self.layers{lL+1}.es(:,:,kM,:);
						propES = propES + convn(es,rotFilt,'full');
					end
					self.layers{lL}.es(:,:,jM,:) = propES;
					if any(isnan(self.layers{lL}.es(:))), keyboard; end
					if any(isinf(self.layers{lL}.es(:))), keyboard; end
				end
			end
		end

		% CALCULATE THE GRADIENTS
		for lL = 2:self.nLayers-1
			[nX,nY,nM,nObs] = size(self.layers{lL}.fm);
			switch self.layers{lL}.type
			case 'conv'
				for jM = 1:self.layers{lL}.nFM
					es = self.layers{lL}.es(:,:,jM,:);
					for iM = 1:self.layers{lL-1}.nFM
						input = self.FLIPDIMS(self.layers{lL-1}.fm(:,:,iM,:));
						dEdFilter = convn(input,es,'valid')/nObs;
						self.layers{lL}.dFilter(:,:,iM,jM) = dEdFilter;
						if isnan(any(dEdFilter)), keyboard; end
					end
					self.layers{lL}.db(jM) = sum(es(:))/nObs;
				end
			case 'rect'
			case 'lcn'
			case 'pool'
			end
		end

		% GRADIENTS FOR OUTPUT LAYER WEIGHTS AND BIASES
		self.layers{end}.dW = outES*self.layers{end}.features'/nObs;
		self.layers{end}.db = mean(outES,2);

	end

	function self = updateParams(self)
	%net = updateParams()
	%--------------------------------------------------------------------------
	%Update network parameters based on states of netowrk gradient, perform
	%regularization such as weight decay and weight rescaling
	%--------------------------------------------------------------------------
		wPenalty = 0;
		for lL = 2:self.nLayers-1
			switch self.layers{lL}.type
			% CURRENTLY, ONLY UPDATE FILTERS AND FM BIASES
			% PERHAPS, IN THE FUTURE, WE'LL BE FANCY, AND DO FANCY UPDATES
			case {'conv','output'}
				lRate = self.layers{lL}.lRate;
				for jM = 1:self.layers{lL}.nFM
					% UPDATE FEATURE BIASES
					self.layers{lL}.b(jM) = self.layers{lL}.b(jM) - ...
					                    lRate*self.layers{lL}.db(jM);

					% UPDATE FILTERS
					for iM = 1:self.layers{lL-1}.nFM
						if self.wPenalty > 0 % L2 REGULARIZATION
							wPenalty = self.layers{lL}.filter(:,:,iM,jM)*self.wPenalty;
						elseif self.wPenalty < 0 % L1-REGULARIZATION (SUB GRADIENTS)
							wPenalty = sign(self.layers{lL}.filter(:,:,iM,jM))*abs(self.wPenalty);
						end
						self.layers{lL}.filter(:,:,iM,jM) = ...
						self.layers{lL}.filter(:,:,iM,jM) - ...
						lRate*(self.layers{lL}.dFilter(:,:,iM,jM)+wPenalty);
					end
				end
			end
	    end
	end

	function out = calcAct(self,in,actFun)
	%out = calcAct(in,actFun)
	%--------------------------------------------------------------------------
	%Calculate the output activation <out> from an input <in> for activation
	%function <actFun>. Available activation functions include 'linear','exp',
	%'sigmoid', 'softmax', 'tanh', and 'softrect'.
	%--------------------------------------------------------------------------

		switch actFun
			case 'linear'
				out = self.stabilizeInput(in,1);

			case 'exp'
				in = self.stabilizeInput(in,1);
				out = exp(in);

			case 'sigmoid'
				in = self.stabilizeInput(in,1);
				out = 1./(1 + exp(-in));

			case 'softmax'
				in = self.stabilizeInput(in,1);
				maxIn = max(in, [], 2);
				tmp = exp(bsxfun(@minus,in,maxIn));
				out = bsxfun(@rdivide,tmp,sum(tmp,2));

			case 'tanh'
				in = self.stabilizeInput(in,1);
				out = tanh(in);

			case 'softrect'
				k = 8;
				in = self.stabilizeInput(in,k);
				out = 1/k.*log(1 + exp(k*in));
		end
	end

	function dAct = calcActDeriv(self,in,actFun)
	%dAct = calcActDeriv(in,actFun)
	%--------------------------------------------------------------------------
	%Calculate the output activation derivatives <dAct> from an input <in> for
	%activation function <actFun>. Available activation functions derivatives
	% include 'linear','exp', sigmoid','tanh', and 'softrect'.
	%--------------------------------------------------------------------------

		switch actFun
			case 'linear'
				dAct = ones(size(in));

			case 'exp';
				in = self.stabilizeInput(in,1);
				dAct = in;

			case 'sigmoid'
				in = self.stabilizeInput(in,1);
				dAct = in.*(1-in);

			case 'tanh'
				in = self.stabilizeInput(in,1);
				dAct = 1 - in.^2;

			case 'softrect'
				k = 8;
				in = self.stabilizeInput(in,k);
				dAct = 1./(1 + exp(-k*in));
		end
	end

	function out = DOWN(self,data,stride)
	%out = DOWN(data,stride)
	%--------------------------------------------------------------------------
	% Downsample 1st 2 dimensions of a tensor
	%--------------------------------------------------------------------------
		tmp = ones(stride(1),stride(2));
		tmp = tmp/prod(stride(:));
		out = convn(data,tmp,'valid');
		out = out(1:stride(1):end,1:stride(2):end,:,:,:);
	end

	function out = UP(self,data,scale);
	%out = UP(data,stride)
	%--------------------------------------------------------------------------
	% Upsample 1st 2 dimensions of a tensor
	%--------------------------------------------------------------------------
	% UPSAMPLE DIMESIONS OF A TENSOR
		dataSz = size(data);
		idx = cell(numel(dataSz),1);
		for iD = 1:numel(dataSz)
			tmp = zeros(dataSz(iD)*scale(iD),1);
			tmp(1:scale(iD):dataSz(iD)*scale(iD)) = 1;
			idx{iD} = cumsum(tmp);
		end
		out = data(idx{:});
	end

	function out = ROT(self,out)
	%out = ROT(self,in)
	%--------------------------------------------------------------------------
	% Rotate the 1st two dimensions of a tensor by one-half rotation
	%--------------------------------------------------------------------------
		out = out(end:-1:1,end:-1:1,:,:);
	end

	function out = FLIPDIMS(self,out)
	%out = FLIPDIMS(in)
	%--------------------------------------------------------------------------
	% Flip all dimensions of a tensor <in>
	%--------------------------------------------------------------------------
		for iD = 1:numel(size(out))
			out = flipdim(out,iD);
		end
	end

	function self = makeBatches(self,data);
	% net = makeBatches(data);
	%--------------------------------------------------------------------------
	% Create batches based on data. Observations are along the rows of data.
	% (ASSUME THAT data IS RANDOMIZED ACROSS SAMPLES (ROWS))
	%--------------------------------------------------------------------------

		nObs = size(data,4);

		if self.nXVal < 1
			nVal = round(self.nXVal*nObs);
		else
			nVal = self.nXVal;
		end

		xValIdx = nObs-nVal+1:nObs;
		nObs = nObs - nVal;
		nBatches = ceil(nObs/self.batchSize);
		idx = round(linspace(1,nObs+1,nBatches+1));

		for iB = 1:nBatches
			if iB == nBatches
				batchIdx{iB} = idx(iB):nObs;
			else
				batchIdx{iB} = idx(iB):idx(iB+1)-1;
			end
		end
		self.trainBatches = batchIdx;

		nxBatches = ceil(nVal/self.batchSize);
		if ~isempty(xValIdx)
			xValIdx = round(linspace(xValIdx(1),xValIdx(end)+1,nxBatches));

			for iB = 1:nxBatches-1
				if iB == nxBatches
					xBatchIdx{iB} = xValIdx(iB):nVal;
				else
					xBatchIdx{iB} = xValIdx(iB):xValIdx(iB+1)-1;
				end
			end
			self.xValBatches = xBatchIdx;
		end
	end

	function self = crossValidate(self,data,targets)
	%net = crossValidate(data,targets)
	%--------------------------------------------------------------------------
	%Run cross-validation on current model parameters.
	%--------------------------------------------------------------------------

		xValCost = 0;
		for iB = 1:numel(self.xValBatches)
			idx = self.xValBatches{iB};
			tmpCost = self.test(data(:,:,:,idx),targets(:,idx),self.costFun);
			xValCost = xValCost + mean(tmpCost);
		end
		% AVERAGE data-VALIDATION ERROR
		self.xValCost(self.epoch) = xValCost/iB;
	end

	% ASSES PREDICTIONS/ERRORS ON TEST DATA
	function [cost,pred] = test(self,data,targets,costFun)
		if notDefined('costFun') costFun=self.costFun; end

		for lL = 1:self.nLayers
			try
				self.layers{lL}.fm = [];
			catch
			end
		end
		self = self.fProp(data,targets);
		pred = self.netOut;
		cost = self.cost(targets,costFun);
	end

	function self = assessNet(self)
	%assessNet()
	%--------------------------------------------------------------------------
	%Utility function to assess the quality of current netork parameters and
	%store net, if necessary.
	%--------------------------------------------------------------------------

		if self.epoch > 1
			if self.xValCost(self.epoch) < self.bestxValCost
				self.bestNet = self.layers;
				self.bestxValCost = self.xValCost(self.epoch);
				self.stopEarlyCnt = 0;
			else
				self.stopEarlyCnt = self.stopEarlyCnt + 1;
			end
		else
			self.bestNet = self.layers; % STORE FIRST NET BY DEFAULT
		end
	end

	function printProgress(self,type)
	%printProgress(type)
	%--------------------------------------------------------------------------
	%Verbose utility function. <type> is the type of message to print.
	%--------------------------------------------------------------------------
		switch type
		case 'epoch'
			fprintf('Epoch: %i/%i',self.epoch,self.nEpoch);
		case 'trainCost'
			fprintf('\t%s: %2.3f\n',self.costFun,self.trainCost(self.epoch));
		case 'time'
			fprintf('\tTime: %g\n', toc);
		case 'xValCost'
			if ~self.stopEarlyCnt
				fprintf('\tCrossValidation Error:  %g (best net) \n',self.xValCost(self.epoch));
			else
				fprintf('\tCrossValidation Error:  %g\n',self.xValCost(self.epoch));
			end
		case 'gradCheck'
			netGrad = self.auxVars.netGrad;
			numGrad = self.auxVars.numGrad;
			gradFailed = self.auxVars.gradFailed;
			switch gradFailed
				case 1, gradStr = '(Failed)';
				otherwise, gradStr = '(Passed)';
			end
			fprintf('\tNetwork = %2.6f  \t Numerical = %2.6f  %s\n' ,netGrad,numGrad,gradStr);
		case 'save'
			fprintf('\nSaving...\n\n');
		end
	end

	function self = init(self,arch)
	%net = init(arch)
	%--------------------------------------------------------------------------
	%Utility function to used intitialize a neural network given an architecture
	%<arch> is a cell array of structs, one for each layer in the network. The
	%fields of each structure will depend on the type of layer. Supported lay-
	%ers include 'input','conv','subsample','output'.
	%
	%Note, the first and last layers should be of 'type' 'input' and 'output'
	%respectively.
	%
	% Returns a mlcnn object, <net>.
	%--------------------------------------------------------------------------
	% INTITIALIZE A NEURAL NETWORK GIVEN AN ARCHITECTURE
	% <arch> IS A CELLARRAY OF STRUCTS WITH LAYER-SPECIFIC PARAMETERS

		arch = self.ensureArchitecture(arch);
		self.nLayers = numel(arch);

		% INITIALIZE LAYERS FROM ARCHITECTURE
	    for lL = 1:numel(arch)
		    self.layers{lL}.type = arch{lL}.type;
		    switch arch{lL}.type
		    case 'input'
			    self.layers{lL}.dataSize = arch{lL}.dataSize;
			    self.layers{lL}.fmSize = arch{lL}.dataSize(1:2);
			    self.layers{lL}.nFM = arch{lL}.dataSize(3);

			case 'conv'
				if strcmp(arch{lL-1}.type,'input')
					nInY = arch{lL-1}.dataSize(1);
					nInX = arch{lL-1}.dataSize(2);
					nInFM = arch{lL-1}.dataSize(3);
				else
					nInX = self.layers{lL-1}.fmSize(2);
					nInY = self.layers{lL-1}.fmSize(1);
					nInFM = self.layers{lL-1}.nFM;

				end
				% INTERMEDIATE VARIABLES
				nFM = arch{lL}.nFM;
				filtSize = arch{lL}.filterSize;
				fmSize = [nInY,nInX] - filtSize + 1;
				fanIn = nInFM*prod(filtSize);
				fanOut = nFM*prod(filtSize);
				range = 2*sqrt(6/((fanIn + fanOut)));

				% INITIALIZE LAYER PARAMETERS
				self.layers{lL}.actFun = arch{lL}.actFun;
				self.layers{lL}.lRate = arch{lL}.lRate;
				self.layers{lL}.filterSize = filtSize;
				self.layers{lL}.fmSize = fmSize;
				self.layers{lL}.nFM = nFM;
				self.layers{lL}.fm = [];
				self.layers{lL}.filter = range*(rand([filtSize,nInFM,nFM])-.5);
				self.layers{lL}.dFilter = zeros(size(self.layers{lL}.filter));
				self.layers{lL}.b = zeros(nFM,1);
				self.layers{lL}.db = zeros(nFM,1);

			case 'subsample'
				% INTERMEDIATE VARIABLES
				stride = arch{lL}.stride;
				fmSize = floor(self.layers{lL-1}.fmSize./stride);
				nFM = self.layers{lL-1}.nFM;

				% INITIALIZE LAYER PARAMETERS
				self.layers{lL}.stride = stride;
				self.layers{lL}.fmSize = fmSize;
				self.layers{lL}.nFM = nFM;
				self.layers{lL}.fm = [];
				self.layers{lL}.b = zeros(nFM,1);
				self.layers{lL}.db = zeros(nFM,1);

			case 'output'

				nOut = arch{lL}.nOut;
				nFMIn = self.layers{lL-1}.nFM;
				nOutFeats = prod([self.layers{lL-1}.fmSize,nFMIn]);
				range = 2*sqrt(6/(nOutFeats + nOut));

				% ADJUST NETWORK OUTPUT LAYER PARAMETERS
				self.layers{lL}.nOut = nOut;
				self.layers{lL}.actFun = arch{lL}.actFun;
				self.layers{lL}.lRate = arch{lL}.lRate;
				self.layers{lL}.W = range*(rand(nOut,nOutFeats)-.5);
				self.layers{lL}.dW = zeros(size(self.layers{lL}.W));
				self.layers{lL}.b = zeros(nOut,1);
				self.layers{lL}.db = zeros(nOut,1);
		    end
		end
	end

	function arch = ensureArchitecture(self,arch)
	%arch = ensureArchitecture(arch)
	%--------------------------------------------------------------------------
	%Utility function to reprocess a supplied architecture, <arch>
	%--------------------------------------------------------------------------

		if ~iscell(arch), error('<arch> needs to be a cell array of layer params');end
		if ~strcmp(arch{1}.type,'input'), error('define an input layer'); end
		if ~strcmp(arch{end}.type,'output'), error('define an output layer'); end

		% ENSURE LAYER-SPECIFIC PARAMS
		for lL = 1:numel(arch)
			lParams = fields(arch{lL});
			switch arch{lL}.type
			case 'input'
				if ~any(strcmp(lParams,'dataSize')), error('must provide data size');end

			case 'conv'
				if ~any(strcmp(lParams,'filterSize')) || isempty(arch{lL}.filterSize)
					arch{lL}.filterSize = [5 5];
				elseif numel(arch{lL}.filterSize) == 1;
					arch{lL}.filterSize = repmat(arch{lL}.filterSize,[1,2]);
				end
				if ~any(strcmp(lParams,'nFM'));
					arch{lL}.nFM = 10;
				end
				if ~any(strcmp(lParams,'lRate'));
					arch{lL}.lRate = .5;
				end
				if ~any(strcmp(lParams,'actFun'));
					arch{lL}.actFun = 'sigmoid';
				end

			case 'subsample'
				if ~any(strcmp(lParams,'stride')) || isempty(arch{lL}.stride);
					arch{lL}.stride = [2 2];
				elseif numel(arch{lL}.stride) == 1;
					arch{lL}.stride = repmat(arch{lL}.stride,[1,2]);
				end
			case 'output'
				if ~any(strcmp(lParams,'nOut'))
					error('must provide number of outputs');
				end
				if ~any(strcmp(lParams,'actFun'));
					arch{lL}.actFun = 'sigmoid';
				end
				if ~any(strcmp(lParams,'lRate'));
					arch{lL}.lRate = .5;
				end
			case 'rect'
			case 'lcn'
			case 'pool'
			end
		end
	end

	function in = stabilizeInput(self,in,k);
	%in = stabilizeInput(in,k);
	%--------------------------------------------------------------------------
	%Utility function to ensure numerical stability. Clips values of <in>
	%such that exp(k*in) is within single numerical precision.
	%--------------------------------------------------------------------------
		cutoff = log(realmin('single'));
		in(in*k>-cutoff) = -cutoff/k;
		in(in*k<cutoff) = cutoff/k;
	end

	function visLearning(self)
	%visLearning()
	%--------------------------------------------------------------------------
	%Utility function to perform learning isualizations.
	%--------------------------------------------------------------------------
		try
			self.visFun(self);
		catch
			if ~isfield(self.auxVars,'printVisWarning')
				fprintf('\nWARNING: visualization failed.')
				self.auxVars.printVisWarning = true;
			end
		end
	end

	function save(self);
	%save();
	%--------------------------------------------------------------------------
	%Utility function to save current network.
	%--------------------------------------------------------------------------
		if self.verbose, self.printProgress('save'); end
		if ~isdir(self.saveDir)
			mkdir(self.saveDir);
		end
		fileName = fullfile(self.saveDir,sprintf('mlcnn.mat'));
		net = self.bestNet;
		save(fileName,'net');
	end
end % END METHODS
end % END CLASSDEF


function [Fopt,OPT_stats_out]=obj_DEMO_FEBio_iFEA_uniaxial_transiso_02(Pn,objectiveStruct)

%% Unnormalize and constrain parameters

P=Pn.*objectiveStruct.parNormFactors; %Scale back, undo normalization
P_in=P; %Proposed P

%Constraining parameters
for q=1:1:numel(P);
    [P(q)]=parLimNat(objectiveStruct.Pb_struct.xx_c(q),objectiveStruct.Pb_struct.xxlim(q,:),P(q));
end

%% SETTING MATERIAL PARAMETERS

mat_struct.par_names={{'solid','Ogden unconstrained','c1'},...
     {'solid','Ogden unconstrained','m1'},...
    {'solid','Ogden unconstrained','c2'},...
     {'solid','Ogden unconstrained','m2'},...
    {'solid','Ogden unconstrained','cp'},...
    {'solid','ellipsoidal fiber distribution','ksi'},...
    {'solid','ellipsoidal fiber distribution','beta'},...
    };

%Acces material parameters
mat_struct.id=1;

c1=P(1);
m1=P(2);
ksi=[P(3) P(3) P(3)*P(4)];
beta=P(5)*ones(1,3);
cp=(2*c1+mean(ksi))*objectiveStruct.k_factor;

mat_struct.par_values={c1 m1 c1 -m1 cp ksi beta};

disp('SETTING MATERIAL PARAMETERS...');
disp(['Proposed (norm.): ',sprintf(repmat('%6.16e ',[1,numel(Pn)]),Pn)]);
disp(['Proposed        : ',sprintf(repmat('%6.16e ',[1,numel(P_in)]),P_in)]);
disp(['Set (constr.)   : ',sprintf(repmat('%6.16e ',[1,numel(P)]),P)]);

%Assign material parameters
docNode=set_mat_par_FEBIO(objectiveStruct.FEB_struct.run_filename,objectiveStruct.FEB_struct.run_filename,{mat_struct});

disp('Done')

%%

for q=1:1:2 %Direction cases

    disp('SETTING FIBRE DIRECTIONS...');

    switch q
        case 1
            alphaFib=0;
        case 2
            alphaFib=0.5*pi;
    end

    [R,~]=euler2DCM([0,alphaFib,0]);
    v_fib=(R*[0 0 1]')';
    V_fib=v_fib(ones(numel(objectiveStruct.FEB_struct.Geometry.ElementData.MatAxis.ElementIndices),1),:);

    %Adding fibre direction, construct local orthonormal basis vectors
    [a,d]=vectorOrthogonalPair(V_fib);
    VF_E=zeros(size(V_fib,1),size(V_fib,2),2);
    VF_E(:,:,1)=a; %a1 ~ e1 ~ X or first direction
    VF_E(:,:,2)=d; %a2 ~ e2 ~ Y or second direction
    %Vf_E %a3 ~ e3 ~ Z, third direction, or fibre direction
    objectiveStruct.FEB_struct.Geometry.ElementData.MatAxis.Basis=VF_E;
    docNode=addMatAxisFibreElementData_FEB(docNode,objectiveStruct.FEB_struct);
    write_XML_no_extra_lines(objectiveStruct.FEB_struct.run_filename,docNode)% Saving XML file
    disp('Done')

    %% START FEBio NOW

    [runFlag]=runMonitorFEBio(objectiveStruct.FEBioRunStruct);

    stretch_exp=objectiveStruct.stretch_exp;
    stress_cauchy_exp=objectiveStruct.stress_cauchy_exp(:,q);

    if runFlag==1

        %Importing stress
        [~,S_mat,~]=importFEBio_logfile(objectiveStruct.FEB_struct.run_output_names{2}); %Element Cauchy stresses
        S_mat=S_mat(:,2:end,:); %Final stress, no element labels
        S_mat=squeeze(mean(S_mat,1)); %Mean across elements
        stress_cauchy_sim=[0; S_mat(:)]; %Cauchy stress
        stress_cauchy_sim=stress_cauchy_sim.*1e3; %Scale to kPa

        %Import principal strains
        [~, E_mat,~]=importFEBio_logfile(objectiveStruct.FEB_struct.run_output_names{1}); %Nodal displacements
        E_mat=E_mat(:,2:end,:);

        %Derive x stretch
        EX_mat=E_mat(:,1,:);
        EX_mat=squeeze(mean(EX_mat,1)); %Mean across elements
        stretch_sim_X=sqrt(2*EX_mat+1);
        stretch_sim_X=[1; stretch_sim_X(:)];
        stretch_sim_X_end=(stretch_sim_X(end));

        %Derive y stretch
        EY_mat=E_mat(:,2,:);
        EY_mat=squeeze(mean(EY_mat,1)); %Mean across elements
        stretch_sim_Y=sqrt(2*EY_mat+1);
        stretch_sim_Y=[1; stretch_sim_Y(:)];
        stretch_sim_Y_end=(stretch_sim_Y(end));

        %Derive z stretch
        EZ_mat=E_mat(:,3,:);
        EZ_mat=squeeze(mean(EZ_mat,1)); %Mean across elements
        stretch_sim_Z=sqrt(2*EZ_mat+1);
        stretch_sim_Z=[1; stretch_sim_Z(:)];
        stretch_sim_Z_end=(stretch_sim_Z(end));

        %Interpolate experiment onto simulated points
        stress_cauchy_sim_exp = interp1(stretch_sim_Z,stress_cauchy_sim,stretch_exp,'pchip');

        %Derive Fopt
        stressDev=stress_cauchy_exp-stress_cauchy_sim_exp;
        Fopt_stress=mean(abs(stressDev)./max(abs(stress_cauchy_exp)));

        switch q
            case 1
                stretchDev=[1./(sqrt(0.7)) 1./(sqrt(0.7))]-[stretch_sim_X_end stretch_sim_Y_end];
            case 2
                stretchDev=[exp(log(stretch_sim_Z_end)*-0.36) exp(log(stretch_sim_Z_end)*-0.65)]-[stretch_sim_X_end stretch_sim_Y_end];
        end

        Fopt_stretch=mean(abs(stretchDev));

        [R_sq]=R_squared(stress_cauchy_sim_exp,stress_cauchy_exp);

    else %Output NaN
        stress_cauchy_sim=NaN(size(stretch_exp));
        stretch_sim_Z=NaN(size(stretch_exp));
        stressDev=NaN(size(stretch_exp));
        stretchDev=NaN(1,2);
        Fopt_stress=Nan;
        Fopt_stretch=NaN;
    end

    OPT_stats_out.stress_cauchy_sim{q}=stress_cauchy_sim;
    OPT_stats_out.stretch_sim{q}=stretch_sim_Z;
    OPT_stats_out.stretch_sim_end(q,:)=[stretch_sim_X_end stretch_sim_Y_end stretch_sim_Z_end];
    OPT_stats_out.stressDev{q}=stressDev;
    OPT_stats_out.stretchDev{q}=stretchDev;
    OPT_stats_out.Fopt(:,q)=[Fopt_stress Fopt_stretch];
    OPT_stats_out.R_sq(q)=R_squared(stress_cauchy_exp,stress_cauchy_sim_exp);

end

switch objectiveStruct.method
    case 1
        Fopt=OPT_stats_out.Fopt(1,1)+OPT_stats_out.Fopt(1,2)+OPT_stats_out.Fopt(2,2); %Scalar objective function
    case 2
        Fopt=[OPT_stats_out.Fopt(1,1) OPT_stats_out.Fopt(1,2) OPT_stats_out.Fopt(2,2)]; %Objective function vectors
end

%%
% _*GIBBON footer text*_
%
% License: <https://github.com/gibbonCode/GIBBON/blob/master/LICENSE>
%
% GIBBON: The Geometry and Image-based Bioengineering add-On. A toolbox for
% image segmentation, image-based modeling, meshing, and finite element
% analysis.
%
% Copyright (C) 2006-2022 Kevin Mattheus Moerman and the GIBBON contributors
%
% 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/>.


function [xUpdate,SUpdate,innov,Szz,W]=sqrtEKFUpdate(xPred,SPred,z,SR,h,HJacob,innovTrans,stateTrans)
%SQRTEKFUPDATE Perform the measurement update step in a square-root version
%              of the first-order Extended Kalman Filter (EKF).
%
%INPUTS: xPred The xDim X 1 predicted target state.
%        SPred The xDim X xDim lower-triangular square root predicted state
%              covariance matrix.
%            z The zDim X 1 measurement vector.
%           SR The zDim X zDim lower-triangular square root of the
%              measurement covariance matrix in the native coordinate
%              system of the measurement.
%            h A function handle for the measurement function that takes
%              the state as its argument.
%       HJacob A function handle for the measurement Jacobian matrix that
%              takes the target state as a parameter. If not supplied or an
%              empty matrix is passed, then HJacob will be found using
%              numerical differentiation via the numDiff function with
%              default parameters.
%   innovTrans An optional function handle that computes and optionally
%              transforms the value of the difference between the
%              observation and any predicted points. This is called as
%              innovTrans(a,b) and the default if omitted or an empty
%              matrix is passed is @(a,b)bsxfun(@minus,a,b). This must be
%              able to handle sets of values. For a zDimX1 measurement,
%              either of the inputs could be zDimXN in size while one of
%              the inputs could be zDimX1 in size.  This only needs to be
%              supplied when a measurement difference must be restricted
%              to a certain range. For example, the innovation between two
%              angles will be 2*pi if one angle is zero and the other
%              2*pi, even though they are the same direction. In such an
%              instance, a function handle to the
%              wrapRange(bsxfun(@minus,a,b),-pi,pi) function with the
%              appropriate parameters should be passed for innovTrans.
%   stateTrans An optional function that takes a state estimate and
%              transforms it. This is useful if one wishes the elements of
%              the state to be bound to a certain domain. For example, if
%              an element of the state is an angle, one should generally
%              want to bind it to the region +/-pi.
%
%OUTPUTS: xUpdate The xDim X 1 updated state vector.
%         SUpdate The updated xDim X xDim lower-triangular square root
%                 state covariance matrix.
%      innov, Szz The zDimX1 innovation and the zDimXzDim square root
%                 innovation covariance matrix are returned in case one
%                 wishes to analyze the consistency of the estimator or use
%                 those values in gating or likelihood evaluation.
%               W The xDimXzDim gain used in the update. This can be
%                 useful when gating and using the function
%                 calcMissedGateCov.
%
%The first-order EKF is summarized in Figure 10.3.3-1 in Chapter 10.3.3 of
%[1]. The Joseph-form covariance update given in Chapter 5 of the same book
%is used for improved numerical stability. The mathematics behind the
%specific square root implementation used here are those used in the
%standard Kalman filter, which are described in [2] with a flow chart given
%in Appendix G.
%
%The optional parameter innovTrans is not described in the above
%reference, but allows for possible modifications to the filter as
%described in [3].
%
%The parameters have been added to allow the filter to be used with
%angular quantities. For example, if the measurement consisted of
%range and angle, z=[r;theta], then
%innovTrans=@(a,b)[bsxfun(@minus,a(1,:),b(1,:));
%                  wrapRange(bsxfun(@minus,a(2,:),b(2,:)),-pi,pi)];
%should be used to approximately deal with the circular nature of the
%measurements.
%
%REFERENCES:
%[1] Y. Bar-Shalom, X. R. Li, and T. Kirubarajan, Estimation with
%    Applications to Tracking and Navigation. New York: John Wiley and
%    Sons, Inc, 2001.
%[2] David F. Crouse , "Basic tracking using nonlinear 3D monostatic and
%    bistatic measurements," IEEE Aerospace and Electronic Systems
%    Magazine, vol. 29, no. 8, Part II, pp. 4-53, Aug. 2014.
%[3] David F. Crouse, "Cubature/ unscented/ sigma point Kalman filtering
%    with angular measurement models," in Proceedings of the 18th
%    International Conference on Information Fusion, Washington, D.C.,
%    6-9 Jul. 2015.
%
%March 2015 David Karnick, Naval Research Laboratory, Washington D.C.
%Updated with additional inputs, August 2015 David F. Crouse,
%                               Naval Research Laboratory, Washington D.C.
%(UNCLASSIFIED) DISTRIBUTION STATEMENT A. Approved for public release.

zDim=size(z,1);

if(nargin<8||isempty(stateTrans))
    stateTrans=@(x)x;
end

if(nargin<7||isempty(innovTrans))
    innovTrans=@(a,b)bsxfun(@minus,a,b);
end

if(nargin<6||isempty(HJacob))
    HJacob=@(x)numDiff(x,h,zDim);
end

zPred=h(xPred);

H=HJacob(xPred);
Pxz=SPred*SPred'*H';

Szz=tria([H*SPred,SR]);
W=(Pxz/Szz')/Szz;

innov=innovTrans(z,zPred);
xUpdate=stateTrans(xPred+W*innov);

temp=W*H;
SUpdate=tria([(eye(size(temp))-temp)*SPred,W*SR]);

end

%LICENSE:
%
%The source code is in the public domain and not licensed or under
%copyright. The information and software may be used freely by the public.
%As required by 17 U.S.C. 403, third parties producing copyrighted works
%consisting predominantly of the material produced by U.S. government
%agencies must provide notice with such work(s) identifying the U.S.
%Government material incorporated and stating that such material is not
%subject to copyright protection.
%
%Derived works shall not identify themselves in a manner that implies an
%endorsement by or an affiliation with the Naval Research Laboratory.
%
%RECIPIENT BEARS ALL RISK RELATING TO QUALITY AND PERFORMANCE OF THE
%SOFTWARE AND ANY RELATED MATERIALS, AND AGREES TO INDEMNIFY THE NAVAL
%RESEARCH LABORATORY FOR ALL THIRD-PARTY CLAIMS RESULTING FROM THE ACTIONS
%OF RECIPIENT IN THE USE OF THE SOFTWARE.


function out = SC_MMA(y,doOverlap,scaleRange,qRange)
% SC_MMA   Physionet implementation of multiscale multifractal analysis
%
% Scale-dependent estimates of multifractal scaling in a time series.

% ------------------------------------------------------------------------------
% Modified by Ben Fulcher for use in hctsa, 2015-05-12.
% If you use this code for your research, please cite the following two papers:
%
% (1) B.D. Fulcher and N.S. Jones, "hctsa: A Computational Framework for Automated
% Time-Series Phenotyping Using Massive Feature Extraction, Cell Systems 5: 527 (2017).
% DOI: 10.1016/j.cels.2017.10.001
%
% (2) B.D. Fulcher, M.A. Little, N.S. Jones, "Highly comparative time-series
% analysis: the empirical structure of time series and their methods",
% J. Roy. Soc. Interface 10(83) 20130048 (2013).
% DOI: 10.1098/rsif.2013.0048
% ------------------------------------------------------------------------------

% ------------------------------------------------------------------------------
% Copyright (C) 2014 Jan Gieraltowski
% ------------------------------------------------------------------------------
%
% 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 2 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, write to the Free Software Foundation, Inc., 59 Temple
% Place - Suite 330, Boston, MA 02111-1307, USA.
%
% Author: Jan Gieraltowski
% Warsaw University of Technology
% Faculty of Physics
% gieraltowski@if.pw.edu.pl
% http://gieraltowski.fizyka.pw.edu.pl/
%
% Method was first proposed in:
% J. Gieraltowski, J. J. Zebrowski, and R. Baranowski,
% Multiscale multifractal analysis of heart rate variability recordings
% with a large number of occurrences of arrhythmia,
% Phys. Rev. E 85, 021915 (2012).
% http://dx.doi.org/10.1103/PhysRevE.85.021915
%
% Please cite the above publication when referencing this material,
% and also include the standard citation for PhysioNet:
% Goldberger AL, Amaral LAN, Glass L, Hausdorff JM, Ivanov PCh, Mark RG,
% Mietus JE, Moody GB, Peng C-K, Stanley HE.
% PhysioBank, PhysioToolkit, and PhysioNet: Components of a New Research Resource
% for Complex Physiologic Signals.
% Circulation 101(23):e215-e220
% [Circulation Electronic Pages; http://circ.ahajournals.org/cgi/content/full/101/23/e215]; 2000 (June 13).
% ------------------------------------------------------------------------------

% Generate a plot:
doPlot = false;

% Time-series length:
N = length(y);

% ------------------------------------------------------------------------------
% Check Inputs:
% ------------------------------------------------------------------------------
if nargin < 2 || isempty(doOverlap)
    doOverlap = false;
    % 0 - time series is partitioned into non overlapping windows of analysis,
    % 1- time series is partitioned into overlapping windows of analysis, step between consecutive windows is = 1 (much longer calculations)
end

if nargin < 3 || isempty(scaleRange)
    scaleRange = [10,round(N/40)];
    % minimal s scale used, when calculating Fq(s) functions family (default 10)
    % maximal s scale used, when calculating Fq(s) functions family, has to be multiple of 5 (default 600; in general should be near to N/50, where N is a time series length)
end
minScale = scaleRange(1);
maxScale = scaleRange(2);
if (maxScale/5) < minScale
    warning('Time-series (N=%u) too short for multiscale multifractal analysis',N);
    out = NaN;
    return
elseif rem(maxScale,5)~=0
    maxScale = round(maxScale/5)*5;
    fprintf(1,'adjusted maxScale to %u\n',maxScale);
end

if nargin < 4 || isempty(qRange)
    qRange = [-5,5];
    % minimal/maximal multifractal parameter q used (default -5)
end
qMin = qRange(1);
qMax = qRange(2);

qList = qMin:0.1:qMax;
qList(qList == 0) = 0.0001;

% if nargin < 5
%     precisionMode = 0;
%     % 0 (default) better looking plot, smaller files, faster calculations;
%     % set 1 for enhanced precision (smaller q and s steps)
% end

% ------------------------------------------------------------------------------

signal = y;

prof = cumsum(signal);
slength = size(prof);
fqs = [];

numIncrements = 20;
sListFull = unique(round(linspace(minScale,maxScale,numIncrements)));

timer = tic;
for s = sListFull

    if doOverlap
        vec = [0:s-1];
        ind = [1:slength-s+1]';
        coordinates = bsxfun(@plus, vec, ind);
    else
        ind2 = [1:size(prof,1)];
        coordinates = reshape(ind2(1:(size(prof,1)-mod(size(prof,1),s))),s,(size(prof,1)-mod(size(prof,1),s))/s)';
    end

    segments = prof(coordinates);
    xbase = [1:1:s];
    f2nis = [];

    for ni = 1:size(segments,1)
        seg = segments(ni,:);
        fit = polyfit(xbase,seg,2);
        variance = mean((seg - polyval(fit,xbase)).^2);
        f2nis(end+1) = variance;
    end

    for q = qList
        fqs = [fqs; q s (mean(f2nis.^(q/2)))^(1/q)];
    end
end
% fprintf(1,'Detrended fluctuations computed in %s\n',BF_TheTime(toc(timer)));

fqsll = [fqs(:,1) fqs(:,2) log(fqs(:,2)) log(fqs(:,3))];

% ------------------------------------------------------------------------------
% Now compute hurst exponents as the gradients of F(q) curves
% ------------------------------------------------------------------------------
hqs = [];

% if precisionMode == 0
%     % Take 11 points through the space
%     sspacing = ((maxScale/5)-minScale)/10;
%     sList = minScale:sspacing:(maxScale/5);
%     % Coarser sampling of q space
%     qList = qMin:1:qMax; qList(qList == 0) = 0.0001;
% else
    % sspacing = 1;
% sList = minScale:sspacing:(maxScale/5);

if sum(sListFull<=maxScale/5)>=10
    sList = sListFull(sListFull<=maxScale/5);
else
    % sample higher in the scale dimension:
    sspacing = ((maxScale/5)-minScale)/10;
    sList = minScale:sspacing:(maxScale/5);
end

% Coarser sampling of q space
qList = qMin:0.5:qMax; qList(qList == 0) = 0.0001;
% end

% sList = minScale:sspacing:(maxScale/5);

hqs = zeros(length(qList),length(sList));
for si = 1:length(sList)
    sit = sList(si);
    for qi = 1:length(qList)
        qit = qList(qi);

        fitTemp = fqsll(fqsll(:,1) == qit & fqsll(:,2) >= sit & fqsll(:,2) <= 5*sit,:);
        hTemp = polyfit(fitTemp(:,3),fitTemp(:,4),1);

        hqs(qi,si) = hTemp(1);
        % hqs = [hqs; qit 3*sit hTemp(1)];
    end
end

sListScaled = sList*3; % Not completely on top of the algorithm, but for some reason
                       % this was recorded as a multiple of 3 in the original algorithm

% hqsPlotData = reshape(hqs(:,3),size(qList,2),length(minScale:sspacing:(maxScale/5)));

% ------------------------------------------------------------------------------
% Plotting
% ------------------------------------------------------------------------------

if doPlot
    if max(max(hqs)) < 1.5
        hLim = 1.5;
    elseif max(max(hqs)) < 2.5
        hLim = 2.5;
    else
        hLim = ceil((max(max(hqs))*10))/10;
    end

    f = figure('color','w'); box('on');
    hqsplot = surf(sListScaled,qList,hqs);
    colormap(jet);
    colorbar;
    caxis([0,hLim]);
    set(gca,'YDir','reverse');
    view(-62,50);
    axis([sListScaled(1),sListScaled(end),qMin,qMax,0,hLim]);
    xlabel('scale')
    ylabel('q')
    zlabel('h')
end

% ------------------------------------------------------------------------------
% Output statistics:
% ------------------------------------------------------------------------------

% hqsPlotData (scale,q)

% Global properties:
allExponents = hqs(:);
out.meanHurstExponent = mean(allExponents);
out.stdHurstExponent = std(allExponents);
out.minHurstExponent = min(allExponents);
out.maxHurstExponent = max(allExponents);

% Changes with scale:
out.scaleHurstStd = std(mean(hqs,1));
out.scaleHurstTrend = GiveMeGradient(sListScaled,mean(hqs,1));

% Changes with q:
out.qHurstStd = std(mean(hqs,2));
out.qHurstTrend = GiveMeGradient(qList,mean(hqs,2));

% max/min points are where in scale/q space?
[qi,si] = find(hqs==max(hqs(:)),1);
out.maxHurstQ = qList(qi);
out.maxHurstScale = sListScaled(si);
[qi,si] = find(hqs==min(hqs(:)),1);
out.minHurstQ = qList(qi);
out.minHurstScale = sListScaled(si);

% phase transitions
% there is some peak or trough somewhere, so the standard deviation across
% scales or q is inconsistent
stdS = std(hqs,[],1);
stdQ = std(hqs,[],2);
out.stdStdHurstQ = std(stdQ); % will be large if variance changes alot with Q
out.stdStdHurstScale = std(stdS); % will be large if variance changes alot with scale

% saveas(hqsplot, [dirname filesep 'MMA_results' filesep 'MMA_' n1 '.jpg']);

% ------------------------------------------------------------------------------
function m = GiveMeGradient(xData,yData)
    if size(xData,1) ~= size(yData,1);
        yData = yData';
    end
    p = polyfit(xData,yData,1);
    m = p(1);
end
% ------------------------------------------------------------------------------

end