26

Follow up question see pgfplots: x vs \x.


I have a rather straightforward pgfplots diagram but I do not understand why there is a bump in my tanh function (should be smooth). The domain (and samples) key seems to influence the outcome. Can you explain this behavior and is there a systematic logic to prevent that (or do I need to use a trial-and-error approach)?

\documentclass[tikz,border=1mm]{standalone}
\usepackage{pgfplots}
\pgfplotsset{compat=newest} % Updated after comments, was 1.15 before.

\begin{document}

\begin{tikzpicture} \begin{axis}[ width = 160mm, height = 90mm, axis lines = center, xmin = -10, xmax = 10, ymin = -1, ymax = 1, enlarge x limits = 0.05, enlarge y limits = 0.10, ] \addplot[ color = blue, mark = none, smooth, line width = 1pt, domain = -30:30, % When changed to -10:10 then the bump is not visible. samples = 200, ] {tanh(1*\x)};
%
\end{axis} \end{tikzpicture}

\end{document}

enter image description here

2 Answers2

41

tanh is defined to be sinh/cosh. And the unexpected spike is a result of a bug in the division routine. More precisely, the macro \pgfmathdivide@@ defined in pgfmathfunctions.basic.code line 10 (github link) contains an unwanted behavior.

\pgfmathdivide@@

This macro uses \pgfmath@x (numerator) and \pgfmath@y (denominator) to implement long division. Here is the pseudo code

While (x > δ & y > δ):
    If x < y:
        append and `0` at the end of \pgfmathresult;
        divide y by 10;
    Else: %%% x ≥ y
        compute a:= floor(x/y);
        If a is single digit:
            append that digit at the end of \pgfmathresult;
            subtract y by x * a
        Else: %%% in this case we always have 10 ≤ a < 20
            subtract a by 10
            depending on how many digits \pgfmathresult has, add 10^-d to \pgfmathresult
            append a to the end of \pgfmathresult

You see that the code becomes nasty when x/y ≥ 10. Usually, you would expected that x/y is always less than 10 because the previous y (the y before it was divided by 10) is less than x and the single-digit branch will be executed. So this branch is never executed; it doesn't matter if the code is ugly. But that is not true.

What could go wrong?

Thanks to finite precision, the following could happen

  • (x, y) = (0.00037, 0.00038) so the x < y branch is executed; y is divided by 10.
  • now (x, y) = (0.00037, 0.00003) so the x > y branch is executed; now floor(x/y) = 12 ≥ 10

"Fine," you might say. Since 0.00038 -> 0.00003 only happens when \pgfmathresult already has many digits, adding 0.000012 or 0.00009 doesn't really matter. Except that it sometimes does.

You see, adding 10^-d to \pgfmathresult isn't an easy job because 0.001 + 0.999 includes carrying. So instead of textually manipulating \pgfmathresult, this is done by

  • converting \pgfmathresult to a TeX dimension (\pgfmath@xa),
  • adding 1pt, 0.1pt, ..., or 0.00001pt,
  • and then converting TeX dimension back to text.

The problem is, if \pgfmathresult is 0.99999, then the result of adding 0.00001pt to 0.99999pt is 1pt, not 1.00000pt. Recall that we are in the a ≥ 10 branch and we will append a - 10 at the end of the new \pgfmathresult. So if a = 12, we will get 1.2 as the result, not 1.000002.

Fix

I don't know how to fix this.

Symbol 1
  • 36,855
18

Symbol 1 shows in his answer the root cause of the problem. Maybe missing is the information, that all this (only) happens, when TeX is used as calculation engine. Since Symbol 1 has no idea how to fix this (in TeX) (of course this is no offense) the "fix" is to use another calculation engine ... ;)

% used PGFPlots v1.18.1
\documentclass[border=5pt]{standalone}
\usepackage{pgfmath-xfp}
    % define `tanh` function
    \pgfmxfpdeclarefunction{Tanh}{1}{1 - 2/(exp(2*#1) + 1)}
\usepackage{pgfplots}
    % use this `compat` level or higher to use Lua as calculation engine
    \pgfplotsset{compat=1.12}
\begin{document}
\begin{tikzpicture}
    \begin{axis}[
        domain = 6:7,
        samples = 200,
        no markers,
    ]
        % use TeX as calculation engine
        \addplot {tanh(\x)};
        % use Lua as calculation engine (when compiled with LuaLaTeX of course)
        % (when you compile with pdfLaTeX or use a lower `compat` level than given
        %  in the preamble, this is equivalent to the previous `\addplot`)
        \addplot+ [very thick] {tanh(x)};
        % use xfp as calculation engine
        \addplot+ [thick] {Tanh(x)};
        % use gnuplot as calculation engine
        % (assuming gnuplot is installed, available and it is compiled with
        %  `shell-escape` enabled)
        \addplot gnuplot {tanh(x)};
    \end{axis}
\end{tikzpicture}
\end{document}

image showing the result of above code

Stefan Pinnow
  • 29,535
  • +1: Is there a difference in using tanh(\x) vs tanh(x)? I was not aware of that if that's the case. – Dr. Manuel Kuehner Aug 22 '21 at 21:50
  • Could tanh be made a default function within pgfmath-xfp? – Dr. Manuel Kuehner Aug 22 '21 at 21:54
  • My thought also was that tanh(\x) is the same as tanh(x) but it seems that Lua "doesn't like" "commands", i.e. \<something. But to my experience I never used \x in \addplot calls. Since this is easier to read and easier to type ... But maybe @Symbol1 also has an idea here ;) – Stefan Pinnow Aug 23 '21 at 05:43
  • 1
    Then I think you have to ask the author of the package. But as I understand that it is just a wrapper to make it easy to use the xfp package. So I guess you have to ask the LaTeX3 team if they are willing to add it to xfp. – Stefan Pinnow Aug 23 '21 at 05:45
  • Ok, thanks for your comments. Maybe I ask a question about \x and x, unless I find an explanation in the pgfplots manual. – Dr. Manuel Kuehner Aug 23 '21 at 15:31
  • FYI: I posted a follow-up question, see https://tex.stackexchange.com/questions/611793. – Dr. Manuel Kuehner Aug 23 '21 at 23:21
  • Isn't your definition of Tanh false? At x=0, you obtain infinity. According to https://en.wikipedia.org/wiki/Hyperbolic_functions#Exponential_definitions I would use \pgfmxfpdeclarefunction{Tanh}{1}{(exp(2*#1) -1)/(exp(2*#1) +1)}. But perhaps your definition is very near the real tanh for x between 6 and 7 and faster to calculate? – quark67 Aug 25 '21 at 00:56
  • @quark67, according to the German Wiki and Wolfram Alpha "your" and "my" definition are equal ... – Stefan Pinnow Aug 25 '21 at 06:30
  • 1
    No, at denominator, you have a minus sign ((exp(2*#1) -1)), it must be a plus sign. With your minus sign, at 0 you obtain infinity. You must obtain 0. – quark67 Aug 25 '21 at 07:04
  • @quark67, you are right, I corrected that, but that doesn't change the output shown in the graph. – Stefan Pinnow Aug 25 '21 at 13:00
  • Thanks again for your help, just FYI, I created a follow-up question about the x vs \x topic: https://tex.stackexchange.com/questions/611793. – Dr. Manuel Kuehner Sep 28 '21 at 18:31