6

Consider having to compute some value using an operation that might not be defined for certain values, like e.g. the tangent function. To avoid raising exceptions, I tried using pgfmath's ifthenelse(<expression>, <true code>, <false code>), like in the following example:

\documentclass[tikz, border=2mm]{standalone}
\usepackage{xifthen}

\begin{document}

\begin{tikzpicture}
    \foreach \D [count=\C] in {85,...,95}
    {   \pgfmathsetmacro{\goodAngle}{ifthenelse(or(\D<=86, \D>=94), 1, 0)}
        \pgfmathsetmacro{\Tan}{ifthenelse(\goodAngle == 1, tan(\D), 0)}
        %\ifthenelse{\goodAngle = 1}
        %   {   \pgfmathsetmacro{\Tan}{tan(\D)}}
        %   {   \pgfmathsetmacro{\Tan}{0}}
        \node[right] at (0,-\C*0.7) {\D$^{\circ}$, \goodAngle, \Tan};
    }
\end{tikzpicture}

\end{document}

I expected this to only compute the tangent of the given angle if it was previousely determinded to be a good angle (it would have been sufficient to exclude just 90°, but here I excluded anles from 87° to 93°), but it appears to be evaluating both expressions which obviousely fails for 90°. The result is the same when doing it in "one step", that is without computing the \goodAngle first, as well as using the shorthand notation for ifthenelse (which is <expression> ? <true code> : <false code>). To check that it's not my thinking that is flawed, I used the \ifthenelse macro from the xifthen package, which, as expected, only evaluates the correct expression. If one uncomments the three commented lines and comments out the line above like this

\begin{tikzpicture}
    \foreach \D [count=\C] in {85,...,95}
    {   \pgfmathsetmacro{\goodAngle}{ifthenelse(or(\D<=86, \D>=94), 1, 0)}
        %\pgfmathsetmacro{\Tan}{ifthenelse(\goodAngle == 1, tan(\D), 0)}
        \ifthenelse{\goodAngle = 1}
            {   \pgfmathsetmacro{\Tan}{tan(\D)}}
            {   \pgfmathsetmacro{\Tan}{0}}
        \node[right] at (0,-\C*0.7) {\D$^{\circ}$, \goodAngle, \Tan};
    }
\end{tikzpicture}

one obtains the expected result:

enter image description here

This behaviour is not limited to the tangent function, it also emerges for e.g. simple division by zero, like in

\begin{tikzpicture}
    \foreach \D [count=\C] in {-5,...,5}
    {   \pgfmathsetmacro{\goodValue}{ifthenelse(or(\D<=-2, \D>=2), 1, 0)}
        \pgfmathsetmacro{\byZero}{ifthenelse(\goodValue == 1, 1/\D, -1)}
        %\ifthenelse{\goodValue = 1}
        %   {   \pgfmathsetmacro{\byZero}{1/\D}}
        %   {   \pgfmathsetmacro{\byZero}{-1}}
        \node[right] at (0,-\C*0.7) {\D, \goodValue, \byZero};
    }
\end{tikzpicture}

Is this expected behaviour (I doubt it), or is this a bug? Are there facilities in pgfmath who work like a try ... except statement, or even better, a non-Pokémon exception approch that lets me catch specific error types, like a try ... except ErrorType1 ... except ErrorTypeN ... finally construct? If not, does something like this exist at all in the world of TeX, and is it even possible?

3

At least for the moment, the answer seems to be "It just does."

Fortunately, one can find ways around it. pgfplots is capable of handling values like infinity or undefines expressions. For instance one can define custom functions like "sinc" or "bounded tan" and they will work fine with pgfmath's ifthenelse:

