[Tex/LaTex] Problems with vertical lines in algorithmicx

algorithmicxalgorithms

I use algorithmicx with vertical lines. And the code is working good:

\documentclass{article}

\usepackage{algorithm} 
\usepackage{algpseudocode}

% Vertical line
\makeatletter
\def\therule{\makebox[\algorithmicindent][l]{\hspace*{.5em}\vrule height .75\baselineskip depth .25\baselineskip}}%

\newtoks\therules% Contains rules
\therules={}% Start with empty token list
\def\appendto#1#2{\expandafter#1\expandafter{\the#1#2}}% Append to token list
\def\gobblefirst#1{% Remove (first) from token list
  #1\expandafter\expandafter\expandafter{\expandafter\@gobble\the#1}}%
\def\LState{\State\unskip\the\therules}% New line-state
\def\pushindent{\appendto\therules\therule}%
\def\popindent{\gobblefirst\therules}%
\def\printindent{\unskip\the\therules}%
\def\printandpush{\printindent\pushindent}%
\def\popandprint{\popindent\printindent}%

%      ***      DECLARED LOOPS      ***
% (from algpseudocode.sty)
\algdef{SE}[WHILE]{While}{EndWhile}[1]
  {\printandpush\algorithmicwhile\ #1\ \algorithmicdo}
  {\popandprint\algorithmicend\ \algorithmicwhile}%
\algdef{SE}[FOR]{For}{EndFor}[1]
  {\printandpush\algorithmicfor\ #1\ \algorithmicdo}
  {\popandprint\algorithmicend\ \algorithmicfor}%
\algdef{S}[FOR]{ForAll}[1]
  {\printindent\algorithmicforall\ #1\ \algorithmicdo}%
\algdef{SE}[LOOP]{Loop}{EndLoop}
  {\printandpush\algorithmicloop}
  {\popandprint\algorithmicend\ \algorithmicloop}%
\algdef{SE}[REPEAT]{Repeat}{Until}
  {\printandpush\algorithmicrepeat}[1]
  {\popandprint\algorithmicuntil\ #1}%
\algdef{SE}[IF]{If}{EndIf}[1]
  {\printandpush\algorithmicif\ #1\ \algorithmicthen}
  {\popandprint\algorithmicend\ \algorithmicif}%
\algdef{C}[IF]{IF}{ElsIf}[1]
  {\popandprint\pushindent\algorithmicelse\ \algorithmicif\ #1\ \algorithmicthen}%
\algdef{Ce}[ELSE]{IF}{Else}{EndIf}
  {\popandprint\pushindent\algorithmicelse}%
\algdef{SE}[PROCEDURE]{Procedure}{EndProcedure}[2]
   {\printandpush\algorithmicprocedure\ \textproc{#1}\ifthenelse{\equal{#2}{}}{}{(#2)}}%
   {\popandprint\algorithmicend\ \algorithmicprocedure}%
\algdef{SE}[FUNCTION]{Function}{EndFunction}[2]
   {\printandpush\algorithmicfunction\ \textproc{#1}\ifthenelse{\equal{#2}{}}{}{(#2)}}%
   {\popandprint\algorithmicend\ \algorithmicfunction}%

\makeatother

\begin{document}

\begin{algorithm}
  \caption{Examples}\label{alg:Examples}
  \begin{algorithmic}[1]
  \LState $X=45$
  \For{\textbf{от} $i=0$ \textbf{до} 5}
  \LState $X=X-2$
  \LState \Call {Find}{$X$}
  \While{$Y_2<5$}

  \If{$quality\ge 9$}
  \LState $a\gets perfect$
  \ElsIf{$quality\ge 7$}
  \LState $a\gets good$
  \ElsIf{$quality\ge 5$}
  \LState $a\gets medium$
  \ElsIf{$quality\ge 3$}
  \LState $a\gets bad$
  \Else
  \LState $a\gets unusable$
  \EndIf

  \EndWhile
  \LState \Return $X$
  \EndFor
  \end{algorithmic}
\end{algorithm}


\end{document}

a busy cat http://harrix.org/2013-04-24_151430.png

  1. I want to translate commands to my language. For common commands the translation does not break the vertical line.

     \algrenewcommand\algorithmicwhile{\textbf{BlaBlaWhile}}
     \algrenewcommand\algorithmicif{\textbf{BlaBlaIf}}
    

But when I translate the end of the team structures, broken vertical line.

     \algrenewtext{EndWhile}{\textbf{BlaBlaEndWhile}}
     \algrenewtext{ElsIf}{\textbf{BlaBlaElsIf }}

Code:

    \documentclass{article}

    \usepackage{algorithm} 
    \usepackage{algpseudocode}

    % Vertical line
    \makeatletter
    \def\therule{\makebox[\algorithmicindent][l]{\hspace*{.5em}\vrule height .75\baselineskip depth .25\baselineskip}}%

    \newtoks\therules% Contains rules
    \therules={}% Start with empty token list
    \def\appendto#1#2{\expandafter#1\expandafter{\the#1#2}}% Append to token list
    \def\gobblefirst#1{% Remove (first) from token list
      #1\expandafter\expandafter\expandafter{\expandafter\@gobble\the#1}}%
    \def\LState{\State\unskip\the\therules}% New line-state
    \def\pushindent{\appendto\therules\therule}%
    \def\popindent{\gobblefirst\therules}%
    \def\printindent{\unskip\the\therules}%
    \def\printandpush{\printindent\pushindent}%
    \def\popandprint{\popindent\printindent}%

    %      ***      DECLARED LOOPS      ***
    % (from algpseudocode.sty)
    \algdef{SE}[WHILE]{While}{EndWhile}[1]
      {\printandpush\algorithmicwhile\ #1\ \algorithmicdo}
      {\popandprint\algorithmicend\ \algorithmicwhile}%
    \algdef{SE}[FOR]{For}{EndFor}[1]
      {\printandpush\algorithmicfor\ #1\ \algorithmicdo}
      {\popandprint\algorithmicend\ \algorithmicfor}%
    \algdef{S}[FOR]{ForAll}[1]
      {\printindent\algorithmicforall\ #1\ \algorithmicdo}%
    \algdef{SE}[LOOP]{Loop}{EndLoop}
      {\printandpush\algorithmicloop}
      {\popandprint\algorithmicend\ \algorithmicloop}%
    \algdef{SE}[REPEAT]{Repeat}{Until}
      {\printandpush\algorithmicrepeat}[1]
      {\popandprint\algorithmicuntil\ #1}%
    \algdef{SE}[IF]{If}{EndIf}[1]
      {\printandpush\algorithmicif\ #1\ \algorithmicthen}
      {\popandprint\algorithmicend\ \algorithmicif}%
    \algdef{C}[IF]{IF}{ElsIf}[1]
      {\popandprint\pushindent\algorithmicelse\ \algorithmicif\ #1\ \algorithmicthen}%
    \algdef{Ce}[ELSE]{IF}{Else}{EndIf}
      {\popandprint\pushindent\algorithmicelse}%
    \algdef{SE}[PROCEDURE]{Procedure}{EndProcedure}[2]
       {\printandpush\algorithmicprocedure\ \textproc{#1}\ifthenelse{\equal{#2}{}}{}{(#2)}}%
       {\popandprint\algorithmicend\ \algorithmicprocedure}%
    \algdef{SE}[FUNCTION]{Function}{EndFunction}[2]
       {\printandpush\algorithmicfunction\ \textproc{#1}\ifthenelse{\equal{#2}{}}{}{(#2)}}%
       {\popandprint\algorithmicend\ \algorithmicfunction}%

    \makeatother

    \begin{document}

    \algrenewcommand\algorithmicwhile{\textbf{BlaBlaWhile}}
    \algrenewcommand\algorithmicif{\textbf{BlaBlaIf}}

    \algrenewtext{EndWhile}{\textbf{BlaBlaEndWhile}}
    \algrenewtext{ElsIf}{\textbf{BlaBlaElsIf }}

    \begin{algorithm}
      \caption{Examples}\label{alg:Examples}
      \begin{algorithmic}[1]
      \LState $X=45$
      \For{\textbf{от} $i=0$ \textbf{до} 5}
      \LState $X=X-2$
      \LState \Call {Find}{$X$}
      \While{$Y_2<5$}

      \If{$quality\ge 9$}
      \LState $a\gets perfect$
      \ElsIf{$quality\ge 7$}
      \LState $a\gets good$
      \ElsIf{$quality\ge 5$}
      \LState $a\gets medium$
      \ElsIf{$quality\ge 3$}
      \LState $a\gets bad$
      \Else
      \LState $a\gets unusable$
      \EndIf

      \EndWhile
      \LState \Return $X$
      \EndFor
      \end{algorithmic}
    \end{algorithm}


    \end{document}

a busy cat http://harrix.org/2013-04-24_153318.png

How to fix it?

  1. When I use a big formula with a height greater than the height line, vertical line breaks.

    \documentclass{article}
    
    \usepackage{algorithm} 
    \usepackage{algpseudocode}
    
    \usepackage{amsthm,amsfonts,amsmath,amssymb,amscd} 
    
    % Vertical line
    \makeatletter
    \def\therule{\makebox[\algorithmicindent][l]{\hspace*{.5em}\vrule height .75\baselineskip depth .25\baselineskip}}%
    
    \newtoks\therules% Contains rules
    \therules={}% Start with empty token list
    \def\appendto#1#2{\expandafter#1\expandafter{\the#1#2}}% Append to token list
    \def\gobblefirst#1{% Remove (first) from token list
      #1\expandafter\expandafter\expandafter{\expandafter\@gobble\the#1}}%
    \def\LState{\State\unskip\the\therules}% New line-state
    \def\pushindent{\appendto\therules\therule}%
    \def\popindent{\gobblefirst\therules}%
    \def\printindent{\unskip\the\therules}%
    \def\printandpush{\printindent\pushindent}%
    \def\popandprint{\popindent\printindent}%
    
    %      ***      DECLARED LOOPS      ***
    % (from algpseudocode.sty)
    \algdef{SE}[WHILE]{While}{EndWhile}[1]
      {\printandpush\algorithmicwhile\ #1\ \algorithmicdo}
      {\popandprint\algorithmicend\ \algorithmicwhile}%
    \algdef{SE}[FOR]{For}{EndFor}[1]
      {\printandpush\algorithmicfor\ #1\ \algorithmicdo}
      {\popandprint\algorithmicend\ \algorithmicfor}%
    \algdef{S}[FOR]{ForAll}[1]
      {\printindent\algorithmicforall\ #1\ \algorithmicdo}%
    \algdef{SE}[LOOP]{Loop}{EndLoop}
      {\printandpush\algorithmicloop}
      {\popandprint\algorithmicend\ \algorithmicloop}%
    \algdef{SE}[REPEAT]{Repeat}{Until}
      {\printandpush\algorithmicrepeat}[1]
      {\popandprint\algorithmicuntil\ #1}%
    \algdef{SE}[IF]{If}{EndIf}[1]
      {\printandpush\algorithmicif\ #1\ \algorithmicthen}
      {\popandprint\algorithmicend\ \algorithmicif}%
    \algdef{C}[IF]{IF}{ElsIf}[1]
      {\popandprint\pushindent\algorithmicelse\ \algorithmicif\ #1\ \algorithmicthen}%
    \algdef{Ce}[ELSE]{IF}{Else}{EndIf}
      {\popandprint\pushindent\algorithmicelse}%
    \algdef{SE}[PROCEDURE]{Procedure}{EndProcedure}[2]
       {\printandpush\algorithmicprocedure\ \textproc{#1}\ifthenelse{\equal{#2}{}}{}{(#2)}}%
       {\popandprint\algorithmicend\ \algorithmicprocedure}%
    \algdef{SE}[FUNCTION]{Function}{EndFunction}[2]
       {\printandpush\algorithmicfunction\ \textproc{#1}\ifthenelse{\equal{#2}{}}{}{(#2)}}%
       {\popandprint\algorithmicend\ \algorithmicfunction}%
    \makeatother
    
    \begin{document}
    
    \begin{algorithm}
      \caption{Examples}\label{alg:Examples}
      \begin{algorithmic}[1]
      \LState $X=45$
      \For{\textbf{от} $i=0$ \textbf{до} 5}
      \LState $X=X-2$
      \LState \Call {Find}{$X$}
      \While{$Y_2<5$}
    
      \If{$quality\ge 9$}
      \LState $a\gets perfect$
      \ElsIf{$quality\ge 7$}
      \LState Try
      \begin{flalign*}
        &\left( \begin{array}{c} X_{B} \\ f_B\left( \bar{x}_{B}\right)  \\ {g_i}_B\left( \bar{x}_B\right) \\ {h_j}_B\left( \bar{x}_B\right) \\ ParametersOfBinaryGA\end{array}\right)\\
        &=ConvertingIntoBinaryGA\left( \begin{array}{c} X \\ f\left( \bar{x}\right) \\ g_i\left( \bar{x}\right) \\ h_j\left( \bar{x}\right) \\ ParametersOfBinaryGA \\ ParametersOfConvertingIntoBinaryGA \end{array}\right)
        \end{flalign*}
      \ElsIf{$quality\ge 5$}
      \LState $a\gets medium$
      \ElsIf{$quality\ge 3$}
      \LState $a\gets bad$
      \Else
      \LState $\left( \begin{array}{c} X_{B} \\ f_B\left( \bar{x}_{B}\right)  \\ {g_i}_B\left( \bar{x}_B\right) \\ {h_j}_B\left( \bar{x}_B\right) \\ ParametersOfBinaryGA\end{array}\right)$
      \EndIf
    
      \EndWhile
      \LState \Return $X$
      \EndFor
      \end{algorithmic}
    \end{algorithm}
    
    
    \end{document}
    

a busy cat http://harrix.org/2013-04-24_155014.png

How to fix it?

Best Answer

The indentation issues are answered by a simple extension to the solution to Forced indentation in algorithmicx.

The vertical lines pose a harder problem. One cannot introduce any solution that creates a group where the vertical line should start and ends the group where the vertical line should end, because this breaks algorithmicx. Therefore, the only solution that comes to mind involves tikz and zpos. I have adapted code from marking conditional versions with line in margin for algorithmicx, to cope with nested blocks and being the right distance from the margin. Of course, this needs a few runs to stabilise as it essentially uses tikzmark. However, my solution does not currently work well with matrices (or other lines that are taller), and I cannot see how to fix it (note: based on the answer to Determine height of the line you are in, this is either not easy or impossible, at least without LuaTeX). It also doesn't currently work when the algorithm spans more than one column (some lines are in the wrong place or not drawn at all).

Lastly, instead of working with your code example, I have based this on the one at How to automatically keep the indentation level when a line has to be broken instead, for simplicity.

\documentclass[twocolumn]{article}

\usepackage[paperheight=10cm]{geometry}% to help test the twocolumn behaviour

\usepackage{algpseudocode}

\errorcontextlines\maxdimen



% begin vertical rule patch for algorithmicx
% borrowing from https://tex.stackexchange.com/questions/41956/marking-conditional-versions-with-line-in-margin

\RequirePackage{tikz}
\RequirePackage{zref-abspage}
\RequirePackage{zref-user}
\RequirePackage{tikz}
\RequirePackage{atbegshi}
\usetikzlibrary{calc}
\RequirePackage{tikzpagenodes}
\RequirePackage{etoolbox}

\makeatletter
\newcommand*\ALG@lastblockb{b}
\newcommand*\ALG@lastblocke{e}
\apptocmd{\ALG@beginblock}{%
    %\typeout{beginning block, nesting level \theALG@nested, line \arabic{ALG@line}}%
    \ifx\ALG@lastblock\ALG@lastblockb
        \ifnum\theALG@nested>1\relax\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi{\ALG@tikzborder}{}%
    \fi
    \let\ALG@lastblock\ALG@lastblockb%
}{}{\errmessage{failed to patch}}

\pretocmd{\ALG@endblock}{%
    %\typeout{ending block, nesting level \theALG@nested, line \arabic{ALG@line}}%
    \ifx\ALG@lastblock\ALG@lastblocke
        \addtocounter{ALG@nested}{1}%
        \addtolength\ALG@tlm{\csname ALG@ind@\theALG@nested\endcsname}%
        \ifnum\theALG@nested>1\relax\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi{\endALG@tikzborder}{}%
        \addtolength\ALG@tlm{-\csname ALG@ind@\theALG@nested\endcsname}%
        \addtocounter{ALG@nested}{-1}%
    \fi
    \let\ALG@lastblock\ALG@lastblocke%
}{}{\errmessage{failed to patch}}


\tikzset{ALG@tikzborder/.style={line width=0.5pt,black}}

\newcommand*\currenttextarea{current page text area}

\newcommand*{\updatecurrenttextarea}{%
    \if@twocolumn
        \if@firstcolumn
            \renewcommand*{\currenttextarea}{current page column 1 area}%
        \else
            \renewcommand*{\currenttextarea}{current page column 2 area}%
        \fi
    \else
        \renewcommand*\currenttextarea{current page text area}%
    \fi
}


\newcounter{ALG@tikzborder}
\newcounter{ALG@totaltikzborder}
\newenvironment{ALG@tikzborder}[1][]{%
    % Allow user to overwrite the used style locally
    \ifx&#1&\else
        \tikzset{ALG@tikzborder/.style={#1}}%
    \fi
    \stepcounter{ALG@totaltikzborder}%
    \expandafter\edef\csname ALG@ind@border@\theALG@nested\endcsname{\theALG@totaltikzborder}%
    \setcounter{ALG@tikzborder}{\csname ALG@ind@border@\theALG@nested\endcsname}%
    %\typeout{begin ALG border nesting level=\theALG@nested, tikzborder=\theALG@tikzborder, tlm=\the\ALG@tlm}%
    \tikz[overlay,remember picture] \coordinate (ALG@tikzborder-\theALG@tikzborder);% node {\theALG@tikzborder};% Modified \tikzmark macro
    \zlabel{ALG@tikzborder-begin-\theALG@tikzborder}%
    % Test if end-label is at the same page and draw first half of border if not, from start place to the end of the page
    \ifnum\zref@extract{ALG@tikzborder-begin-\theALG@tikzborder}{abspage}=\zref@extract{ALG@tikzborder-end-\theALG@tikzborder}{abspage} \else
        \updatecurrenttextarea
        \ALG@drawvline{[shift={(0pt,.5\ht\strutbox)}]ALG@tikzborder-\theALG@tikzborder}{\currenttextarea.south east}{\ALG@thistlm}%
        % If it spreads over more than two pages:
        \newcounter{ALG@tikzborderpages\theALG@tikzborder}%
        \setcounter{ALG@tikzborderpages\theALG@tikzborder}{\numexpr-\zref@extract{ALG@tikzborder-begin-\theALG@tikzborder}{abspage}+\zref@extract{ALG@tikzborder-end-\theALG@tikzborder}{abspage}}%
        \ifnum\value{ALG@tikzborderpages\theALG@tikzborder}>1
            \edef\nextcmd{\noexpand\AtBeginShipoutNext{\noexpand\ALG@tikzborderpage{\theALG@tikzborder}{\the\ALG@thistlm}}}%some pages need a border on the whole page
            \nextcmd
        \fi
    \fi
}{%
    \setcounter{ALG@tikzborder}{\csname ALG@ind@border@\theALG@nested\endcsname}%
    %\typeout{end ALG border nesting level=\theALG@nested, tikzborder=\theALG@tikzborder, tlm=\the\ALG@tlm}%
    \tikz[overlay,remember picture] \coordinate (ALG@tikzborder-end-\theALG@tikzborder);% node {\theALG@tikzborder};% Modified \tikzmark macro
    \zlabel{ALG@tikzborder-end-\theALG@tikzborder}%
    % Test if begin-label is at the same page and draw whole border if so, from start place to end place
    \updatecurrenttextarea
    \ifnum\zref@extract{ALG@tikzborder-begin-\theALG@tikzborder}{abspage}=\zref@extract{ALG@tikzborder-end-\theALG@tikzborder}{abspage}\relax
        \ALG@drawvline{[shift={(0pt,.5\ht\strutbox)}]ALG@tikzborder-\theALG@tikzborder}{ALG@tikzborder-end-\theALG@tikzborder}{\ALG@thistlm}%
    % Otherwise draw second half of border, from the top of the page to the end place
    \else
        %\settextarea
        \ALG@drawvline{\currenttextarea.north west}{ALG@tikzborder-end-\theALG@tikzborder}{\ALG@thistlm}%
    \fi
}

\newcommand*{\ALG@drawvline}[3]{%#1=from, #2=to, #3=value of \ALG@tlm/\ALG@thisthm
    \begin{tikzpicture}[overlay,remember picture]
        \draw [ALG@tikzborder]
            let \p0 = (\currenttextarea.north west), \p1=(#1), \p2 = (#2)
             in
            (#3+\fboxsep+.5\pgflinewidth+\x0,\y1+\fboxsep+.5\pgflinewidth)%-\fboxsep-.5\pgflinewidth
             --
            (#3+\fboxsep+.5\pgflinewidth+\x0,\y2-\fboxsep-.5\pgflinewidth)
            %node[midway,anchor=east] {\ALG@tikzbordertext}
        ;
    \end{tikzpicture}%
}

\newcommand{\ALG@tikzborderpage}[2]{%the whole page gets a border, #1=value of \theALG@tikzborder, #2=value of \ALG@tlm/\ALG@thistlm
    \updatecurrenttextarea
    \setcounter{ALG@tikzborder}{#1}%
    \ALG@drawvline{\currenttextarea.north west}{\currenttextarea.south east}{#2}%
    \addtocounter{ALG@tikzborderpages\theALG@tikzborder}{-1}%
    \ifnum\value{ALG@tikzborderpages\theALG@tikzborder}>1
        \AtBeginShipoutNext{\ALG@tikzborderpage{#1}{#2}}%
    \fi
    \vspace{-0.5\baselineskip}% Compensate for the generated extra space at begin of the page. No idea why exactly this happens.
}

\def\ALG@tikzbordertext{\the\ALG@tlm}
\makeatother
% end vertical rule patch for algorithmicx



% continuation indent patch, slightly extended from https://tex.stackexchange.com/questions/78776/forced-indentation-in-algorithmicx to support multiple paragraphs in one block
\RequirePackage{etoolbox}
\makeatletter
\newlength{\ALG@continueindent}
\setlength{\ALG@continueindent}{2em}
\newcommand*{\ALG@customparshape}{\parshape 2 \leftmargin \linewidth \dimexpr\ALG@tlm+\ALG@continueindent\relax \dimexpr\linewidth+\leftmargin-\ALG@tlm-\ALG@continueindent\relax}
\newcommand*{\ALG@customparshapex}{\parshape 1 \dimexpr\ALG@tlm+\ALG@continueindent\relax \dimexpr\linewidth+\leftmargin-\ALG@tlm-\ALG@continueindent\relax}
\apptocmd{\ALG@beginblock}{\ALG@customparshape\everypar{\ALG@customparshapex}}{}{\errmessage{failed to patch}}
\makeatother
% end continuation indent patch


% user level stuff
\usepackage{amsmath}
\usepackage{lipsum}

% example copied from https://tex.stackexchange.com/questions/130199/how-to-automatically-keep-the-indentation-level-when-a-line-has-to-be-broken

% A command for defining assignments within the algorithmic environment
\newcommand{\Let}[2]{\State #1 $\gets$ #2}


\begin{document}

test in second column
\newpage

\begin{algorithmic}[1]
  \Function{test}{$(x, y)$}%
%  \State Some lines extend \raisebox{1ex}{higher} but we don't like that at the beginning of a block (leaves space above the vertical line).
    \Let{$a$}{some math expression}
    \Let{$b$}{some very very long expression that doesn't fit on one line and is
    even longer and longer \par\noindent and a new paragraph with another very very long expression that doesn't fit on one line}
    \For{each $e$ in a list}
        \Let{$l(e)$}{the length of element $e$}
        \If{some condition on $l(e)$}
            \State{run some complex sub-routine and get the result and this
            description is very very long, long indeed...}
            \Let{$a$}{some math expression}
            \Let{$b$}{some very very long expression that doesn't fit on one
            line and is even longer and longer}
            \If{some other condition}
                \Let{$c$}{another math expression}
                \State \(\begin{bmatrix} 1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \end{bmatrix}\)
            \EndIf
        \EndIf
%        \State \lipsum*[1-9]
    \EndFor
  \EndFunction
\end{algorithmic}

\end{document}

I have removed floating structure, to show that the lines still look reasonable when the algorithm takes up multiple pages (uncomment the \lipsum line to see).

The output demonstrates the issues with the tall lines such as those with matrices:

Output