%qrscorrect() -  Correct for false positive and negative QRS peaks
%   and align peaks for maximum correlation.
%
% Usage:
%   >>Peaks=qrscorrect(Peaks,Ecg,fs)
%
% Inputs:
%   Peaks: Indeces of located QRS peaks
%   Ecg: vector of ecg data
%   sampling frequency.
%
% Ouput:
%   Peaks: Corrected peaks
%
% Note: progress bar designed to work as part of fmrib_qrsdetect.
%
% Author: Rami Niazy, FMRIB Centre, University of Oxford.
%
%
%
% Copyright (c) 2004 Rami Niazy, FMRIB Centre, University of Oxford.
%

% Copyright (C) 2004 Rami K. Niazy, FMRIB Centre, University of Oxford
%                    rami@fmrib.ox.ac.uk
%
% This program is free software; you can redistribute it and/or modify
% it under the terms of the GNU General Public License as published by
% the Free Software Foundation; either version 2 of the License, or
% (at your option) any later version.
%
% This program is distributed in the hope that it will be useful,
% but WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
% GNU General Public License for more details.
%
% You should have received a copy of the GNU General Public License
% along with this program; if not, write to the Free Software
% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
%
%

%   Dec 23, 2004
%   use binary prcorr2
%   instead of corrcoef

%   Dec 17, 2004
%   use corrcoef instead of
%   corr2

%   Dec 15, 2004
%   Median of all peaks used
%   for correction instead of
%   section median.

%   Nov 4, 2004
%   Changed order of correction
%   Added additional alignment

%   Nov 3, 2004
%   Added check and
%   adjustment of edge peaks clearance

%   Oct 26, 2004
%   Fixed bug for non-int fs case

function Peaks=qrscorrect(Peaks,Ecg,fs)

% init
%------
P=zeros(1,length(Ecg));
P(Peaks)=1;
dP_std=std(diff(Peaks));
dP_med=median(diff(Peaks));
pad=round(5*fs);
secL=round(10*fs);
sections=floor(length(P)/secL)+1;

if length(P((sections-1)*secL+1:end))<pad
    sections=sections-1;
end

barth=2;
barth_step=barth;
if isempty(javachk('desktop'))
    clearcom='clc';
else
    clearcom='home';
end

% Correct for FP
%---------------
for s=1:sections

    %wait bar
    if s==1
        barth=5;
        barth_step=barth;
        Flag25=0;
        Flag50=0;
        Flag75=0;
        fprintf('\nStage 2 of 5: Correcting for False Positive detection.\n');

    end
    %-----------------calc------------------------------------------

    f_flag=0;
    if s==1
        sec=P(1:secL+pad);
    elseif s==sections
        sec=P((s-1)*secL+1-pad:end);
    else
        sec=P((s-1)*secL+1-pad:s*secL+pad);
    end

    sec_P=find(sec==1);
    sec_dP=diff(sec_P);
%     dP_med=median(sec_dP);

    false_p=1;
    while ~isempty(false_p)
        false_p=find(sec_dP<(dP_med-3*dP_std));%-3*dP_std
        if ~isempty(false_p)
            f_flag=1;
            sec(sec_P(false_p(1)+1))=0;
            sec_P=find(sec==1);
            sec_dP=diff(sec_P);
            false_p=find(sec_dP<(dP_med-3*dP_std));
        end
    end

    if f_flag==1
        if s==1
            P(1:secL+pad)=sec;
        elseif s==sections
            P((s-1)*secL+1-pad:end)=sec;
        else
            P((s-1)*secL+1-pad:s*secL+pad)=sec;
        end
    end

    %---------------------------------------------------------------
    %update wait bar
    percentdone=floor(s*100/sections);
    if floor(percentdone)>=barth
        if percentdone>=25 & Flag25==0
            fprintf('25%% ')
            Flag25=1;
        elseif percentdone>=50 & Flag50==0
            fprintf('50%% ')
            Flag50=1;
        elseif percentdone>=75 & Flag75==0
            fprintf('75%% ')
            Flag75=1;
        elseif percentdone==100
            fprintf('100%%\n')
        else
            fprintf('.')
        end

        while barth<=percentdone
            barth=barth+barth_step;
        end
        if barth>100
            barth=100;
        end
    end
end

%Check Edge Peaks
%----------------
Peaks=find(P==1);
pc1=2;
pc2=1;

while (pc1>1 | pc2>0)
	dP=diff(Peaks);
	hwinl=round(mean(dP)/2);
	searchw=round(0.33*mean(dP));

	pc1=1;
	if length(Peaks)>5
        for p=1:5
            try
                tmp=Ecg(Peaks(p)-hwinl-searchw:Peaks(p)+hwinl);
                break;
            catch
                pc1=pc1+1;
            end
        end
	end

	if pc1>1
        Peaks(1:pc1-1)=[];
	end

	pc2=0;
	if length(Peaks)>5
        for p=length(Peaks):-1:length(Peaks-4)
            try
                tmp=Ecg(Peaks(p)-hwinl:Peaks(p)+hwinl+searchw);
                break;
            catch
                pc2=pc2+1;
            end
        end
	end

	if pc2>0
        Peaks(end-pc2:end)=[];
	end
end

%Finding corrected peaks
%-----------------------
searchwL=length(searchw);
peaksL=length(Peaks);
qrs=zeros(hwinl*2+1,peaksL);
qrs2=zeros(hwinl*2+1,peaksL);
barth=2;

% Find average Heart Beat for use in alignment
%---------------------------------------------
for p=1:peaksL
    qrs(:,p)=Ecg(Peaks(p)-hwinl:Peaks(p)+hwinl);
end

m_qrs=mean(qrs,2);
% figure;plot(qrs)

% Align Peaks (1)
%----------------
for p=1:peaksL

    %wait bar
    if p==1
        barth=5;
        barth_step=barth;
        Flag25=0;
        Flag50=0;
        Flag75=0;
        fprintf('\nStage 3 of 5: Aligning QRS Peaks  (1)\n');
    end

    %-----------------calc------------------------------------------

    ppn=1;
    for B=Peaks(p)-searchw:Peaks(p)+searchw
        C(ppn)=prcorr2(Ecg(B-hwinl:B+hwinl),m_qrs);;
        ppn=ppn+1;
	end
	[CV,CP]=max(C);
	Beta=CP-(searchw+1);
	Peaks(p)=Peaks(p)+Beta;

    %---------------------------------------------------------------
    %update wait bar
    percentdone=floor(p*100/(peaksL));
    if floor(percentdone)>=barth
        if percentdone>=25 & Flag25==0
            fprintf('25%% ')
            Flag25=1;
        elseif percentdone>=50 & Flag50==0
            fprintf('50%% ')
            Flag50=1;
        elseif percentdone>=75 & Flag75==0
            fprintf('75%% ')
            Flag75=1;
        elseif percentdone==100
            fprintf('100%%\n')
        else
            fprintf('.')
        end

        while barth<=percentdone
            barth=barth+barth_step;
        end
        if barth>100
            barth=100;
        end
    end
end
barth=2;
P=zeros(1,length(Ecg));
P(Peaks)=1;

% Correct for FN
%---------------
for s=1:sections

    %wait bar
    if s==1
        barth=5;
        barth_step=barth;
        Flag25=0;
        Flag50=0;
        Flag75=0;
        fprintf('\nStage 4 of 5: Correcting for False Negative detection.\n');
    end

    %-----------------calc------------------------------------------

    f_flag=0;
    if s==1
        sec=P(1:secL+pad);
    elseif s==sections
        sec=P((s-1)*secL+1-pad:end);
    else
        sec=P((s-1)*secL+1-pad:s*secL+pad);
    end

    sec_P=find(sec==1);
    sec_dP=diff(sec_P);
    dP_med=median(sec_dP);

    false_n=1;
    while ~isempty(false_n)
        false_n=find(sec_dP>(1.5*dP_med));
        if ~isempty(false_n)
            f_flag=1;
            sec(sec_P(false_n(1))+round(dP_med))=1;%sec_dP(false_n(1)-1)
            sec_P=find(sec==1);
            sec_dP=diff(sec_P);
            false_n=find(sec_dP>(1.5*dP_med));
        end
    end

    if f_flag==1
        if s==1
            P(1:secL+pad)=sec;
        elseif s==sections
            P((s-1)*secL+1-pad:end)=sec;
        else
            P((s-1)*secL+1-pad:s*secL+pad)=sec;
        end
    end

    %---------------------------------------------------------------
    %update wait bar
    percentdone=floor(s*100/sections);
    if floor(percentdone)>=barth
        if percentdone>=25 & Flag25==0
            fprintf('25%% ')
            Flag25=1;
        elseif percentdone>=50 & Flag50==0
            fprintf('50%% ')
            Flag50=1;
        elseif percentdone>=75 & Flag75==0
            fprintf('75%% ')
            Flag75=1;
        elseif percentdone==100
            fprintf('100%%\n')
        else
            fprintf('.')
        end

        while barth<=percentdone
            barth=barth+barth_step;
        end
        if barth>100
            barth=100;
        end
    end
end

barth=2;
Peaks=find(P==1);
peaksL=length(Peaks);
barth=2;
% Align Peaks (2)
%----------------
for p=2:peaksL-1

    %wait bar
    if p==2
        barth=5;
        barth_step=barth;
        Flag25=0;
        Flag50=0;
        Flag75=0;
         fprintf('\nStage 5 of 5: Aligning QRS Peaks  (2)\n');
    end

    %-----------------calc------------------------------------------

    ppn=1;
    for B=Peaks(p)-searchw:Peaks(p)+searchw
        C(ppn)=prcorr2(Ecg(B-hwinl:B+hwinl),m_qrs);
        ppn=ppn+1;
	end
	[CV,CP]=max(C);
	Beta=CP-(searchw+1);
	Peaks(p)=Peaks(p)+Beta;

    %---------------------------------------------------------------
    %update wait bar
    percentdone=floor((p-1)*100/(peaksL-2));
    if floor(percentdone)>=barth
        if percentdone>=25 & Flag25==0
            fprintf('25%% ')
            Flag25=1;
        elseif percentdone>=50 & Flag50==0
            fprintf('50%% ')
            Flag50=1;
        elseif percentdone>=75 & Flag75==0
            fprintf('75%% ')
            Flag75=1;
        elseif percentdone==100
            fprintf('100%%\n')
        else
            fprintf('.')
        end

        while barth<=percentdone
            barth=barth+barth_step;
        end
        if barth>100
            barth=100;
        end
    end
end

% for p=2:peaksL-1
%     qrs2(:,p-1)=Ecg(Peaks(p)-hwinl:Peaks(p)+hwinl);
% end
% figure;plot(qrs2);
return;


function hcell_node_display ( node_xy_file_name )

%*****************************************************************************80
%
%% HCELL_NODE_DISPLAY displays the nodes in the H-Cell.
%
%  Usage:
%
%    hcell_node_display ( node_xy_file_name )
%
%    A typical invocation might be
%
%      hcell_node_display ( 'xy3.txt' )
%
%    or
%
%      hcell_node_display ( 'xy6.txt' )
%
%    But if you simply say
%
%      hcell_node_display
%
%    the program will give you a chance to enter the file names
%    interactively.
%
%  Licensing:
%
%    This code is distributed under the GNU LGPL license.
%
%  Modified:
%
%    22 April 2004
%
%  Author:
%
%    John Burkardt
%
%  Parameters:
%
%    Input, filename NODE_XY_FILE_NAME, the name of a file
%    containing the XY coordinates of nodes.
%
  timestamp ( );
  fprintf ( 1, '\n' );
  fprintf ( 1, 'HCELL_NODE_DISPLAY:\n' );
  fprintf ( 1, '  MATLAB version\n' );
  fprintf ( 1, '\n' );
  fprintf ( 1, '  Display the nodes in the HCELL problem.\n' );
%
%  Do we have the XY file?
%
  if ( nargin < 1 )

    fprintf ( 1, '\n' );
    fprintf ( 1, 'HCELL_NODE_DISPLAY:\n' );
    node_xy_file_name = input ( 'Enter the name of the XY coordinate file:' );

  end

  [ m1, n1 ] = r8mat_header_read ( node_xy_file_name );

  node_xy = r8mat_data_read ( node_xy_file_name, m1, n1 );
%
%  Plot the nodes.
%
  plot ( node_xy(1,:), node_xy(2,:), 'bo' )
%
%  Add the boundary of the region to the plot.
%
  hcell_boundary_add ( 'r' );
%
%  Add the invisible bounding box.
%
  hcell_box_add ( 'w' )

  axis equal

  title ( 'Node coordinates' )

  text ( 45.0, 20.0, node_xy_file_name )

  fprintf ( 1, '\n' );
  fprintf ( 1, 'HCELL_NODE_DISPLAY:\n' );
  fprintf ( 1, '  Normal end of execution.\n' );
  fprintf ( 1, '\n' );
  timestamp ( );

  return
end
function column_num = file_column_count ( input_file_name )

%*****************************************************************************80
%
%% FILE_COLUMN_COUNT counts the columns in the first line of a file.
%
%  Discussion:
%
%    The file is assumed to be a simple text file.
%
%    Most lines of the file are presumed to consist of COLUMN_NUM words,
%    separated by spaces.  There may also be some blank lines, and some
%    comment lines, which have a "#" in column 1.
%
%    The routine tries to find the first non-comment non-blank line and
%    counts the number of words in that line.
%
%    If all lines are blanks or comments, it goes back and tries to analyze
%    a comment line.
%
%  Licensing:
%
%    This code is distributed under the GNU LGPL license.
%
%  Modified:
%
%    21 February 2004
%
%  Author:
%
%    John Burkardt
%
%  Parameters:
%
%    Input, string INPUT_FILE_NAME, the name of the file.
%
%    Output, integer COLUMN_NUM, the number of columns in the file.
%
  FALSE = 0;
  TRUE = 1;
%
%  Open the file.
%
  input_unit = fopen ( input_file_name );

  if ( input_unit < 0 )
    fprintf ( 1, '\n' );
    fprintf ( 1, 'FILE_COLUMN_COUNT - Error!\n' );
    fprintf ( 1, '  Could not open the file "%s".\n', input_file_name );
    error ( 'FILE_COLUMN_COUNT - Error!' );
    column_num = -1;
    return;
  end