\pgfmathdeclarefunction{sincf}{1}%
{\pgfmathparse{(abs(#1)<0.01) ? 1 : sin(pi*#1 r)/(pi*#1)}%
}

\pgfmathdeclarefunction{tanny}{1}%
{\pgfmathparse{abs(tan(#1 r))> 1.345 ? 0 : tan(#1 r)}%
}

\begin{tikzpicture}
    \begin{axis}
        \addplot[domain=-2*pi:2*pi, samples=100, red] {sincf(x)};
        \addplot[domain=-2*pi:2*pi, samples=1000, blue] {tanny(x)};
    \end{axis}  
\end{tikzpicture}

enter image description here

But even without taking manually care of "problematic" values, pgfplot can handle such values by either interrupting the plot at such points, or connecting the graph to the next valid point:

\begin{tikzpicture}
    \begin{axis}
    [   restrict y to domain=-1.345:1.345,
    ]
        \addplot[domain=-2*pi:2*pi, samples=1000, blue, unbounded coords=jump] {tan(x/pi*180)};
        \addplot[domain=-2*pi:2*pi, samples=1000, red, unbounded coords=discard] {tan(x/pi*180-90)};
    \end{axis}  
\end{tikzpicture}

enter image description here


However, if one wants to compute some lengths to use for instance as a distance in a drawing, things become complicated. In the following one would expect 19 vertical lines, the center one being of length zero. ( Warning: the following code fails! Do not use! )

\begin{tikzpicture}
    \foreach \x in {0,10,...,180}
    { \pgfmathsetmacro{\Length}{\x==90 ? 0 : tan(\x)}
        \draw (\x/18,0) -- ++ (0,\Length);
    }
\end{tikzpicture}

Due to pgfmath evaluating both alternatives this will raise an error (a zero division error, as tan(x)=sin(x)/cos(x) and cos(90°) = 0). In such cases one will have to use an alternative method of excluding problematic points.

The first alternative would be to use \ifnum<n1><relation><n2> <true expression> \else <false expression \fi>. The previous use case can then be fixed as follows:

\begin{tikzpicture}
    \foreach \x in {0,10,...,180}
    { \pgfmathsetmacro{\Length}{\ifnum\x=90 0 \else tan(\x)\fi}
        \draw (\x/18,0) -- ++ (0,\Length);
    }
\end{tikzpicture}

It is also possible to use an external package like xifthen, which provides a function \ifthenelse{<test>}{<true expression>}{false expression}. It can not be used inside the argument of pgfmath's macro assignment, instead one has to write the macro assignment in both expressions:

\begin{tikzpicture}
    \foreach \x in {0,10,...,180}
    { \ifthenelse{\x=90}
            {   \pgfmathsetmacro{\Length}{0}}
            {   \pgfmathsetmacro{\Length}{tan(\x)}}
        \draw (\x/18,0) -- ++ (0,\Length);
    }
\end{tikzpicture}

Both these alternatives have a big downside: they work only for checks on integers. If non-integer values are used, one can use a combination way. As pgfmath is capable of handling tests with floating points, it is used to determine whether or not the argument is problematic or not and a variable is set to an integer value. Then one of the alternatives above is used.

If in the above example the angles are transformed to radiant, one can for example do the following:

\begin{tikzpicture}
    \foreach \x in {0,10,...,180}
    { \pgfmathsetmacro{\RadAngle}{\x/180*pi}
        \pgfmathsetmacro{\GoodAngle}{abs(\RadAngle-pi/2) > 0.01 ? 1 : 0}
        \pgfmathsetmacro{\Length}{\ifnum\GoodAngle=1 tan(\RadAngle*180/pi)\else 0\fi}
        \draw (\x/18,0) -- ++ (0,\Length);
    }
\end{tikzpicture}

All three workarounds yield the same output:

enter image description here


The complete code for all examples:

\documentclass[tikz, border=2mm]{standalone}
\usepackage{pgfplots}
\pgfplotsset{compat=1.12}
\usepackage{xifthen}

\pgfmathdeclarefunction{sincf}{1}%
{\pgfmathparse{(abs(#1)<0.01) ? 1 : sin(pi*#1 r)/(pi*#1)}%
}

\pgfmathdeclarefunction{tanny}{1}%
{\pgfmathparse{abs(tan(#1 r))> 1.345 ? 0 : tan(#1 r)}%
}

\begin{document}

\begin{tikzpicture}
    \begin{axis}
        \addplot[domain=-2*pi:2*pi, samples=100, red] {sincf(x)};
        \addplot[domain=-2*pi:2*pi, samples=1000, blue] {tanny(x)};
    \end{axis}  
\end{tikzpicture}

\begin{tikzpicture}
    \begin{axis}
    [   restrict y to domain=-1.345:1.345,
    ]
        \addplot[domain=-2*pi:2*pi, samples=1000, blue, unbounded coords=jump] {tan(x/pi*180)};
        \addplot[domain=-2*pi:2*pi, samples=1000, red, unbounded coords=discard] {tan(x/pi*180-90)};
    \end{axis}  
\end{tikzpicture}

% DOES NOT WORK; DO NOT USE!
%
%\begin{tikzpicture}
%   \foreach \x in {0,10,...,180}
%   { \pgfmathsetmacro{\Length}{\x==90 ? 0 : tan(\x)}
%       \draw (\x/18,0) -- ++ (0,\Length);
%   }
%\end{tikzpicture}

\begin{tikzpicture}
    \foreach \x in {0,10,...,180}
    { \pgfmathsetmacro{\Length}{\ifnum\x=90 0 \else tan(\x)\fi}
        \draw (\x/18,0) -- ++ (0,\Length);
    }
\end{tikzpicture}

\begin{tikzpicture}
    \foreach \x in {0,10,...,180}
    { \ifthenelse{\x=90}
            {   \pgfmathsetmacro{\Length}{0}}
            {   \pgfmathsetmacro{\Length}{tan(\x)}}
        \draw (\x/18,0) -- ++ (0,\Length);
    }
\end{tikzpicture}

\begin{tikzpicture}
    \foreach \x in {0,10,...,180}
    { \pgfmathsetmacro{\RadAngle}{\x/180*pi}
        \pgfmathsetmacro{\GoodAngle}{abs(\RadAngle-pi/2) > 0.01 ? 1 : 0}
        \pgfmathsetmacro{\Length}{\ifnum\GoodAngle=1 tan(\RadAngle*180/pi)\else 0\fi}
        \draw (\x/18,0) -- ++ (0,\Length);
    }
\end{tikzpicture}

\end{document}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.