9

I am trying to plot this function with lualatex

enter image description here

g.kov
  • 21,864
  • 1
  • 58
  • 95
primeTime
  • 123

4 Answers4

25

This answer has been published as an article in

  • H. Menke, “Tutorial: Using external C libraries with the LuaTeX FFI”, TUGboat, 39 (1), 37–40 (2018)

The PDF is available here: https://tug.org/TUGboat/tb39-1/tb121menke-ffi.pdf

You can use the GNU Scientific Library (GSL) via the FFI (Foreign Function Interface) of LuaJITTeX (and LuaTeX ≥ 1.0.3). The FFI needs the --shell-escape option.

The function you are plotting is a complete Fermi-Dirac integral for which the GSL has functions as well. You are only missing the prefactor with the Gamma function (which I compensate using yet another GSL function). I plot this on top. As you can see the curves match perfectly.

\documentclass{article}

\usepackage{pgfplots}
\pgfplotsset{compat=newest}

\usepackage{luacode}
\begin{luacode*}
local ffi = require("ffi")

ffi.cdef[[
typedef double (*gsl_callback) (double x, void * params);

typedef struct {
  gsl_callback F;
  void * params;
} gsl_function;

typedef void gsl_integration_workspace;

gsl_integration_workspace * gsl_integration_workspace_alloc (size_t n);

void gsl_integration_workspace_free (gsl_integration_workspace * w);

int gsl_integration_qagiu(gsl_function * f, double a, double epsabs, double epsrel, size_t limit,
                          gsl_integration_workspace * workspace, double * result, double * abserr);

double gsl_sf_gamma(double x);
double gsl_sf_fermi_dirac_half(double x);
]]

local gsl = ffi.load("gsl")

local gsl_function = ffi.new("gsl_function")
local result = ffi.new('double[1]')
local abserr = ffi.new('double[1]')

function gsl_qagiu(f, a, epsabs, epsrel, limit)
   local limit = limit or 50
   local epsabs = epsabs or 1e-8
   local epsrel = epsrel or 1e-8

   gsl_function.F = ffi.cast("gsl_callback", f)
   gsl_function.params = nil

   local workspace = gsl.gsl_integration_workspace_alloc(limit)
   gsl.gsl_integration_qagiu(gsl_function, a, epsabs, epsrel, limit, workspace, result, abserr)
   gsl.gsl_integration_workspace_free(workspace)

   gsl_function.F:free()

   return result[0]
end

function F_one_half(eta)
    tex.sprint(gsl_qagiu(function(x) return math.sqrt(x)/(math.exp(x-eta) + 1) end, 0))
end

function F_one_half_builtin(eta)
    tex.sprint(gsl.gsl_sf_gamma(1.5)*gsl.gsl_sf_fermi_dirac_half(eta))
end
\end{luacode*}

\begin{document}

\begin{tikzpicture}[
  declare function={F_one_half(\eta) = \directlua{F_one_half(\eta)};},
  declare function={F_one_half_builtin(\eta) = \directlua{F_one_half_builtin(\eta)};}
  ]
  \begin{axis}[
    use fpu=false, % very important!
    no marks, samples=101,
    xlabel={$\eta$},
    ylabel={$F_{1/2}(\eta)$},
    ]
    \addplot {F_one_half(x)};
    \addplot {F_one_half_builtin(x)};
  \end{axis}
\end{tikzpicture}

\end{document}

enter image description here