%
%  Read one line, but skip blank lines and comment lines.
%  Use FGETL so we drop the newline character!
%
  got_one = FALSE;

  while ( 1 )

    line = fgetl ( input_unit );

    if ( line == -1 )
      break;
    end

    if ( s_len_trim ( line ) == 0 )

    elseif ( line(1) == '#' )

    else
      got_one = TRUE;
      break;
    end

  end

  fclose ( input_unit );

  if ( got_one == FALSE )
    fprintf ( 1, '\n' );
    fprintf ( 1, 'FILE_COLUMN_COUNT - Warning!\n' );
    fprintf ( 1, '  The file does not seem to contain any data.\n' );
    column_num = -1;
    return;
  end

  column_num = s_word_count ( line );

  return
end
function row_num = file_row_count ( input_file_name )

%*****************************************************************************80
%
%% FILE_ROW_COUNT counts the number of row records in a file.
%
%  Discussion:
%
%    Each input line is a "RECORD".
%
%    The records are divided into three groups:
%
%    * BLANK LINES (nothing but blanks)
%    * COMMENT LINES (begin with a '#')
%    * DATA RECORDS (anything else)
%
%    The value returned by the function is the number of data records.
%
%    By the way, if the MATLAB routine FGETS is used, instead of
%    FGETL, then the variable LINE will include line termination
%    characters, which means that a blank line would not actually
%    have zero characters.
%
%  Licensing:
%
%    This code is distributed under the GNU LGPL license.
%
%  Modified:
%
%    31 December 2006
%
%  Author:
%
%    John Burkardt
%
%  Parameters:
%
%    Input, string INPUT_FILE_NAME, the name of the input file.
%
%    Output, integer ROW_NUM, the number of rows found.
%
  input_unit = fopen ( input_file_name );

  if ( input_unit < 0 )
    fprintf ( 1, '\n' );
    fprintf ( 1, 'FILE_ROW_COUNT - Error!\n' );
    fprintf ( 1, '  Could not open the file "%s".\n', input_file_name );
    error ( 'FILE_ROW_COUNT - Error!' );
    row_num = -1;
    return;
  end

  blank_num = 0;
  comment_num = 0;
  row_num = 0;

  record_num = 0;

  while ( 1 )

    line = fgetl ( input_unit );

    if ( line == -1 )
      break;
    end

    record_num = record_num + 1;
    record_length = s_len_trim ( line );

    if ( record_length <= 0 )
      blank_num = blank_num + 1;
    elseif ( line(1) == '#' )
      comment_num = comment_num + 1;
    else
      row_num = row_num + 1;
    end

  end

  fclose ( input_unit );

  return
end
function hcell_box_add ( box_color )

%*****************************************************************************80
%
%% HCELL_BOX_ADD adds a box around the HCELL region.
%
%  Discussion:
%
%    This routine can only ADD graphics to an already existing plot.
%    So call PLOT first and then this routine.
%
%    The box is normally drawn with the color white ('w').
%    In this case, it doesn't actually appear on the screen.
%    However, it does force MATLAB to make the picture bigger,
%    giving us space to write some titles.
%
%  Usage:
%
%    hcell_box_add ( box_color )
%
%    A typical invocation might be
%
%      hcell_box_add ( 'w' )
%
%  Licensing:
%
%    This code is distributed under the GNU LGPL license.
%
%  Modified:
%
%    23 April 2004
%
%  Author:
%
%    John Burkardt
%
%  Parameters:
%
%    Input, character BOX_COLOR, the color to be used to draw the box.
%
  fprintf ( 1, '\n' );
  fprintf ( 1, 'HCELL_BOX_ADD:\n' );
  fprintf ( 1, '  Add a bounding box to a plot of the HCELL problem.\n' );
%
%  Do we have a color?
%
  if ( nargin < 1 )
    box_color = 'w'
  end
%
%  Set the coordinates of the invisible bounding box that
%  allows us to display the file name within the plot area.
%
  box_x = [ -5.00,  110.00, 110.00, -5.00, -5.00 ];
  box_y = [ -5.00,   -5.00, 21.00,  21.00, -5.00 ];

  line ( box_x, box_y, 'color', box_color )

  fprintf ( 1, '\n' );
  fprintf ( 1, 'HCELL_BOX_ADD:\n' );
  fprintf ( 1, '  Normal end of execution.\n' );

  return
end
function table = r8mat_data_read ( input_filename, m, n )

%*****************************************************************************80
%
%% R8MAT_DATA_READ reads data from an R8MAT file.
%
%  Licensing:
%
%    This code is distributed under the GNU LGPL license.
%
%  Modified:
%
%    27 January 2006
%
%  Author:
%
%    John Burkardt
%
%  Parameters:
%
%    Input, string INPUT_FILENAME, the name of the input file.
%
%    Input, integer M, N, the number of rows and columns of data.
%
%    Output, real TABLE(M,N), the point coordinates.
%
  table = [];
%
%  Build up the format string for reading M real numbers.
%
  string = ' ';

  for i = 0 : m
    string = strcat ( string, ' %f' );
  end

  input_unit = fopen ( input_filename );

  if ( input_unit < 0 )
    fprintf ( 1, '\n' );
    fprintf ( 1, 'R8MAT_DATA_READ - Error!\n' );
    fprintf ( 1, '  Could not open the file.\n' );
    error ( 'R8MAT_DATA_READ - Error!' );
    return;
  end

  i = 0;

  while ( i < n )

    line = fgets ( input_unit );

    if ( line == -1 )
      break;
    end

    if ( line(1) == '#' )

    elseif ( s_len_trim ( line ) == 0 )

    else

      [ x, count ] = sscanf ( line, string );

      if ( count == m )
        i = i + 1;
        table(1:m,i) = x(1:m);
      end

    end

  end

  fclose ( input_unit );

  return
end
function [ m, n ] = r8mat_header_read ( input_filename )

%*****************************************************************************80
%
%% R8MAT_HEADER_READ reads the header from an R8MAT file.
%
%  Licensing:
%
%    This code is distributed under the GNU LGPL license.
%
%  Modified:
%
%    22 October 2004
%
%  Author:
%
%    John Burkardt
%
%  Parameters:
%
%    Input, string INPUT_FILENAME, the name of the input file.
%
%    Output, integer M, the spatial dimension.
%
%    Output, integer N, the number of points.
%
  m = file_column_count ( input_filename );

  if ( m <= 0 )
    fprintf ( 1, '\n' );
    fprintf ( 1, 'R8MAT_HEADER_READ - Fatal error!\n' );
    fprintf ( 1, '  There was some kind of I/O problem while trying\n' );
    fprintf ( 1, '  to count the number of data columns in\n' );
    fprintf ( 1, '  the file %s.\n', input_filename );
  end

  n = file_row_count ( input_filename );

  if ( n <= 0 )
    fprintf ( 1, '\n' );
    fprintf ( 1, 'R8MAT_HEADER_READ - Fatal error!\n' );
    fprintf ( 1, '  There was some kind of I/O problem while trying\n' );
    fprintf ( 1, '  to count the number of data rows in\n' );
    fprintf ( 1, '  the file %s\n', input_filename );
  end

  return
end
function timestamp ( )

%*****************************************************************************80
%
%% TIMESTAMP prints the current YMDHMS date as a timestamp.
%
%  Licensing:
%
%    This code is distributed under the GNU LGPL license.
%
%  Modified:
%
%    14 February 2003
%
%  Author:
%
%    John Burkardt
%
  t = now;
  c = datevec ( t );
  s = datestr ( c, 0 );
  fprintf ( 1, '%s\n', s );

  return
end


function varargout = subsref(F, index)
%SUBSREF   BALLFUNV subsref.
%
% ( )
%   F(X,Y,Z) returns the values of the BALLFUNV F evaluated on the array
%   (X,Y,Z) in cartesian coordinates.
%
%   F(R, L, TH, 'spherical') returns the values of the BALLFUNV F evaluated
%   on the array (R,TH,LAM) in spherical coordinates.
%
%   F(K) returns the first component of F if K = 1, the second if K = 2, and
%   the third if K = 3.
%
%   F(R, :, :, 'spherical') returns a SPHEREFUNV representing the BALLFUNV
%   F along a radial shell.
%
%   F(:, :, :) returns F.
%
%  .
%   F.PROP returns the property PROP of F as defined by GET(F,'PROP').
%
% { }
%    Throws an error.

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

% check for empty CHEBFUN2V object.
if ( isempty(F) )
    varargout = {[]};
    return
end

% Recursive going through the ref structure:
idx = index(1).subs;
switch index(1).type

    case '.'
        % Call GET() for .PROP access.
        out = get(F, idx);
        if ( numel(index) > 1 )
            % Recurse on SUBSREF():
            index(1) = [];
            out = subsref(out, index);
        end
        varargout = {out};

    case '()'
        if ( isa(idx{1}, 'double') && numel(idx)==1 )
            Fc = F.comp;
                if all( idx{1} == 1  )
                    varargout = {Fc{1}};
                elseif ( all( idx{1} == 2 ) )
                    varargout = {Fc{2}};
                elseif ( ( all(idx{1} == 3) ) )
                    varargout = {Fc{3}};
                else
                    error('CHEBFUN:BALLFUNV:subsref:index', ...
                        'BALLFUNV only contains three components');
                end
        else
            % F(X,Y,Z), F(X,Y,Z,str), etc.
           Fc = F.comp;
           v1 = subsref(Fc{1}, index);
           v2 = subsref(Fc{2}, index);
           v3 = subsref(Fc{3}, index);
           varargout = {[v1 ; v2 ; v3]};
        end

    otherwise
        error('CHEBFUN:BALLFUNV:subsref:unexpectedType', ...
            ['??? Unexpected index.type of ' index(1).type]);

end

% Recurse down:
if ( numel(index) > 1 )
    index(1) = [];
    varargout = { subsref( varargout{ : }, index ) };
end

end


function mpc = toggle_dcline(mpc, on_off)
%TOGGLE_DCLINE Enable, disable or check status of DC line modeling.
%   MPC = TOGGLE_DCLINE(MPC, 'on')
%   MPC = TOGGLE_DCLINE(MPC, 'off')
%   T_F = TOGGLE_DCLINE(MPC, 'status')
%
%   Enables, disables or checks the status of a set of OPF userfcn
%   callbacks to implement DC lines as a pair of linked generators.
%   While it uses the OPF extension mechanism, this implementation
%   works for simple power flow as well as OPF problems.
%
%   These callbacks expect to find a 'dcline' field in the input MPC,
%   where MPC.dcline is an ndc x 17 matrix with columns as defined
%   in IDX_DCLINE, where ndc is the number of DC lines.
%
%   The 'int2ext' callback also packages up flow results and stores them
%   in appropriate columns of MPC.dcline.
%
%   NOTE: Because of the way this extension modifies the number of
%   rows in the gen and gencost matrices, caution must be taken
%   when using it with other extensions that deal with generators.
%
%   Examples:
%       mpc = loadcase('t_case9_dcline');
%       mpc = toggle_dcline(mpc, 'on');
%       results1 = runpf(mpc);
%       results2 = runopf(mpc);
%
%   See also IDX_DCLINE, ADD_USERFCN, REMOVE_USERFCN, RUN_USERFCN.

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

