function [Lambda]=div_wolf_fixed_evolv(states, fs, min_dist, max_dist, max_dist_mult, max_theta, period, evolv)

%% Description
% Calculate maximum lyapunov exponent from state space according to Wolf's method: 
%    Wolf, A., et al., Determining Lyapunov exponents from a time series. 
%    Physica D: 8 Nonlinear Phenomena, 1985. 16(3): p. 285-317.
%
% Input: 
%   states: states in state space
%   fs: sample frequency 
%   min_dist: neighbours should be more than this distandce away to be
%             selected (assumed noise amplitude )
%   max_dist: maximum distance to which the divergence may grow before 
%             replacing the neighbour
%   max_theta: maximum angle between vectors from feducial point to old and
%              new neighbours, this is the absolute maximum, first search
%              nearer
%   period: indicates period of time-wise near samples to exclude in 
%           nearest neighbour search
%   evolv: time step after which the neighbour is replaced
% 
% Output: 
%   Lambda: the estimated Lyapunov exponent
%   ExtraArgs: optional struct containing additional info for debugging or
%              analysis of the method
%
%% Copyright
%     COPYRIGHT (c) 2012 Sietse Rispens, VU University Amsterdam
%
%     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/>.

%% Author
%  Sietse Rispens

%% History
%  23 October 2012, initial version based on div_wolf

[N,D]=size(states);
N_opt = N-evolv;
states_opt = states(1:N_opt,:);

One2N_opt=(1:N_opt)';

is=1;

% search closest point to states(1,:), but further than min_dist away
DeltaFidPoint = zeros(N_opt,D);
for dim = 1:D,
    DeltaFidPoint(:,dim) = states_opt(:,dim)-states(is,dim);
end
DeltaFidPointSqr = DeltaFidPoint.^2;
DistSqr = sum(DeltaFidPointSqr,2);
Distances = sqrt(DistSqr);
CosTh = zeros(N_opt,1);
in = find(DistSqr==min(DistSqr(DistSqr>=min_dist^2 & abs(One2N_opt-is)>period)) & abs(One2N_opt-is)>period,1,'first'); % Find states min_dist from starting point. 
if isempty(in)
    warning('Cannot calculate Wolf''s Lyapunov exponent, no states further than min_dist from starting point');
    Lambda = nan;
    return;
end

DistStart = Distances(in);
LnDistReplacementsSum = 0;

% If multiple points are found in this range, the one which points best in
% the same direction as the old nearby point is selected. 

for is=(1+evolv):evolv:N,
    in = in+evolv;
    % fill distance and cos matrices with new values
    DeltaOld = (states(in,:)-states(is,:))';
    DistOld = sqrt(sum(DeltaOld.^2));
    for dim = 1:D,
        DeltaFidPoint(:,dim) = states_opt(:,dim)-states(is,dim);
    end
    DeltaFidPointSqr(:,:) = DeltaFidPoint.^2;
    DistSqr(:,:) = sum(DeltaFidPointSqr,2);
    Distances(:,:) = sqrt(DistSqr);
    CosTh(:,:) = abs(DeltaFidPoint*DeltaOld)./DistOld./Distances;
    Index = [];
    search_theta = max_theta;
    while isempty(Index) && search_theta < pi % if Necessary, the direction limit is repeatedly doubled to maximally pi radians
        dist_mult = 1;
        while isempty(Index) && dist_mult <= max_dist_mult % Whenever points cannot be found, the distance limit is increased stepwise to maximally five times the original limit. 
            search_dist = dist_mult*max_dist; 
            Candidates = find(Distances <= search_dist & CosTh >= cos(search_theta) ...
                & Distances>= min_dist & abs(One2N_opt-is)>period);
            if ~isempty(Candidates)
                % find the best of candidates
                if numel(Candidates) > 1
                    Candidates = Candidates(CosTh(Candidates)==max(CosTh(Candidates)));
                    if numel(Candidates) > 1
                        Candidates = Candidates(Distances(Candidates)==min(Distances(Candidates)));
                        if numel(Candidates) > 1
                            Candidates = Candidates(abs(One2N_opt(Candidates)-is)==max(abs(One2N_opt(Candidates)-is)));
                        end
                    end
                end
                Index = Candidates(1);
            end
            dist_mult = dist_mult+1;
        end
        search_theta = search_theta*2;
    end
    if isempty(Index) && in > N_opt  % we can not evolve the old point and no new one was found
        Index = find(DistSqr==min(DistSqr(DistSqr>=min_dist^2 & abs(One2N_opt-is)>period))& abs(One2N_opt-is)>period,1,'first'); % The nearest neighbours are found by selecting data point closest to X1, from separate cycles with a temporal separation larger than twice the first minimum in the mutual information function. 
        if isempty(Index)
            warning('Cannot calculate Wolf''s Lyapunov exponent, no states further than min_dist from fiducial point');
            Lambda = nan;
            return;
        end
    end
    if ~isempty(Index)
        LnDistReplacementsSum = LnDistReplacementsSum + log(DistOld/Distances(Index));
        in = Index;
    end
end
DistEnd = sqrt(sum((states(is,:)-states(in,:)).^2)); % The dt between neighbouring trajectories in state space were computed as a function of time, and averaged over many original pairs of initially nearest neighbours. 
% The maximum Lyapunov exponent is determined by averaging the rate of exponential divergence between the reference 
% and nearby point as the trajectory evolves between replacement steps.
Lambda = (log(DistEnd/DistStart) + LnDistReplacementsSum)/((is-1)/fs); 
