Passing multiple arguments from a macro to a lua function

The problem is to obtain the barycenter of any number of points. If lua is not used then TikZ is used otherwise lua. (Sorry the question is long)

With only TikZ' the code is as follows:

\documentclass{article}
\usepackage{tikz}

\def\barycenter(#1)#2{%
\path[coordinate]  (barycentric cs:#1) coordinate (#2);}
\begin{document}
\begin{tikzpicture}
\coordinate (A) at (1,0);
\coordinate (B) at (5,-1);
\coordinate (C) at (2,5);
\barycenter(A=1,B=1,C=1){G} % #1 the list A=1,B=1,C=1 #2 G barycenter
\draw (A) -- (B) -- (C) --cycle;
\node[circle, inner sep = 0pt,fill=lightgray,
draw=black,minimum size =4pt] at (G) {};
\end{tikzpicture}
\end{document}


For the coordinate system barycentric, the ⟨coordinate specification⟩ should be a comma-separated list of expressions of the form ⟨node name⟩=⟨number⟩. Note that (currently) the list should not contain any spaces before or after the ⟨node name⟩ (unlike normal key–value pairs).

Without transfer to lua, the code is as follows: (some explanations are necessary and they follow the code)

\documentclass[landscape]{article}
\usepackage{tikz,luacode}
\directlua{bary = require("bary")}
\begin{document}
\begin{tikzpicture}
require "complex"
z = {}
z.A = complex.new (1,0)
z.B = complex.new (5,-1)
z.C = complex.new (2,5)
z.G = barycenter ({z.A,1},{z.B,1},{z.C,1})
for K,V in pairs(z) do
tex.print("\\coordinate ("..K..") at ("..V.re..","..V.im..") ;")
end
}
\draw (A) -- (B) -- (C) --cycle;
\node[circle, inner sep = 0pt,fill=lightgray,
draw=black,minimum size =4pt] at (G) {};
\end{tikzpicture}
\end{document}


The barycenter function is in the file bary.lua that here is

function barycenter (...)
local cp = table.pack(...)
local i
local sum = 0
local weight=0
for i=1,cp.n do
sum = sum + cp[i][1]*cp[i][2]
weight = weight + cp[i][2]
end
return sum/weight
end


The function is based on the calculation with complex numbers, also you have to download the library complex.lua here https://gist.github.com/MihailJP/3500284

z ={}creates the table, the keys are the names of the points which will be future "nodes" and the values are obviously the coordinates of the points.

 z.C = complex.new (2,5) associates the point C with its affix z.C = 2+5i.

The barycenter is obtained with  z.G = barycenter ({z.A,1},{z.B,1},{z.C,1})

The question: how to modify the next macro to call the barycenter function.

\def\barycenter(#1)#2{%
\path[coordinate]  (barycentric cs:#1) coordinate (#2);
% How to replace the previous line to call the barycenter function of lua ?
}


The problem is how to go from A=1,B=1,C=1 to {A,1},{B,1},{C,1} knowing that it can be for two, three or even four points! I don't know if it is better to work on arguments in TeX or in lua.

Update of the question: Unfortunately I made a mistake because to work with affixes you have to go from A=1,B=1,C=1 to {z.A,1},{z.B,1},{z.C,1} and not to {A,1},{B,1},{C,1}.

The problem is how to go from A=1,B=1,C=1 to {A,1},{B,1},{C,1} knowing that it can be for two, three or even four points! I don't know if it is better to work on arguments in TeX or in lua.

It is very easy to do with TeX. Here's a user-level solution with just what TikZ provides already.

PGFKeys and PGFFor provides the .list handler which loops over a list (via \foreach) without its body being grouped.

For each element in your list <node>=<weight> the element {<x>,<y>,<weight>} will be added to a list which is just a value-key (here /utils/tempa) which later can be fully expanded.

The first element will be handled differently because we don't want a , at the start.

Unfortunately, PGFKeys loses some braces on the way which makes it necessary to use four pairs when initializing the value. Though, we can fallback to the \pgfkeyssetvalue macro (which is just a fancy \def).

Using PGFkeys (mostly \pgfkeysaddvalue) avoids higher level packages like etoolbox (\appto) or l3clist (\clist_put_right:Nn) and using PGFFor (via .list) avoids lower level loops (like \pgfutil@for or LaTeX's \@for) which are faster, most likely.

I'll provide a different barycenter Lua function that doesn't use any complex numbers but just returns two values which will be forwarded to PGF directly.

The usage of \tikz@parse@node makes sure that name prefix and name suffix gets used if necessary when accessing the coordinates (could be nodes even). The same is true for \tikz@pp@name when defining the new coordinate.

In the code below I've define both #2' via the barycentric cs (red cross) and just #2 via Lua (circle).

For the Lua solution via .list, spaces before the , are allowed, however spaces before the = will still raise an error – just as they do for TikZ' own barycentric cs.

With the same definition of \tikz@baryget as in the main code and with these two additions:

\pgfqkeys{/tikz/cs/@Lua@bary}{
.unknown/.code=%
\let\tikz@key\pgfkeyscurrentname
@@initial/.style={
\tikz@parse@node\tikz@baryget(##1)%
\pgfkeyssetevalue{/tikz/cs/@Lua@bary/@@temp}{{\tikz@temp,##2}}%
\tikz@parse@node\tikz@baryget(####1)%
{/tikz/cs/@Lua@bary/@@temp}{,{\tikz@temp,####2}}}%
\tikz@temp},
}
}
\tikzdeclarecoordinatesystem{Lua barycentric}{%
\pgfkeys{/tikz/cs/@Lua@bary/.cd,@@initial,#1}%
\directlua{
sumx, sumy = barycenter(\pgfkeysvalueof{/utils/bary from lua/to lua})
tex.print("\\pgfqpoint{"..sumx.."pt}{"..sumy.."pt}")
}%
}


a coordinate specification like

\draw[green] (Lua barycentric cs: A = 1, B = 1, C = 1) circle[radius=4pt];


is possible without having to deal with any spaces (since PGFKeys parses the list) and it allows to specify the coordinate on-the-fly without an extra macro.

It also doesn't use PGFFor anymore. (A combination of both is possible.)

Code

% !TeX TS-program = lualatex
\documentclass[tikz]{standalone}
\directlua{
function barycenter (...)
local cp = table.pack(...)
local i
local sumx = 0
local sumy = 0
local weight = 0
for i=1,cp.n do
sumx = sumx + cp[i][1]*cp[i][3]
sumy = sumy + cp[i][2]*cp[i][3]
weight = weight + cp[i][3]
end
return sumx/weight, sumy/weight
end
}
\makeatletter
\def\tikz@baryget#1{%
\pgf@process{#1}%
\edef\tikz@temp{\pgf@sys@tonumber\pgf@x,\pgf@sys@tonumber\pgf@y}}%
\pgfkeys{
/utils/bary from lua/.style args={#1,#2}{
/utils/temp/.code args={##1=##2}{%
\tikz@parse@node\tikz@baryget(##1)%
\pgfkeyssetevalue{/utils/bary from lua/to lua}{{\tikz@temp,##2}}},
/utils/temp={#1},
/utils/temp/.code args={##1=##2}{%
\tikz@parse@node\tikz@baryget(##1)%
{/utils/bary from lua/to lua}{}{,{\tikz@temp,##2}}}%
\tikz@temp},
/utils/temp/.list={#2}}}
\def\barycenter(#1)#2{%
\coordinate (#2') at (barycentric cs:#1);
\pgfutil@in@{,}{#1}% just in case
\ifpgfutil@in@
\pgfkeys{/utils/bary from lua={#1}}%
\else
\pgfkeys{/utils/bary from lua={#1,}}% \foreach ignores empty
\fi
\directlua{
sumx, sumy = barycenter(\pgfkeysvalueof{/utils/bary from lua/to lua})
tex.print("\\pgfcoordinate{\\csname tikz@pp@name\\endcsname{#2}}
{\\pgfqpoint{"..sumx.."pt}{"..sumy.."pt}}")
}%
}
\makeatother
\begin{document}
\begin{tikzpicture}
\coordinate (A) at (1,0);
\coordinate (B) at (5,-1);
\coordinate (C) at (2,5);
\barycenter(A=1,B=1,C=1){G}
\draw (A) -- (B) -- (C) --cycle;
\node[circle, inner sep = 0pt, fill=lightgray,
draw=black, minimum size =4pt] at (G) {};
\draw[red] (G') +(north west:2pt) -- +(south east:2pt)
+(north east:2pt) -- +(south west:2pt);
\end{tikzpicture}
\end{document}


Output

• It seems that getting {A,1},{B,1},{C,1} is not my only problem. I admit I don't know how to get this into Lua. In addition to the difficulties of TeX, there is also the difficulties of Lua. Feb 8, 2023 at 17:42
• @AlainMatthes I'm not proficient in Lua(TeX) but I'll updated my answer with two changes: 1. the x and y values get converted by TeX to float numbers for Lua and 2. Lua calculates the new coordinate without any complex numbers. Feb 8, 2023 at 18:39
• @AlainMatthes You might need to evaluate the weight before giving it to Lua since it could include PGFMath function that won't be understood by Lua. Feb 8, 2023 at 18:55
• Thank you for your answer. The first solution was fine ! I made a mistake because to use the complexes the arguments for lua are {z.A,1},{z.B,1},{z.C,1}. Your first code works fine with \barycenter(z.A=1,z.B=1,z.C=1){G} but in this case there is a problem without lua. I can also use a little trick and before determining the barycenter do A=z.A B=z.B et C=z.C`. The best is that I try to modify the code of PGFKeys. Feb 8, 2023 at 21:27
• The second code is good too and I could reintroduce the complexes before the création of "coordinate". However, I prefer to stay with complex because I want to allow users to be able to create all the necessary points while staying in lua. It's simpler and more efficient, then to create the nodes at the end and switch to TikZ. Feb 8, 2023 at 21:36