function [positionDecodingGLM_gaussian] = bz_positionDecodingGLM_gauss(varargin)
% USAGE
%   [] = bz_positionDecodingGLM_gauss()
%
% INPUTS
%
%   spikes
%
%   behavior
%
%   lfp
%
%   smoothingRange
%
%   plotting   - cell UID to plot
%
%   saveMat
%
% OUTPUTS
%
%   positionDecodingGLM_gaussian     cellinfo struct with the following fields
%                      .UID
%                      .region
%                      .sessionName
%                      .results{1:nCells}
%                      .tau
%                      .condition
%
%
%
%
% SEE
%
% written by david tingley, 2017

%% parse inputs
p = inputParser();
addRequired(p,'spikes',@isstruct);
addRequired(p,'behavior',@isstruct);
addRequired(p,'lfp',@isstruct);
addRequired(p,'smoothingRange',@isvector);

addParameter(p,'plotting',true,@isnumeric);
addParameter(p,'saveMat',[],@islogical);
parse(p,varargin{:})

spikes = p.Results.spikes;
behavior = p.Results.behavior;
lfp = p.Results.lfp;
smoothingRange = p.Results.smoothingRange;
plotting = p.Results.plotting;
saveMat = p.Results.saveMat;

%% set up data format and cellinfo struct that will be returned
disp('initializing data structs and formatting input data...')

positionDecodingGLM_gaussian.UID = spikes.UID;
positionDecodingGLM_gaussian.region = spikes.region;
positionDecodingGLM_gaussian.sessionName = spikes.sessionName; % session name

conditions = unique(behavior.events.trialConditions);
nCells = length(spikes.times);
positionSamplingRate = behavior.samplingRate;

% find a better way to get spike phase relationship...
[firingMaps] = bz_firingMap1D(spikes,behavior,lfp,5);

% iterate through conditions and compile spike trains and spike-phase
% trains
for cond = conditions
    trials = find(behavior.events.trialConditions==cond);
    intervals = behavior.events.trialIntervals(trials,:);
    for t = 1:length(trials)
        trial = trials(t);
        spk_trains{cond}{t} = zeros(nCells,ceil((intervals(t,2)-intervals(t,1))*1000)); % assumes intervals are in seconds, rounds to nearest millisecond
        phase_trains{cond}{t} = zeros(nCells,ceil((intervals(t,2)-intervals(t,1))*1000));
        for cell = 1:nCells
            if ~isempty(firingMaps.phaseMaps{cond}{cell})
                f = find(firingMaps.phaseMaps{cond}{cell}(:,2)==t);
                if ~isempty(f)
                for s=1:length(f)
                    phase_trains{cond}{t}(cell,ceil(firingMaps.phaseMaps{cond}{cell}(f(s),5)*1000)) = ...
                        firingMaps.phaseMaps{cond}{cell}(f(s),end);
                end
                end
            end
            sp = find(InIntervals(spikes.times{cell},intervals(t,:)));
            if ~isempty(sp)
                spks = Restrict(ceil((spikes.times{cell}(sp)-intervals(t,1))*1000+.0000001),[1 size(spk_trains{cond}{t},2)]);
                spk_trains{cond}{t}(cell,spks)=1;
            end
        end

%         position{cond}{t} = interp1(1:length(behavior.events.trials{trial}.x)...
%             ,behavior.events.trials{trial}.mapping,1:positionSamplingRate/1000:length(...
%             behavior.events.trials{trial}.x));
        nBins = size(spk_trains{cond}{t},2);
        nPos = length(behavior.events.trials{trial}.x);
        position{cond}{t} = interp1(1:length(behavior.events.trials{trial}.x)...
            ,behavior.events.trials{trial}.mapping,1:(nPos-1)/nBins:length(...
            behavior.events.trials{trial}.x)); % the -1 gaurantees the length to be longer than the above spk/phase trains
        position{cond}{t} = position{cond}{t}(1:length(spk_trains{cond}{t}));

    end
end

% collapse across trials..
for cond = conditions
%    phase_trains{cond} = cell2mat(phase_trains{cond});
%    spk_trains{cond} = cell2mat(spk_trains{cond});
% this was a bad idea because it lead to smoothing across trial boundaries,
% fixed below by smoothing first, then concatenating trials...
   position{cond} = cell2mat(position{cond});
end

%% set up GLM
for cell=1:nCells  % initialize table for data
    positionDecodingGLM_gaussian.results{cell} = table;
end
disp('running models...')
for cond = conditions
    warning off
    for wind = smoothingRange
       for cell = 80%1:nCells
            %smoothing
            phase_trains_smooth=[];
            cos_phase_trains_smooth=[];
            sin_phase_trains_smooth=[];
            rates_trains_smooth = [];
            for t = 1:length(phase_trains{cond})
