Programmer en Lua avec LuaLaTeX : exemples concrets d'utilisation
par Damien Mégy latex lua lualatex maths
Table des matières
Introduction
Premiers exemples : tableaux de valeurs
La première partie de cet article explique comment produire un tableau relativement simple du genre suivant :

Commençons déjà par afficher les entiers et leurs carrés, de sorte à obtenir quelque chose comme ceci :

(Le tableau est mis en forme avec le package booktabs, et on vera plus bas comment afficher «$4$» au lieu de «$4.0$».)
Voici trois façons de parvenir à ce résultat :
\begin{tabular}{cc}\toprule
$n$ & $n^2$ \\ \midrule
\directlua{
for i=1,10 do
tex.print(i .. " & " .. (i^2) .. " \\\\")
end
}
\bottomrule\end{tabular}
Si l'on désire utiliser l'environnement luacode*, on ne peut pas le mettre à l'intérieur du tableau, il faut imprimer le début et la fin du tableau avec Lua :
\begin{luacode*}
tex.print("\\begin{tabular}{cc}\\toprule")
tex.print("$n$ & $n^2$ \\\\ \\midrule")
for i=1,10 do
tex.print(i .. " & " .. (i^2) .. " \\\\")
end
tex.print("\\bottomrule\\end{tabular}")
\end{luacode*}
L'environnement luacode* permet de pouvoir écrire du code Lua valide, d'utilise les commentaires, de pouvoir utiliser les caractères comme % dans le code lua (modulo, f-strings). Dans ce cas précis, il n'est pas nécessaire et la première version est plus courte et efficace, mais il devient rapidement indispensable pour pouvoir écrire du code lua sans se poser de questions sur l'interaction avec TeX.
Dans les exemples précédents, on a imprimé les lignes du tableau ligne par ligne dans la sortie LaTeX. En général, il est conseillé de fabriquer un tableau avec toutes les lignes à écrire, puis d'imprimer toutes les lignes à la fin en une seule fois.
La fonction tex.print accepte non seulement des chaînes de caractères mais aussi une table, donc on peut simplement écrire ceci :
\begin{luacode*}
local lines={}
lines[#lines+1] = "\\begin{tabular}{cc}\\toprule"
lines[#lines+1] = "$n$ & $n^2$ \\\\ \\midrule"
for i=1,10 do
lines[#lines+1] = i .. " & " .. (i^2) .. " \\\\"
end
lines[#lines+1] = "\\bottomrule\\end{tabular}"
-- impression :
tex.print(lines)
\end{luacode*}
Ensuite, plutôt qu'utiliser l'opérateur de concaténation, on peut utiliser string.format() pour imprimer les lignes.
Ceci permet aussi d'imprimer les entiers comme des entiers, sans décimales inutiles, et ceci permet aussi de contrôler combien de décimales on affiche pour les flottants.
Voici un dernier exemple avec plus de valeurs à imprimer :
\begin{luacode*}
tex.print("\\begin{tabular}{crrrr} \\toprule")
tex.print("$n$ & $\\sqrt{n}$ & $n^2$ & $n^3$ & $n^4$ \\\\ \\midrule")
for n=1,6 do
tex.print(
string.format(
"%d & %.2f & %d & %d & %d \\\\",
n, math.sqrt(n), n^2, n^3, n^4
)
)
end
tex.print("\\bottomrule\\end{tabular}")
\end{luacode*}
Ceci produit la table:

Variation : on sépare le calcul des valeurs et l'affichage.
Tout d'abord, dans un bloc \begin{luacode*} :
lines = {}
for n = 1,6 do
lines[#lines+1] = string.format(
"%d & %.2f & %d & %d & %d \\\\",
n, math.sqrt(n), n^2, n^3, n^4
)
end
Ensuite, dans le document LaTeX :
\begin{tabular}{crrrr}\toprule
$n$ & $\sqrt{n}$ & $n^2$ & $n^3$ & $n^4$ \\ \midrule
\directlua{tex.print(lines)}
\bottomrule\end{tabular}
Exemples plus avancés
Les exemples précédents sont facilement faisables sans Lua. Les suivants seraient un peu plus pénibles à coder en TeX, là où leur implémentation en Lua reste très claire à lire.
Diviseurs, décompositions en facteurs premiers
Dans un bloc luacode* :
function divisors(n)
local divisors = {}
for d=1,n do
if n % d == 0 then
divisors[#divisors+1] = d
end
end
return divisors
end
Puis, dans le document latex, définition puis utilisation d'une macro :
\newcommand\listOfDivisors[1]{%
\directlua{tex.sprint(table.concat(divisors(#1),", "))}%
}
Les diviseurs positifs de $60$ sont \listOfDivisors{60}.
(On peut ensuite adapter le code pour gérer les entiers négatifs.)
Prochain nombre premier
Dans un bloc luacode* :
function is_prime(n)
if n < 2 then return false end
for i=2,math.floor(math.sqrt(n)) do
if n % i == 0 then return false end
end
return true
end
function next_prime(n)
while not is_prime(n) do
n = n+1
end
return n
end
Puis, dans le document latex, définition puis utilisation d'une macro :
\newcommand{\nextPrime}[1]{\directlua{tex.sprint(next_prime(#1))}}
Le prochain nombre premier après 1000 est \nextPrime{1000}.
% affiche 1009
Décompositions en facteurs premiers
Les fonctions Lua à l'intérieur d'un bloc luacode* :
function factor(n)
local t, d = {}, 2
while n > 1 do
while n % d == 0 do
t[#t+1] = d
n = n / d
end
d = d + 1
end
return t
end
Puis, dans la suite du document latex:
% Dans le préambule :
\newcommand{\factor}[1]{\directlua{tex.sprint(table.concat(factor(#1),"\\times"))}}
% Ailleurs dans le document :
Décompositions en produit de facteurs premiers :
\begin{itemize}
\item $17 = \factor{17}$.
\item $60 = \factor{60}$. %% affiche 2x2x3x5
\item $720 = \factor{720}$.
\item $160 = \factor{160}$.
\end{itemize}
Remarque : on peut relativement facilement modifier la fonction pour qu'elle affiche $2^2\times 3\times 5$ au lieu de $2\times 2 \times 3 \times 5$.
Calculs de PGCD
Tout d'abord, une fonction de pgcd en Lua dans un bloc luacode* :
function gcd(a,b)
while b ~= 0 do
a, b = b, a % b
end
return math.abs(a)
end
Ensuite, la macro LaTeX puis son utilisation :
\newcommand\mathgcd[2]{
\directlua{tex.sprint(gcd(#1,#2))}
}
Test : le pgcd de $-4$ et $14$ est $\computegcd{-4}{14}$.
% affiche 2
On a nommé la macro \computegcd et non \pgcd ou \gcd car en général il y a déjà un opérateur pgcd ou gcd défini dans la feuille de style avec Declaremathoperator.
Avec très peu de code, on peut alors afficher facilement le tableau suivant qui récapitule tous les pgcd des nombres inférieurs à 15 :

Écriture de nombres en base b
L'objectif ici est de pouvoir écrire dans son fichier texte la chose suivante :
Le nombre $255$ en base $16$ s'écrit \tobase{255}{16}. % affiche "FF"
Le nombre $13$ en base $2$ s'écrit \tobase{13}{2}. % affiche "1101"
Pour cela, on définit la macro LaTeX \tobase{}{} de la façon suivante :
\newcommand{\tobase}[2]{%
\directlua{tex.print(toBase(#1, #2))}%
}
et on définit la fonction Lua toBase(n,b) comme ceci :
function toBase(n, b)
assert(b >= 2 and b <= 36, "La base b doit être entre 2 et 36")
assert(n >= 0, "L'entier n doit être positif")
if n == 0 then
return "0"
end
local digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
local result = ""
while n > 0 do
result = digits:sub((n % b) + 1, (n % b) + 1) .. result
n = n // b
end
return result
end
Ou encore, en version récursive :
local digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
function toBase(n, b)
assert(b >= 2 and b <= 36, "La base b doit être entre 2 et 36")
assert(n >= 0, "L'entier n doit être positif")
if n < b then
return digits:sub(n + 1, n + 1)
end
return toBase(n // b, b)
.. digits:sub((n % b) + 1, (n % b) + 1)
end
Table de nombres premiers jumeaux
Comme dernier exemple, on montre comment obtenir la table suivante, qui affiche les nombres premiers jumeaux inférieurs à N (avec N=100 dans cet exemple):

\begin{luacode*}
local N=100
local is_prime = {false}
for i=2,N do
if is_prime[i] == nil then
is_prime[i]=true
for j=2*i,N,i do
is_prime[j] = false
end
end
end
tex.print("\\begin{tabular}{cc} \\toprule")
tex.print("p premier & p+2 premier jumeau \\\\ \\midrule")
for n=2,N do
if is_prime[n] and is_prime[n+2] then
tex.print(string.format("%d & %d \\\\", n, n+2))
end
end
tex.print("\\bottomrule\\end{tabular}")
\end{luacode*}
Conclusion
Programmer en Lua dans LaTeX en compilant avec le moteur LuaLaTeX est facile et amusant. On peut réussir à faire des choses qu'on n'aurait jamais eu le courage de faire avec LaTeX seul et pgf, surtout avec l'aide d'une intelligence artificielle, qui est bien plus efficace pour déboguer Lua que du TeX de bas niveau ou un obscur package en PsTricks.
Un certain nombre d'exemples présentés plus haut sont en fait directement disponibles dans certains packages LuaLaTeX, donc il n'y a en fait pas besoin de recoder toutes les fonctions de pgcd etc: elles existent souvent déjà.