if strcmp(upper(on_off), 'ON')
    %% define named indices into data matrices
    c = idx_dcline;

    %% check for proper input data
    if ~isfield(mpc, 'dcline') || size(mpc.dcline, 2) < c.LOSS1
        error('toggle_dcline: case must contain a ''dcline'' field, an ndc x %d matrix.', c.LOSS1);
    end
    if isfield(mpc, 'dclinecost') && size(mpc.dcline, 1) ~= size(mpc.dclinecost, 1)
        error('toggle_dcline: number of rows in ''dcline'' field (%d) and ''dclinecost'' field (%d) do not match.', ...
            size(mpc.dcline, 1), size(mpc.dclinecost, 1));
    end
    l0 = mpc.dcline(:, c.LOSS0);
    l1 = mpc.dcline(:, c.LOSS1);
    k = find( (l0 + l1 .* mpc.dcline(:, c.PMIN) < 0) | ...
              (l0 + l1 .* mpc.dcline(:, c.PMAX) < 0) );
    if ~isempty(k)
        warning('toggle_dcline: loss can be negative for DC line from bus %d to %d\n', ...
            [mpc.dcline(k, c.F_BUS:c.T_BUS)]');
    end

    %% add callback functions
    %% note: assumes all necessary data included in 1st arg (mpc, om, results)
    %%       so, no additional explicit args are needed
    mpc = add_userfcn(mpc, 'ext2int', @userfcn_dcline_ext2int);
    mpc = add_userfcn(mpc, 'formulation', @userfcn_dcline_formulation);
    mpc = add_userfcn(mpc, 'int2ext', @userfcn_dcline_int2ext);
    mpc = add_userfcn(mpc, 'printpf', @userfcn_dcline_printpf);
    mpc = add_userfcn(mpc, 'savecase', @userfcn_dcline_savecase);
    mpc.userfcn.status.dcline = 1;
elseif strcmp(upper(on_off), 'OFF')
    mpc = remove_userfcn(mpc, 'savecase', @userfcn_dcline_savecase);
    mpc = remove_userfcn(mpc, 'printpf', @userfcn_dcline_printpf);
    mpc = remove_userfcn(mpc, 'int2ext', @userfcn_dcline_int2ext);
    mpc = remove_userfcn(mpc, 'formulation', @userfcn_dcline_formulation);
    mpc = remove_userfcn(mpc, 'ext2int', @userfcn_dcline_ext2int);
    mpc.userfcn.status.dcline = 0;
elseif strcmp(upper(on_off), 'STATUS')
    if isfield(mpc, 'userfcn') && isfield(mpc.userfcn, 'status') && ...
            isfield(mpc.userfcn.status, 'dcline')
        mpc = mpc.userfcn.status.dcline;
    else
        mpc = 0;
    end
else
    error('toggle_dcline: 2nd argument must be ''on'', ''off'' or ''status''');
end

%%-----  ext2int  ------------------------------------------------------
function mpc = userfcn_dcline_ext2int(mpc, mpopt, args)
%
%   mpc = userfcn_dcline_ext2int(mpc, mpopt, args)
%
%   This is the 'ext2int' stage userfcn callback that prepares the input
%   data for the formulation stage. It expects to find a 'dcline' field
%   in mpc as described above. The optional args are not currently used.
%   It adds two dummy generators for each in-service DC line, with the
%   appropriate upper and lower generation bounds and corresponding
%   entries in gencost. It also expands columns of any A and N matrices
%   accordingly, if present.

%% define named indices into data matrices
[PQ, PV, REF, NONE, BUS_I, BUS_TYPE, PD, QD, GS, BS, BUS_AREA, VM, ...
    VA, BASE_KV, ZONE, VMAX, VMIN, LAM_P, LAM_Q, MU_VMAX, MU_VMIN] = idx_bus;
[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;
[PW_LINEAR, POLYNOMIAL, MODEL, STARTUP, SHUTDOWN, NCOST, COST] = idx_cost;
c = idx_dcline;

%% initialize some things
if isfield(mpc, 'dclinecost')
    havecost = 1;
else
    havecost = 0;
end

%% save version with external indexing
mpc.order.ext.dcline = mpc.dcline;              %% external indexing
if havecost
    mpc.order.ext.dclinecost = mpc.dclinecost;  %% external indexing
end

%% work with only in-service DC lines
mpc.order.dcline.status.on  = find(mpc.dcline(:, c.BR_STATUS) >  0);
mpc.order.dcline.status.off = find(mpc.dcline(:, c.BR_STATUS) <= 0);

%% remove out-of-service DC lines
dc = mpc.dcline(mpc.order.dcline.status.on, :); %% only in-service DC lines
if havecost
    dcc = mpc.dclinecost(mpc.order.dcline.status.on, :);    %% only in-service DC lines
    mpc.dclinecost = dcc;
end
ndc = size(dc, 1);          %% number of in-service DC lines
o = mpc.order;

%%-----  convert stuff to internal indexing  -----
dc(:, c.F_BUS) = o.bus.e2i(dc(:, c.F_BUS));
dc(:, c.T_BUS) = o.bus.e2i(dc(:, c.T_BUS));
mpc.dcline = dc;

%%-----  create gens to represent DC line terminals  -----
%% ensure consistency of initial values of PF, PT and losses
%% (for simple power flow cases)
dc(:, c.PT) = dc(:, c.PF) - (dc(:, c.LOSS0) + dc(:, c.LOSS1) .* dc(:, c.PF));

%% create gens
fg = zeros(ndc, size(mpc.gen, 2));
fg(:, MBASE)        = 100;
fg(:, GEN_STATUS)   =  dc(:, c.BR_STATUS);  %% status (should be all 1's)
fg(:, PMIN)         = -Inf;
fg(:, PMAX)         =  Inf;
tg = fg;
fg(:, GEN_BUS)      =  dc(:, c.F_BUS);      %% from bus
tg(:, GEN_BUS)      =  dc(:, c.T_BUS);      %% to bus
fg(:, PG)           = -dc(:, c.PF);         %% flow (extracted at "from")
tg(:, PG)           =  dc(:, c.PT);         %% flow (injected at "to")
fg(:, QG)           =  dc(:, c.QF);         %% VAr injection at "from"
tg(:, QG)           =  dc(:, c.QT);         %% VAr injection at "to"
fg(:, VG)           =  dc(:, c.VF);         %% voltage set-point at "from"
tg(:, VG)           =  dc(:, c.VT);         %% voltage set-point at "to"
k = find(dc(:, c.PMIN) >= 0);           %% min positive direction flow
if ~isempty(k)                              %% contrain at "from" end
    fg(k, PMAX)     = -dc(k, c.PMIN);       %% "from" extraction lower lim
end
k = find(dc(:, c.PMAX) >= 0);           %% max positive direction flow
if ~isempty(k)                              %% contrain at "from" end
    fg(k, PMIN)     = -dc(k, c.PMAX);       %% "from" extraction upper lim
end
k = find(dc(:, c.PMIN) < 0);            %% max negative direction flow
if ~isempty(k)                              %% contrain at "to" end
    tg(k, PMIN)     =  dc(k, c.PMIN);       %% "to" injection lower lim
end
k = find(dc(:, c.PMAX) < 0);            %% min negative direction flow
if ~isempty(k)                              %% contrain at "to" end
    tg(k, PMAX)     =  dc(k, c.PMAX);       %% "to" injection upper lim
end
fg(:, QMIN)         =  dc(:, c.QMINF);      %% "from" VAr injection lower lim
fg(:, QMAX)         =  dc(:, c.QMAXF);      %% "from" VAr injection upper lim
tg(:, QMIN)         =  dc(:, c.QMINT);      %%  "to"  VAr injection lower lim
tg(:, QMAX)         =  dc(:, c.QMAXT);      %%  "to"  VAr injection upper lim

%% fudge PMAX a bit if necessary to avoid triggering
%% dispatchable load constant power factor constraints
fg(isload(fg), PMAX) = -1e-6;
tg(isload(tg), PMAX) = -1e-6;

%% set all terminal buses to PV (except ref bus)
refbus = find(mpc.bus(:, BUS_TYPE) == REF);
mpc.bus(dc(:, c.F_BUS), BUS_TYPE) = PV;
mpc.bus(dc(:, c.T_BUS), BUS_TYPE) = PV;
mpc.bus(refbus, BUS_TYPE) = REF;

%% expand A and N, if present
nb = size(mpc.bus, 1);
ng = size(mpc.gen, 1);
if isfield(mpc, 'A') && ~isempty(mpc.A)
    [mA, nA] = size(mpc.A);
    if nA >= 2*nb + 2*ng    %% assume AC dimensions
        mpc.A = [   mpc.A(:, 1:2*nb+ng)      sparse(mA, 2*ndc) ...
                    mpc.A(:, 2*nb+ng+(1:ng)) sparse(mA, 2*ndc) ...
                    mpc.A(:, (2*nb+2*ng+1:nA)) ];
    else                    %% assume DC dimensions
        mpc.A = [   mpc.A(:, 1:nb+ng)   sparse(mA, 2*ndc) ...
                    mpc.A(:, (nb+ng+1:nA)) ];
    end
end
if isfield(mpc, 'N') && ~isempty(mpc.N)
    [mN, nN] = size(mpc.N);
    if nN >= 2*nb + 2*ng    %% assume AC dimensions
        mpc.N = [   mpc.N(:, 1:2*nb+ng)      sparse(mN, 2*ndc) ...
                    mpc.N(:, 2*nb+ng+(1:ng)) sparse(mN, 2*ndc) ...
                    mpc.N(:, (2*nb+2*ng+1:nN)) ];
    else                    %% assume DC dimensions
        mpc.N = [   mpc.N(:, 1:nb+ng)   sparse(mN, 2*ndc) ...
                    mpc.N(:, (nb+ng+1:nN)) ];
    end
end

%% append dummy gens
mpc.gen = [mpc.gen; fg; tg];

%% gencost
if isfield(mpc, 'gencost') && ~isempty(mpc.gencost)
    ngcc = size(mpc.gencost, 2);    %% dimensions of gencost
    [pc, qc] = pqcost(mpc.gencost, ng); %% split out re/active costs
    if havecost                 %% user has provided costs
        ndccc = size(dcc, 2);       %% number of dclinecost columns
        ccc = max([ngcc; ndccc]);   %% number of columns in new gencost
        if ccc > ngcc               %% right zero-pad gencost
            pc = [pc zeros(ng, ccc-ngcc)];
            if ~isempty(qc)         %% pad Qg costs, too
                qc = [qc zeros(ng, ccc-ngcc)];
            end
            ngcc = ccc;
        end

        %% flip function across vertical axis and append to gencost
        %% (PF for DC line = -PG for dummy gen at "from" bus)
        for k = 1:ndc
            if dcc(k, MODEL) == POLYNOMIAL
                nc = dcc(k, NCOST);
                temp = dcc(k, NCOST+(1:nc));
                %% flip sign on coefficients of odd terms
                %% (every other starting with linear term,
                %%  that is, the next to last one)
                temp((nc-1):-2:1) = -temp((nc-1):-2:1);
            else  %% dcc(k, MODEL) == PW_LINEAR
                nc = dcc(k, NCOST);
                temp = dcc(k, NCOST+(1:2*nc));
                %% switch sign on horizontal coordinate
                xx = -temp(1:2:2*nc);
                yy =  temp(2:2:2*nc);
                temp(1:2:2*nc) = xx(end:-1:1);
                temp(2:2:2*nc) = yy(end:-1:1);
            end
            padding = zeros(1, ngcc-NCOST-length(temp));
            gck = [dcc(k, 1:NCOST) temp padding];

            %% append to gencost
            pc = [pc; gck];
        end
        %% define zero cost for "to" end gen
        dcgc = ones(ndc, 1) * [2 0 0 2 zeros(1, ngcc-4)];
    else
        %% use zero cost as default on "from" end gen
        dcgc = ones(ndc, 1) * [2 0 0 2 zeros(1, ngcc-4)];
        pc = [pc; dcgc];
    end
    %% always use zero cost on "to" end gen
    pc = [pc; dcgc];

    %% reassemble gencost
    if ~isempty(qc)
        qc = [qc; dcgc; dcgc];  %% always use zero cost Qg, both ends
        mpc.gencost = [pc; qc];
    else
        mpc.gencost = pc;
    end
end

%%-----  formulation  --------------------------------------------------
function om = userfcn_dcline_formulation(om, mpopt, args)
%
%   om = userfcn_dcline_formulation(om, mpopt, args)
%
%   This is the 'formulation' stage userfcn callback that defines the
%   user constraints for the dummy generators representing DC lines.
%   It expects to find a 'dcline' field in the mpc stored in om, as
%   described above. By the time it is passed to this callback,
%   MPC.dcline should contain only in-service lines and the from and
%   two bus columns should be converted to internal indexing. The
%   optional args are not currently used.
%
%   If Pf, Pt and Ploss are the flow at the "from" end, flow at the
%   "to" end and loss respectively, and L0 and L1 are the linear loss
%   coefficients, the the relationships between them is given by:
%       Pf - Ploss = Pt
%       Ploss = L0 + L1 * Pf
%   If Pgf and Pgt represent the injections of the dummy generators
%   representing the DC line injections into the network, then
%   Pgf = -Pf and Pgt = Pt, and we can combine all of the above to
%   get the following constraint on Pgf ang Pgt:
%       -Pgf - (L0 - L1 * Pgf) = Pgt
%   which can be written:
%       -L0 <= (1 - L1) * Pgf + Pgt <= -L0

%% define named indices into data matrices
c = idx_dcline;

%% initialize some things
mpc = om.get_mpc();
dc = mpc.dcline;
ndc = size(dc, 1);              %% number of in-service DC lines
ng  = size(mpc.gen, 1) - 2*ndc; %% number of original gens/disp loads

%% constraints
nL0 = -dc(:, c.LOSS0) / mpc.baseMVA;
L1  =  dc(:, c.LOSS1);
Adc = [sparse(ndc, ng) spdiags(1-L1, 0, ndc, ndc) speye(ndc, ndc)];

%% add them to the model
om.add_lin_constraint('dcline', Adc, nL0, nL0, {'Pg'});

%%-----  int2ext  ------------------------------------------------------
function results = userfcn_dcline_int2ext(results, mpopt, args)
%
%   results = userfcn_dcline_int2ext(results, mpopt, args)
%
%   This is the 'int2ext' stage userfcn callback that converts everything
%   back to external indexing and packages up the results. It expects to
%   find a 'dcline' field in the results struct as described for mpc
%   above. It also expects that the last 2*ndc entries in the gen and
%   gencost matrices correspond to the in-service DC lines (where ndc is
%   the number of rows in MPC.dcline. These extra rows are removed from
%   gen and gencost and the flow is taken from the PG of these gens and
%   placed in the flow column of the appropiate dcline row. Corresponding
%   columns are also removed from any A and N matrices, if present. The
%   optional args are not currently used.

%% 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;
c = idx_dcline;

%% initialize some things
o = results.order;
k = find(o.ext.dcline(:, c.BR_STATUS));
ndc = length(k);                    %% number of in-service DC lines
ng  = size(results.gen, 1) - 2*ndc; %% number of original gens/disp loads
nb  = size(results.bus, 1);

%% extract dummy gens
fg = results.gen(ng    +(1:ndc), :);
tg = results.gen(ng+ndc+(1:ndc), :);

%% remove dummy gens
results.gen     = results.gen(1:ng, :);
if isfield(results, 'gencost') && ~isempty(results.gencost)
    results.gencost = results.gencost(1:ng, :);
end

%% delete corresponding columns from A and N, if present
if isfield(results, 'A') && ~isempty(results.A)
    [mA, nA] = size(results.A);
    if nA >= 2*nb + 2*ng + 4*ndc    %% assume AC dimensions
        results.A = results.A(:, [1:2*nb+ng 2*nb+ng+2*ndc+(1:ng) 2*nb+2*ng+4*ndc+1:nA]);
    else                            %% assume DC dimensions
        results.A = results.A(:, [1:nb+ng nb+ng+2*ndc+1:nA]);
    end
end
if isfield(results, 'N') && ~isempty(results.N)
    [mN, nN] = size(results.N);
    if nN >= 2*nb + 2*ng + 4*ndc    %% assume AC dimensions
        results.N = results.N(:, [1:2*nb+ng 2*nb+ng+2*ndc+(1:ng) 2*nb+2*ng+4*ndc+1:nN]);
    else                            %% assume DC dimensions
        results.N = results.N(:, [1:nb+ng nb+ng+2*ndc+1:nN]);
    end
end

%% get the solved flows
results.dcline(:, c.PF) = -fg(:, PG);
results.dcline(:, c.PT) =  tg(:, PG);
results.dcline(:, c.QF) =  fg(:, QG);
results.dcline(:, c.QT) =  tg(:, QG);
results.dcline(:, c.VF) =  fg(:, VG);
results.dcline(:, c.VT) =  tg(:, VG);
if size(fg, 2) >= MU_QMIN
    results.dcline(:, c.MU_PMIN ) = fg(:, MU_PMAX) + tg(:, MU_PMIN);
    results.dcline(:, c.MU_PMAX ) = fg(:, MU_PMIN) + tg(:, MU_PMAX);
    results.dcline(:, c.MU_QMINF) = fg(:, MU_QMIN);
    results.dcline(:, c.MU_QMAXF) = fg(:, MU_QMAX);
    results.dcline(:, c.MU_QMINT) = tg(:, MU_QMIN);
    results.dcline(:, c.MU_QMAXT) = tg(:, MU_QMAX);
end

%%-----  convert stuff back to external indexing  -----
results.order.int.dcline = results.dcline;  %% save internal version
%% copy results to external version
o.ext.dcline(k, c.PF:c.VT) = results.dcline(:, c.PF:c.VT);
if size(results.dcline, 2) == c.MU_QMAXT
    o.ext.dcline(k, c.MU_PMIN:c.MU_QMAXT) = results.dcline(:, c.MU_PMIN:c.MU_QMAXT);
end
results.dcline = o.ext.dcline;              %% use external version

%%-----  printpf  ------------------------------------------------------
function results = userfcn_dcline_printpf(results, fd, mpopt, args)
%
%   results = userfcn_dcline_printpf(results, fd, mpopt, args)
%
%   This is the 'printpf' stage userfcn callback that pretty-prints the
%   results. It expects a results struct, a file descriptor and a MATPOWER
%   options struct. The optional args are not currently used.

%% define named indices into data matrices
c = idx_dcline;

%% options
SUPPRESS        = mpopt.out.suppress_detail;
if SUPPRESS == -1
    if size(results.bus, 1) > 500
        SUPPRESS = 1;
    else
        SUPPRESS = 0;
    end
end
OUT_ALL = mpopt.out.all;
OUT_BRANCH      = OUT_ALL == 1 || (OUT_ALL == -1 && ~SUPPRESS && mpopt.out.branch);
if OUT_ALL == -1
    OUT_ALL_LIM = ~SUPPRESS * mpopt.out.lim.all;
elseif OUT_ALL == 1
    OUT_ALL_LIM = 2;
else
    OUT_ALL_LIM = 0;
end
if OUT_ALL_LIM == -1
    OUT_LINE_LIM    = ~SUPPRESS * mpopt.out.lim.line;
else
    OUT_LINE_LIM    = OUT_ALL_LIM;
end
ctol = mpopt.opf.violation; %% constraint violation tolerance
ptol = 1e-4;                %% tolerance for displaying shadow prices

%%-----  print results  -----
dc = results.dcline;
ndc = size(dc, 1);
kk = find(dc(:, c.BR_STATUS) ~= 0);
if OUT_BRANCH
    fprintf(fd, '\n================================================================================');
    fprintf(fd, '\n|     DC Line Data                                                             |');
    fprintf(fd, '\n================================================================================');
    fprintf(fd, '\n Line    From     To        Power Flow           Loss     Reactive Inj (MVAr)');
    fprintf(fd, '\n   #      Bus     Bus   From (MW)   To (MW)      (MW)       From        To   ');
    fprintf(fd, '\n------  ------  ------  ---------  ---------  ---------  ---------  ---------');
    loss = 0;
    for k = 1:ndc
        if dc(k, c.BR_STATUS)   %% status on
            fprintf(fd, '\n%5d%8d%8d%11.2f%11.2f%11.2f%11.2f%11.2f', ...
                        k, dc(k, c.F_BUS:c.T_BUS), dc(k, c.PF:c.PT), ...
                        dc(k, c.PF) - dc(k, c.PT), dc(k, c.QF:c.QT) );
            loss = loss + dc(k, c.PF) - dc(k, c.PT);
        else
            fprintf(fd, '\n%5d%8d%8d%11s%11s%11s%11s%11s', ...
                        k, dc(k, c.F_BUS:c.T_BUS), '-  ', '-  ', '-  ', '-  ', '-  ');
        end
    end
    fprintf(fd, '\n                                              ---------');
    fprintf(fd, '\n                                     Total:%11.2f\n', loss);
end

if OUT_LINE_LIM == 2 || (OUT_LINE_LIM == 1 && ...
        (any(dc(kk, c.PF) > dc(kk, c.PMAX) - ctol) || ...
         any(dc(kk, c.MU_PMIN) > ptol) || ...
         any(dc(kk, c.MU_PMAX) > ptol)))
    fprintf(fd, '\n================================================================================');
    fprintf(fd, '\n|     DC Line Constraints                                                      |');
    fprintf(fd, '\n================================================================================');
    fprintf(fd, '\n Line    From     To          Minimum        Actual Flow       Maximum');
    fprintf(fd, '\n   #      Bus     Bus    Pmin mu     Pmin       (MW)       Pmax      Pmax mu ');
    fprintf(fd, '\n------  ------  ------  ---------  ---------  ---------  ---------  ---------');
    for k = 1:ndc
        if OUT_LINE_LIM == 2 || (OUT_LINE_LIM == 1 && ...
                (dc(k, c.PF) > dc(k, c.PMAX) - ctol || ...
                 dc(k, c.MU_PMIN) > ptol || ...
                 dc(k, c.MU_PMAX) > ptol))
            if dc(k, c.BR_STATUS)   %% status on
                fprintf(fd, '\n%5d%8d%8d', k, dc(k, c.F_BUS:c.T_BUS) );
                if dc(k, c.MU_PMIN) > ptol
                    fprintf(fd, '%11.3f', dc(k, c.MU_PMIN) );
                else
                    fprintf(fd, '%11s', '-  ' );
                end
                fprintf(fd, '%11.2f%11.2f%11.2f', ...
                            dc(k, c.PMIN), dc(k, c.PF), dc(k, c.PMAX) );
                if dc(k, c.MU_PMAX) > ptol
                    fprintf(fd, '%11.3f', dc(k, c.MU_PMAX) );
                else
                    fprintf(fd, '%11s', '-  ' );
                end
            else
                fprintf(fd, '\n%5d%8d%8d%11s%11s%11s%11s%11s', ...
                            k, dc(k, c.F_BUS:c.T_BUS), '-  ', '-  ', '-  ', '-  ', '-  ');
            end
        end
    end
    fprintf(fd, '\n');
end

%%-----  savecase  -----------------------------------------------------
function mpc = userfcn_dcline_savecase(mpc, fd, prefix, args)
%
%   mpc = userfcn_dcline_savecase(mpc, fd, prefix, args)
%
%   This is the 'savecase' stage userfcn callback that prints the M-file
%   code to save the 'dcline' field in the case file. It expects a
%   MATPOWER case struct (mpc), a file descriptor and variable prefix
%   (usually 'mpc.'). The optional args are not currently used.

%% define named indices into data matrices
c = idx_dcline;

%% save it
ncols = size(mpc.dcline, 2);
fprintf(fd, '\n%%%%-----  DC Line Data  -----%%%%\n');
if ncols < c.MU_QMAXT
    fprintf(fd, '%%\tfbus\ttbus\tstatus\tPf\tPt\tQf\tQt\tVf\tVt\tPmin\tPmax\tQminF\tQmaxF\tQminT\tQmaxT\tloss0\tloss1\n');
else
    fprintf(fd, '%%\tfbus\ttbus\tstatus\tPf\tPt\tQf\tQt\tVf\tVt\tPmin\tPmax\tQminF\tQmaxF\tQminT\tQmaxT\tloss0\tloss1\tmuPmin\tmuPmax\tmuQminF\tmuQmaxF\tmuQminT\tmuQmaxT\n');
end
template = '\t%d\t%d\t%d\t%.9g\t%.9g\t%.9g\t%.9g\t%.9g\t%.9g\t%.9g\t%.9g\t%.9g\t%.9g\t%.9g\t%.9g\t%.9g\t%.9g';
if ncols == c.MU_QMAXT
    template = [template, '\t%.4f\t%.4f\t%.4f\t%.4f\t%.4f\t%.4f'];
end
template = [template, ';\n'];
fprintf(fd, '%sdcline = [\n', prefix);
fprintf(fd, template, mpc.dcline.');
fprintf(fd, '];\n');

%% to do, make it save mpc.dclinecost too (somehow I forgot this)
%% have a look at the saving of gencost in savecase for template


function [C_smooth]=patchSmoothFaceMeasure(varargin)

% function [C_smooth]=patchSmoothFaceMeasure(F,V,C,smoothPar)

%% Parse input
switch nargin
    case 3
        F=varargin{1};
        V=varargin{2};
        C=varargin{3};
        smoothPar=[];
    case 4
        F=varargin{1};
        V=varargin{2};
        C=varargin{3};
        smoothPar=varargin{4};
end

smoothParDefault.lambda=0.5;
smoothParDefault.n=1;
smoothParDefault.faceFaceConnectivity=[];
smoothPar=structComplete(smoothPar,smoothParDefault,1);

%% Get connectivity array
if isempty(smoothPar.faceFaceConnectivity)
    [connectivityStruct]=patchConnectivity(F,V);
    faceFaceConnectivity=connectivityStruct.face.face;
end

%%

numSmoothIterations=smoothPar.n;
lambdaSmooth=smoothPar.lambda;

%%

nDims=size(C,2); %Number of dimensions
logicValid=faceFaceConnectivity>0;
C_smooth=C;
C_smooth_step=C;
for qIter=1:numSmoothIterations
    %Loop for all dimensions
    for qDim=1:1:nDims
        Xp=NaN(size(C,1),size(faceFaceConnectivity,2));
        Xp(logicValid)=C_smooth(faceFaceConnectivity(logicValid),qDim);
        Xp=gnanmean(Xp,2);
        C_smooth_step(:,qDim)=Xp;
    end
    C_smooth=((1-lambdaSmooth).*C_smooth)+(lambdaSmooth.*C_smooth_step);
end
%%
% _*GIBBON footer text*_
%
% License: <https://github.com/gibbonCode/GIBBON/blob/master/LICENSE>
%
% GIBBON: The Geometry and Image-based Bioengineering add-On. A toolbox for
% image segmentation, image-based modeling, meshing, and finite element
% analysis.
%
% Copyright (C) 2006-2022 Kevin Mattheus Moerman and the GIBBON contributors
%
% This program is free software: you can redistribute it and/or modify
% it under the terms of the GNU General Public License as published by
% the Free Software Foundation, either version 3 of the License, or
% (at your option) any later version.
%
% This program is distributed in the hope that it will be useful,
% but WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
% GNU General Public License for more details.
%
% You should have received a copy of the GNU General Public License
% along with this program.  If not, see <http://www.gnu.org/licenses/>.


% THIS SOFTWARE AND ANY ACCOMPANYING DOCUMENTATION IS RELEASED "AS IS."  THE U.S. GOVERNMENT MAKES NO WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, CONCERNING THIS SOFTWARE AND ANY ACCOMPANYING DOCUMENTATION, INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.  IN NO EVENT WILL THE U.S. GOVERNMENT BE LIABLE FOR ANY DAMAGES, INCLUDING ANY LOST PROFITS, LOST SAVINGS OR OTHER INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE, OR INABILITY TO USE, THIS SOFTWARE OR ANY ACCOMPANYING DOCUMENTATION, EVEN IF INFORMED IN ADVANCE OF THE POSSIBILITY OF SUCH DAMAGES.
%
% file: poisspdf_of_lambda.m
% Used when you want to integrate a poisson pdf with
% respect to lambda.
%
% 022300 tdr created
% 030400 tdr replaced global variable approach to getting x and n to binopdf.
% 031600 tdr converted from binopdf_of_p.m to poisson dist.
% 032200 tdr replaced for loop with vector argument.

function y = poisspdf_of_lambda(lambda, x)

if nargin ~= 2,
    error('Requires two input arguments (lambda,x)');
end

y = poisspdf_0ok(x,lambda);


function [y,newp,retcode]=ssfile(obj,y,p,d,id) %#ok<INUSL>

% risessfile --  computes the steady state of ... analytically
%
% ::
%
%
%   [y,newp,retcode]=risessfile(obj,y,p,d,id)
%
% Args:
%
%    - **obj** [rise|dsge]: model object (not always needed)
%
%    - **y** [vector]: endo_nbr x 1 vector of initial steady state
%
%    - **p** [struct]: parameter structure
%
%    - **d** [struct]: definitions
%
%    - **id** [vector]: location of the variables to calculate
%
% Returns:
%    :
%
%    - **y** []: endo_nbr x 1 vector of updated steady state
%
%    - **newp** [struct]: structure containing updated parameters if any
%
%    - **retcode** [0|number]: return 0 if there are no problems, else return
%      any number different from 0
%
% Note:
%
%    - this is new approach has three main advantages relative to the previous
%      one:
%      - The file is valid whether we have many regimes or not
%      - The user does not need to know what regime is being computed
%      - It is in sync with the steady state model
%
% Example:
%
%    See also:

% Conversion of Dynare file [Dynare_edo_steadystate.m] into RISE file [risessfile.m]
%
%  Done 23-Jul-2017 00:31:15.

retcode=0;

if nargin==1
    % list of endogenous variables to be calculated
    %----------------------------------------------
    % y=get(obj,'endo_list');
    y={'RC','RK','WC','WK','YC','YK','MCC','MCK','KC','KK','PKB','R','L','QK',...
        'HC','HSC','HK','HSK','UHC','UHSC','UHK','UHSK','empC','HrC',...
        'empK','HrK','empSC','HrSC','empSK','HrSK','unemp','EIK','EC','INFWC',...
        'INFWK','INFC','INFK','DIFFNORMGDP','NORMINFGDP','DIFFREALGDP',...
        'DIFFREALEC','DIFFREALEIK','DIFFREALW','AH','INFGDP','INFCNA','INFCOR',...
        'GAP','PFGAP','INFC10','ECD','KD','RCD','QCD','KCH','RCH','ECH','QCH',...
        'LAGKD','LAGKCH','UK','UC','DIFFREALECH','DIFFREALECD','betas','XiL',...
        'Lpref','EFFK','MUZK','MUZM','HG','MUC','MUK','EFFECD','EFFECH','STAR',...
        'RL1','RL2','RL3','RL4','RL5','RL6','RL7','RT2','DIFFREALGDP_obs',...
        'DIFFREALEC_obs','DIFFREALEIK_obs','DIFFREALECD_obs','DIFFREALECH_obs',...
        'DIFFREALW_obs','AH_obs','INFCNA_obs','INFCOR_obs','INFK_obs','R_obs',...
        'RT2_obs','unemp_obs'};
    % list of parameters to be computed during steady state calculation
    %-------------------------------------------------------------------
    newp={'AA','AHSS','A_HC','A_HK','DD','DIFFREALECDSS','DIFFREALECDSS_obs',...
        'DIFFREALECHSS','DIFFREALECHSS_obs','DIFFREALECSS','DIFFREALECSS_obs',...
        'DIFFREALEIKSS','DIFFREALEIKSS_obs','DIFFREALGDPSS','DIFFREALGDPSS_obs',...
        'DIFFREALWSS','DIFFREALWSS_obs','ECDSS','ECHSS','ECSS','EIKSS','HCSS',...
        'HKSS','HSCSS','HSKSS','HSS','HrCSS','HrKSS','HrSCSS','HrSKSS',...
        'IMPHSSS','INFC10SS','INFCNASS','INFCNASS_obs','INFCORSS','INFCORSS_obs',...
        'INFCSS','INFGDPSS','INFKSS','INFKSS_obs','INFWCSS','INFWKSS','KCDSS',...
        'KCHSS','KCSS','KKSS','LSS','MCCSS','MCKSS','MUCSS','MUCSShabit','MUKSS',...
        'MUKSShabit','MUZCSS','ONE','PKBSS','PYSS','QCDSS','QCHSS','QKSS',...
        'RCDSS','RCHSS','RCSS','RKSS','RL1SS','RL2SS','RL3SS','RL4SS','RL5SS',...
        'RL6SS','RL7SS','RR','RSS','RSS_obs','RT2SS','RT2SS_obs','Rnr','UCSS',...
        'UHCSS','UHKSS','UHSCSS','UHSKSS','UKSS','USS','WCSS','WKSS','YCSS',...
        'YKSS','YYSS','beta_','beta_0','beta_2','empCSS','empKSS','empSCSS',...
        'empSKSS','eta_cd','eta_cd_eta_cnn','eta_ch','eta_ch_eta_cnn','eta_cnn',...
        'hc_hk','mu_','s_c_ech','s_ecdc','s_k','s_k_ecd','s_k_eik','s_yc',...
        'theta_wc','theta_wk','unempSS','unempSS_obs','xsi_HrC','xsi_HrK',...
        'xsi_NC','xsi_NK','ycbi','ycbi_ykb','ykb'};
else

    newp=struct();

    %start_steady_state;

    newp.beta_0 = p.pbeta;
    newp.beta_2 = p.pbeta*p.rpr; % s.s. funds rate premium
    newp.beta_ = newp.beta_2;
    newp.MUZCSS=1;
    newp.ONE=1;
    newp.USS=1;
    newp.MUKSS=p.MUZKSS*p.MUZMSS;
    newp.MUCSS=p.MUZKSS^p.alpha_*p.MUZMSS;
    newp.MUKSShabit=newp.MUKSS;
    newp.MUCSShabit=newp.MUCSS;
    newp.PKBSS=p.theta_k/(p.theta_k-1)*(p.theta_c-1)/p.theta_c;
    newp.PYSS=1;
    newp.MCCSS=(p.theta_c-1)/p.theta_c;
    newp.MCKSS=(p.theta_k-1)/p.theta_k;
    newp.RKSS=newp.MUKSS/newp.beta_2-(1-p.delta_);
    newp.RCSS=newp.MUKSS/newp.beta_2-(1-p.delta_);
    newp.RCHSS=newp.MUCSS/newp.beta_2-(1-p.delta_ch); % Housing sector
    newp.RCDSS=newp.MUKSS/newp.beta_2-(1-p.delta_cd); % Durable sector
    newp.USS=1;
    newp.mu_=newp.RCSS;
    newp.AA=p.alpha_/newp.RKSS*newp.MCKSS;
    newp.DD = 0.135;
    newp.RR = 0.075;
    newp.eta_cnn=1;
    newp.eta_cd_eta_cnn=newp.DD/((newp.MUKSShabit-newp.beta_2*p.h_cd)/(1-newp.beta_2*p.h/newp.MUCSShabit)*(1-p.h/newp.MUCSShabit)/(1-p.h_cd/newp.MUKSShabit)*(1-(1-p.delta_cd)/newp.MUKSS)/newp.RCDSS);
    newp.eta_ch_eta_cnn=newp.RR/((newp.MUCSShabit-newp.beta_2*p.h_ch)/(1-newp.beta_2*p.h/newp.MUCSShabit)*(1-p.h/newp.MUCSShabit)/(1-p.h_ch/newp.MUCSShabit)*(1-(1-p.delta_ch)/newp.MUCSS)/newp.RCHSS);
    newp.eta_ch=newp.eta_ch_eta_cnn;
    newp.eta_cd=newp.eta_cd_eta_cnn;
    newp.DD=newp.eta_cd_eta_cnn*(newp.MUKSShabit-newp.beta_2*p.h_cd)/(1-newp.beta_2*p.h/newp.MUCSShabit)*(1-p.h/newp.MUCSShabit)/(1-p.h_cd/newp.MUKSShabit)*(1-(1-p.delta_cd)/newp.MUKSS)/newp.RCDSS;
    newp.RR=newp.eta_ch_eta_cnn*(newp.MUCSShabit-newp.beta_2*p.h_ch)/(1-newp.beta_2*p.h/newp.MUCSShabit)*(1-p.h/newp.MUCSShabit)/(1-p.h_ch/newp.MUCSShabit)*(1-(1-p.delta_ch)/newp.MUCSS)/newp.RCHSS;
    newp.Rnr=(1-(1-p.delta_)/newp.MUKSS)*newp.AA*newp.MUKSS;
    newp.ycbi_ykb=((1-p.s_AS)-newp.Rnr)/((newp.DD*(1-p.s_AS)/(1+newp.RR))+newp.Rnr);
    newp.hc_hk=newp.ycbi_ykb*(newp.RCSS*newp.MCKSS/(newp.RKSS*newp.MCCSS))^(p.alpha_/(1-p.alpha_));
    newp.HSS=0.25;
    newp.AHSS=newp.HSS;
    newp.HKSS=newp.HSS/(1+newp.hc_hk);
    newp.HCSS=newp.HSS-newp.HKSS;
    newp.HrCSS=1/3;
    newp.HrKSS=1/3;
    newp.empCSS=newp.HCSS/newp.HrCSS;
    newp.empKSS=newp.HKSS/newp.HrKSS;
    newp.ycbi=newp.HCSS*(newp.AA)^(p.alpha_/(1-p.alpha_));
    newp.ykb=newp.HKSS*(newp.AA)^(p.alpha_/(1-p.alpha_));
    newp.YCSS=newp.ycbi;
    newp.YKSS=newp.ykb;
    newp.KCSS=newp.AA*newp.ycbi*newp.MUKSS;
    newp.KKSS=newp.AA*newp.ykb*newp.MUKSS;
    newp.ECHSS=newp.RR/(1+newp.RR)*newp.ycbi*(1-p.s_AS);
    newp.ECSS=1/(1+newp.RR)*newp.ycbi*(1-p.s_AS);
    newp.ECDSS=newp.DD*newp.PKBSS*newp.ECSS;
    newp.EIKSS=(1-(1-p.delta_)/newp.MUKSS)*(newp.KCSS+newp.KKSS);
    newp.KCDSS=newp.ECDSS/(1-(1-p.delta_cd)/newp.MUKSS);
    newp.KCHSS=newp.ECHSS/(1-(1-p.delta_ch)/newp.MUCSS);
    newp.YYSS=(newp.YCSS+newp.YKSS*newp.PKBSS)/newp.PYSS;
    newp.s_k_ecd=newp.ECDSS/newp.YKSS;
    newp.s_c_ech=newp.ECHSS/newp.YCSS;
    newp.s_k_eik=newp.EIKSS/newp.YKSS;
    newp.s_yc = (newp.YCSS/newp.YYSS);
    newp.s_ecdc=newp.PKBSS*newp.ECDSS/(newp.ECSS+newp.PKBSS*newp.ECDSS+(newp.MUCSS/newp.beta_2-1+p.delta_ch)*newp.KCHSS);
    newp.INFCNASS=exp(.02/4);
    newp.INFCSS = newp.INFCNASS*((newp.MUZCSS/p.MUZKSS)^(1-p.alpha_))^(-newp.s_ecdc);
    newp.INFCORSS=newp.INFCNASS;
    newp.INFKSS=newp.INFCSS*(newp.MUZCSS/p.MUZKSS)^(1-p.alpha_);
    newp.INFWCSS=newp.INFCSS*p.MUZKSS^p.alpha_*p.MUZMSS;
    newp.INFWKSS=newp.INFWCSS;
    newp.RSS=newp.INFCSS/newp.beta_0*newp.MUCSS;
    newp.RT2SS=exp(p.tp2)*newp.RSS;
    newp.INFC10SS = newp.INFCNASS;
    newp.IMPHSSS = newp.RCHSS*newp.KCHSS;
    newp.s_k=newp.PKBSS*newp.YKSS/newp.YYSS;
    newp.INFGDPSS=newp.INFCSS^(newp.YCSS/newp.YYSS)*newp.INFKSS^(newp.YKSS*newp.PKBSS/(newp.YYSS));
    newp.LSS=newp.eta_cnn/(newp.ECSS*(1-p.h/newp.MUCSShabit))-newp.eta_cnn*newp.beta_2*p.h/(newp.ECSS*(newp.MUCSShabit-p.h));
    newp.WCSS=newp.MCCSS*(1-p.alpha_)*newp.YCSS/newp.HCSS;
    newp.WKSS=newp.MCKSS*(1-p.alpha_)*newp.YKSS/newp.HKSS;
    % xsiN_xsiH_C = ((newp.HrCSS/newp.empCSS)^(1+p.sigmah))/(1+1/p.sigmah);
    % xsiN_xsiH_K = ((newp.HrKSS/newp.empKSS)^(1+p.sigmah))/(1+1/p.sigmah);
    % gC = (1/(1+p.sigman) + 1/p.sigmah)*(xsiN_xsiH_C*(1+p.sigmah)/p.sigmah)^(-(1+p.sigman)/(1+p.sigman+p.sigmah));
    % markup_xsiN_C = (newp.HCSS^((1+p.sigmah)*(1+p.sigman)/(1+p.sigmah+p.sigman)-1))*gC/(newp.LSS*newp.WCSS);
    % gK = (1/(1+p.sigman) + 1/p.sigmah)*(xsiN_xsiH_K*(1+p.sigmah)/p.sigmah)^(-(1+p.sigman)/(1+p.sigman+p.sigmah));
    % markup_xsiN_K = (newp.HKSS^((1+p.sigmah)*(1+p.sigman)/(1+p.sigmah+p.sigman)-1))*gK/(newp.LSS*newp.WKSS);
    % markup_w = (1-newp.unempSS)^((1+p.sigmah+p.sigman)/(1+p.sigmah) - 1 - p.sigman);
    markup_w = (1-p.unempSS)^((1+p.sigmah+p.sigman)/(1+p.sigmah) - 1 - p.sigman);
    newp.theta_wc = markup_w/(markup_w -1); newp.theta_wk = newp.theta_wc;
    newp.A_HC=newp.LSS*(newp.theta_wc-1)/newp.theta_wc*newp.WCSS/(((1+p.sigman)/(1+p.sigman/(1+p.sigmah)))*newp.HCSS^(-1+(1+p.sigman)/(1+p.sigman/(1+p.sigmah))));
    newp.A_HK=newp.LSS*(newp.theta_wk-1)/newp.theta_wk*newp.WKSS/(((1+p.sigman)/(1+p.sigman/(1+p.sigmah)))*newp.HKSS^(-1+(1+p.sigman)/(1+p.sigman/(1+p.sigmah))));
    newp.xsi_NC=newp.A_HC/((1/(1+p.sigman)+1/p.sigmah)*(newp.HCSS^p.sigman/newp.HrCSS^(1+p.sigman+p.sigmah))^((1+p.sigman)/(1+p.sigman+p.sigmah)));
    newp.xsi_NK=newp.A_HK/((1/(1+p.sigman)+1/p.sigmah)*(newp.HKSS^p.sigman/newp.HrKSS^(1+p.sigman+p.sigmah))^((1+p.sigman)/(1+p.sigman+p.sigmah)));
    newp.xsi_HrC=newp.xsi_NC*(1+p.sigmah)/p.sigmah*(newp.HCSS^p.sigman/newp.HrCSS^(1+p.sigman+p.sigmah));
    newp.xsi_HrK=newp.xsi_NK*(1+p.sigmah)/p.sigmah*(newp.HKSS^p.sigman/newp.HrKSS^(1+p.sigman+p.sigmah));
    newp.UHCSS=newp.A_HC*((1+p.sigman)/(1+p.sigman/(1+p.sigmah)))*newp.HCSS^(-1+(1+p.sigman)/(1+p.sigman/(1+p.sigmah)))/newp.LSS;
    newp.UHKSS=newp.A_HK*((1+p.sigman)/(1+p.sigman/(1+p.sigmah)))*newp.HKSS^(-1+(1+p.sigman)/(1+p.sigman/(1+p.sigmah)))/newp.LSS;
    newp.HSCSS=(newp.WCSS*newp.LSS/(newp.A_HC*((1+p.sigman)/(1+p.sigman/(1+p.sigmah)))))^(1/(-1+(1+p.sigman)/(1+p.sigman/(1+p.sigmah))));
    newp.HSKSS=(newp.WKSS*newp.LSS/(newp.A_HK*((1+p.sigman)/(1+p.sigman/(1+p.sigmah)))))^(1/(-1+(1+p.sigman)/(1+p.sigman/(1+p.sigmah))));
    newp.empSCSS=((1+p.sigmah)/p.sigmah*newp.xsi_NC/newp.xsi_HrC)^(-1/(1+p.sigmah+p.sigman))*newp.HSCSS^(1/(1+p.sigman/(1+p.sigmah)));
    newp.empSKSS=((1+p.sigmah)/p.sigmah*newp.xsi_NK/newp.xsi_HrK)^(-1/(1+p.sigmah+p.sigman))*newp.HSKSS^(1/(1+p.sigman/(1+p.sigmah)));
    newp.HrSCSS=newp.HSCSS/newp.empSCSS;
    newp.HrSKSS=newp.HSKSS/newp.empSKSS;
    newp.UHSCSS=newp.A_HC*((1+p.sigman)/(1+p.sigman/(1+p.sigmah)))*newp.HSCSS^(-1+(1+p.sigman)/(1+p.sigman/(1+p.sigmah)))/newp.LSS;
    newp.UHSKSS=newp.A_HK*((1+p.sigman)/(1+p.sigman/(1+p.sigmah)))*newp.HSKSS^(-1+(1+p.sigman)/(1+p.sigman/(1+p.sigmah)))/newp.LSS;
    newp.unempSS=(newp.empSCSS+newp.empSKSS-(newp.empCSS+newp.empKSS))/(newp.empSCSS+newp.empSKSS);
    newp.QKSS=1;
    newp.QCDSS=1;
    newp.QCHSS=1;
    newp.UCSS=1;
    newp.UKSS=1;
    % XiBSS=1;
    % XiDSS=1;
    % XiHSS=1;
    newp.RL1SS=newp.RSS;
    newp.RL2SS=newp.RSS;
    newp.RL3SS=newp.RSS;
    newp.RL4SS=newp.RSS;
    newp.RL5SS=newp.RSS;
    newp.RL6SS=newp.RSS;
    newp.RL7SS=newp.RSS;
    newp.DIFFREALECSS =exp( log(newp.MUCSS));
    newp.DIFFREALEIKSS =exp( log(newp.MUKSS));
    newp.DIFFREALECDSS =exp( log(newp.MUKSS));
    newp.DIFFREALECHSS =exp( log(newp.MUCSS));
    newp.DIFFREALWSS =exp( log(newp.MUCSS) );
    newp.DIFFREALGDPSS =exp( (1-newp.s_k)*log(newp.MUCSS)+(newp.s_k)*log(newp.MUKSS));

    %end_steady_state;

    %trends;

    newp.DIFFREALGDPSS_obs=(1-newp.s_k)*log(newp.MUCSS)*100+(newp.s_k)*log(newp.MUKSS)*100;
    newp.DIFFREALECSS_obs=log(newp.MUCSS)*100;
    newp.DIFFREALEIKSS_obs=log(newp.MUKSS)*100;
    newp.DIFFREALECDSS_obs=log(newp.MUKSS)*100;
    newp.DIFFREALECHSS_obs=log(newp.MUCSS)*100;
    newp.DIFFREALWSS_obs=log(newp.MUCSS)*100;
    newp.INFCNASS_obs=(1-newp.s_ecdc)*log(newp.INFCSS)*100+newp.s_ecdc*log(newp.INFKSS)*100;
    newp.INFCORSS_obs=(1-newp.s_ecdc)*log(newp.INFCSS)*100+newp.s_ecdc*log(newp.INFKSS)*100;
    newp.INFKSS_obs=log(newp.INFCSS)*100-log(newp.MUKSS)*100+log(newp.MUCSS)*100;
    newp.RSS_obs=log(newp.RSS)*100;
    newp.RT2SS_obs=log(newp.RT2SS)*100;
    newp.unempSS_obs=100*log(newp.unempSS);

    %end_trends;

    ys = [
        newp.RCSS
        newp.RKSS
        newp.WCSS
        newp.WKSS
        newp.YCSS
        newp.YKSS
        newp.MCCSS
        newp.MCKSS
        newp.KCSS
        newp.KKSS
        newp.PKBSS
        newp.RSS
        newp.LSS
        newp.QKSS
        newp.HCSS
        newp.HSCSS
        newp.HKSS
        newp.HSKSS
        newp.UHCSS
        newp.UHSCSS
        newp.UHKSS
        newp.UHSKSS
        newp.empCSS
        newp.HrCSS
        newp.empKSS
        newp.HrKSS
        newp.empSCSS
        newp.HrSCSS
        newp.empSKSS
        newp.HrSKSS
        newp.unempSS
        newp.EIKSS
        newp.ECSS
        newp.INFWCSS
        newp.INFWKSS
        newp.INFCSS
        newp.INFKSS
        newp.ONE
        newp.ONE
        newp.DIFFREALGDPSS
        newp.DIFFREALECSS
        newp.DIFFREALEIKSS
        newp.DIFFREALWSS
        newp.AHSS
        newp.INFGDPSS
        newp.INFCNASS
        newp.INFCORSS
        newp.ONE
        newp.ONE
        newp.INFC10SS
        newp.ECDSS
        newp.KCDSS
        newp.RCDSS
        newp.QCDSS
        newp.KCHSS
        newp.RCHSS
        newp.ECHSS
        newp.QCHSS
        newp.KCDSS
        newp.KCHSS
        newp.USS
        newp.USS
        newp.DIFFREALECHSS
        newp.DIFFREALECDSS
        newp.beta_
        newp.ONE
        newp.ONE
        newp.ONE
        p.MUZKSS
        p.MUZMSS
        newp.ONE
        newp.MUCSS
        newp.MUKSS
        newp.ONE
        newp.ONE
        newp.ONE
        newp.RL1SS
        newp.RL2SS
        newp.RL3SS
        newp.RL4SS
        newp.RL5SS
        newp.RL6SS
        newp.RL7SS
        newp.RT2SS
        newp.DIFFREALGDPSS_obs
        newp.DIFFREALECSS_obs
        newp.DIFFREALEIKSS_obs
        newp.DIFFREALECDSS_obs
        newp.DIFFREALECHSS_obs
        newp.DIFFREALWSS_obs
        newp.ONE
        newp.INFCNASS_obs
        newp.INFCORSS_obs
        newp.INFKSS_obs
        newp.RSS_obs
        newp.RT2SS_obs
        newp.unempSS_obs
        ];

    y(id)=ys;

end

end


function [vert, face] = read_ply(fn)

% READ_PLY reads triangles, tetraheders or hexaheders from a Stanford *.ply file
%
% Use as
%   [vert, face, prop, face_prop] = read_ply(filename)
%
% Documentation is provided on
%   http://paulbourke.net/dataformats/ply/
%   http://en.wikipedia.org/wiki/PLY_(file_format)
%
% See also WRITE_PLY, WRITE_VTK, READ_VTK

% Copyright (C) 2013-2018, Robert Oostenveld
%
% $Id$

fid = fopen_or_error(fn, 'r');

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% the file starts with an ascii header
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

line = readline(fid);
if ~strcmp(line, 'ply')
  fclose(fid);
  ft_error('unexpected header line');
end
line = readline(fid);

if ~strncmp(line, 'format', 6)
  fclose(fid);
  ft_error('unexpected header line');
else
  format = line(8:end);
end
line = readline(fid);

if ~strncmp(line, 'element vertex', 14)
  fclose(fid);
  ft_error('unexpected header line');
else
  nvert = str2double(line(16:end));
end
line = readline(fid);

prop = [];
while strncmp(line, 'property', 8)
  tok = tokenize(line);
  prop(end+1).format = tok{2};
  prop(end  ).name   = tok{3};
  line = readline(fid);
end

if ~strncmp(line, 'element face', 12)
  fclose(fid);
  ft_error('unexpected header line');
else
  nface = str2double(line(14:end));
end
line = readline(fid);

if ~strcmp(line, 'property list uchar int vertex_index')    && ...
    ~strcmp(line, 'property list uchar int vertex_indices') && ...
    ~strcmp(line, 'property list uchar uint vertex_index')  && ...
    ~strcmp(line, 'property list uchar uint vertex_indices')
  % the wikipedia documentation specifies vertex_index, but the OPTOCAT files have vertex_indices

  % it would not be very difficult to enhance the reader here with another
  % representation of the faces, i.e. something else than "uchar int"
  fclose(fid);
  ft_error('unexpected header line');
end
line = readline(fid);

while ~strcmp(line, 'end_header')
  line = readline(fid);
end

offset = ftell(fid);
fclose(fid);

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% the file continues with a section of data, which can be ascii or binary
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

switch format

  case 'ascii 1.0'

    fid = fopen_or_error(fn, 'rt');
    fseek(fid, offset, 'cof');
    dat = fscanf(fid,'%f',[numel(prop), nvert])';
    for j=1:length(prop)
      vert.(prop(j).name) = dat(:,j);
    end
    face = zeros(nface,0);
    num = zeros(nface,1);
    for i=1:nface
      % each polygon can have a different number of elements
      num(i) = fscanf(fid,'%f',1);
      face(i,1:num(i)) = fscanf(fid,'%f',num(i));
    end
    fclose(fid);

  case 'binary_little_endian 1.0'

    fid = fopen_or_error(fn, 'rb', 'l');
    fseek(fid, offset, 'cof');

    dat = zeros(nvert,length(prop));
    for i=1:nvert
      for j=1:length(prop)
        dat(i,j) = fread(fid, 1, prop(j).format);
      end % for each property
    end % for each vertex
    for j=1:length(prop)
      switch prop(j).format
        % the format can be one of the following: char uchar short ushort int uint float double int8 uint8 int16 uint16 int32 uint32 float32 float64
        case 'char'
          vert.(prop(j).name) = uint8(dat(:,j));
        case 'uchar'
          vert.(prop(j).name) = uint8(dat(:,j));
        case 'short'
          vert.(prop(j).name) = int16(dat(:,j));
        case 'ushort'
          vert.(prop(j).name) = uint16(dat(:,j));
        otherwise
          vert.(prop(j).name) = dat(:,j);
      end
    end
    clear dat;

    face = zeros(nface,0);
    num  = zeros(nface,1);
    for i=1:nface
      % each polygon can have a different number of elements
      num(i) = fread(fid, 1, 'uint8');
      face(i,1:num(i)) = fread(fid, num(i), 'int32');
    end
    fclose(fid);

  case 'binary_big_endian 1.0'
    % this is exactly the same as the code above, except that the file is opened as big endian

    fid = fopen_or_error(fn, 'rb', 'b');
    fseek(fid, offset, 'cof');

    dat = zeros(nvert,length(prop));
    for i=1:nvert
      for j=1:length(prop)
        dat(i,j) = fread(fid, 1, prop(j).format);
      end % for each property
    end % for each vertex
    for j=1:length(prop)
      switch prop(j).format
        % the format can be one of the following: char uchar short ushort int uint float double int8 uint8 int16 uint16 int32 uint32 float32 float64
        case 'char'
          vert.(prop(j).name) = uint8(dat(:,j));
        case 'uchar'
          vert.(prop(j).name) = uint8(dat(:,j));
        case 'short'
          vert.(prop(j).name) = int16(dat(:,j));
        case 'ushort'
          vert.(prop(j).name) = uint16(dat(:,j));
        otherwise
          vert.(prop(j).name) = dat(:,j);
      end
    end
    clear dat;

    face = zeros(nface,0);
    num  = zeros(nface,1);
    for i=1:nface
      % each polygon can have a different number of elements
      num(i) = fread(fid, 1, 'uint8');
      face(i,1:num(i)) = fread(fid, num(i), 'int32');
    end
    fclose(fid);

  otherwise
    ft_error('unsupported format');
end % switch

% each polygon can have a different number of elements
% mark all invalid entries with nan
if any(num<size(face,2))
  for i=1:nface
    face(i,(num(i)+1):end) = nan;
  end
end % if

if numel(face)>0
  % MATLAB indexes start at 1, inside the file they start at 0
  face = face+1;
end

end % function read_ply

function line = readline(fid)
% read the next line from the ascii header, skip all comment lines
line = fgetl(fid);
while strncmp(line, 'comment', 7)
  line = fgetl(fid);
end
end % function readline


% [INPUT]
% ds = A structure representing the dataset.
% sn = A string representing the serial number of the result file.
% temp = A string representing the full path to the Excel spreadsheet used as template for the result file.
% out = A string representing the full path to the Excel spreadsheet to which the results are written, eventually replacing the previous ones.
% bw = An integer [21,252] representing the dimension of each rolling window (optional, default=252).
% bws = An integer [1,10] representing the number of steps between each rolling window (optional, default=10).
% fevd = A string representing the FEVD type used by the variance decomposition (optional, default='G'):
%   - 'G' for generalized FEVD;
%   - 'O' for orthogonal FEVD.
% lags = An integer [1,3] representing the number of lags of the VAR model used by the variance decomposition (optional, default=2).
% h = An integer [1,10] representing the prediction horizon used by the variance decomposition (optional, default=4).
% analyze = A boolean that indicates whether to analyse the results and display plots (optional, default=false).
%
% [OUTPUT]
% result = A structure representing the original dataset inclusive of intermediate and final calculations.
% stopped = A boolean that indicates whether the process has been stopped through user input.

function [result,stopped] = run_spillover(varargin)

    persistent ip;

    if (isempty(ip))
        ip = inputParser();
        ip.addRequired('ds',@(x)validateattributes(x,{'struct'},{'nonempty'}));
        ip.addRequired('sn',@(x)validateattributes(x,{'char'},{'nonempty' 'size' [1 NaN]}));
        ip.addRequired('temp',@(x)validateattributes(x,{'char'},{'nonempty' 'size' [1 NaN]}));
        ip.addRequired('out',@(x)validateattributes(x,{'char'},{'nonempty' 'size' [1 NaN]}));
        ip.addOptional('bw',252,@(x)validateattributes(x,{'double'},{'real' 'finite' 'integer' '>=' 21 '<=' 252 'scalar'}));
        ip.addOptional('bws',10,@(x)validateattributes(x,{'double'},{'real' 'finite' 'integer' '>=' 1 '<=' 10 'scalar'}));
        ip.addOptional('fevd','G',@(x)any(validatestring(x,{'G' 'O'})));
        ip.addOptional('lags',2,@(x)validateattributes(x,{'double'},{'real' 'finite' 'integer' '>=' 1 '<=' 3 'scalar'}));
        ip.addOptional('h',4,@(x)validateattributes(x,{'double'},{'real' 'finite' 'integer' '>=' 1 '<=' 10 'scalar'}));
        ip.addOptional('analyze',false,@(x)validateattributes(x,{'logical'},{'scalar'}));
    end

    ip.parse(varargin{:});

    ipr = ip.Results;
    ds = validate_dataset(ipr.ds,'Spillover');
    sn = ipr.sn;
    temp = validate_template(ipr.temp);
    out = validate_output(ipr.out);
    bw = ipr.bw;
    bws = ipr.bws;
    fevd = ipr.fevd;
    lags = ipr.lags;
    h = ipr.h;
    analyze = ipr.analyze;

    nargoutchk(1,2);

    [result,stopped] = run_spillover_internal(ds,sn,temp,out,bw,bws,fevd,lags,h,analyze);

end

function [result,stopped] = run_spillover_internal(ds,sn,temp,out,bw,bws,fevd,lags,h,analyze)

    result = [];
    stopped = false;
    e = [];

    indices = unique([1:bws:ds.T ds.T]);
    ds = initialize(ds,sn,bw,bws,indices,fevd,lags,h);

    rng(double(bitxor(uint16('T'),uint16('B'))));
    cleanup_1 = onCleanup(@()rng('default'));

    bar = waitbar(0,'Initializing spillover measures...','CreateCancelBtn',@(src,event)setappdata(gcbf(),'Stop',true));
    setappdata(bar,'Stop',false);
    cleanup_2 = onCleanup(@()delete(bar));

    pause(1);
    waitbar(0,bar,'Calculating spillover measures...');
    pause(1);

    try

        windows = extract_rolling_windows(ds.Returns,ds.BW);
        windows = windows(indices);
        windows_len = numel(windows);

        futures(1:windows_len) = parallel.FevalFuture;
        futures_max = 0;
        futures_results = cell(windows_len,1);

        for i = 1:windows_len
           futures(i) = parfeval(@main_loop,1,windows{i},ds.FEVD,ds.Lags,ds.H);
        end

        for i = 1:windows_len
            if (getappdata(bar,'Stop'))
                stopped = true;
                break;
            end

            [future_index,value] = fetchNext(futures);
            futures_results{future_index} = value;

            futures_max = max([future_index futures_max]);
            waitbar((futures_max - 1) / windows_len,bar);

            if (getappdata(bar,'Stop'))
                stopped = true;
                break;
            end
        end

    catch e
    end

    try
        cancel(futures);
    catch
    end

    if (~isempty(e))
        delete(bar);
        rethrow(e);
    end

    if (stopped)
        delete(bar);
        return;
    end

    pause(1);
    waitbar(1,bar,'Finalizing spillover measures...');
    pause(1);

    try
        ds = finalize(ds,futures_results);
    catch e
        delete(bar);
        rethrow(e);
    end

    pause(1);
    waitbar(1,bar,'Writing spillover measures...');
    pause(1);

    try
        write_results(ds,temp,out);
        delete(bar);
    catch e
        delete(bar);
        rethrow(e);
    end

    if (analyze)
        analyze_result(ds);
    end

    result = ds;

end

%% PROCESS

function ds = initialize(ds,sn,bw,bws,indices,fevd,lags,h)

    n = ds.N;
    t = ds.T;

    ds.Result = 'Spillover';
    ds.ResultDate = now(); %#ok<TNOW1>
    ds.ResultAnalysis = @(ds)analyze_result(ds);
    ds.ResultSerial = sn;

    ds.BW = bw;
    ds.BWS = bws;
    ds.FEVD = fevd;
    ds.H = h;
    ds.Lags = lags;
    ds.Windows = numel(indices);
    ds.WindowsIndices = indices;

    all_label = [' (' fevd ', H=' num2str(ds.H) ', LAGS=' num2str(ds.Lags) ')'];

    ds.LabelsIndicatorsSimple = {'SI'};
    ds.LabelsIndicators = {['SI' all_label]};

    ds.LabelsSheetsSimple = {'From' 'To' 'Net' 'Indicators'};
    ds.LabelsSheets = {['From' all_label] ['To' all_label] ['Net' all_label] 'Indicators'};

    ds.VarianceDecompositions = cell(t,1);

    ds.SpilloversFrom = NaN(t,n);
    ds.SpilloversTo = NaN(t,n);
    ds.SpilloversNet = NaN(t,n);

    ds.Indicators = NaN(t,numel(ds.LabelsIndicators));

    ds.ComparisonReferences = {'Indicators' [] strcat({'SP-'},ds.LabelsIndicatorsSimple)};

end

function window_results = main_loop(r,fevd,lags,h)

    window_results = struct();

    vd = variance_decomposition(r,fevd,lags,h);
    window_results.VarianceDecomposition = vd;

    [sf,st,sn,si] = spillover_metrics(vd);
    window_results.SpilloversFrom = sf;
    window_results.SpilloversTo = st;
    window_results.SpilloversNet = sn;
    window_results.SI = si;

end

function ds = finalize(ds,results)

    window_indices = ds.WindowsIndices;

    for i = 1:numel(results)
        result = results{i};
        index = window_indices(i);

        ds.VarianceDecompositions{index} = result.VarianceDecomposition;

        ds.SpilloversFrom(index,:) = result.SpilloversFrom;
        ds.SpilloversTo(index,:) = result.SpilloversTo;
        ds.SpilloversNet(index,:) = result.SpilloversNet;

        ds.Indicators(index) = result.SI;
    end

    if (ds.BWS > 1)
        n = ds.N;
        t = ds.T;
        x = ds.DatesNum;

        nan_indices = ~ismember((1:t).',window_indices);
        step_indices = find(nan_indices);
        step_indices_len = numel(step_indices);

        vd = ds.VarianceDecompositions;
        vd(cellfun(@isempty,vd)) = {NaN(n,n)};

        for i = 1:n
            for j = 1:n
                vd_ij = cellfun(@(vdf)vdf(i,j),vd);
                vd_ij(nan_indices) = spline(x(~nan_indices),vd_ij(~nan_indices),x(nan_indices));

                for k = 1:step_indices_len
                    step_index = step_indices(k);

                    vd_k = vd{step_index};
                    vd_k(i,j) = vd_ij(step_index);
                    vd{step_index} = vd_k;
                end
            end
        end

        for i = 1:step_indices_len
            step_index = step_indices(i);

            vd_i = vd{step_index};
            vd_i = bsxfun(@rdivide,vd_i,sum(vd_i,2));
            ds.VarianceDecompositions{step_index} = vd_i;

            [sf,st,sn,si] = spillover_metrics(vd_i);
            ds.SpilloversFrom(step_index,:) = sf;
            ds.SpilloversTo(step_index,:) = st;
            ds.SpilloversNet(step_index,:) = sn;
            ds.Indicators(step_index,1) = si;
        end
    end

    ds.SpilloversFrom = distress_data(ds.SpilloversFrom,ds.Defaults);
    ds.SpilloversTo = distress_data(ds.SpilloversTo,ds.Defaults);
    ds.SpilloversNet = distress_data(ds.SpilloversNet,ds.Defaults);

end

function write_results(ds,temp,out)

    [out_path,~,~] = fileparts(out);

    try
        if (exist(out_path,'dir') ~= 7)
            mkdir(out_path);
        end

        if (exist(out,'file') == 2)
            delete(out);
        end
    catch
        error('A system I/O error occurred while writing the results.');
    end

    copy_result = copyfile(temp,out,'f');

    if (copy_result == 0)
        error('The output file could not be created from the template file.');
    end

    dates_str = cell2table(ds.DatesStr,'VariableNames',{'Date'});

    for i = 1:(numel(ds.LabelsSheetsSimple) - 1)
        sheet = ds.LabelsSheetsSimple{i};
        measure = ['Spillovers' strrep(sheet,' ','')];

        tab = [dates_str array2table(ds.(measure),'VariableNames',ds.FirmNames)];
        writetable(tab,out,'FileType','spreadsheet','Sheet',sheet,'WriteRowNames',true);
    end

    tab = [dates_str array2table(ds.Indicators,'VariableNames',strrep(ds.LabelsIndicatorsSimple,' ','_'))];
    writetable(tab,out,'FileType','spreadsheet','Sheet',ds.LabelsSheetsSimple{end},'WriteRowNames',true);

    worksheets_batch(out,ds.LabelsSheetsSimple,ds.LabelsSheets);

end

%% PLOTTING

function analyze_result(ds)

    safe_plot(@(id)plot_index(ds,id));
    safe_plot(@(id)plot_spillovers(ds,id));
    safe_plot(@(id)plot_sequence(ds,id));

end

function plot_index(ds,id)

    si = smooth_data(ds.Indicators(:,1));

    f = figure('Name','Spillover Measures > Spillover Index','Units','normalized','Position',[100 100 0.85 0.85],'Tag',id);

    sub_1 = subplot(1,6,1:5);
    plot(sub_1,ds.DatesNum,si,'Color',[0.000 0.447 0.741]);
    set(sub_1,'XLim',[ds.DatesNum(1) ds.DatesNum(end)],'XTickLabelRotation',45);
    set(sub_1,'XGrid','on','YGrid','on');
    title(sub_1,ds.LabelsIndicators{1});

    if (ds.MonthlyTicks)
        date_ticks(sub_1,'x','mm/yyyy','KeepLimits','KeepTicks');
    else
        date_ticks(sub_1,'x','yyyy','KeepLimits');
    end

    sub_2 = subplot(1,6,6);
    boxplot(sub_2,si,'Notch','on','Symbol','k.');
    set(findobj(f,'type','line','Tag','Median'),'Color','g');
    set(findobj(f,'-regexp','Tag','\w*Whisker'),'LineStyle','-');
    set(sub_2,'TickLength',[0 0],'XTick',[],'XTickLabels',[]);

    figure_title(f,'Spillover Index');

    maximize_figure(f);

end

function plot_spillovers(ds,id)

    from = smooth_data(ds.SpilloversFrom);
    from = bsxfun(@rdivide,from,sum(from,2,'omitnan'));
    from(isnan(from)) = 0;

    to = smooth_data(ds.SpilloversTo);
    to = bsxfun(@rdivide,to,sum(to,2,'omitnan'));
    to(isnan(to)) = 0;

    net = smooth_data(ds.SpilloversNet);
    net = [min(net,[],2,'omitnan') max(net,[],2,'omitnan')];

    f = figure('Name','Spillover Measures > Spillovers','Units','normalized','Position',[100 100 0.85 0.85],'Tag',id);

    sub_1 = subplot(2,2,1);
    area(sub_1,ds.DatesNum,from,'EdgeColor',[0.000 0.447 0.741],'FaceColor','none');
    set(sub_1,'XLim',[ds.DatesNum(1) ds.DatesNum(end)],'XTickLabelRotation',45);
    set(sub_1,'YLim',[0 1],'YTick',0:0.2:1,'YTickLabels',arrayfun(@(x)sprintf('%.f%%',x),(0:0.2:1) .* 100,'UniformOutput',false));
    title(sub_1,'Spillovers From Others');

    sub_2 = subplot(2,2,3);
    area(sub_2,ds.DatesNum,to,'EdgeColor',[0.000 0.447 0.741],'FaceColor','none');
    set(sub_2,'XLim',[ds.DatesNum(1) ds.DatesNum(end)],'XTickLabelRotation',45);
    set(sub_2,'YLim',[0 1],'YTick',0:0.2:1,'YTickLabels',arrayfun(@(x)sprintf('%.f%%',x),(0:0.2:1) .* 100,'UniformOutput',false));
    title(sub_2,'Spillovers To Others');

    sub_3 = subplot(2,2,[2 4]);
    fill(sub_3,[ds.DatesNum; flipud(ds.DatesNum)],[net(:,1); fliplr(net(:,2))],[0.65 0.65 0.65],'EdgeColor','none','FaceAlpha',0.35);
    hold on;
        plot(sub_3,ds.DatesNum,mean(net,2),'Color',[0.000 0.447 0.741]);
        plot(sub_3,ds.DatesNum,zeros(ds.T,1),'Color',[1.000 0.400 0.400]);
    hold off;
    set(sub_3,'XLim',[ds.DatesNum(1) ds.DatesNum(end)],'XTickLabelRotation',45);
    set(sub_3,'YLim',[-1 1],'YTick',-1:0.2:1,'YTickLabels',arrayfun(@(x)sprintf('%.f%%',x),(-1:0.2:1) .* 100,'UniformOutput',false));
    set(sub_3,'XGrid','on','YGrid','on');
    title(sub_3,'Net Spillovers');

    if (ds.MonthlyTicks)
        date_ticks([sub_1 sub_2 sub_3],'x','mm/yyyy','KeepLimits','KeepTicks');
    else
        date_ticks([sub_1 sub_2 sub_3],'x','yyyy','KeepLimits');
    end

    figure_title(f,['Spillovers (' ds.FEVD ', H=' num2str(ds.H) ', LAGS=' num2str(ds.Lags) ')']);

    maximize_figure(f);

end

function plot_sequence(ds,id)

    n = ds.N;
    t = ds.T;
    dn = ds.DatesNum;
    mt = ds.MonthlyTicks;

    from_all = smooth_data(ds.SpilloversFrom);
    to_all = smooth_data(ds.SpilloversTo);
    net_all = smooth_data(ds.SpilloversNet);

    data = [repmat({dn},1,n); mat2cell(from_all,t,ones(1,n)); mat2cell(to_all,t,ones(1,n)); mat2cell(net_all,t,ones(1,n))];

    plots_title = [repmat({'From'},1,n); repmat({'To'},1,n); repmat({'Net'},1,n)];

    x_limits = [dn(1) dn(end)];

    ft = [from_all to_all];
    y_limits_from = [0 1];
    y_limits_to = plot_limits(max(max(ft)),0.1,0);
    y_limits_to(2) = ceil(y_limits_to(2) * 10) / 10;
    y_limits_net = [-1 1];

    y_ticks_from = 0:0.2:1;
    y_ticks_to = 0:0.2:y_limits_to(2);
    y_ticks_net = -1:0.2:1;
    y_tick_labels = @(x)sprintf('%.f%%',x * 100);

    core = struct();

    core.N = n;
    core.Data = data;
    core.Function = @(subs,data)plot_function(subs,data);

    core.OuterTitle = 'Spillover Measures > Spillovers Time Series';
    core.InnerTitle = 'Spillovers Time Series';
    core.SequenceTitles = ds.FirmNames;

    core.PlotsAllocation = [3 1];
    core.PlotsSpan = {1 2 3};
    core.PlotsTitle = plots_title;

    core.XDates = {mt mt mt};
    core.XGrid = {true true true};
    core.XLabel = {[] [] []};
    core.XLimits = {x_limits x_limits x_limits};
    core.XRotation = {45 45 45};
    core.XTick = {[] [] []};
    core.XTickLabels = {[] [] []};

    core.YGrid = {true true true};
    core.YLabel = {[] [] []};
    core.YLimits = {y_limits_from y_limits_to y_limits_net};
    core.YRotation = {[] [] []};
    core.YTick = {y_ticks_from y_ticks_to y_ticks_net};
    core.YTickLabels = {y_tick_labels y_tick_labels y_tick_labels};

    sequential_plot(core,id);

    function plot_function(subs,data)

        x = data{1};
        from = data{2};
        to = data{3};
        net = data{4};

        d = find(isnan(net),1,'first');

        if (isempty(d))
            xd = [];
        else
            xd = x(d) - 1;
        end

		sub_1 = subs(1);
        plot(sub_1,x,from,'Color',[0.000 0.447 0.741]);
        if (~isempty(xd))
            hold(sub_1,'on');
                plot(sub_1,[xd xd],get(sub_1,'YLim'),'Color',[1.000 0.400 0.400]);
            hold(sub_1,'off');
        end

		sub_2 = subs(2);
        plot(sub_2,x,to,'Color',[0.000 0.447 0.741]);
        hold(sub_2,'on');
            plot(sub_2,x,ones(numel(x),1),'Color',[1.000 0.400 0.400]);
            if (~isempty(xd))
                plot(sub_2,[xd xd],get(sub_2,'YLim'),'Color',[1.000 0.400 0.400]);
            end
        hold(sub_2,'off');

		sub_3 = subs(3);
        plot(sub_3,x,net,'Color',[0.000 0.447 0.741]);
        hold(sub_3,'on');
            plot(sub_3,x,zeros(numel(x),1),'Color',[1.000 0.400 0.400]);
            if (~isempty(xd))
                plot(sub_3,[xd xd],get(sub_3,'YLim'),'Color',[1.000 0.400 0.400]);
            end
        hold(sub_3,'off');

    end

end

%% VALIDATION

function out = validate_output(out)

    [path,name,extension] = fileparts(out);

    if (~strcmpi(extension,'.xlsx'))
        out = fullfile(path,[name extension '.xlsx']);
    end

end

function temp = validate_template(temp)

    sheets = {'From' 'To' 'Net' 'Indicators'};
    file_sheets = validate_xls(temp,'T');

    if (~all(ismember(sheets,file_sheets)))
        error(['The template must contain the following sheets: ' sheets{1} sprintf(', %s',sheets{2:end}) '.']);
    end

    worksheets_batch(temp,sheets);

end


function gpcf = gpcf_mask(varargin)
%GPCF_MASK  Create a mask covariance function
%
%  Description
%    GPCF = GPCF_MASK('PARAM1',VALUE1,'PARAM2,VALUE2,...)
%    creates a mask covariance function structure in
%    which the named parameters have the specified values. Any
%    unspecified parameters are set to default values.
%
%    GPCF = GPCF_MASK(GPCF,'PARAM1',VALUE1,'PARAM2,VALUE2,...)
%    modify a covariance function structure with the named
%    parameters altered with the specified values.
%
%    Mask covariance function returns correlation 1 if input
%    values X_i and X_j are both non zero and 0 otherwise.
%
%    Parameters for mask covariance function
%      selectedVariables - vector defining which inputs are used
%
%  See also
%    GP_SET, GPCF_*, PRIOR_*, MEAN_*

% Copyright (c) 2007-2010 Jarno Vanhatalo
% Copyright (c) 2008-2010 Jaakko Riihim�ki
% Copyright (c) 2010 Aki Vehtari

% This software is distributed under the GNU General Public
% License (version 3 or later); please refer to the file
% License.txt, included with the software, for details.

  ip=inputParser;
  ip.FunctionName = 'GPCF_MASK';
  ip.addOptional('gpcf', [], @isstruct);
  ip.addParamValue('selectedVariables',[], ...
                   @(x) isempty(x) || (isvector(x) && all(x>0)));
  ip.parse(varargin{:});
  gpcf=ip.Results.gpcf;

  if isempty(gpcf)
    % Initialize a covariance function structure
    init=true;
    gpcf.type = 'gpcf_mask';
  else
    % Modify a covariance function structure
    if ~isfield(gpcf,'type') && ~isequal(gpcf.type,'gpcf_mask')
      error('First argument does not seem to be a valid covariance function structure')
    end
    init=false;
  end

  if ~ismember('selectedVariables',ip.UsingDefaults)
    if ~isempty(ip.Results.selectedVariables)
      gpcf.selectedVariables = ip.Results.selectedVariables;
    elseif isfield(gpcf,'selectedVariables')
      gpcf=rmfield(gpcf,'selectedVariables');
    end
  end
  if init
    gpcf.fh.pak = @gpcf_mask_pak;
    gpcf.fh.unpak = @gpcf_mask_unpak;
    gpcf.fh.lp = @gpcf_mask_lp;
    gpcf.fh.lpg = @gpcf_mask_lpg;
    gpcf.fh.cfg = @gpcf_mask_cfg;
    gpcf.fh.ginput = @gpcf_mask_ginput;
    gpcf.fh.cov = @gpcf_mask_cov;
    gpcf.fh.trcov  = @gpcf_mask_trcov;
    gpcf.fh.trvar  = @gpcf_mask_trvar;
    gpcf.fh.recappend = @gpcf_mask_recappend;
  end

end

function [w,s] = gpcf_mask_pak(gpcf, w)
%GPCF_MASK_PAK  Combine GP covariance function parameters into
%              one vector.
%
%  Description
%    W = GPCF_MASK_PAK(GPCF) takes a covariance function
%    structure GPCF and combines the covariance function
%    parameters and their hyperparameters into a single row
%    vector W. This is a mandatory subfunction used for
%    example in energy and gradient computations.
%
%       w = []
%
%  See also
%    GPCF_MASK_UNPAK

  w = []; s = {};
end

function [gpcf, w] = gpcf_mask_unpak(gpcf, w)
%GPCF_MASK_UNPAK  Sets the covariance function parameters into
%                the structure
%
%  Description
%    [GPCF, W] = GPCF_MASK_UNPAK(GPCF, W) takes a covariance
%    function structure GPCF and a parameter vector W, and
%    returns a covariance function structure identical to the
%    input, except that the covariance parameters have been set
%    to the values in W. Deletes the values set to GPCF from W
%    and returns the modified W. This is a mandatory subfunction
%    used for example in energy and gradient computations.
%
%    Assignment is inverse of
%       w = []
%
%  See also
%   GPCF_MASK_PAK

end

function lp = gpcf_mask_lp(gpcf)
%GPCF_MASK_LP  Evaluate the energy of prior of covariance function parameters
%
%  Description
%    LP = GPCF_MASK_LP(GPCF) takes a covariance function
%    structure GPCF and returns log(p(th)), where th collects the
%    parameters. This is a mandatory subfunction used for example
%    in energy computations.
%
%  See also
%    GPCF_MASK_PAK, GPCF_MASK_UNPAK, GPCF_MASK_LPG, GP_E

  lp = 0;

end

function lpg = gpcf_mask_lpg(gpcf)
%GPCF_MASK_LPG  Evaluate gradient of the log prior with respect
%               to the parameters.
%
%  Description
%    LPG = GPCF_MASK_LPG(GPCF) takes a covariance function
%    structure GPCF and returns LPG = d log (p(th))/dth, where th
%    is the vector of parameters. This is a mandatory subfunction
%    used for example in gradient computations.
%
%  See also
%    GPCF_MASK_PAK, GPCF_MASK_UNPAK, GPCF_MASK_LP, GP_G

  lpg = [];

end

function DKff = gpcf_mask_cfg(gpcf, x, x2, mask,i1)
%GPCF_MASK_CFG  Evaluate gradient of covariance function
%               with respect to the parameters.
%
%  Description
%    DKff = GPCF_MASK_CFG(GPCF, X) takes a covariance function
%    structure GPCF, a matrix X of input vectors and returns
%    DKff, the gradients of covariance matrix Kff = k(X,X) with
%    respect to th (cell array with matrix elements). This is a
%    mandatory subfunction used in gradient computations.
%
%    DKff = GPCF_MASK_CFG(GPCF, X, X2) takes a covariance function
%    structure GPCF, a matrix X of input vectors and returns
%    DKff, the gradients of covariance matrix Kff = k(X,X2) with
%    respect to th (cell array with matrix elements). This subfunction
%    is needed when using sparse approximations (e.g. FIC).
%
%    DKff = GPCF_MASK_CFG(GPCF, X, [], MASK) takes a covariance
%    function structure GPCF, a matrix X of input vectors and
%    returns DKff, the diagonal of gradients of covariance matrix
%    Kff = k(X,X2) with respect to th (cell array with matrix
%    elements). This subfunction is needed when using sparse
%    approximations (e.g. FIC).
%
%    DKff = GPCF_MASK_CFG(GPCF, X, X2, [], i) takes a covariance function
%    structure GPCF, a matrix X of input vectors and returns
%    DKff, the gradients of covariance matrix Kff = k(X,X2), or k(X,X)
%    if X2 is empty, with respect to ith hyperparameter. This subfunction
%    is needed when using memory save option in gp_set.
%
%  See also
%    GPCF_MASK_PAK, GPCF_MASK_UNPAK, GPCF_MASK_LP, GP_G

  DKff = {};

end

function [DKff, lpg]  = gpcf_mask_ginput(gpcf, x, x2, i1)
%GPCF_MASK_GINPUT  Evaluate gradient of covariance function with
%                  respect to x.
%
%  Description
%    DKff = GPCF_MASK_GINPUT(GPCF, X) takes a covariance function
%    structure GPCF, a matrix X of input vectors and returns
%    DKff, the gradients of covariance matrix Kff = k(X,X) with
%    respect to X (cell array with matrix elements). This subfunction
%    is needed when computing gradients with respect to inducing
%    inputs in sparse approximations.
%
%    DKff = GPCF_MASK_GINPUT(GPCF, X, X2) takes a covariance
%    function structure GPCF, a matrix X of input vectors
%    and returns DKff, the gradients of covariance matrix Kff =
%    k(X,X2) with respect to X (cell array with matrix elements).
%    This subfunction is needed when computing gradients with
%    respect to inducing inputs in sparse approximations.
%
%    DKff = GPCF_MASK_GINPUT(GPCF, X, X2, i) takes a covariance
%    function structure GPCF, a matrix X of input vectors
%    and returns DKff, the gradients of covariance matrix Kff =
%    k(X,X2), or k(X,X) if X2 is empty, with respect to ith covariate
%    in X. This subfunction is needed when using memory saving option
%    in gp_set.
%
%  See also
%   GPCF_MASK_PAK, GPCF_MASK_UNPAK, GPCF_MASK_LP, GP_G

  [n, m] =size(x);

  if nargin == 2 || isempty(x2)
    ii1 = 0;
    for i=1:m
      for j = 1:n
        ii1 = ii1 + 1;
        DKff{ii1} = zeros(n);
        lpg(ii1) = 0;
      end
    end
  elseif nargin == 3 || nargin == 4
    ii1 = 0;
    for i=1:m
      for j = 1:n
        ii1 = ii1 + 1;
        DKff{ii1} = zeros(n, size(x2,1));
        lpg(ii1) = 0;
      end
    end
  end
  if nargin==4
    DKff=DKff{1};
  end
end

function C = gpcf_mask_cov(gpcf, x1, x2, varargin)
%GP_MASK_COV  Evaluate covariance matrix between two input vectors
%
%  Description
%    C = GP_MASK_COV(GP, TX, X) takes in covariance function of a
%    Gaussian process GP and two matrixes TX and X that contain
%    input vectors to GP. Returns covariance matrix C. Every
%    element ij of C contains covariance between inputs i in TX
%    and j in X. This is a mandatory subfunction used for example in
%    prediction and energy computations.
%
%  See also
%    GPCF_MASK_TRCOV, GPCF_MASK_TRVAR, GP_COV, GP_TRCOV

  if isempty(x2)
    x2=x1;
  end
  [n1,m1]=size(x1);
  [n2,m2]=size(x2);

  if m1~=m2
    error('the number of columns of X1 and X2 has to be same')
  end

  C=repmat(true,n1,n2);
  if isfield(gpcf, 'selectedVariables')
    for j = 1:length(gpcf.selectedVariables)
      jj=gpcf.selectedVariables(j);
      C = C & bsxfun(@and,x1(:,jj)~=0,x2(:,jj)'~=0);
    end
  else
    for j = 1:m1
      C = C & bsxfun(@and,x1(:,j)~=0,x2(:,j)'~=0);
    end
  end
  C=double(C);

end

function C = gpcf_mask_trcov(gpcf, x)
%GP_MASK_TRCOV  Evaluate training covariance matrix of inputs
%
%  Description
%    C = GP_MASK_TRCOV(GP, TX) takes in covariance function of a
%    Gaussian process GP and matrix TX that contains training
%    input vectors. Returns covariance matrix C. Every element ij
%    of C contains covariance between inputs i and j in TX. This
%    is a mandatory subfunction used for example in prediction
%    and energy computations.
%
%  See also
%    GPCF_MASK_COV, GPCF_MASK_TRVAR, GP_COV, GP_TRCOV

  [n,m]=size(x);

  C=repmat(true,n,n);
  if isfield(gpcf, 'selectedVariables')
    for j = 1:length(gpcf.selectedVariables)
      jj=gpcf.selectedVariables(j);
      C = C & bsxfun(@and,x(:,jj)~=0,x(:,jj)'~=0);
    end
  else
    for j = 1:m
      C = C & bsxfun(@and,x(:,j)~=0,x(:,j)'~=0);
    end
  end
  C=double(C);

end

function C = gpcf_mask_trvar(gpcf, x)
%GP_MASK_TRVAR  Evaluate training variance vector
%
%  Description
%    C = GP_MASK_TRVAR(GPCF, TX) takes in covariance function of a
%    Gaussian process GPCF and matrix TX that contains training
%    inputs. Returns variance vector C. Every element i of C
%    contains variance of input i in TX. This is a mandatory
%    subfunction used for example in prediction and energy
%    computations.
%
%  See also
%    GPCF_MASK_COV, GP_COV, GP_TRCOV

  [n,m]=size(x);
  C=true(n,1);
  if isfield(gpcf, 'selectedVariables')
    for j = 1:length(gpcf.selectedVariables)
      jj=gpcf.selectedVariables(j);
      C = C & x(:,jj)~=0;
    end
  else
    for j = 1:m
      C = C & x(:,j)~=0;
    end
  end
  C=double(C);

end

function reccf = gpcf_mask_recappend(reccf, ri, gpcf)
%RECAPPEND  Record append
%
%  Description
%    RECCF = GPCF_MASK_RECAPPEND(RECCF, RI, GPCF) takes a
%    covariance function record structure RECCF, record index RI
%    and covariance function structure GPCF with the current MCMC
%    samples of the parameters. Returns RECCF which contains
%    all the old samples and the current samples from GPCF.
%    This subfunction is needed when using MCMC sampling (gp_mc).
%
%  See also
%    GP_MC and GP_MC -> RECAPPEND

  if nargin == 2
    % Initialize the record
    reccf.type = 'gpcf_mask';

    % Initialize parameters
    reccf.coeffSigma2= [];

    % Set the function handles
    reccf.fh.pak = @gpcf_mask_pak;
    reccf.fh.unpak = @gpcf_mask_unpak;
    reccf.fh.lp = @gpcf_mask_lp;
    reccf.fh.lpg = @gpcf_mask_lpg;
    reccf.fh.cfg = @gpcf_mask_cfg;
    reccf.fh.cov = @gpcf_mask_cov;
    reccf.fh.trcov  = @gpcf_mask_trcov;
    reccf.fh.trvar  = @gpcf_mask_trvar;
    reccf.fh.recappend = @gpcf_mask_recappend;
  else
    % Append to the record
    if isfield(gpcf, 'selectedVariables')
      reccf.selectedVariables = gpcf.selectedVariables;
    end
  end
end