Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Suggestion to comment in the manual on using \ifnum in paths #966

Closed
marmotghost opened this issue Dec 21, 2020 · 17 comments · Fixed by #967
Closed

Suggestion to comment in the manual on using \ifnum in paths #966

marmotghost opened this issue Dec 21, 2020 · 17 comments · Fixed by #967

Comments

@marmotghost
Copy link

This is not a real issue, but a suggestion. It may have to do with a LaTeX kernel change, but I am not really sure (yet I would have thought that this problem wasn't there with some older kernel). Anyway, consider the example

\documentclass[border=3mm,tikz]{standalone} 
\begin{document}
\begin{tikzpicture}
 \path foreach \X in {1,...,6}
 {(\X,0) node[circle,draw](\X){}}
 foreach  \X [evaluate=\X as \Xm using {int(\X-1)}]  in {1,...,6}
 {foreach  \Y in {1,...,6}
 {\ifnum\Y<\X
 \ifnum\Y=\Xm 
   (\Y) edge[->] (\X) 
 \else  
  \ifodd\Y
  (\Y) edge[->,bend left] (\X) 
  \fi
 \fi
 \fi}};
\end{tikzpicture}
\end{document}

On my updated TeXLive2020 installation this yields an error. (I totally understand that this is a stupid way to get the output, it is just a small example.) According to what I find, these errors can be avoided by adding seemingly useless \numexpr "commands". In this example, replacing \ifnum\Y<\X by \ifnum\Y<\numexpr\X does the trick.

\documentclass[border=3mm,tikz]{standalone} 
\begin{document}
\begin{tikzpicture}
 \path foreach \X in {1,...,6}
 {(\X,0) node[circle,draw](\X){}}
 foreach  \X [evaluate=\X as \Xm using {int(\X-1)}]  in {1,...,6}
 {foreach  \Y in {1,...,6}
 {\ifnum\Y<\numexpr\X
 \ifnum\Y=\Xm 
   (\Y) edge[->] (\X) 
 \else  
  \ifodd\Y
  (\Y) edge[->,bend left] (\X) 
  \fi
 \fi
 \fi}};
\end{tikzpicture}
\end{document}

So this "issue" is to propose to add a comment in the manual that informs users about the possibility to potentially fix nested (?) \ifnums by adding \numexpr in places where they do not seem to be required.

@muzimuzhi
Copy link
Member

(yet I would have thought that this problem wasn't there with some older kernel)

Testing on overleaf.com, each of its texlive versions (ranging from 2014 to 2020) raises errors with the first one being

! Package tikz Error: Giving up on this path. Did you forget a semicolon?.

So this at least is not caused by recent changes.

@hmenke
Copy link
Member

hmenke commented Dec 21, 2020

Well, the first question that comes to mind is, why are you doing all of this on a single path? If you just use regular nested \foreach it works fine. Also a lot less likely to trip up the parser.

\documentclass[border=3mm,tikz]{standalone}
\begin{document}
\begin{tikzpicture}
    \foreach \X in {1,...,6} {
        \node[circle,draw] at (\X,0) (\X) {};
    }
    \foreach \X [evaluate=\X as \Xm using {int(\X-1)}]  in {1,...,6} {
        \foreach \Y in {1,...,6} {
            \ifnum\Y<\X
                \ifnum\Y=\Xm
                    \draw (\Y) edge[->] (\X);
                \else
                    \ifodd\Y
                        \draw (\Y) edge[->,bend left] (\X);
                    \fi
                \fi
            \fi
        }
  };
\end{tikzpicture}
\end{document}

What you are observing is just an expansion problem with nested \ifnum which causes an incomplete \if because on a path TikZ expands tokens looking for commands. The gratuitous \numexpr is in fact doing the job of expanding tokens for you while looking for a number and saves you from the incomplete \if.

If you do complicated things an a path make sure that everything is expandable, i.e. just use expandable tests.

\documentclass[border=3mm,tikz]{standalone} 
\usepackage{etoolbox}
\begin{document}
\begin{tikzpicture}
    \path foreach \X in {1,...,6} {%
        (\X,0) node[circle,draw](\X) {}%
    } foreach \X [evaluate=\X as \Xm using {int(\X-1)}]  in {1,...,6} {%
        foreach \Y in {1,...,6} {%
            \ifnumless{\Y}{\X}{%
                \ifnumequal{\Y}{\Xm}{% 
                    (\Y) edge[->] (\X)%
                }{% 
                    \ifnumodd{\Y}{%
                        (\Y) edge[->,bend left] (\X)%
                    }{}%
                }%
            }{}%
        }%
  };
\end{tikzpicture}
\end{document}

@marmotghost
Copy link
Author

I understand that there are many ways of improving the above code (which is why I wrote "I totally understand that this is a stupid way to get the output, it is just a small example."), and that there are tools for the expansion. Yet there are real-world examples in which the nested \ifnums do make sense, yet they are more complicated, so not really suitable for the issues. If you want to tell users to load etoolbox to deal with nested \ifnums, that's also fine, the only purpose of this "issue" is to spare users the frustration of getting unnecessary errors, the more though some may not immediately realize that the nested \ifnums are the "culprit".

@hmenke
Copy link
Member

hmenke commented Dec 21, 2020

Using nested \ifnums never makes sense. Also this nested \ifnum problem has in fact nothing to do with TikZ. This example here “fails” as well in the sense that you get \relax where would have expected an empty control sequence.

\def\X{2}
\edef\foo{%
    \ifnum0<\X
        \ifnum0=\X
            foo%
        \else
            \ifodd\X
                bar%
            \fi
        \fi
    \fi
}
\show\foo

Teaching the intricacies of TeX is outside of the scope of the PGF manual in my opinion. There are better resources for learning TeX elsewhere.

@hmenke
Copy link
Member

hmenke commented Dec 21, 2020

@davidcarlisle Just provided a nice explanation of this issue in the TeX.SX chat: https://chat.stackexchange.com/transcript/message/56505161#56505161

@marmotghost
Copy link
Author

If nested \ifnums never make sense, tikz.code.tex does not make sense. See e.g. \tikz@compute@direction for an example where nested \ifnums do (IMHO) make perfect sense.

@hmenke
Copy link
Member

hmenke commented Dec 21, 2020

Fixed in 17a95e4

@hmenke hmenke closed this as completed Dec 21, 2020
@ilayn
Copy link
Member

ilayn commented Dec 21, 2020

In other words avoid TeX code while TikZ parser is active.

@muzimuzhi
Copy link
Member

This can be fixed by making \relax and the frozen \relax handled by tikz (currently done in \tikz@handle and \tikz@handle@more). This will allow normal TeXniques like ending a conditional by \relax permissible in tikz paths.

\documentclass[border=3mm,tikz]{standalone}
\usepackage{xpatch}

\makeatletter
\edef\tikz@frozen@relax@token{\ifnum0=0\fi}

\def\tikz@handle@next{%
  \afterassignment\tikz@handle\let\pgf@let@token=%
}

\xpatchcmd\tikz@handle@more
  {\let\pgfutil@next=\tikz@expand}
  {%
    \ifx\pgf@let@token\relax
      \let\pgfutil@next=\tikz@handle@next
    \else
      \ifx\pgf@let@token\tikz@frozen@relax@token
        \let\pgfutil@next=\tikz@handle@next
      \else
        \let\pgfutil@next=\tikz@expand
      \fi
    \fi
  }
  {}{\fail}
\makeatother

\begin{document}

\begin{tikzpicture}
  \path
    foreach \X in {1,...,6} { (\X,0) node[circle,draw] (\X) {} }
    foreach \X[evaluate=\X as \Xm using {int(\X-1)}] in {1,...,6}
    {
      foreach \Y in {1,...,6}
      {
        \ifnum\Y<\X\relax % recommended
          \ifnum\Y=\Xm\relax % optional because "(" is not expandable
            (\Y) edge[->] (\X) 
          \else  
            \ifodd\Y
              (\Y) edge[->,bend left] (\X) 
            \fi
          \fi
        \fi
      }
    };
\end{tikzpicture}
\end{document}

image

@hmenke
Copy link
Member

hmenke commented Dec 21, 2020

@muzimuzhi PR please

@muzimuzhi
Copy link
Member

What's your suggestions about the naming and the location (added in which source file) of \tikz@frozen@relax@token and \tikz@handle@next?

@marmotghost
Copy link
Author

@ilayn But why? First of all, TikZ does that internally, and second it works. Now the user will get to read

This however implies that the expansion has to be fully expandable up to the point where it results in a valid path operation.

I am sure that everybody will precisely understand what is meant by an "expandable expansion". Luckily we are living in an expanding universe, and the expansion is even accelerating, so this is all fine. (Anyway, now we have one more reason to forgive users for not reading the manual.)

@ilayn
Copy link
Member

ilayn commented Dec 22, 2020

Because tikz cannot solve all of the world's TeX problems. You have to draw the line somewhere. If they don't read it's on them.

@marmotghost
Copy link
Author

@ilayn I beg to disagree. I never suggested that TikZ should solve all the problems. All I was suggesting is to inform the reader what they may do to make some \ifnums work.

@ilayn
Copy link
Member

ilayn commented Dec 22, 2020

You are not disagreeing or suggesting. You want ifnum to be mentioned in the manual and also make them work.

Sometimes you have to give in if others don't agree with you. We are dealing with such an obscure case that I am having difficulty to actually keep discussing this. If somebody drills down this much then they should better know what they are doing. The statement at the top of the manual "TikZ is parsing don't do funny stuff" should be enough for all possible edge cases. There is a limited maintainer attention and time that can be spent much more valid issues instead of this. I'm pretty sure all of us here answered hundreds of questions using ifnums or other esoteric stuff back in the day. So they can google it instead of arming tikz parser more and more not-so-much-used features.

@hmenke
Copy link
Member

hmenke commented Dec 22, 2020

I have amended the wording, “the expansion has to be fully expandable” was indeed misleading. However, I don't think it is reasonable for users to expect that if they pass broken \ifnum code to the parser that anything sensible will happen. Thanks to @muzimuzhi the path parser is now also able to forward complete nonsense. We'll see whether that is useful.

@marmotghost
Copy link
Author

@ilayn Just one comment: I personally do not care if this will be part of the manual because I found a way that works for me. The only purpose of this request was to provide users information if they get error messages that they may not understand. All I can do is to make suggestions. And. as a matter of fact there is a number of questions in the Q & A sites asking about \ifnums and so on in paths. And actually it can make sense, e.g. if you use the calc syntax and have measured some \x1, say, and want to make further actions depend on this. Or if you use the count (or total) of some name intersectionssyntax. That is to say that there more than enough scenarios in which some \ifnum or \ifdim statements make sense. Example:

\documentclass[tikz,border=3mm]{standalone}
\usetikzlibrary{calc}
\begin{document}
\begin{tikzpicture}[semicircle/.style={to path={let \p1=($(\tikztotarget)-(\tikztostart)$)
            in \ifdim\x1>0pt
              (\tikztostart.north) arc[start angle=180,end angle=0,radius=0.5*\x1]
            \else
              (\tikztostart.south) arc[start angle=0,end angle=-180,radius=-0.5*\x1]
            \fi
			\tikztonodes}}]
 \path foreach \X in {1,...,4} {(\X,0) node[circle,inner sep=1pt,draw](\X){\X}};
 \draw (1) to[semicircle] (3) (4) to[semicircle] (2); 		
\end{tikzpicture}
\end{document}

@pgf-tikz pgf-tikz deleted a comment from marmotghost Dec 24, 2020
hmenke added a commit that referenced this issue Dec 27, 2020
muzimuzhi added a commit to muzimuzhi/pgf that referenced this issue Dec 28, 2020
muzimuzhi added a commit to muzimuzhi/pgf that referenced this issue Jan 4, 2021
muzimuzhi added a commit to muzimuzhi/pgf that referenced this issue Jan 4, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging a pull request may close this issue.

4 participants