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

11
  • 2
    New bug? Interesting! I would just chalk it up to the poor dancing bear having problems. Commented Aug 22, 2021 at 0:20
  • 1
    Must be fpu's problem. I see tanh(6.656) = 1Y1.2e0].
    – Symbol 1
    Commented Aug 22, 2021 at 2:09
  • 3
    I manage to boil this down to (without fpu) \pgfmathdivide@{38.87068}{38.8708}\pgfmathresult = 1.2. Note that the numerator is smaller than the denominator yet the result is > 1.
    – Symbol 1
    Commented Aug 22, 2021 at 2:50
  • 5
    You can't expect accuracy from pgfmath. (The amazing thing about a dancing bear is that it dances at all.) For accuracy, do you calculations using Matlab etc. and store the data in a pgfplots compatible table. Commented Aug 22, 2021 at 14:48
  • 1
    Also note that the Lua backend is only for pgfplots, as far as I can see it's impossible to switch other parts of TikZ to use Lua.
    – user202729
    Commented Dec 28, 2021 at 2:58

2 Answers 2

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

11
  • +1: Is there a difference in using tanh(\x) vs tanh(x)? I was not aware of that if that's the case. Commented Aug 22, 2021 at 21:50
  • Could tanh be made a default function within pgfmath-xfp? Commented Aug 22, 2021 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 ;) Commented Aug 23, 2021 at 5: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. Commented Aug 23, 2021 at 5:45
  • 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
    Commented Aug 25, 2021 at 7:04
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.

11
  • 2
    I will call you Sherlock Holmes from now on. :) Commented Aug 22, 2021 at 5:16
  • 1
    Remark: this bug is easy to trigger if you know what you are looking for. For instance 38.89993/38.9=1.2 and 38.8999/38.9=1.1
    – Symbol 1
    Commented Aug 22, 2021 at 6:59
  • 2
    @Dr.ManuelKuehner To Sherlock Holmes i give my upvoted. :-)
    – Sebastiano
    Commented Aug 22, 2021 at 16:09
  • 1
    @Dr.ManuelKuehner Sure! No pressure =)
    – Symbol 1
    Commented Aug 22, 2021 at 22:11
  • 2
    This reminds me (somewhat) of Scientific Computing SE's What algorithm does (or did?) Excel use for Bessel functions that is discontinuous at x=8? (currently unanswered but some helpful comments)
    – uhoh
    Commented Aug 23, 2021 at 10:39

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .