2

This is a follow-up question to Declaring a function to be used by pgfplots.

The MWE

\documentclass{article}

\usepackage{pgfplots} 

\usetikzlibrary{arrows.meta}
\pgfplotsset{compat=1.15}

\begin{document}

\begin{tikzpicture}
[
  declare function = {binom(\x,\y) = \x! / \y! / (\x - \y)!;},
  declare function = {binompdf(\x,\y,\z) = binom(\x, \z) * \y^\z * (1 - \y)^(\x - \z);}
]
\begin{axis}
[
  grid = none,
  tick style = {black},
  tick label style = {
    /pgf/number format/use comma,
    /pgf/number format/fixed,
    /pgf/number format/fixed zerofill},
  scaled ticks = false,
  % x axis
  xmin = 0, xmax = 0.01,
  axis x line = middle, x axis line style = -{Stealth},
  xlabel = $p$, xlabel style = {below},
  xtick = {0, 0.001, ..., 0.01}, xticklabels = {},
  extra x ticks = {0.001},
  xticklabel style = {/pgf/number format/precision = 3},
  % y axis
  ymin = 0, ymax = 1.1,
  axis y line = middle, y axis line style = -{Stealth},
  ylabel = $P(X \ge 10)$, ylabel style = {left},
  ytick = {0, 0.1, ..., 1.1}, yticklabels = {},
  extra y ticks = {0.1},
]
\addplot[domain = 0 : 0.01, samples = 100] {0.8};
\addplot[domain = 0 : 0.01, samples = 100] {1 - binompdf(2100, x, 0) - binompdf(2100, x, 1) - binompdf(2100, x, 2) - binompdf(2100, x, 3) - binompdf(2100, x, 4) - binompdf(2100, x, 5) - binompdf(2100, x, 6) - binompdf(2100, x, 7) - binompdf(2100, x, 8) - binompdf(2100, x, 9)};
\end{axis}
\end{tikzpicture}

\end{document}

produces the following result:

Output of the MWE

There are three problems:

  1. The calculation is not very accurate (see the Maple output below). This is probably due to the large intermediate result when calculating 2100!.
  2. LaTeX takes ages to compile the document.
  3. Writing the second \addplot command is quite inconvenient. Is there a way to declare a function for the cumulation?

Maple output

1
  • I have provided an answer which was occasion for me to see how to plug-in another math engine into pgfplots function definition. xintexpr has extended syntax, can work with varying precision but is currently lacking most everything apart square root you need for math. Thus surely an internal solution would be preferable. It is clear that computing binomial by ratio of factorial is not optimal and presumably roof ot problem. Perhaps make a feature request at pgf to get "partial factorial function"? or it exists already? – user4686 Apr 8 '18 at 9:51
4

This addresses all three issues. For 2) to some extent only:

real    0m5.005s
user    0m4.964s
sys     0m0.036s

hmm, actually I had not timed original code. Here it is, but on a computer about 20% slower than the one used for above timing.

real    3m56.351s
user    3m55.698s
sys     0m0.387s

So the speed-up is about 40x, although the plot still needs some seconds.

We use xintexpr:

edit: original answer did some unneeded hack of \pgfmathfloatvalueof. But it can be used directly. I had done some error elsewhere causing me to think it needed a change to make it f-expandable. But expandability in usual sense suffices. Thus the \FVof below is all that is needed to pass over inputs from pgf to \xintfloatexpr.

\documentclass{article}

\usepackage{pgfplots} 

\usetikzlibrary{arrows.meta}
\pgfplotsset{compat=1.15}

% I was very happy to find \pgfmathfloatvalueof in TikZ/pgf manual
\def\FVof(#1){\pgfmathfloatvalueof{#1}}

\usepackage{xintexpr}

\begin{document}

\begin{tikzpicture}
[
% original declarations
%  declare function = {binom(\x,\y) = \x! / \y! / (\x - \y)!;},
%  declare function = {binompdf(\x,\y,\z) = binom(\x, \z) * \y^\z * (1 - \y)^(\x - \z);}
% declaration using xintexpr.sty
  declare function = {cumulbinompdf(\x,\y,\z) = \xintthefloatexpr
   add(binomial(\FVof(\x),i) * \FVof(\y)^i * (1 -\FVof(\y))^(\FVof(\x)- i),
       i = 0..\FVof(\z)
       )
   \relax;}
]
\begin{axis}
[
  grid = none,
  tick style = {black},
  tick label style = {
    /pgf/number format/use comma,
    /pgf/number format/fixed,
    /pgf/number format/fixed zerofill},
  scaled ticks = false,
  % x axis
  xmin = 0, xmax = 0.01,
  axis x line = middle, x axis line style = -{Stealth},
  xlabel = $p$, xlabel style = {below},
  xtick = {0, 0.001, ..., 0.01}, xticklabels = {},
  extra x ticks = {0.001},
  xticklabel style = {/pgf/number format/precision = 3},
  % y axis
  ymin = 0, ymax = 1.1,
  axis y line = middle, y axis line style = -{Stealth},
  ylabel = $P(X \ge 10)$, ylabel style = {left},
  ytick = {0, 0.1, ..., 1.1}, yticklabels = {},
  extra y ticks = {0.1},
]
\addplot[domain = 0 : 0.01, samples = 100] {0.8};
\addplot[domain = 0 : 0.01, samples = 100] {1 - cumulbinompdf(2100, x, 9)};
\end{axis}
\end{tikzpicture}

\end{document}

enter image description here

Caveat: there is a deficiency of the binomial function in \xintfloatexpr which is that it does accept floating point inputs but truncates them to integers. It should round them to integers. Similarly the i = 0..Z construct will if I recall correctly truncate to an integer the Z. It should perhaps round.

In the case at hand, I think the variables are small enough integers so that no rounding errors arises in the pgf handling, so when they reach \xintfloatexpr truncation does give 2100and not 2099per mistake. Not that the difference would show much I guess in the graphics (not thought over).

It also works with

  declare function = {cumulbinompdf(\x,\y,\z) = \xintthefloatexpr
   add(binomial(\FVof{\x},i) * \FVof{\y}^i * (1 -\FVof{\y})^(\FVof{\x}- i),
       i = 0..\FVof{\z}
       )
   \relax;}

and \let\FVof\pgfmathfloatvalueof which is then only an alias. But I found more aesthetic to define above \FVof as macro using parentheses as delimiters, so that no braces are used inside macro definition.

3
  • 1
    a further gain in execution time (roughly from 6s down to 4.5s on my laptop) is obtained by adding a \xintDigits:=6; line before the second \addplot. This tells \xintfloatexpr to do computations with only 6 digits of floating point precision and does not seem to modify the output plot in any visible way. But with \xintDigits:=4; the output is visibly altered; and execution time drops only to about 4s in place of about 4.5s. I would recommend \xintDigits:=8; which is only a bit slower than with 6. But not all time is taken by xintexpr ... – user4686 Apr 8 '18 at 7:45
  • Just impressive. The compuation time of 6s is really acceptable for me, since the code produces a very smooth graph. – Matthias Apr 8 '18 at 15:17
  • At tex.stackexchange.com/a/470203/4686 I experimented also with using xfp's \fpeval, and it works the same. But one can not use it yet for this answer because it does not binomial/factorial afaict. When it will get it, it will be faster because it is coded for only 16 digits of precision, whereas xintexpr has user-settable arbitrary precision to start with. Additionally the internal data of xintexpr for float numbers is very rudimentary (it all started purely as big integer engine) and it is certain some speed gains will be achieved in future in this area. – user4686 Jan 19 '19 at 15:42

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.