22

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

10
  • If changing the domain to -10:10 fixes it, why not? Aug 22 at 0:12
  • 2
    New bug? Interesting! I would just chalk it up to the poor dancing bear having problems. Aug 22 at 0:20
  • 1
    Must be fpu's problem. I see tanh(6.656) = 1Y1.2e0].
    – Symbol 1
    Aug 22 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
    Aug 22 at 2:50
  • 4
    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. Aug 22 at 14:48
17

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 now 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. Aug 22 at 21:50
  • Could tanh be made a default function within pgfmath-xfp? Aug 22 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 ;) Aug 23 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. Aug 23 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
    Aug 25 at 7:04
37

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

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.