Henri Menke
  • 109,596
  • 1
    Never thought such things are possible with latex (LuaTeX)! – yarchik Nov 29 '17 at 22:01
  • If needed, I can elaborate on what is going on in the code. Just ping me. – Henri Menke Nov 30 '17 at 04:47
  • 1
    @HenriMenke: Your answer inspired me to try and understand how the ffi interface works. Here is my attempt to interface with Julia – Aditya Dec 28 '17 at 00:47
  • @Aditya Amazing! I have also submitted this answer with more details to TUGboat. – Henri Menke Dec 28 '17 at 01:07
  • @Aditya Very nice blog article! If you want to parse C headers rather than repeating definitions, have a look at https://github.com/umegaya/ffiex (seems to be pure Lua) – Henri Menke Dec 28 '17 at 01:14
  • @HenriMenke: Thanks for the link to ffiex. I'll try it out. Nice that you are writing a more detailed answer to TUGboat. Would you mind sharing the draft with me? Thanks. – Aditya Dec 28 '17 at 01:39
  • @Aditya I sent it to the email address you use on the ConTeXt mailing list. – Henri Menke Dec 28 '17 at 01:45
  • @HenriMenke: Thanks! BTW, here is another approach for including .h. files: http://lua.2524044.n2.nabble.com/LuaJIT-FFI-purely-using-h-files-and-handling-the-multiple-definition-attempts-td6068068.html. The linked code basically just reads the .h file and uses it as an argument for ffi.cdef. – Aditya Dec 28 '17 at 01:51
  • @Aditya I see. But that will not work with the majority of header file out there, as ffi.cdef does not support any C preprocessor statements. – Henri Menke Dec 28 '17 at 01:56
  • @HenriMenke: I see. Reading more closely, I see that it runs gcc -E on the file (which asks gcc to stop after running the preprocessor stage). Running that on julia.h gives a 44k LOC. Not sure if ffi.cdef will handle that. But it does make it easier to copy paste the function signatures that are needed. – Aditya Dec 28 '17 at 03:47
  • 1
    This stuff is amazing. Kudos to Henri and Aditya for coming up with really clever stuff like this. – Dr Krishnakumar Gopalakrishnan Sep 08 '18 at 12:22
  • @HenriMenke With my up-to-date TeXLive 2019, it does not work anymore. I get [\directlua]:24: could not load library gsl stack traceback: [C]: in function 'ffi.load' [\directlua]:24: in main chunk. \luacode@dbg@exec ...code@maybe@printdbg {#1} #1 }. Do you know if something changed? Note: I do have gsl on my system but I thought it came with luatex anyway. Am I correct? – cjorssen Nov 08 '19 at 12:11
  • @cjorssen No, it doesn't come with LuaTeX. You say gsl is installed but the error message says that gsl can't be found. Try ffi.load("/full/path/to/gsl/dynamic/libray"). – Henri Menke Nov 08 '19 at 23:17
  • If this code does not work, see this answer. – cjorssen Nov 30 '21 at 16:35
7

This function from the GNU Scientific Library. can be also accessed by means of the internal Asymptote module gsl.

Unfortunately, it is not mentioned in the main Asymptote docs among the other available GSL functions, but its name (FermiDiracHalf()) can be deduced from the source files.

// plotFermiDiracHalf.asy
// 
//   run 
//
// asy plotFermiDiracHalf.asy
//
//   to get a stangalone plotFermiDiracHalf.pdf 

settings.tex="lualatex";
import graph; import math; import gsl;
size(9cm);
import fontsize;defaultpen(fontsize(8pt));
texpreamble("\usepackage{lmodern}"+"\usepackage{amsmath}"
+"\usepackage{amsfonts}"+"\usepackage{amssymb}");

real sc=0.5;
add(shift(-11*sc,-2*sc)*scale(sc)*grid(22,19,paleblue+0.2bp));

xaxis("$\eta$",-5.2,5.2,
  RightTicks(Step=1,step=0.5),above=true);
yaxis("$F_{1\!/2}(\eta)$",0,8.2,
  LeftTicks (Step=1,step=0.5,OmitTick(0)),above=true);
pair f(real eta){return (eta, FermiDiracHalf(eta));}
draw(graph(f,-5,4.7),darkblue+0.7bp);

enter image description here

g.kov
  • 21,864
  • 1
  • 58
  • 95
5

PSTricks is very good at numerical integration. Here we make use of package pst-ode to numerically solve this integral function by the RKF45 method.

The same method is used for plotting the error function, erf(x), which is also an integral function. Yet another integral function example: https://tex.stackexchange.com/a/145174 .

First, we re-formulate the function integral as an initial value problem:

Then, we solve the initial value problem by means of \pstODEsolve several times with different η-values to get the solution curve. Note that the precision does not depend on the number of plot points. 201 η-values are sufficient to get a visually smooth curve:

enter image description here

\documentclass[pstricks,border=5pt,12pt]{standalone}
\usepackage{pst-ode,pst-plot}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Fermi-Dirac integral
% #1: eta
% #2: PS variable for result list
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\def\F(#1)#2{%     two output points are enough---v   v---y[0](0) (initial value)
  \pstODEsolve[algebraicAll]{retVal}{y[0]}{0}{80}{2}{0.0}{sqrt(t)/(Euler^(t-#1)+1)}
  %             integration interval t_0---^   ^---"\infty"
  % `retVal' contains [y(eta,0) y(eta,\infty)], i.e. the solution at t=0 and t=\infty.
  %
  %  initialise empty solution list given as arg #2, if necessary
  \pstVerb{/#2 where {pop}{/#2 {} def} ifelse}
  %  From `retVal', we throw away y(eta,0), and append eta and y(eta,\infty) to our solution
  %  list (arg #2):
  \pstVerb{/#2 [#2 #1 retVal exch pop] cvx def}
}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\begin{document}

%solve function integral, 201 plotpoints: -5,-4.95,...,4.95,5
\multido{\nEta=-5.00+0.05}{201}{
  \F(\nEta){diracInt} %result appended to solution list `diracInt'
}

%plot result
\begin{pspicture}(-1.3,-1)(0.2,5)
  \psset{xAxisLabel={$\eta$},yAxisLabel={$F_{1/2}(\eta)$},xAxisLabelPos={c,-1.8},yAxisLabelPos={-1.6,c}}
  \begin{psgraph}[axesstyle=frame,Oy=-1,Ox=-6,Dx=2,Dy=1](-6,-1)(-6,-1)(6,8.5){8cm}{5cm}
  \listplot[linecolor=red]{diracInt}
  \end{psgraph}
\end{pspicture}
\end{document}
AlexG
  • 54,894
3

One can plot the series expansion around zero:

\documentclass[tikz,border=5pt]{standalone}
\usepackage{amsmath}

\begin{document}
\begin{tikzpicture}
  \draw[->] (-.1,0) -- (2.2,0) node[right] {$\eta$};
  \draw[-] (-.05,.7) -- (+.05,.7) node[left] {\tiny$0.7$\text{ }};
  \draw[->] (0,-.1) -- (0,2.2) node[above] {$F_{\frac12}(\eta)$};
  \draw[-] (0,-.05) -- (0,+.05) node[below] {\tiny$0$};
  \draw[scale=1,domain=0:1.5,smooth,variable=\x,blue] plot ({\x},{
    -0.5*pi^(0.5)*(
    0.5*(2^(0.5)-2)*(2.61238)
    +(2^(0.5)-1)*(-1.46035)*\x
    -0.5*(1-2^(1.5))*(-0.207886)*\x^2
    -(1/6)*(1-2^(2.5))*(-0.0254852)*\x^3
    -(1/24)*(1-2^(3.5))*(0.00851693)*\x^4
    )
    });
\end{tikzpicture}
\end{document}

Output:

enter image description here

primeTime
  • 123