%                 phase_trains_smooth=[phase_trains_smooth;...
%                     circ_smoothTS(phase_trains{cond}{t}(cell,:),wind,'method','mean','exclude',0)];
                phase_trains_smooth=[phase_trains_smooth;...
                     gaussfilt(1:length(phase_trains{cond}{t}(cell,:)),phase_trains{cond}{t}(cell,:),wind)'];
                cos_phase_trains_smooth=[cos_phase_trains_smooth;...
                     gaussfilt(1:length(phase_trains{cond}{t}(cell,:)),cos(phase_trains{cond}{t}(cell,:)),wind)'];
                sin_phase_trains_smooth=[sin_phase_trains_smooth;...
                     gaussfilt(1:length(phase_trains{cond}{t}(cell,:)),sin(phase_trains{cond}{t}(cell,:)),wind)'];
                rates_trains_smooth = [rates_trains_smooth; ...
                                       gaussfilt(1:length(phase_trains{cond}{t}(cell,:)),spk_trains{cond}{t}(cell,:),wind)'];
            end
            % phase coding
            [b dev stats] = glmfit(phase_trains_smooth',position{cond}','normal');
            yfit = glmval(b,phase_trains_smooth,'identity');
            struct.mse_phase = mean((yfit-position{cond}').^2); % mean squared error for rate code
            struct.mse_phase_pval = stats.p';

            % phase coding
            [b dev stats] = glmfit(cos_phase_trains_smooth',position{cond}','normal');
            yfit = glmval(b,cos_phase_trains_smooth,'identity');
            struct.mse_phase_cos = mean((yfit-position{cond}').^2); % mean squared error for rate code
            struct.mse_phase_cos_pval = stats.p';

            % phase coding
            [b dev stats] = glmfit(sin_phase_trains_smooth',position{cond}','normal');
            yfit = glmval(b,sin_phase_trains_smooth,'identity');
            struct.mse_phase_sin = mean((yfit-position{cond}').^2); % mean squared error for rate code
            struct.mse_phase_sin_pval = stats.p';

            % phase coding all
            [b dev stats] = glmfit([sin_phase_trains_smooth,cos_phase_trains_smooth],position{cond}','normal');
            yfit = glmval(b,[sin_phase_trains_smooth,cos_phase_trains_smooth],'identity');
            struct.mse_phase_all = mean((yfit-position{cond}').^2); % mean squared error for rate code
            struct.mse_phase_all_pval = stats.p';

            % rate coding
            [b dev stats] = glmfit(rates_trains_smooth',position{cond}','normal');
            yfit = glmval(b,rates_trains_smooth,'identity');
            struct.mse_rate = mean((yfit-position{cond}').^2);  % mean squared error for rate code
            struct.mse_rate_pval = stats.p';

            % extra variables to save
            struct.dfe = stats.dfe;
            struct.tau = wind;
            struct.condition = cond;
            positionDecodingGLM_gaussian.results{cell} = ...
                [positionDecodingGLM_gaussian.results{cell};struct2table(struct)];

            if ~isempty(plotting) & cell == plotting  % can only display one neuron for now..
%                 figure(cond);
                subplot(2,2,1)
                title('GLM decoding of pos, r-rate, g-phase')
                rows = positionDecodingGLM_gaussian.results{plotting}.condition == cond;
                plot(positionDecodingGLM_gaussian.results{plotting}.tau(rows),...
                    positionDecodingGLM_gaussian.results{plotting}.mse_rate(rows),'r')
                hold on
                plot(positionDecodingGLM_gaussian.results{plotting}.tau(rows),...
                    positionDecodingGLM_gaussian.results{plotting}.mse_phase_all(rows),'g')
                hold off
                subplot(2,2,2)
                plot(positionDecodingGLM_gaussian.results{plotting}.tau(rows),...
                    positionDecodingGLM_gaussian.results{plotting}.mse_phase_cos(rows),'g')
                subplot(2,2,3)
                plot(positionDecodingGLM_gaussian.results{plotting}.tau(rows),...
                    positionDecodingGLM_gaussian.results{plotting}.mse_phase_sin(rows),'g')
                subplot(2,2,4)
                plot(positionDecodingGLM_gaussian.results{plotting}.tau(rows),...
                    positionDecodingGLM_gaussian.results{plotting}.mse_phase(rows),'g')
                pause(.0001)
            end
       end
       disp(['finished with wind: ' num2str(wind) ' out of ' num2str(smoothingRange(end)) ' total'])
    end
    positionDecodingGLM_gaussian.dateRun = date;  % this can take a very long time so lets save each loop...
    if saveMat
        save([spikes.sessionName '.positionDecodingGLM_gaussian.cellinfo.mat'],'positionDecodingGLM_gaussian')
    end
    disp(['finished with condition: ' num2str(cond) ' out of ' num2str(length(conditions)) ' total'])
end


function []=sb_make_closure_loops()
%SB_MAKE_CLOSURE_LOOPS
%   Produce a matrix containign 1,-1 or 0 elements definining which
%   interferograms are used in a closed loop test.
%   Ekbal Hussain @ Uni Leeds
%   25/10/2013
%
%   ======================================================================
%   06/2014 AH: Modify for StaMPS
%   08/2014 EH: Fixed bug for determining higher order loops
%   08/2014 EH: Ignores interferograms not connected in a closed loop, bug fixes
%   01/2015 EH: User input required for individual ints (not in a loop) connecting two looped networks
%   01/2015 EH: Bug fixes
%   04/2015 EH: Checks if you are running small baselines and not single master. Speed optimisations
%   04/2015 EH: Removed user input setion for ints connecting two loops
%   04/2015 EH: Allows for up to 12 interferograms in a loop
%   ======================================================================

logit;
fprintf('Making closure loops...\n')

load psver
psname=['ps',num2str(psver)];
loopname=['phuw_loops',num2str(psver)];

ps=load(psname);

% Check you are running small baselines and not single master
small_baseline_flag = getparm('small_baseline_flag');
if strcmpi(small_baseline_flag,'n')
    error('Error: This method does not work for single master. Use small baselines instead.')
end

%Interferogram indices
x = 1:ps.n_ifg; x = x';
intfg = [ps.ifgday_ix,x];          %where: col1=date 1, col2=dat2, col3=int_no.

%Remove interferograms dropped in previous stamps steps
drop_ifg = getparm('drop_ifg_index',1);
if ~isempty(drop_ifg)
    for n = 1:size(drop_ifg,2)
       intfg = intfg(intfg(:,3)~=drop_ifg(n),:);
    end
end

% loading the single master bperp
small_baseline_flag = getparm('small_baseline_flag');
if strcmpi(small_baseline_flag,'y')
    temp = load('../psver');
    psname_ps = ['..' filesep 'ps' num2str(temp.psver) '.mat'];
    load(psname_ps,'bperp')
else
   load(psname,'bperp')
end

clear x n

%% Create interferogram triangulalation matrix

%Acquisition date (node) indexes
iz = unique(intfg(:,1));

%Interferograms used
ints_used = [];
closed_loops = [];
for m=1:size(iz,1)

    %set node ID
    node_id = iz(m);

    iy = intfg(intfg(:,1)==node_id,:);      %Interferograms that connect to this node [nID nID intID]
    a = unique(iy(:,1:2));                  %The nodes used in the combinations

    if length(a) >= 3                       %Can be used in one or more loops

        %Create all possible combinations using these nodes and output the subset that
        %only begins with the selected start node.
        combinations = combnk(a,3);
        usecom = combinations((combinations(:,1)==node_id),:);

        int_list1 = [usecom(:,1) usecom(:,2)];  %Positive interfeograms in the loop
        int_list2 = [usecom(:,2) usecom(:,3)];  %Positive interfeograms in the loop
        int_list3 = [usecom(:,1) usecom(:,3)];  %Negative interfeograms in the loop

        %Find the interferogram index connecting the nodes
        %First positive interferogram
        temp_column = nan(size(int_list1,1),1);
        for k=1:size(int_list1,1)
           [~,~,ix] = intersect(int_list1(k,:),intfg(:,1:2),'rows');
           if isempty(ix)
               temp_column(k,1)=NaN;
           else
               temp_column(k,1)=intfg(ix,3);
           end
        end
        int_list1 = [int_list1 temp_column];
        inan1 = ~isnan(int_list1(:,3));
        clear temp_column temp

        %Second positive interferogram
        temp_column = nan(size(int_list2,1),1);
        for k=1:size(int_list2,1)
           [~,~,ix] = intersect(int_list2(k,:),intfg(:,1:2),'rows');
           if isempty(ix)
               temp_column(k,1)=NaN;
           else
               temp_column(k,1)=intfg(ix,3);
           end
        end
        int_list2 = [int_list2 temp_column];
        inan2 = ~isnan(int_list2(:,3));
        clear temp_column temp

        %The negative interferogram
        temp_column = nan(size(int_list3,1),1);
        for k=1:size(int_list3,1)
           [~,~,ix] = intersect(int_list3(k,:),intfg(:,1:2),'rows');
           if isempty(ix)
               temp_column(k,1)=NaN;
           else
               temp_column(k,1)=intfg(ix,3);
           end
        end
        int_list3 = [int_list3 temp_column];
        inan3 = ~isnan(int_list3(:,3));
        clear temp_column temp

        %Remove any rows that contain NaNs
        igood = [inan1, inan2, inan3];
        for k=1:size(igood,1)
            if igood(k,:) == [1 1 1];
                ixgood(k,1) = 1;
            else
                ixgood(k,1) = NaN;
            end
        end
        int_list1 = int_list1(~isnan(ixgood),:);
        int_list2 = int_list2(~isnan(ixgood),:);
        int_list3 = int_list3(~isnan(ixgood),:);
        clear inan1 inan2 inan3 ixgood

        %Create a vector of the interferograms used
        ints_used = unique([ints_used;int_list1(:,3);int_list2(:,3);int_list3(:,3);]);

        %Add these to a loop closure matrix
        closed_loops = [closed_loops; int_list1(:,3) int_list2(:,3) -int_list3(:,3);];      %This array contains only int IDs
        clear int_list1 int_list2 int_list3
    end
end

clear combinations usecom iy a iz k igood ix node_id small_baseline_flag

%% Higher order loops for those not fitted by a triangle

%Find the ints that were not fitted by a triangle
A = [closed_loops(:,1:2),-closed_loops(:,3)];
missing_loops = intfg(~ismember(intfg(:,3),A),:);

%Ignore unconnected single interferograms
missing = [];
if ~isempty(missing_loops)
    for i=1:size(missing_loops,1)
        occurences_node1  = sum(sum(intfg(:,1:2)==missing_loops(i,1)),2);
        occurences_node2  = sum(sum(intfg(:,1:2)==missing_loops(i,2)),2);
        if occurences_node1 ~= 1 && occurences_node2 ~= 1
            missing = vertcat(missing,missing_loops(i,3));
        end
    end
end

remove_from_missing = [];
if ~isempty(missing)

    % loading the single master perpendicular baselines
    temp = load('../psver');
    psname_ps = ['..' filesep 'ps' num2str(temp.psver) '.mat'];
    sm_bp = load(psname_ps,'bperp');
    bp = sm_bp.bperp;

    Nodes = [(1:size(bp,1))', ps.day, bp];
    segments = [intfg(:,3), intfg(:,1:2)];

    % Allow for loops containing up to 8 interferograms
    higherO_loops = nan(size(missing,1),12);
    for m=1:size(missing,1)

        A = intfg(intfg(:,3)==missing(m,1),1:2);

        %Remove the shortest link between the two nodes
        Segments = segments;
        Segments(Segments(:,1) == missing(m,1),:) = [];

        %Calculate the shortest path that links the two nodes using
        %dijkstra's algorithm
        [~,path] = dijkstra(Nodes,Segments,A(:,1),A(:,2));

        %Remove individual ints (not in a looped network) connecting two looped networks
        if isnan(path)
            disp(['Interferogram ',num2str(missing(m,:)),' is not connected to a loop.']);

            %remove this interferogram from the missing list
            remove_from_missing = [remove_from_missing;m];
        else
            %close the loop
            path = [path, path(1,1)];

            %Find the interferogram index for this path
            for k=1:(size(path,2)-1)
                if path(1,k+1) > path(1,k);
                    temp_vector = [path(1,k),path(1,k+1)];
                    [~,~,ix] = intersect(temp_vector,intfg(:,1:2),'rows');
                    higherO_loops(m,k)=intfg(ix,3);
                else
                    temp_vector = [path(1,k+1),path(1,k)];
                    [~,~,ix] = intersect(temp_vector,intfg(:,1:2),'rows');
                    higherO_loops(m,k)=-intfg(ix,3);
                end
            end
        end
    end

    %Remove rows and colums containing just nans
    higherO_loops = higherO_loops(~all(isnan(higherO_loops),2),~all(isnan(higherO_loops),1));
    %replace nans with zero (makes the next bit easier)
    higherO_loops(isnan(higherO_loops)) = 0;

    %Remove duplicated loops
    x = sort(abs(higherO_loops),2);
    y =  unique(x,'rows');
    [~,ix] = ismember(y,x,'rows');

    higherO_loops = higherO_loops(ix,:);
    %replace any zeros with nans
    higherO_loops(higherO_loops==0) = nan;
end

clear A i occurences_node1 occurences_node2 missing_loops psname_ps temp sm_bp bp m path k temp_vector ix Nodes segments Segments x y

%% The final closed network

%Remove the single interferograms connecting two loops
missing(remove_from_missing,:) = [];

if ~isempty(missing)
    intfg_network = nan((size(closed_loops,1)+size(higherO_loops,1)),size(higherO_loops,2));

    %Create the new matrix here. Add the triangular loops
    intfg_network(1:size(closed_loops,1),1:size(closed_loops,2)) = closed_loops;
    %Add the higher order loops
    intfg_network((size(closed_loops,1)+1):end,:) = higherO_loops;
else
    intfg_network = closed_loops;
end

%Turn this into a full matrix including flags, where 1,-1 = use and 0 = don't use
%intfg_loops: rows = number of closed loops, columns = total no. of ints in network
intfg_loops = zeros(size(intfg_network,1),size(intfg,1));

for m=1:size(intfg_network,1);
    for n=1:nnz(~isnan(intfg_network(m,:)));
        if intfg_network(m,n)<0;
            intfg_loops(m,(abs(intfg_network(m,n))))=-1;
        else
            intfg_loops(m,intfg_network(m,n))=1;
        end
    end
end
logit(1);
save(loopname,'intfg_loops')


function r = subsref(this, selector)
%SUBSREF   Access parameters of HS optical flow algorithm
%   R = SUBSREF(THIS, SEL) extracts and returns parameter of a HS
%   optical algorithm THIS using given selector SEL.
%   Available selectors:
%     - ".optical_flow":   Flow base object
%     - ".pyramid_levels": Levels of the image pyramid
%     - ".warping_mode":   'forward' for forward warping and 'backward'
%                          for backward warping
%     - ".solver":         Selector for the linear equation solver
%                          ('backslash' for the builtin solver and 'sor'
%                          for a MEX-based SOR solver)
%     - ".max_iters":      Maximum number of iterations at each pyramid
%                          level
%     - ".limit_update":   Flag whether to restrict the incremental flow
%                          at each level to [-1, 1]
%
%   This is a member function of the class 'hs_optical_flow'.
%
%   Author: Deqing Sun, Department of Computer Science, Brown University
%   Contact: dqsun@cs.brown.edu
%   $Date: 2007-11-30 $
%   $Revision: $
%
% Copyright 2007-2010, Brown University, Providence, RI. USA
%
%                          All Rights Reserved
%
% All commercial use of this software, whether direct or indirect, is
% strictly prohibited including, without limitation, incorporation into in
% a commercial product, use in a commercial service, or production of other
% artifacts for commercial purposes.
%
% Permission to use, copy, modify, and distribute this software and its
% documentation for research purposes is hereby granted without fee,
% provided that the above copyright notice appears in all copies and that
% both that copyright notice and this permission notice appear in
% supporting documentation, and that the name of the author and Brown
% University not be used in advertising or publicity pertaining to
% distribution of the software without specific, written prior permission.
%
% For commercial uses contact the Technology Venture Office of Brown University
%
% THE AUTHOR AND BROWN UNIVERSITY DISCLAIM ALL WARRANTIES WITH REGARD TO
% THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
% FITNESS FOR ANY PARTICULAR PURPOSE.  IN NO EVENT SHALL THE AUTHOR OR
% BROWN UNIVERSITY BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL
% DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
% PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
% ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
% THIS SOFTWARE.

  % Allow full access to all fields of the structure
  r = builtin('subsref', this, selector);


fprintf(1,'Script that merges two "Cabib_Results.mat" data sets of the same camera into a single dataset\n')

dir;

cont = 1;
while cont
    data_set1 = input('Filename of the first dataset (with complete path if necessary): ','s');
    cont = ((exist(data_set1)~=2));
    if cont,
        fprintf(1,'File not found. Try again.\n');
    end;
end;
cont = 1;
while cont
    data_set2 = input('Filename of the second dataset (with complete path if necessary): ','s');
    cont = ((exist(data_set2)~=2));
    if cont,
        fprintf(1,'File not found. Try again.\n');
    end;
end;

load(data_set1); % part1\Calib_Results;

shift = n_ima;

load(data_set2); % part2\Calib_Results;

active_images2 = active_images;
n_ima2 = n_ima;

for kk = 1:n_ima

eval(['X_' num2str(kk+shift) ' = X_' num2str(kk) ';']);

eval(['dX_' num2str(kk+shift) ' = dX_' num2str(kk) ';']);
eval(['dY_' num2str(kk+shift) ' = dY_' num2str(kk) ';']);

eval(['wintx_' num2str(kk+shift) ' = wintx_' num2str(kk) ';']);
eval(['winty_' num2str(kk+shift) ' = winty_' num2str(kk) ';']);

eval(['x_' num2str(kk+shift) ' = x_' num2str(kk) ';']);
eval(['y_' num2str(kk+shift) ' = y_' num2str(kk) ';']);

eval(['n_sq_x_' num2str(kk+shift) ' = n_sq_x_' num2str(kk) ';']);
eval(['n_sq_y_' num2str(kk+shift) ' = n_sq_y_' num2str(kk) ';']);

eval(['omc_' num2str(kk+shift) ' = omc_' num2str(kk) ';']);
eval(['Tc_' num2str(kk+shift) ' = Tc_' num2str(kk) ';']);

end;

load(data_set1); % part1\Calib_Results;

n_ima = n_ima + n_ima2;
active_images = [active_images active_images2];

no_image = 1;

% Recompute the error (in the vector ex):
comp_error_calib;

fprintf('The two calibration datasets are now merged. You are now ready to run calibration. \n');


classdef dl_list < handle

% Copyright (C) 2012 Jean-Pierre de la Croix
% see the LICENSE file included with this software

    properties
        head_
        tail_
        size_
        iterator_
    end

    methods
        function obj = dl_list()
            obj.head_ = [];
            obj.tail_ = obj.head_;
            obj.size_ = 0;
        end

        function insert_key(obj, key, index)
            n = mcodekit.list.dl_list_node(key);
            index = max(min(index, obj.size_+1), 1);
            if (obj.size_ == 0)
                obj.head_ = n;
                obj.tail_ = obj.head_;
            else
                if (index == 1)
                    n.next_ = obj.head_;
                    obj.head_.prev_ = n;
                    obj.head_ = n;
                elseif (index == obj.size_+1)
                    n.prev_ = obj.tail_;
                    obj.tail_.next_ = n;
                    obj.tail_ = n;
                else
                    m = obj.head_;
                    for i = 2:index
                        m = m.next_;
                    end
                    n.prev_ = m.prev_;
                    m.prev_.next_ = n;
                    n.next_ = m;
                    m.prev_ = n;
                end
            end
            obj.size_ = obj.size_+1;
        end

        function key = remove_key(obj, index)
            index = max(min(index, obj.size_), 1);
            if (obj.size_ == 0)
                key = [];
            elseif (obj.size_ == 1)
                key = obj.tail_.key_;
                delete(obj.tail_);
                obj.tail_ = [];
                obj.head_ = obj.tail_;
                obj.size_ = 0;
            elseif (index == 1)
                key = obj.head_.key_;
                n = obj.head_;
                obj.head_ = n.next_;
                obj.head_.prev_ = [];
                delete(n);
                obj.size_ = obj.size_-1;
            elseif (index == obj.size_)
                key = obj.tail_.key_;
                n = obj.tail_;
                obj.tail_ = n.prev_;
                obj.tail_.next_ = [];
                delete(n);
                obj.size_ = obj.size_-1;
            else
                m = obj.head_;
                for i = 2:index
                    m = m.next_;
                end
                key = m.key_;
                m.prev_.next_ = m.next_;
                m.next_.prev_ = m.prev_;
                delete(m);
                obj.size_ = obj.size_-1;
            end
        end

        function append_key(obj, key)
            obj.insert_key(key, obj.size_+1);
        end

        function key = get_key(obj, index)
            assert(index > 0, 'index has to be positive');
            index = max(min(index, obj.size_), 1);
            n = obj.head_;
            for i = 2:index
                n = n.next_;
            end
            key = n.key_;
        end

        function key = get_first_key(obj)
            assert(obj.size_ > 0, 'list is empty');
            key = obj.head_.key_;
        end

        function key = get_last_key(obj)
            assert(obj.size_ > 0, 'list is empty');
            key = obj.tail_.key_;
        end

        function iterator = get_iterator(obj)
            iterator = mcodekit.list.dl_list_iterator(obj);
        end
    end
end


function [status]=seof(HDR)
% SEOF checks for end of signal-file
%    status = seof(HDR)
%
% returns 1 if End-of-EDF-File is reached
% returns 0 otherwise
%
% See also: SOPEN, SREAD, SWRITE, SCLOSE, SSEEK, SREWIND, STELL, SEOF

% 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.

%	$Id: seof.m 2205 2009-10-27 12:18:15Z schloegl $
%	(C) 1997-2005,2007,2008 by Alois Schloegl <a.schloegl@ieee.org>
%    	This is part of the BIOSIG-toolbox http://biosig.sf.net/

%status=feof(HDR.FILE.FID);  % does not work properly
%if HDR.FILE.POS~=HDR.AS.startrec+HDR.AS.numrec;

if strmatch(HDR.TYPE,{'CTF','RDF','EEG','AVG','SIGIF'}),
	%status=feof(EDF.FILE.FID);  % does not work properly
	%if EDF.FILE.POS~=EDF.AS.startrec+EDF.AS.numrec;
        status = (HDR.FILE.POS >= HDR.NRec);

elseif strmatch(HDR.TYPE,{'RG64','LABVIEW','Nicolet'}),
	status = (HDR.FILE.POS >= (HDR.AS.endpos-HDR.HeadLen));

elseif strmatch(HDR.TYPE,{'ACQ','AINF','BDF','BKR','BrainVision','CNT','CTF','EDF','ET-MEG','GDF','MIT','SMA','CFWB','DEMG','EEProbe-CNT','EEProbe-AVR','MFER','alpha','native','SCP','BCI2000','TMS32','WG1','Sigma'}),
	status = (HDR.FILE.POS >= HDR.SPR*HDR.NRec);

elseif strmatch(HDR.TYPE,{'EGI'}),
        if HDR.FLAG.TRIGGERED,
	        status = (HDR.FILE.POS >= HDR.NRec);
        else
                status = (HDR.FILE.POS >= HDR.SPR);
        end;

elseif strmatch(HDR.TYPE,{'FIF'}),
        [buf, status] = rawdata('next');
        status = strcmp(status,'eof');

else
	status=feof(HDR.FILE.FID);
end;


function V = scn_write_plane(filenames_or_V, dat, wh_slice, varargin)
% Images can be 3D or 4D (4D is possible with SPM2+ using ,xx indexing, or SPM5+).
%
% :Usage:
% ::
%
%     V = scn_write_plane(filenames_or_V, dat, wh_slice, [exampleV])
%
% :Inputs:
%
%   **ofilenames_or_V:**
%        is string matrix or cell array of names, or structures created with spm_vol(names)
%
%   **dat:**
%        is 3-D array of data, vox x vox x slices, with data on 3rd dim being
%        written to separate image files
%
%   **wh_slice:**
%        slice number (voxel space)
%
%   **exampleV:**
%        is optional, but required if filenames are passed in.
%
% Write a plane, given filenames or spm_vol structures and 4-D data with
% the slice to write.
%
% SPM2/5 compatible, and creates image if necessary from file name and
% example V structure.
%
% ::
%
%    P = char({'one.img', 'two.img', 'three.img'})
%    dat = randn(64, 64, 3);
%    Vout = scn_write_plane(P, dat, wh_slice, V)
%    spm_image('init', Vout(1).fname);
%
%
% Notes on SPM5 and why we need to do what we do the way we do it:
% Write data...in SPM5, by accessing file_array object in
% V.private directly (with spm_write_plane. spm_write_vol
% first creates NIFTI obj/file array and then uses spm_write_plane.)
%
% The file_array object points to data in
% the actual file, so when values are assigned to the array
% object, they are written directly in the file.
% These values depend on the offset and slope values (spm
% scaling factors!) in V.private.dat as well, so care must be
% taken to assign data to a file_array object with the correct
% scaling factors.  This is why it is better to load the
% structure one wants to write to with spm_vol first, so that
% the name in V.private.dat.fname will be correct, and
% V.private.dat.scl_slope and ...inter will be correct as well.
% Vout = spm_vol(V(i));  % loads correct .private info from V.fname
%
% in SPM5, this simply assigns data in Vout.private.dat
% in SPM2, it does something different, but should be
% compatible, since spm_vol was used above...
% ::
%
%    spm_write_plane(Vout, fsl(:, :, i), slicei);
%    if isfield(V, 'dt'), Vout.dt = V.dt; end      % SPM5 only
%    if isfield(V, 'n'), Vout.n = V.n;  end        % SPM5 only
%        Vout = spm_create_vol(Vout);
%    else
%        Vout = spm_vol(V(i).fname);
%    end
%    spm_write_plane(Vout, fsl(:, :, i), slicei);
%
% ..
%    Tor Wager, March 2008
% ..

% ..
%    Make sure all images exist, and create if not
%    Return V structure of spm_vol mapped image structures
% ..

if ~isstruct(filenames_or_V)
    % Assume we have a string matrix of file names
    % Make sure they exist...
    % create volumes, if necessary

    % Make sure char array, from cell...
    if iscell(filenames_or_V), filenames_or_V = char(filenames_or_V{:}); end

    for i = 1:size(filenames_or_V, 1)
        currentfilename = deblank(filenames_or_V(i, :));

        if ~(exist(currentfilename, 'file'))

            fprintf('Creating Image: %3.0f ', i);

            if isempty(varargin) && ~exist('exampleV', 'var')
                error('Must pass in example V struct!');
            elseif ~exist('exampleV', 'var')
                exampleV = varargin{1};
            end

            % Create empty float image with no spm scaling (pinfo intercept is 0,
            % slope is 1)
            % --------------------------------------------------------------
            %       V.fname - the filename of the image.
            %       V.dim   - the x, y and z dimensions of the volume
            %       V.dt    - A 1x2 array.  First element is datatype (see spm_type).
            %                 The second is 1 or 0 depending on the
            %                 endian-ness.
            %                   * 1 is big-endian, 0 is little-endian
            %       V.mat   - a 4x4 affine transformation matrix mapping from
            %                 voxel coordinates to real world coordinates.
            %       V.pinfo - plane info for each plane of the volume.
            %              V.pinfo(1,:) - scale for each plane
            %              V.pinfo(2,:) - offset for each plane
            %                 The true voxel intensities of the jth image are given
            %                 by: val*V.pinfo(1,j) + V.pinfo(2,j)
            %              V.pinfo(3,:) - offset into image (in bytes).
            %                 If the size of pinfo is 3x1, then the volume is assumed
            %                 to be contiguous and each plane has the same scalefactor
            %                 and offset.

            Vout = struct('fname', currentfilename, 'mat', exampleV.mat, 'dim', exampleV.dim, 'pinfo', [1 0 0]');
            %if isfield(exampleV, 'dt'), Vout.dt = exampleV.dt; end      % SPM5 only; data type
            if isfield(exampleV, 'n'), Vout.n = exampleV.n;  end        % SPM5 only

            % Make sure datatype is float / float32
            switch(spm('Ver'))
                case 'SPM2'
                    Vout.dim(4) = spm_type('float');

                case 'SPM5'
                    Vout.dt = [spm_type('float32') 0];
                    if isfield(exampleV, 'dt'), Vout.dt(2) = exampleV.dt(2); end      % use endian-ness of example.

                case 'SPM8'
                    Vout.dt = [spm_type('float32') 0];
                    if isfield(exampleV, 'dt'), Vout.dt(2) = exampleV.dt(2); end      % use endian-ness of example.

                otherwise
                    error('Unknown SPM version.');
            end

            try
                V(i) = spm_create_vol(Vout);
                create_empty(V(i));

            catch
                try
                    % You cannot create only some volumes in a list, if
                    % others exist...work-around...
                    Vtmp = spm_create_vol(Vout);
                    Vtmp = spm_vol(Vtmp);
                    create_empty(Vtmp);
                    V(i) = spm_vol(Vtmp.fname);
                catch

                    disp('Cannot create vol.  This could occur for several reasons:')
                    disp('1) you may not have write permission.')
                    disp('2) Other reasons...?');
                    error('Quitting.');
                end
            end

        else
            % it exists; get it
            V(i) = spm_vol(currentfilename);
        end
    end

else
    % fprintf('Checking spm_vol structures ');
    % Make sure spm_vol structure (SPM5 only)
    % alter data type if needed
    switch(lower(spm('Ver')))
        case 'spm2'
            V = filenames_or_V; % Skip because hard to check.
        case {'spm5', 'spm8', 'spm12'}
            V = spm_vol(filenames_or_V);
%         otherwise
%             error('Fix or verify image writing for SPMxx. Not implemented yet.');
        otherwise
            error('Unknown SPM version.');
    end
end

str = sprintf('Writing slice data: %04d', 0);
fprintf('%s', str)

% V is a structure array, one structure per filename
% For each file to write to...write a plane

for i = 1:length(V)
    if mod(i, 10) == 0, fprintf('\b\b\b\b%04d', i); end

    spm_write_plane(V(i), dat(:, :, i), wh_slice);
end

erase_string(str)
%fprintf('\n');

end

function create_empty(V)
% Write empty data
switch(spm('Ver'))
    case 'SPM2'
        spm_write_vol(V, zeros(V.dim(1:3)));
    case {'SPM5', 'SPM8'}
        % assign directly
        for i = 1:length(V), V(i).private.dat(:, :, :) = 0; end
    otherwise
        error('Unknown SPM version.')
end
end


function gt = g01_title ( )

%*****************************************************************************80
%
%% G01_TITLE returns the title for grid 1.
%
%  Licensing:
%
%    This code is distributed under the GNU LGPL license.
%
%  Modified:
%
%    29 January 2012
%
%  Author:
%
%    John Burkardt
%
%  Parameters:
%
%    Output, string GT, the grid title.
%
  gt = 'Franke''s 100 node set';

  return
end


function y = getNumericScalarFieldSafe(data,fld,mode)
% y = getNumericScalarFieldSafe(data,fld,mode)
% mode: optional, either 'safe' or 'safest' (default).
% y: col vector

if nargin < 3
    mode = 'safest';
end

if isempty(data)
    y = zeros(size(data));
else
    switch mode
        case 'safest'
            y = {data.(fld)}';
            cellfun(@(x)assert((isnumeric(x)||islogical(x))&&isscalar(x)),y);
            y = cell2mat(y);
            assert(isequal(size(y),[numel(data) 1]));
        case 'safe'
            y = cat(1,data.(fld));
            assert(isequal(size(y),[numel(data) 1]));
        otherwise
            assert(false);
    end
end


function out = ne(sR1,sR2)

out = ~eq(sR1,sR2);


function X = caffe_init(X, solver_file)

if ~exist(solver_file,'file') || isempty(solver_file)
    error('You need a solver prototxt definition.')
end
X.Solver_ = caffe.Solver(solver_file); % to cpp
if isfield(X, 'state_file') && ~isempty(X.state_file) && exist(X.state_file, 'file')
    X.Solver_.restore(X.state_file);
    fprintf('resume from snapshot file %s...\n', X.state_file);
elseif isfield(X, 'model_file') && ~isempty(X.model_file)
    X.Solver_.net.copy_from(X.model_file);
    fprintf('resume from caffemodel file %s...\n', X.model_file);
end

% set caffe mode
if strcmp(X.solver_mode,'GPU')
    caffe.set_mode_gpu();
    caffe.set_device(X.device_id);
else
    caffe.set_mode_cpu();
end
fprintf('Done with init %s\n', solver_file);
end


%kkimagpart 'Output = Imaginary Component of (Complex) Input'
% This MatLab function was automatically generated by a converter (KhorosToMatLab) from the Khoros kimagpart.pane file
%
% Parameters:
% InputFile: i 'Input ', required: 'input data object'
% OutputFile: oi 'Output', required: 'resulting output data object'
%
% Example: oi = kkimagpart(i, {'i','';'oi',''})
%
% Khoros helpfile follows below:
%
%  PROGRAM
% kimagpart - Output = Imaginary Component of (Complex) Input
%
%  DESCRIPTION
% The "Imaginary Part" operator returns the imaginary component of each
% complex data point in the input data object, \fBInput".  If the input
% data is not complex, the imaginary component is assumed to be zero.
%
% The output object data type will be the same as the
% input (regarding single or double floats).
%
%  "Map Data" 5
% .cI $DATAMANIP/repos/shared/man/sections/map_1input
%
%  "Validity Mask" 5
% .cI $DATAMANIP/repos/shared/man/sections/mask_1input
%
%  "Explicit Location and Time Data" 5
% .cI $DATAMANIP/repos/shared/man/sections/loc_and_time_1input
%
%  "Failure Modes"
% .cI $DATAMANIP/repos/shared/man/sections/fail_1input
%
% Executing "Imaginary Part" runs the program \fIkcmplx2real\fP with the -oi
% output option.
%
%
%
%  EXAMPLES
%
%  "SEE ALSO"
% DATAMANIP::kcmplx2real, DATAMANIP::kcmplx, DATAMANIP::kreal2cmplx
%
%  RESTRICTIONS
%
%  REFERENCES
%
%  COPYRIGHT
% Copyright (C) 1993 - 1997, Khoral Research, Inc. ("KRI")  All rights reserved.
%

function varargout = kkimagpart(varargin)
if nargin ==0
  Inputs={};arglist={'',''};
elseif nargin ==1
  Inputs=varargin{1};arglist={'',''};
elseif nargin ==2
  Inputs=varargin{1}; arglist=varargin{2};
else error('Usage: [out1,..] = kkimagpart(Inputs,arglist).');
end
if size(arglist,2)~=2
  error('arglist must be of form {''ParameterTag1'',value1;''ParameterTag2'',value2}')
 end
narglist={'i', '__input';'oi', '__output'};
maxval={0,0};
minval={0,0};
istoggle=[0,0];
was_set=istoggle * 0;
paramtype={'InputFile','OutputFile'};
% identify the input arrays and assign them to the arguments as stated by the user
if ~iscell(Inputs)
Inputs = {Inputs};
end
NumReqOutputs=1; nextinput=1; nextoutput=1;
  for ii=1:size(arglist,1)
  wasmatched=0;
  for jj=1:size(narglist,1)
   if strcmp(arglist{ii,1},narglist{jj,1})  % a given argument was matched to the possible arguments
     wasmatched = 1;
     was_set(jj) = 1;
     if strcmp(narglist{jj,2}, '__input')
      if (nextinput > length(Inputs))
        error(['Input ' narglist{jj,1} ' has no corresponding input!']);
      end
      narglist{jj,2} = 'OK_in';
      nextinput = nextinput + 1;
     elseif strcmp(narglist{jj,2}, '__output')
      if (nextoutput > nargout)
        error(['Output nr. ' narglist{jj,1} ' is not present in the assignment list of outputs !']);
      end
      if (isempty(arglist{ii,2}))
        narglist{jj,2} = 'OK_out';
      else
        narglist{jj,2} = arglist{ii,2};
      end

      nextoutput = nextoutput + 1;
      if (minval{jj} == 0)
         NumReqOutputs = NumReqOutputs - 1;
      end
     elseif isstr(arglist{ii,2})
      narglist{jj,2} = arglist{ii,2};
     else
        if strcmp(paramtype{jj}, 'Integer') & (round(arglist{ii,2}) ~= arglist{ii,2})
            error(['Argument ' arglist{ii,1} ' is of integer type but non-integer number ' arglist{ii,2} ' was supplied']);
        end
        if (minval{jj} ~= 0 | maxval{jj} ~= 0)
          if (minval{jj} == 1 & maxval{jj} == 1 & arglist{ii,2} < 0)
            error(['Argument ' arglist{ii,1} ' must be bigger or equal to zero!']);
          elseif (minval{jj} == -1 & maxval{jj} == -1 & arglist{ii,2} > 0)
            error(['Argument ' arglist{ii,1} ' must be smaller or equal to zero!']);
          elseif (minval{jj} == 2 & maxval{jj} == 2 & arglist{ii,2} <= 0)
            error(['Argument ' arglist{ii,1} ' must be bigger than zero!']);
          elseif (minval{jj} == -2 & maxval{jj} == -2 & arglist{ii,2} >= 0)
            error(['Argument ' arglist{ii,1} ' must be smaller than zero!']);
          elseif (minval{jj} ~= maxval{jj} & arglist{ii,2} < minval{jj})
            error(['Argument ' arglist{ii,1} ' must be bigger than ' num2str(minval{jj})]);
          elseif (minval{jj} ~= maxval{jj} & arglist{ii,2} > maxval{jj})
            error(['Argument ' arglist{ii,1} ' must be smaller than ' num2str(maxval{jj})]);
          end
        end
     end
     if ~strcmp(narglist{jj,2},'OK_out') &  ~strcmp(narglist{jj,2},'OK_in')
       narglist{jj,2} = arglist{ii,2};
     end
   end
   end
   if (wasmatched == 0 & ~strcmp(arglist{ii,1},''))
        error(['Argument ' arglist{ii,1} ' is not a valid argument for this function']);
   end
end
% match the remaining inputs/outputs to the unused arguments and test for missing required inputs
 for jj=1:size(narglist,1)
     if  strcmp(paramtype{jj}, 'Toggle')
        if (narglist{jj,2} ==0)
          narglist{jj,1} = '';
        end;
        narglist{jj,2} = '';
     end;
     if  ~strcmp(narglist{jj,2},'__input') && ~strcmp(narglist{jj,2},'__output') && istoggle(jj) && ~ was_set(jj)
          narglist{jj,1} = '';
          narglist{jj,2} = '';
     end;
     if strcmp(narglist{jj,2}, '__input')
      if (minval{jj} == 0)  % meaning this input is required
        if (nextinput > size(Inputs))
           error(['Required input ' narglist{jj,1} ' has no corresponding input in the list!']);
        else
          narglist{jj,2} = 'OK_in';
          nextinput = nextinput + 1;
        end
      else  % this is an optional input
        if (nextinput <= length(Inputs))
          narglist{jj,2} = 'OK_in';
          nextinput = nextinput + 1;
        else
          narglist{jj,1} = '';
          narglist{jj,2} = '';
        end;
      end;
     else
     if strcmp(narglist{jj,2}, '__output')
      if (minval{jj} == 0) % this is a required output
        if (nextoutput > nargout & nargout > 1)
           error(['Required output ' narglist{jj,1} ' is not stated in the assignment list!']);
        else
          narglist{jj,2} = 'OK_out';
          nextoutput = nextoutput + 1;
          NumReqOutputs = NumReqOutputs-1;
        end
      else % this is an optional output
        if (nargout - nextoutput >= NumReqOutputs)
          narglist{jj,2} = 'OK_out';
          nextoutput = nextoutput + 1;
        else
          narglist{jj,1} = '';
          narglist{jj,2} = '';
        end;
      end
     end
  end
end
if nargout
   varargout = cell(1,nargout);
else
  varargout = cell(1,1);
end
global KhorosRoot
if exist('KhorosRoot') && ~isempty(KhorosRoot)
w=['"' KhorosRoot];
else
if ispc
  w='"C:\Program Files\dip\khorosBin\';
else
[s,w] = system('which cantata');
w=['"' w(1:end-8)];
end
end
[varargout{:}]=callKhoros([w 'kcmplx2real"  '],Inputs,narglist);


function a = r8cvv_iset ( mn, a, m, roff, i, j, aij )

%*****************************************************************************80
%
%% R8CVV_ISET sets item J from row I in an R8CVV.
%
%  Discussion:
%
%    An R8CVV is a "vector of vectors" of R8's.
%
%  Licensing:
%
%    This code is distributed under the GNU LGPL license.
%
%  Modified:
%
%    27 November 2012
%
%  Author:
%
%    John Burkardt
%
%  Parameters:
%
%    Input, integer MN, the size of the cell array.
%
%    Input, real A(MN), the cell array.
%
%    Input, integer M, the number of rows in the array.
%
%    Input, integer ROFF(M+1), the row offsets.
%
%    Input, integer I, the row of the item.
%    1 <= I <= M.
%
%    Input, integer J, the column of the item.
%    1 <= J <= NR(I).
%
%    Input, real AIJ, the new value of item A(I,J).
%
%    Output, real A(MN), the updated cell array.
%
  k = roff(i) + j;
  a(k) = aij;

  return
end


function quenchWriteFibersFromState(stateFile,fg,fgBins,outName,outType)
%
% quenchWriteFibersFromState([stateFile],[fg],[fgBins],[outName],[outType='pdb'])
%
% OVERVIEW: Take a quench state file and from that file read the unique
%           subgroup indices and save out a fiber group (pdb) for each
%           unique value.
%
% NOTE:
%           This function will be parsing out a text file. If the text file
%           has been altered in any way this will fail spectacularly.
%
% INPUTS:
%       stateFile - a Quench stateFile. -- if there is one qst file in the
%                   directory where this function is called then it
%                   will be selected and used. If there are multiple then
%                   it will prompt the user if it's not provided.
%
%       fg        - a pbd fiber group associated with the qst file. This
%                   will be read in from the qst file. If the fg is not
%                   within the same directory then the user will be
%                   prompted if not provided.
%
%       fgbins    - The bins in the qst for each of the fiber groups user
%                   wants pulled out of the state file. Defaults to all.
%
%       outName   - A [numel(fgBins) X 1] cell array containing the pdb
%                   filenames. If not provided the names will be taken from
%                   the qst file by default.
%
%       outType   - Type of file to save out: 'pdb' or 'mat'
%
% WEB RESOURCES:
%       mrvBrowseSVN('quenchWriteFibersFromState');
%
%
% EXAMPLE USAGE:
%
%   1. Default usage where the '.qst' and '.pdb' file are in the same
%      directory and only one '.qst' file exists.
%       >> quenchWriteFibersFromState;
%
%   2. Same as 1 above, but return only bins 1 4 8 from the 'qst' file and
%      retain the names used within the 'qst' file:
%       >> quenchWriteFibersFromState([],[],[1 4 8],[],[]);
%
%   3. Same as 2, but give each group a specific name:
%       >> quenchWriteFibersFromState([],[],[1 4 8],{'fg_1','fg_2','fg_3'},[]);
%
%   4. Provide all inputs:
%       >> sateFile = 'greatFileName.qst';
%       >> fg = 'greatFileName.pdb';
%       >> fgBins = [1 12 29];
%       >> outName = {'fg_1','fg_2','fg_3');
%       >> outType = 'pdb';
%       >> quenchWriteFibersFromState(stateFile,fg,fgBins,outName,outType);
%
%
% (C) Stanford VISTA, 2012 [lmp]
%

%% Check inputs

% QST file: If there is only one qst file in the CWD then we load that one
% If there is more than one prompt user for the file
if notDefined('stateFile')
    qstName = dir('*qst');
    s = size(qstName);
    if s(1) > 1
       disp('More that one Quench state file found in this directory... Opening dialog...');
        stateFile = mrvSelectFile('r','*.qst','Selcte QUENCH state file',pwd);
    else
       stateFile = fullfile(pwd,qstName.name);
    end
end

% Check that fg is a struct or a file that we can read in
% Perhaps we should read this in later, when we need it.
if notDefined('fg')
    fg = [];
end

% Set outname to empy if they didn't pass it in - we'll set it later
if notDefined('outName')
    outName = '';
end

% They didn't pass in fGroups so we set it to empty and use all unique
% values later
if notDefined('fgBins')
   fgBins = [];
end

% By default we save out pdb files
if notDefined('outType')
    outType = 'pdb';
end

%% Read the qst file

% Open the state file for reading
qst = fopen(stateFile,'r');

% Find out how many lines are in the qst text file
num = 1;
tline = fgetl(qst);
while ischar(tline)
   tline = fgetl(qst);
   num   = num+1;
end
fclose(qst);

% Open the file back up for reading
qst = fopen(stateFile,'r');

% Loop over the file and read each line individually into a cell array - Q
Q = cell(1,num);
for ii = 1:num
   tline = fgetl(qst);
   Q{ii} = tline;
end

fclose(qst);

% Q is now a cell array containing each line of the qst file
Q = Q';

%% Load the fiber group and begin to parse the Quench State .qst file

% If the user did not pass in a fiber group then 'fg' will be empty and we
% will try to load the fiber group from the path listed within the quench
% state file
if isempty(fg)
    disp('Loading fibers from path in the ''qst'' file...');
    fg = Q{14}; % The path in the qst file
    try
        fg = fgRead(fg);
    catch ME
        % That didn't work so we prompt the user to select the fiber group
        disp(ME);
        fg = mrvSelectFile('r','*.pdb','Select PDB file',pwd);
        fg = fgRead(fg);
    end

% It's not empty and it's a file - load it.
elseif ~isstruct(fg) && exist(fg,'file')
    disp('Loading fibers from file ...');
    fg = fgRead(fg);

% It was passed in as a structure - so we're done
elseif isfield(fg,'fibers')
    disp('Fibers already loaded.');
end

% Sanity check: Check the number of inds against the number of fibers in
% fg.fibers
if str2num(Q{18}(15:end)) ~= numel(fg.fibers)
    error('The number of fibers referenced in the ''qst'' file does not match that of the fiber group passed in!')
end

% The fiber indices from the qst file
fInds = str2num(Q{19});

% The unique fiber group indices
fGroups = unique(fInds);

% The total number of fiber groups
nGroups = numel(fGroups);

%% Get names for the fiber groups

% Initialize the fName cell array
fName = cell(nGroups,1);

% Set up the fName cell array for naming each fiber group
if notDefined('outName') || numel(outName) ~= numel(fgBins)
    % Starting number of lines before first name. Names occur every 5 lines.
    nind = 28;
    for nn = 1:nGroups
        % Remove any spaces in the file names and replace with '_'
        fName{nn} = regexprep(Q{nind}(7:end),'\W','_');
        % Increase the line ind by 5 each pass.
        nind = nind+5;
    end
else
    fName = outName;
end

% If outName was not passed in but fgBins are reuested then take those
% fgBins out of the fNames structure ans use only those for the names
if notDefined('outName') && ~notDefined('fgBins');
   outName = fName(fgBins);
   fName   = outName;
end

%% Create the fiber groups and write them out

fprintf('Creating %s groups from ''qst'' file...',num2str(numel(fGroups)));

% The ID of unique fiber group bins - if passed in then use those
if ~notDefined('fgBins')
   fGroups = fgBins;
end

% For each unique fg index in fgGroups assign the fibers to a new group and
% write them out
for ff = 1:numel(fGroups)

    % fgI are the indices for a given fibergroup (ff) - fInds is the long
    % array of indices from the state file
    fgI = find(fInds == fGroups(ff));

    % Keep only fgI from fg.fibers
    fgOut = fgExtract(fg,fgI,'keep');

    % Set the name from the fName cell array
    fgOut.name = fName{ff};

    % Show the user what's going on
    fprintf('\n  %s - %s',num2str(ff), fName{ff});

    % Save out the fiber group
    fgWrite(fgOut,fgOut.name,outType);

    % Clear for memory
    clear fgOut
end

fprintf('\nDone.\n');

return

%%
%#ok<*ST2NM>


function hh = plot_storage(md, idx, varargin)
%PLOT_STORAGE   Plot storage unit results
%
%   PLOT_STORAGE(MD)
%   PLOT_STORAGE(MD, IDX)
%   PLOT_STORAGE(MD, IDX, '<option1_name>', '<option1_value', ...)
%   PLOT_STORAGE(MD, IDX, OPT)
%   PLOT_STORAGE(MD, IDX, OPT, '<option1_name>', '<option1_value', ...)
%   H = PLOT_STORAGE(MD, ...)
%
%   IDX is the storage unit index. If IDX is a vector, it sums them first,
%   if empty, it includes all storage units in MD. Options can be
%   specified as an OPT struct or as individual pairs of 'name' and 'value'
%   arguments. The possible options include the following, where the default
%   is shown in parenthesis:
%       'saveit'        (false) flag to indicate whether to create PDF file
%       'saveall'       (false) flag to indicate whether to create individual
%                       PDF files for each element of IDX, as well as
%                       aggregate, when IDX is a vector (or empty)
%       'savepath'      ('') path to directory to save files in
%       'savename'      ('stored-energy-%s.pdf') name of PDF file
%                       %s is optional placeholder for storage unit index
%       'separation'    (0.8)  %% separation of beginning/end of period (0-1)
%           0   = beginning & end of period t, staircase (dispatches are
%                 vertical lines at t)
%           0.5 = evenly separated (both dispatch and transitions visible)
%           1   = beginning of t aligned with end of t-1, smooth,
%                 (transitions are vertical displacements at t+/-0.5)
%       'sort_tol'      (1e-6) round to nearest sort_tol for sorting
%       'size_factor'   (1) to scale font/marker sizes in case you want to
%                       do sub-plots or something
%       'show_grid'                 (true) vertical lines to divide periods
%       'show_expected_initial'     (true)
%       'show_expected_final'       (true)
%       'show_adjusted_dispatches'  (true)
%       'show_dispatches'           (false)
%
%   Returns handle to current figure window.

%   TO DO: Do initial invisible plot to get v axis parameters.

%   MOST
%   Copyright (c) 2013-2020, Power Systems Engineering Research Center (PSERC)
%   by Ray Zimmerman, PSERC Cornell
%
%   This file is part of MOST.
%   Covered by the 3-clause BSD License (see LICENSE file for details).
%   See https://github.com/MATPOWER/most for more info.

%% define named indices into data matrices
[GEN_BUS, PG, QG, QMAX, QMIN, VG, MBASE, GEN_STATUS, PMAX, PMIN, ...
    MU_PMAX, MU_PMIN, MU_QMAX, MU_QMIN, PC1, PC2, QC1MIN, QC1MAX, ...
    QC2MIN, QC2MAX, RAMP_AGC, RAMP_10, RAMP_30, RAMP_Q, APF] = idx_gen;

%% initialize some variables
my_xlabel = 'Period';
nt = md.idx.nt;
ns = md.idx.ns;
nj_max = max(md.idx.nj);
baseMVA = md.mpc.baseMVA;

%% input args
if nargin < 2
    idx = [];
end
if isempty(idx)
    idx = (1:ns)';
end
nidx = length(idx);
if nidx > 1 && size(idx, 1) == 1
    idx = idx';     %% convert row vector to column vector
end
g = md.Storage.UnitIdx(idx);
b = md.mpc.gen(g, GEN_BUS);

%% default options
opt = struct( ...
    'saveit', false, ...
    'saveall', false, ...
    'savepath', '', ...
    'savename', 'stored-energy-%s.pdf', ...
    'separation', 0.8, ...
    'sort_tol', 1e-6, ...
    'size_factor', 1, ...
    'show_grid', true, ...
    'show_adjusted_dispatches', true, ...
    'show_expected_initial', true, ...
    'show_expected_final', true, ...
    'show_dispatches', false );

%% process options
if mod(length(varargin), 2) %% odd number of options, first must be OPT struct
    if ~isstruct(varargin{1})
        error('plot_storage: Single OPT argument must be a struct');
    end
    myopt = varargin{1};
    k = 2;
else                        %% even number of options
    myopt = struct;
    k = 1;
end
while k < length(varargin)
    opt_name = varargin{k};
    opt_val  = varargin{k+1};
    if ~isfield(opt, opt_name)
        error('plot_name: ''%s'' is not a valid option name', opt_name);
    end
    myopt.(opt_name) = opt_val;
    k = k + 2;
end
fields = fieldnames(myopt);
for f = 1:length(fields)
    opt.(fields{f}) = myopt.(fields{f});
end

%% call recursively for individual plots if indicated
if opt.saveall && nidx > 1
    if isempty(strfind(opt.savename, '%s'))
        error('plot_storage: ''savename'' must include a ''%%s'' placeholder when ''saveall'' option is true.');
    end
    for i = 1:nidx
        plot_storage(md, idx(i), opt, 'saveit', true);
    end
end

%% extract and expand storage parameters
MinStorageLevel = md.Storage.MinStorageLevel;
MaxStorageLevel = md.Storage.MaxStorageLevel;
if size(MinStorageLevel, 1) == 1 && ns > 1  %% expand rows
  MinStorageLevel = ones(ns, 1) * MinStorageLevel;
end
if size(MinStorageLevel, 2) == 1 && nt > 1  %% expand cols
  MinStorageLevel = MinStorageLevel * ones(1, nt);
end
if size(MaxStorageLevel, 1) == 1 && ns > 1  %% expand rows
  MaxStorageLevel = ones(ns, 1) * MaxStorageLevel;
end
if size(MaxStorageLevel, 2) == 1 && nt > 1  %% expand cols
  MaxStorageLevel = MaxStorageLevel * ones(1, nt);
end
if isempty(md.Storage.InEff)
  InEff = 1;                        %% no efficiency loss by default
else
  InEff = md.Storage.InEff;
end
if size(InEff, 1) == 1 && ns > 1    %% expand rows
  InEff = ones(ns, 1) * InEff;
end
if size(InEff, 2) == 1 && nt > 1    %% expand cols
  InEff = InEff * ones(1, nt);
end
if isempty(md.Storage.OutEff)
  OutEff = 1;                       %% no efficiency loss by default
else
  OutEff = md.Storage.OutEff;
end
if size(OutEff, 1) == 1 && ns > 1   %% expand rows
  OutEff = ones(ns, 1) * OutEff;
end
if size(OutEff, 2) == 1 && nt > 1   %% expand cols
  OutEff = OutEff * ones(1, nt);
end
if isempty(md.Storage.LossFactor)
  LossFactor = 0;                       %% no losses by default
else
  LossFactor = md.Storage.LossFactor;
end
if size(LossFactor, 1) == 1 && ns > 1   %% expand rows
  LossFactor = ones(ns, 1) * LossFactor;
end
if size(LossFactor, 2) == 1 && nt > 1   %% expand cols
  LossFactor = LossFactor * ones(1, nt);
end
if isempty(md.Storage.rho)
  rho = 1;                      %% use worst case by default (for backward compatibility)
else
  rho = md.Storage.rho;
end
if size(rho, 1) == 1 && ns > 1  %% expand rows
  rho = ones(ns, 1) * rho;
end
if size(rho, 2) == 1 && nt > 1  %% expand cols
  rho = rho * ones(1, nt);
end

%% initialize data structures to be plotted
offset = opt.separation/2;          %% offset for plotting on horiz axis
p = (1:nt)';
pp = 2*p;
ppp(pp-1) = p - offset;
ppp(pp  ) = p + offset;
Sp = md.results.Sp(idx, :);                     %% s+
Sm = md.results.Sm(idx, :);                     %% s-
eS = md.Storage.ExpectedStorageState(idx, :);   %% expected s
Smin = MinStorageLevel(idx, :);     %% physical lower bound
Smax = MaxStorageLevel(idx, :);     %% physical upper bound
SSp   = NaN(2*nt, 1);               %% expanded for 2 pts per period
SSm   = NaN(2*nt, 1);               %%    "
eeS   = NaN(2*nt, 1);               %%    "
SSmin(pp)   = sum(Smin, 1);
SSmax(pp)   = sum(Smax, 1);
SSmin(pp-1) = SSmin(pp);
SSmax(pp-1) = SSmax(pp);

eSi = NaN(nj_max, nt, nidx);
eSf = NaN(nj_max, nt, nidx);
Si_min = NaN(nj_max, nt, nidx);
Si_max = NaN(nj_max, nt, nidx);
dSi = zeros(nj_max, nt, nidx);  %% deltaS, total change in stored energy from injections
Pg = zeros(nj_max, nt, nidx);   %% total dispatch of specified storage units
jmin = zeros(1, nt);
jmax = zeros(1, nt);
LossCoeff = md.Delta_T * LossFactor/2;
beta1 = (1-LossCoeff) ./ (1+LossCoeff);
beta2 = 1 ./ (1+LossCoeff);
for t = 1:nt
    if t == 1
        if md.Storage.ForceCyclicStorage
            SSm(t) = sum(md.Storage.InitialStorage(idx));
            SSp(t) = sum(md.Storage.InitialStorage(idx));
        else
            SSm(t) = sum(md.Storage.InitialStorageLowerBound(idx));
            SSp(t) = sum(md.Storage.InitialStorageUpperBound(idx));
        end
        eeS(t) = sum(md.Storage.InitialStorage(idx));
    else
        SSm(2*t-1) = sum(Sm(:, t-1), 1);
        SSp(2*t-1) = sum(Sp(:, t-1), 1);
        eeS(2*t-1) = sum(eS(:, t-1), 1);
    end
    %% end of period
    SSm(2*t)   = sum(Sm(:, t), 1);
    SSp(2*t)   = sum(Sp(:, t), 1);
    eeS(2*t)   = sum(eS(:, t), 1);

    vv = md.om.get_idx();
    for j = 1:md.idx.nj(t)
        dS = -md.Delta_T * ...
            (InEff(:,t)  .* md.QP.x(vv.i1.Psc(t,j,1)-1+(1:ns)) + ...
             1./OutEff(:,t) .* md.QP.x(vv.i1.Psd(t,j,1)-1+(1:ns)) );
        dSi(j,t,:) = dS(idx) * baseMVA;
        Pg(j,t,:)  = md.flow(t,j,1).mpc.gen(g, PG);
        Lij = md.tstep(t).Li( j:md.idx.nj(t):(ns-1)*md.idx.nj(t)+j, :);
        Lfj = md.tstep(t).Lf( j:md.idx.nj(t):(ns-1)*md.idx.nj(t)+j, :);
        Mj  = md.tstep(t).Mg( j:md.idx.nj(t):(ns-1)*md.idx.nj(t)+j, :) + ...
              md.tstep(t).Mh( j:md.idx.nj(t):(ns-1)*md.idx.nj(t)+j, :);
        Nj  = md.tstep(t).Ng( j:md.idx.nj(t):(ns-1)*md.idx.nj(t)+j, :) + ...
              md.tstep(t).Nh( j:md.idx.nj(t):(ns-1)*md.idx.nj(t)+j, :);
        temp_eSi = Lij * md.Storage.InitialStorage/baseMVA + Mj * md.QP.x;
        temp_eSf = Lfj * md.Storage.InitialStorage/baseMVA + Nj * md.QP.x;
        eSi(j,t,:) = baseMVA * temp_eSi(idx);
        eSf(j,t,:) = baseMVA * temp_eSf(idx);
        if t == 1
            if md.Storage.ForceCyclicStorage
                Si_min(j,t,:) = md.Storage.InitialStorage(idx);
                Si_max(j,t,:) = md.Storage.InitialStorage(idx);
            else
                Si_min(j,t,:) = md.Storage.InitialStorageLowerBound(idx);
                Si_max(j,t,:) = md.Storage.InitialStorageUpperBound(idx);
            end
        else
            tmp_eSi = reshape(eSi(j,t,:), nidx, 1);
            Si_min(j,t,:) = rho(idx,t) .* Sm(:,t-1) + (1-rho(idx,t)) .* tmp_eSi;
            Si_max(j,t,:) = rho(idx,t) .* Sp(:,t-1) + (1-rho(idx,t)) .* tmp_eSi;
        end
    end
%     if nidx == -1
%         rows = opt.sort_tol * round(opt.sort_tol^-1 * [ beta1(idx,t)*Si_min(:,t,1) + beta2(idx,t)*dSi(:,t,1) Si_min(:,t,1) ]);
%         [junk, iii] = sortrows(rows, [1 2]);
%         jmin(t) = iii(1);
%         rows = opt.sort_tol * round(opt.sort_tol^-1 * [ beta1(idx,t)*Si_max(:,t,1) + beta2(idx,t)*dSi(:,t,1) Si_max(:,t,1) ]);
%         [junk, iii] = sortrows(rows, [-1 -2]);
%         jmax(t) = iii(1);
%     else
        tmpS = reshape(Si_min(:,t,:), nj_max, nidx);
        rows = opt.sort_tol * round(opt.sort_tol^-1 * [tmpS * beta1(idx,t) + reshape(dSi(:,t,:), nj_max, nidx) * beta2(idx,t) tmpS]);
        [junk, i] = sortrows(rows, [1 2]);
        jmin(t) = i(1);
        tmpS = reshape(Si_max(:,t,:), nj_max, nidx);
        rows = opt.sort_tol * round(opt.sort_tol^-1 * [tmpS * beta1(idx,t) + reshape(dSi(:,t,:), nj_max, nidx) * beta2(idx,t) tmpS]);
        [junk, i] = sortrows(rows, [-1 -2]);
        jmax(t) = i(1);
%     end
end
SSi_min = zeros(nt,1);
SSi_max = zeros(nt,1);
for t = 1:nt
    SSi_min(t) = sum(Si_min(jmin(t), t, :), 3);
    SSi_max(t) = sum(Si_max(jmax(t), t, :), 3);
end

%% do plots
%% figure out axis limits
plot(ppp, SSmax+0.001, 'LineStyle', 'none')
hold on
plot(ppp, SSmin-0.001, 'LineStyle', 'none')

%% draw grid
v = axis;
if opt.show_grid
    m = v(3) - 100*(v(4) - v(3));
    M = v(4) + 100*(v(4) - v(3));
    for k = 0:nt
        line([k; k], [m; M], 'LineWidth', 0.25, 'Color', 0.9*[1 1 1]);
    end
end
axis(v);

plot(ppp, SSmin, ':k', 'LineWidth', 1);
plot(ppp, SSmax, ':k', 'LineWidth', 1);
for t = 1:nt
    for j = 1:md.idx.nj(t)
        if opt.show_adjusted_dispatches
            Si = sum(Si_min(j,t,:), 3);
            Sf = reshape(Si_min(j,t,:), 1, nidx) * beta1(idx, t) + ...
                    reshape(dSi(j,t,:), 1, nidx) * beta2(idx,t);
            line(t+offset*[-1;1], [Si; Sf], 'Color', 0.8*[1 1 1]);
            Si = sum(Si_max(j,t,:), 3);
            Sf = reshape(Si_max(j,t,:), 1, nidx) * beta1(idx, t) + ...
                    reshape(dSi(j,t,:), 1, nidx) * beta2(idx,t);
            line(t+offset*[-1;1], [Si; Sf], 'Color', 0.8*[1 1 1]);
        end

        if opt.show_dispatches
            Si = sum(Si_min(j,t,:), 3);
            Sf = Si - sum(Pg(j,t,:), 3);
            line(t+offset*[-1;1], [Si; Sf], 'Color', 0.8*[1 0.7 1]);
            Si = sum(Si_max(j,t,:), 3);
            Sf = Si - sum(Pg(j,t,:), 3);
            line(t+offset*[-1;1], [Si; Sf], 'Color', 0.8*[1 0.7 1]);
        end
    end
end
plot(ppp, SSp, '--', 'LineWidth', 1, 'Color', 0.8*[0 1 0]);
plot(ppp, SSm, '--r', 'LineWidth', 1, 'Color', 0.9*[1,0,0]);
plot(p+offset, SSp(pp), 'v', 'LineWidth', 1, 'Color', 0.8*[0 1 0], 'MarkerSize', 6*opt.size_factor);
plot(p+offset, SSm(pp), '^r', 'LineWidth', 1, 'Color', 0.9*[1,0,0], 'MarkerSize', 6*opt.size_factor);
plot(ppp, eeS, 'Color', [0 0 1], 'LineWidth', 2);
if opt.show_expected_initial
    if any(any(rho(idx,:) > 0))
        plot(p-offset, sum(Si_min, 3), '+', 'LineWidth', 1, 'Color', 0.9*[1,0,0], 'MarkerSize', 4*opt.size_factor);
        plot(p-offset, sum(Si_max, 3), '+', 'LineWidth', 1, 'Color', 0.8*[0,1,0], 'MarkerSize', 4*opt.size_factor);
    end
    plot(p-offset, SSi_min, '.', 'LineWidth', 1, 'MarkerSize', 13*opt.size_factor, 'Color', 0.9*[1 0 0]);
    plot(p-offset, SSi_max, '.', 'LineWidth', 1, 'MarkerSize', 13*opt.size_factor, 'Color', 0.8*[0 1 0]);
    plot(p-offset, sum(eSi, 3), 'o', 'LineWidth', 0.5, 'MarkerSize', 5*opt.size_factor);
end
if opt.show_expected_final
    plot(p+offset, sum(eSf, 3), 'x', 'LineWidth', 0.5, 'MarkerSize', 5*opt.size_factor);
end

hold off;

if nidx == 1
    title(sprintf('Stored Energy for Storage Unit %d (Gen %d) @ Bus %d', idx, g, b), 'FontSize', 18*opt.size_factor);
else
    if nidx == ns && all(idx == (1:ns)')
        txt = 'All';
    else
        txt = 'Selected';
    end
    title(sprintf('Stored Energy for %s Storage Units', txt), 'FontSize', 18*opt.size_factor);
end
ylabel('Energy, MWh', 'FontSize', 16*opt.size_factor);
xlabel(my_xlabel, 'FontSize', 16*opt.size_factor);
%legend('contract', wlabels{1}, wlabels{2}, wlabels{3}, wlabels{4}, 'Up Res', 'Dn Res', 'Lim', 'Bind', 'Location', 'EastOutside');
set(gca, 'FontSize', 12*opt.size_factor);
h = gcf;
set(h, 'PaperOrientation', 'landscape');
%set(h, 'PaperPosition', [0.25 0.25 10.5 8]);
set(h, 'PaperPosition', [0 0 11 8.5]);
if opt.saveit || opt.saveall
    if nidx == 1
        txt = sprintf('%d', idx);
    elseif nidx == ns && all(idx == (1:ns)')
        txt = 'all';
    else
        txt = 'selected';
    end
    if isempty(strfind(opt.savename, '%s'))
        pdf_name = opt.savename;
    else
        pdf_name = sprintf(opt.savename, txt);
    end
    print('-dpdf', fullfile(opt.savepath, pdf_name));
end
if nargout
    hh = h;
end


function f = uplus(f)
%+   BALLFUN unary plus.
%   +F of a BALLFUN is F.
%
%   G = UPLUS(F) is called for the syntax '+F'.

% Copyright 2018 by The University of Oxford and The Chebfun Developers.
% See http://www.chebfun.org/ for Chebfun information.

end


%DNA medium test
%
% Author: Jonathan Karr, jkarr@stanford.edu
% Affiliation: Covert Lab, Department of Bioengineering, Stanford University
% Last updated: 4/19/2011
classdef DNADamageRepair_Test < TestCase
    properties
        simulation
    end

    methods
        function this = DNADamageRepair_Test(name)
            this = this@TestCase(name);
        end

        function setUp(this)
            sim = edu.stanford.covert.cell.sim.SimulationFixture.load([], {
                'DNADamage'
                'DNARepair'
                });
            sim.applyOptions('verbosity', 0, 'seed', 1);

            this.simulation = sim;
        end

        function testExpectedDamageRates_sampleAccessibleSites(this)
            %references
            sim = this.simulation;
            c = sim.state('Chromosome');

            %test sampleAccessibleSites
            iterMax = 1000;
            cnts = zeros(iterMax, 1);
            prob = 1e-6;
            for i = 1:iterMax
                cnts(i) = size(c.sampleAccessibleSites(prob, Inf, 'T'), 1);
            end
            assertEqual([0; 1], unique(cnts));
            assertElementsAlmostEqual(...
                sum(cnts), ...
                iterMax * prob * 2 * c.sequenceLen * (1-c.sequenceGCContent)/2, ...
                'relative', 0.10);
        end

        function testExpectedDamageRates_setSiteDamaged(this)
            %references
            sim = this.simulation;
            c = sim.state('Chromosome');

            %test setSiteDamaged
            iterMax = 1000;
            cnts = zeros(iterMax, 1);
            prob = 1e-6;
            for i = 1:iterMax
                c.initialize();
                cnts(i) = size(c.setSiteDamaged('damagedBases', 1, prob, Inf, 'T', 'sequence'), 1);
            end
            assertEqual([0; 1], unique(cnts));
            assertElementsAlmostEqual(...
                sum(cnts), ...
                iterMax * prob * 2 * c.sequenceLen * (1-c.sequenceGCContent)/2, ...
                'relative', 0.10);
        end

        function testExpectedDamageRates_evolveState_simpleReactionSet(this)
            %references
            sim = this.simulation;
            c = sim.state('Chromosome');
            d = sim.process('DNADamage');

            %test evolve state with simplified reaction set
            iterMax = 1000;

            d.reactionWholeCellModelIDs = d.reactionWholeCellModelIDs(1:4);
            d.reactionStoichiometryMatrix = d.reactionStoichiometryMatrix(:, 1:4);
            d.reactionDamageTypes = {'damagedBases'; 'damagedBases'; 'intrastrandCrossLinks'; 'intrastrandCrossLinks'};
            d.reactionBounds = [zeros(4, 1) repmat(1e-6, 4, 1)];
            d.reactionRadiation = zeros(4, 1);
            d.reactionDNAProduct = (1:4)';
            d.reactionVulnerableMotifs = {'A'; 'C'; 'G'; 'T'};
            d.reactionVulnerableMotifTypes = repmat({'sequence'}, 4, 1);

            c.initialize();
            expRates = d.calcExpectedReactionRates() * iterMax;

            d.substrates(any(d.reactionSmallMoleculeStoichiometryMatrix,2)) = 1e6;
            for i = 1:iterMax
                d.evolveState();
            end
            [~, vals] = find(c.damagedBases);
            assertElementsAlmostEqual(histc(vals, d.reactionDNAProduct), expRates .* [1; 1; 0; 0], ...
                'relative', 0.10);
            [~, vals] = find(c.intrastrandCrossLinks);
            assertElementsAlmostEqual(histc(vals, d.reactionDNAProduct), expRates .* [0; 0; 1; 1], ...
                'relative', 0.10);
        end

        function testExpectedDamageRates_evolveState_lessSimpleReactionSet(this)
            %references
            sim = this.simulation;
            c = sim.state('Chromosome');
            m = sim.state('Metabolite');
            d = sim.process('DNADamage');

            %simulate damage
            iterMax = 1000;

            d.reactionBounds(d.reactionBounds(:, 2) < 1000, 2) = 1e-6;
            d.substrates(d.reactionRadiation(d.reactionRadiation ~= 0)) = 1;
            d.reactionBounds(~cellfun(@ischar, d.reactionVulnerableMotifs), 2) = 0;

            expRates = d.calcExpectedReactionRates() * iterMax;

            assertFalse(any(d.reactionDNAProduct == m.m6ADIndexs & strcmp(d.reactionDamageTypes, 'damagedBases')));

            d.substrates(any(d.reactionSmallMoleculeStoichiometryMatrix,2)) = 1e6;
            for i = 1:iterMax
                d.evolveState();
            end

            %assert damage rates ~= expected
            this.assertDamageRateIsExpected(c, d, expRates, iterMax, 0.4, 0.5);
        end

        function testExpectedDamageRates_evolveState_fullReactionSet(this)
            %references
            sim = this.simulation;
            c = sim.state('Chromosome');
            m = sim.state('Metabolite');
            s = sim.state('Stimulus');
            d = sim.process('DNADamage');
            r = sim.process('DNARepair');

            %simulate damage
            iterMax = 1000;

            d.reactionBounds(d.reactionBounds(:, 2) < 1000, 2) = 1e-6;
            d.substrates(:) = 1e12;
            d.substrates(d.reactionRadiation(d.reactionRadiation ~= 0)) = 1;
            s.setValues(s.getIndexs(d.substrateWholeCellModelIDs(d.reactionRadiation(d.reactionRadiation ~= 0))), 3) = 1;

            expRates = d.calcExpectedReactionRates() * iterMax;

            assertFalse(any(d.reactionDNAProduct == m.m6ADIndexs & ...
                strcmp(d.reactionDamageTypes, 'damagedBases')));

            d.reactionSmallMoleculeStoichiometryMatrix(:) = 0;

            r.reactionBounds(:, 2) = 0;
            r.enzymeBounds(:, 2) = 0;

            sim.applyOptions('lengthSec', iterMax);
            sim.state('MetabolicReaction').initialGrowthFilterWidth = Inf;
            sim.run();

            %assert damage rates ~= expected
            this.assertDamageRateIsExpected(c, d, expRates, iterMax, 0.1, 10);
        end

        function testAllDamageCanBeRepaired(this)
            sim = this.simulation;
            c = sim.state('Chromosome');
            d = sim.process('DNADamage');
            r = sim.process('DNARepair');

            repairRates = 10000 * r.enzymeBounds;

            for i = 1:numel(d.reactionWholeCellModelIDs)
                c.initialize();
                d.initializeState();
                r.initializeState();

                sim.state('Metabolite').counts(:, sim.compartment.cytosolIndexs) = 1e6;
                sim.state('Stimulus').values(:) = 0;
                sim.state('Stimulus').setValues(:, 3) = 0;

                d.reactionBounds(:, 2) = 0;

                switch d.reactionVulnerableMotifTypes{i}
                    case 'sequence'
                        d.reactionBounds(i, 2) = 3e-5 * 4 ^ (numel(d.reactionVulnerableMotifs{i})-1);
                    case {'damagedSugarPhosphate', 'damagedBases', 'intrastrandCrossLinks'}
                        c.(d.reactionVulnerableMotifTypes{i})((1:5)*10000, 1) = ...
                            d.reactionVulnerableMotifs{i};
                        c.(d.reactionVulnerableMotifTypes{i})((6:10)*10000, 2) = ...
                            d.reactionVulnerableMotifs{i};
                        d.reactionBounds(i, 2) = 1;
                    case {'gapSites', 'abasicSites', 'strandBreaks', 'hollidayJunctions'}
                        c.(d.reactionVulnerableMotifTypes{i})((1:5) * 10000, 1) = 1;
                        c.(d.reactionVulnerableMotifTypes{i})((6:10) * 10000, 2) = 1;
                        d.reactionBounds(i, 2) = 1;
                    otherwise, throw(MException('DNADamageRepair_Test:error', ...
                            'Unimplemented vulnerable motif %s', d.reactionVulnerableMotifTypes{i}));
                end

                if d.reactionRadiation(i)
                    sim.state('Stimulus').setValues(sim.state('Stimulus').setValues(:,1) == ...
                        d.substrateGlobalIndexs(d.reactionRadiation(i)), 3) = 1;
                end

                r.enzymeBounds(:) = 0;

                %stochastically damage
                for j = 1:2
                    sim.evolveState();
                end

                switch d.reactionDamageTypes{i}
                    case {'damagedSugarPhosphate', 'damagedBases', 'intrastrandCrossLinks'}
                        reactionDNAProduct = d.reactionDNAProduct(i);
                    case {'gapSites', 'abasicSites', 'strandBreaks', 'hollidayJunctions'}
                        reactionDNAProduct = 1;
                    otherwise, throw(MException('DNADamageRepair_Test:error', ...
                            'Unimplemented damage type %s', d.reactionDamageTypes{i}));
                end
                assertIn(nnz(c.(d.reactionDamageTypes{i}) == reactionDNAProduct), [2 50], ...
                    sprintf('%d %s %s\n', i, d.reactionWholeCellModelIDs{i}, d.reactionDamageTypes{i}));

                %repair
                d.reactionBounds(:, 2) = 0;
                r.enzymeBounds = repairRates;

                posStrnds = find(any(c.(d.reactionDamageTypes{i}) == reactionDNAProduct, 2));
                nDamagesMax = sum(diff([posStrnds(:, 1); posStrnds(1,1) + c.sequenceLen]) <= max(r.enzymeDNAFootprints));

                initDamagedSites = nnz(c.damagedSites);

                for j = 1:350
                    sim.evolveState();

                    damagedSites = c.damagedSites;
                    damagedSites(find(c.damagedBases)) = 0; %#ok<FNDSB>
                    damagedSites(c.shiftPositionsStrandsBase3Prime(find(c.damagedBases))) = 0; %#ok<FNDSB>
                    damagedSites(c.shiftPositionsStrandsBase5Prime(find(c.damagedBases))) = 0; %#ok<FNDSB>
                    if nnz(damagedSites) <= nDamagesMax
                        break;
                    end
                end

                assertIn(nnz(damagedSites), [0 nDamagesMax], ...
                    sprintf('%d %s %d\n', i, d.reactionWholeCellModelIDs{i}, initDamagedSites));
            end
        end

        function testDNARepairOutweighsDamage(this)
            %% references
            sim = this.simulation;
            c = sim.state('Chromosome');
            d = sim.process('DNADamage');
            r = sim.process('DNARepair');

            c.initialize();
            r.initializeState();

            %% indices of proteins which should never bind
            monGblIdxs = [];
            cpxGblIdxs = [];
            for i = 1:numel(sim.processes)
                p = sim.processes{i};
                monGblIdxs = [monGblIdxs; ...
                    p.enzymeGlobalIndexs(p.enzymeMonomerLocalIndexs)]; %#ok<*AGROW>
                cpxGblIdxs = [cpxGblIdxs; ...
                    p.enzymeGlobalIndexs(p.enzymeComplexLocalIndexs)];
            end
            monGblIdxs = sort(monGblIdxs);
            cpxGblIdxs = sort(cpxGblIdxs);

            monGblIdxs = setdiff(monGblIdxs, r.enzymeGlobalIndexs(r.enzymeIndexs_recombinationStrandExchange));
            cpxGblIdxs = setdiff(cpxGblIdxs, r.enzymeGlobalIndexs(r.enzymeIndexs_DisA));

            %% indices of allowed damage
            dmgSPIdxs = [];
            dmgBaseIdxs = sort([
                d.reactionDNAProduct(ismember(d.reactionDamageTypes, {'damagedBases'}))
                r.substrateGlobalIndexs(r.substrateIndexs_m6AD)
                ]);
            dmgCLIdxs = sort(...
                d.reactionDNAProduct(ismember(d.reactionDamageTypes, {'intrastrandCrossLinks'})));

            %% simulate
            iterMax = 250;

            d.reactionBounds = 1e4 * d.reactionBounds;
            r.enzymeBounds = 1e4 * r.enzymeBounds;

            sim.state('Metabolite').counts(:, sim.compartment.cytosolIndexs) = 1e6;

            nDamages = zeros(iterMax, 1);
            for i = 1:iterMax
                this.assertSecondChromosomeIgnored(c);
                this.assert1ChromsomePolymerized(c);
                this.assertCorrectProteinsBound(c, monGblIdxs, cpxGblIdxs);
                this.assertCorrectDamages(c, dmgSPIdxs, dmgBaseIdxs, dmgCLIdxs);

                sim.evolveState();

                nDamages(i) = nnz(c.damagedSites);
            end

            this.assertDamageDoesntAccumulate(c, r);

            d.reactionBounds(:, 2) = 0;

            for i = 1:20
                sim.evolveState();
            end

            assertEqual(0, nnz(c.damagedSites));
        end
    end

    %helper methods
    methods
        %check that second chromosome is never modified
        function assertSecondChromosomeIgnored(~, c)
            posStrnds = find(c.polymerizedRegions);     assertFalse(any(posStrnds(:, 2) > 2));
            posStrnds = find(c.linkingNumbers);         assertFalse(any(posStrnds(:, 2) > 2));

            posStrnds = find(c.gapSites);               assertFalse(any(posStrnds(:, 2) > 2));
            posStrnds = find(c.abasicSites);            assertFalse(any(posStrnds(:, 2) > 2));
            posStrnds = find(c.damagedSugarPhosphates); assertFalse(any(posStrnds(:, 2) > 2));
            posStrnds = find(c.damagedBases);           assertFalse(any(posStrnds(:, 2) > 2));
            posStrnds = find(c.strandBreaks);           assertFalse(any(posStrnds(:, 2) > 2));
            posStrnds = find(c.intrastrandCrossLinks);  assertFalse(any(posStrnds(:, 2) > 2));
            posStrnds = find(c.hollidayJunctions);      assertFalse(any(posStrnds(:, 2) > 2));

            posStrnds = find(c.monomerBoundSites);      assertFalse(any(posStrnds(:, 2) > 2));
            posStrnds = find(c.complexBoundSites);      assertFalse(any(posStrnds(:, 2) > 2));

            assertFalse(c.segregated);
        end

        %check that only first chromosome is polymerized
        function assert1ChromsomePolymerized(~, c)
            import edu.stanford.covert.util.CircularSparseMat;

            lk0 = c.sequenceLen / c.relaxedBasesPerTurn * (1 + c.equilibriumSuperhelicalDensity);
            assertEqual(CircularSparseMat([1 1; 1 2], [c.sequenceLen; c.sequenceLen], [c.sequenceLen c.nCompartments], 1), c.polymerizedRegions);
            assertEqual(CircularSparseMat([1 1; 1 2], [lk0; lk0], [c.sequenceLen c.nCompartments], 1), c.linkingNumbers);
        end

        %check that only allowed proteins ever bind chromosome
        function assertCorrectProteinsBound(~, c, monGblIdxs, cpxGblIdxs)
            [~, monVals] = find(c.monomerBoundSites);
            [~, cpxVals] = find(c.complexBoundSites);

            assertFalse(any(ismembc(monVals, monGblIdxs)));
            assertFalse(any(ismembc(cpxVals, cpxGblIdxs)));
        end

        %check damage is of allowed types
        function assertCorrectDamages(~, c, dmgSPIdxs, dmgBaseIdxs, dmgCLIdxs)
            [~, vals] = find(c.gapSites);               assertTrue(all(ismembc(vals, [false; true])));
            [~, vals] = find(c.abasicSites);            assertTrue(all(ismembc(vals, [false; true])));
            [~, vals] = find(c.damagedSugarPhosphates); assertTrue(all(ismembc(vals, dmgSPIdxs)));
            [~, vals] = find(c.damagedBases);           assertTrue(all(ismembc(vals, dmgBaseIdxs)));
            [~, vals] = find(c.strandBreaks);           assertTrue(all(ismembc(vals, [false; true])));
            [~, vals] = find(c.intrastrandCrossLinks);  assertTrue(all(ismembc(vals, dmgCLIdxs)));
            [~, vals] = find(c.hollidayJunctions);      assertTrue(all(ismembc(vals, [false; true])));
        end

        %check damage doesn't accumulate -- damage that occurs is repaired quickly
        function assertDamageDoesntAccumulate(~, c, r)
            assertIn(nnz(c.gapSites), [0 10 + r.NER_UvrABC_IncisionMargin3 + r.NER_UvrABC_IncisionMargin5]);
            assertIn(nnz(c.abasicSites), [0 10 + r.NER_UvrABC_IncisionMargin3 + r.NER_UvrABC_IncisionMargin5]);
            assertIn(nnz(c.damagedSugarPhosphates), [0 10]);
            assertIn(nnz(c.damagedBases), [0 10 + 2 * size(r.RM_MunI_RecognitionSites, 1)]);
            assertIn(nnz(c.strandBreaks), [0 10]);
            assertIn(nnz(c.intrastrandCrossLinks), [0 20]);
            assertIn(nnz(c.hollidayJunctions), [0 20]);
        end

        %check that damage rate follow expectations
        function assertDamageRateIsExpected(~, c, d, expRates, iterMax, relTol, absTol)
            [types, ~, idxs] = unique(d.reactionDamageTypes);
            reactionDNAProduct = d.reactionDNAProduct;
            reactionDNAProduct(ismember(d.reactionDamageTypes, ...
                {'strandBreaks','abasicSites','gapSites','hollidayJunctions'})) = 1;
            dmgTypesProds = unique([idxs reactionDNAProduct], 'rows');
            calcRates = zeros(size(dmgTypesProds, 1), 1);
            obsRates = zeros(size(dmgTypesProds, 1), 1);
            for i = 1:size(dmgTypesProds, 1)
                rxnTfs = ...
                    ismember(d.reactionDamageTypes, types(dmgTypesProds(i,1))) & ...
                    reactionDNAProduct == dmgTypesProds(i, 2);
                calcRates(i) = sum(expRates(rxnTfs));
                obsRates(i) = nnz(c.(types{dmgTypesProds(i, 1)}) == dmgTypesProds(i, 2));
            end

            reactionVulnerableMotifs = d.reactionVulnerableMotifs;
            reactionVulnerableMotifs(cellfun(@ischar, reactionVulnerableMotifs)) = {0};
            reactionVulnerableMotifs(cellfun(@islogical, reactionVulnerableMotifs)) = {1};
            reactionVulnerableMotifs = cell2mat(reactionVulnerableMotifs);
            rates = d.reactionBounds(:, 2);
            rates(d.reactionRadiation~=0) = ...
                rates(d.reactionRadiation~=0) .* ...
                d.substrates(d.reactionRadiation(d.reactionRadiation~=0));
            for i = 1:size(dmgTypesProds, 1)
                j = find(...
                    rates > 0 & ...
                    ismember(reactionVulnerableMotifs, dmgTypesProds(i, 2)) & ...
                    ismember(d.reactionVulnerableMotifTypes, types(dmgTypesProds(i, 1))));

                [~, k] = ismember([idxs(j) reactionDNAProduct(j)], dmgTypesProds, 'rows');

                if isempty(j)
                    continue;
                end

                calcRates(k) = calcRates(k) + min(1, iterMax * rates(j)) * calcRates(i);
                if all(dmgTypesProds(k,1) == dmgTypesProds(i,1))
                    calcRates(i) = max(0, 1 - iterMax * sum(rates(j))) * calcRates(i);
                end
            end

            %metIds = d.metabolite.wholeCellModelIDs(dmgTypesProds(:,2));
            %tfs =ismember(types(dmgTypesProds(:,1)), {'strandBreaks','abasicSites','gapSites','hollidayJunctions'});
            %metIds(tfs) = cell(sum(tfs), 1);
            %[types(dmgTypesProds(:,1)) metIds num2cell(calcRates) num2cell(obsRates)]

            assertElementsAlmostEqual(calcRates', obsRates', 'relative', relTol, absTol);
        end
    end
end


function varargout = gammaincinv_a(varargin)

switch class(varargin{1})

    case 'double'
        varargout{1} = gammaincinv(varargin{2},varargin{1});

    case 'char'

        operator = CreateBasicOperator('increasing','positive','callback');
        operator.range = [0 inf];
        operator.domain = [0 inf];

        varargout{1} = [];
        varargout{2} = operator;
        varargout{3} = varargin{3};

    otherwise
        error(['SDPVAR/' upper(mfilename) ' called with weird argument']);
end


% std_saveindvar - save independent variables of a STUDY
%
% Usage:
%   std_saveindvar(STUDY, indvars, filename);
%   std_saveindvar(STUDY, indvars, filename, session, group);
%
% Input:
%   STUDY - EEGLAB STUDY structure
%   indvars - [cell] cell array of independent variables
%   filename - [string] file name to save variables in
%   session  - [array of value] list of session, first save all the
%              variable for the first session in the list etc...
%   group    - [array of string] list of group, first save all the
%              variable for the first group in the list etc...
%
% Authors: A. Delorme, CERCO/CNRS and SCCN/UCSD, 2022-

% Copyright (C) Arnaud Delorme, SCCN, INC, UCSD, 2010, arno@ucsd.edu
%
% This file is part of EEGLAB, see http://www.eeglab.org
% for the documentation and details.
%
% Redistribution and use in source and binary forms, with or without
% modification, are permitted provided that the following conditions are met:
%
% 1. Redistributions of source code must retain the above copyright notice,
% this list of conditions and the following disclaimer.
%
% 2. Redistributions in binary form must reproduce the above copyright notice,
% this list of conditions and the following disclaimer in the documentation
% and/or other materials provided with the distribution.
%
% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
% ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
% SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
% CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
% ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
% THE POSSIBILITY OF SUCH DAMAGE.

function std_saveindvar(STUDY, indvars, filename, sessions, groups, interaction_sess, interaction_group)

if nargin < 1
    help std_saveindvar;
    return;
end

if nargin < 4
    sessions = [];
    interaction_sess = false;
end
if nargin < 5
    groups = {};
    interaction_group = false;
end

if length(sessions) > 1 && length(groups) > 1
    error('Cannot process groups and session - code untested');
end

fid = fopen(filename, 'w');
if fid == -1
    error('Cannot open file for writing');
end
for iGroup = 1:max(1, length(groups))
    for iSess = 1:max(1, length(sessions))
        for iSubj = 1:length(STUDY.subject)
            for iVar  = 1:length(indvars)
                indS = strmatch(STUDY.subject{iSubj}, {STUDY.datasetinfo.subject}, 'exact');

                % sessions
                if ~isempty(sessions)
                    indSess = find([STUDY.datasetinfo.session] == sessions(iSess));
                    indS = intersect(indS, indSess);
                end

                % groups
                if ~isempty(groups)
                    indGp = strmatch(groups{iGroup}, {STUDY.datasetinfo.group}, 'exact');
                    indS = intersect(indS, indGp);
                end

                if ~isempty(indS)
                    allVals = [ STUDY.datasetinfo(indS).(indvars{iVar})];
                    if isempty(allVals), allVals = NaN; end
                    if length(unique(allVals)) > 1
                        fprintf('Warning: more than 1 value for "%s" for subject %s, taking the average\n', indvars{iVar}, STUDY.subject{iSubj});
                        allVals = mean(allVals);
                    end
                else
                    allVals = NaN;
                    fprintf('Warning: no value for "%s" subject %s (session:%d,group:%d), using NaN\n', indvars{iVar}, STUDY.subject{iSubj}, iSess, iGroup);
                end

                % interactions
                interVal11 = compute_interaction(allVals, sessions, iSess , interaction_sess);
                interVal12 = compute_interaction(allVals, groups  , iGroup, interaction_group);
                vals = [  allVals(1) interVal11 interVal12 ];
                fprintf(fid, '%s', sprintf('%f\t',vals) );
            end
            fprintf(fid, '\n');
        end
    end
end
fclose(fid);

function interVal = compute_interaction(allVals, var, iVar, interaction_var)

interVal = [];
if interaction_var
    if length(var) > 2
        error('Cannot compute interaction with more than 2 sessions');
    end
    if iVar == 1
        interVal = -allVals;
    else
        interVal = allVals;
    end
end


function regions = read_scg_regions(filePath, varargin)
opts.maxNumRegions = +inf ;
opts = vl_argparse(opts, varargin) ;

tmp = load(filePath) ;
regions.basis  = tmp.candidates_scg.superpixels ;
regions.labels = tmp.candidates_scg.labels(:)' ;
regions.scores = tmp.candidates_scg.scores(:)' ;

sareas = accumarray(regions.basis(:), 1) ;
for i=1:numel(regions.labels)
  regions.areas(i) = sum(sareas(regions.labels{i})) ;
end

% take only the top regions
[~,perm] = sort(regions.scores, 'descend') ;
perm = vl_colsubset(perm, opts.maxNumRegions, 'beginning') ;
regions.labels = regions.labels(perm) ;
regions.scores = regions.scores(perm) ;
regions.areas = regions.areas(perm) ;