7

I seem to keep encountering the same issue. In my attempt to learn more about expansions I have written a program that starts with a sequence of 1s and then after each step increments it to generate a sequence of 2s, etc. All the different sequences generated then must be displayed in a tikz matrix in the following format:

enter image description here

I have used the following code:

\documentclass[11pt]{article}
\usepackage{tikz}
\usetikzlibrary{matrix}

\ExplSyntaxOn \NewDocumentCommand{\indexprod}{ m } % indices
{ \seq_set_from_clist:Nn \l_tmpc_seq { #1 } \int_step_inline:nn {\seq_count:N \l_tmpc_seq} { \seq_put_right:Nn \l_tmpa_seq {1} \seq_put_right:Nn \l_tmpb_seq {1} } \int_step_inline:nn {\seq_count:N \l_tmpc_seq - 1} { \tl_put_right:Nn \l_tmpa_tl {\mbox{\tiny $ \seq_item:Nn \l_tmpc_seq {##1} = 1$} &} } \tl_put_right:Nn \l_tmpa_tl {\mbox{\tiny $ \seq_item:Nn \l_tmpc_seq {\seq_count:N \l_tmpc_seq} = 1$} \} \int_new:N \l_tmpc_int \int_new:N \l_tmpd_int \int_set:Nn \l_tmpc_int {4} \int_step_inline:nnn {2} {\seq_count:N \l_tmpc_seq} { \int_set:Nn \l_tmpc_int {\l_tmpc_int * 4} } \int_log:N \l_tmpc_int \int_step_inline:nnn {2} {\l_tmpc_int} { \int_set:Nn \l_tmpb_int {##1} \int_log:N \l_tmpb_int \int_step_inline:nn {\seq_count:N \l_tmpc_seq} { \int_set:Nn \l_tmpa_int {\seq_item:Nn \l_tmpa_seq{####1}} \int_set:Nn \l_tmpd_int { \int_eval:n {\l_tmpa_int + 1}} \int_log:N \l_tmpd_int \seq_set_item:NnV \l_tmpb_seq {####1} \l_tmpd_int } \seq_log:N \l_tmpb_seq \int_step_inline:nn {\seq_count:N \l_tmpc_seq - 1} { \tl_put_right:Nn \l_tmpa_tl {\mbox{\tiny $ \seq_item:Nn \l_tmpc_seq {####1} = \seq_item:Nn \l_tmpb_seq{####1} $} &} } \tl_put_right:Nn \l_tmpa_tl {\mbox{\tiny $ \seq_item:Nn \l_tmpc_seq {\seq_count:N \l_tmpc_seq} = \seq_item:Nn \l_tmpb_seq {\seq_count:N \l_tmpb_seq}$} \} \int_step_inline:nn {\seq_count:N \l_tmpc_seq} { \int_set:Nn \l_tmpa_int {\int_eval:n {\seq_item:Nn \l_tmpb_seq{####1}}} \int_log:N \l_tmpa_int \seq_set_item:NnV \l_tmpa_seq {####1} \l_tmpa_int } \seq_log:N \l_tmpa_seq } \begin{tikzpicture}[baseline={([yshift=-0ex]current~bounding~box.center)}] \matrix(m) [ matrix~of~nodes, ampersand~replacement=&, column~sep=1ex, nodes~in~empty~cells, nodes={ shape=rectangle, minimum~height=3ex, anchor=center }, ]{ \tl_use:N \l_tmpa_tl }; \end{tikzpicture} }

\cs_generate_variant:Nn \seq_set_item:Nnn { NnV } \ExplSyntaxOff

\begin{document} [ \indexprod{i,j}
]

\end{document}

but of course the result is,

enter image description here

since the commands in the token list are frozen and commands like \seq_item:Nn \l_tmpb_seq{####1} evaluate with their latest values which happen to be equal to 16.

I have gone through the documentation trying to find a way to change expressions like,

\tl_put_right:Nn \l_tmpa_tl {\mbox{\tiny $ \seq_item:Nn \l_tmpc_seq {####1} = \seq_item:Nn \l_tmpb_seq{####1} $} \&}

so that I use the values of \seq_item:Nn \l_tmpb_seq{####1} but I have not been able to make a breakthrough.

Any ideas more than welcome...

Ted Black
  • 453
  • 2
  • 8
  • 1
    same issue as before \tl_put_right:Nn is putting variable references in the sequence so when you use the sequence you just get the same value each time, you want Nx to make a sequence of values – David Carlisle Sep 02 '23 at 22:02

2 Answers2

6

You need \tl_put_right:Nx so the argument is fully expanded. But you don't want to fully expand \mbox and \tiny, so a robust wrapper is needed. A command defined with \NewDocumentCommand is never expanded in e-expansion or x-expansion.

\documentclass[11pt]{article}
\usepackage{tikz}
\usetikzlibrary{matrix}

\NewDocumentCommand{\tinybox}{m}{\mbox{\tiny#1}}

\ExplSyntaxOn \NewDocumentCommand{\indexprod}{ m } % indices
{ \seq_set_from_clist:Nn \l_tmpc_seq { #1 } \int_step_inline:nn {\seq_count:N \l_tmpc_seq} { \seq_put_right:Nn \l_tmpa_seq {1} \seq_put_right:Nn \l_tmpb_seq {1} } \int_step_inline:nn {\seq_count:N \l_tmpc_seq - 1} { \tl_put_right:Nx \l_tmpa_tl {\tinybox{$ \seq_item:Nn \l_tmpc_seq {##1} = 1$} &} } \tl_put_right:Nx \l_tmpa_tl {\tinybox{$ \seq_item:Nn \l_tmpc_seq {\seq_count:N \l_tmpc_seq} = 1$} \} \int_new:N \l_tmpc_int \int_new:N \l_tmpd_int \int_set:Nn \l_tmpc_int {4} \int_step_inline:nnn {2} {\seq_count:N \l_tmpc_seq} { \int_set:Nn \l_tmpc_int {\l_tmpc_int * 4} } \int_log:N \l_tmpc_int \int_step_inline:nnn {2} {\l_tmpc_int} { \int_set:Nn \l_tmpb_int {##1} \int_log:N \l_tmpb_int \int_step_inline:nn {\seq_count:N \l_tmpc_seq} { \int_set:Nn \l_tmpa_int {\seq_item:Nn \l_tmpa_seq{####1}} \int_set:Nn \l_tmpd_int { \int_eval:n {\l_tmpa_int + 1}} \int_log:N \l_tmpd_int \seq_set_item:NnV \l_tmpb_seq {####1} \l_tmpd_int } \seq_log:N \l_tmpb_seq \int_step_inline:nn {\seq_count:N \l_tmpc_seq - 1} { \tl_put_right:Nx \l_tmpa_tl {\tinybox{$ \seq_item:Nn \l_tmpc_seq {####1} = \seq_item:Nn \l_tmpb_seq{####1} $} &} } \tl_put_right:Nx \l_tmpa_tl {\tinybox{$ \seq_item:Nn \l_tmpc_seq {\seq_count:N \l_tmpc_seq} = \seq_item:Nn \l_tmpb_seq {\seq_count:N \l_tmpb_seq}$} \} \int_step_inline:nn {\seq_count:N \l_tmpc_seq} { \int_set:Nn \l_tmpa_int {\int_eval:n {\seq_item:Nn \l_tmpb_seq{####1}}} \int_log:N \l_tmpa_int \seq_set_item:NnV \l_tmpa_seq {####1} \l_tmpa_int } \seq_log:N \l_tmpa_seq } \begin{tikzpicture}[baseline={([yshift=-0ex]current~bounding~box.center)}] \matrix(m) [ matrix~of~nodes, ampersand~replacement=&, column~sep=1ex, nodes~in~empty~cells, nodes={ shape=rectangle, minimum~height=3ex, anchor=center }, ]{ \tl_use:N \l_tmpa_tl }; \end{tikzpicture} }

\cs_generate_variant:Nn \seq_set_item:Nnn { NnV } \ExplSyntaxOff

\begin{document} [ \indexprod{i,j}
]

\end{document}

enter image description here

You might enjoy studying the following simpler code.

\documentclass[11pt]{article}
\usepackage{tikz}
\usetikzlibrary{matrix}

\NewDocumentCommand{\tinybox}{m}{\mbox{\tiny#1}}

\ExplSyntaxOn \NewDocumentCommand{\indexprod}{ O{16} m } % indices
{ % the indices \seq_set_from_clist:Nn \l_tmpa_seq { #2 } \seq_set_map:NNn \l_tmpb_seq \l_tmpa_seq { \tinybox{$##1=\int_eval:n { \l_tmpa_int }$} } \tl_clear:N \l_tmpa_tl \int_zero:N \l_tmpa_int \prg_replicate:nn { #1 } { \int_incr:N \l_tmpa_int \tl_set:Nx \l_tmpb_tl { \seq_use:Nn \l_tmpb_seq { & } } \tl_put_right:Nx \l_tmpa_tl { \l_tmpb_tl \exp_not:N \ } } \begin{tikzpicture}[baseline={([yshift=-0ex]current~bounding~box.center)}] \matrix(m) [ matrix~of~nodes, ampersand~replacement=&, column~sep=1ex, nodes~in~empty~cells, nodes={ shape=rectangle, minimum~height=3ex, anchor=center }, ]{ \tl_use:N \l_tmpa_tl }; \end{tikzpicture} }

\ExplSyntaxOff

\begin{document} [ \indexprod{i,j}\qquad \indexprod[5]{i,j,k} ]

\end{document}

enter image description here

Some comments about the above code. If we \seq_show:N \l_tmpa_seq and \seq_show:N \l_tmpb_seq after they're set we get

The sequence \l_tmpa_seq contains the items (without outer braces):
>  {i}
>  {j}.

The sequence \l_tmpb_seq contains the items (without outer braces): > {\tinybox {$i=\int_eval:n {\l_tmpa_int }$}} > {\tinybox {$j=\int_eval:n {\l_tmpa_int }$}}.

Now add \tl_show:N \l_tmpb_tl and \tl_show:N \l_tmpa_tl after they're set in the \prg_replicate:nn loop: at the first iteration we get

> \l_tmpb_tl=\tinybox {$i=\int_eval:n {\l_tmpa_int }$}\&\tinybox
{$j=\int_eval:n {\l_tmpa_int }$}.

> \l_tmpa_tl=\tinybox {$i=1$}&\tinybox {$j=1$}\.

This uses the fact that the items in the sequence are returned with \exp_not:n around them, so \tl_set:Nx just arrives at the “surface” token list; but after \tl_put_right:Nx the items are fully expanded. In this case I exploit that the items in \l_tmpa_seq are “expansion safe”. Other use cases might need other cares.

Safer version

\documentclass[11pt]{article}
\usepackage{tikz}
\usetikzlibrary{matrix}

\NewDocumentCommand{\tinybox}{m}{\mbox{\tiny#1}}

\NewDocumentCommand{\tbinnermatrix}{m}{% \begin{tikzpicture}[baseline={([yshift=-0ex]current bounding box.center)}] \matrix(m) [ matrix of nodes, ampersand replacement=&, column sep=1ex, nodes in empty cells, nodes={ shape=rectangle, minimum height=3ex, anchor=center }, ]{ #1 }; \end{tikzpicture}% }

\ExplSyntaxOn

\NewDocumentCommand{\indexprod}{ O{16} m } % indices
{ \tedblack_indexprod:nn { #1 } { #2 } }

\cs_generate_variant:Nn \cs_set:Nn { NV } \cs_set_eq:NN \tedblack_innermatrix:n \tbinnermatrix \cs_generate_variant:Nn \tedblack_mymatrix:n { V }

\cs_new_protected:Nn \tedblack_indexprod:nn { % the indices \seq_set_from_clist:Nn \l_tmpa_seq { #2 } \seq_set_map:NNn \l_tmpa_seq \l_tmpa_seq { \tinybox{$\exp_not:n { ##1 }=####1$} } \tl_set:Nx \l_tmpa_tl { \seq_use:Nn \l_tmpa_seq { & } } \cs_set:NV __tedblack_temp:n \l_tmpa_tl \tl_clear:N \l_tmpa_tl \int_step_inline:nn { #1 } { \tl_put_right:Nx \l_tmpa_tl { __tedblack_temp:n { ##1 } \exp_not:N \ } } \tedblack_innermatrix:V \l_tmpa_tl }

\ExplSyntaxOff

\begin{document} [ \indexprod{i,j}\qquad \indexprod[5]{i,j,\mathbf{k}} ]

\end{document}

This not only shows that “risky” commands such as \mathbf are safe in the argument to \indexprod, but also shows better programming style where there is a distinction between user level commands and internal functions.

The “matrix building” command is taken outside \ExplSyntaxOn, which is always best when TikZ is involved. An internal version is defined later, with the possibility to define a variant thereof.

A “local” function is defined so it can be used to substitute the current value in the \int_step_inline:nn loop.

\documentclass[11pt]{article}
\usepackage{tikz}
\usetikzlibrary{matrix}

\NewDocumentCommand{\tinybox}{m}{\mbox{\tiny#1}}

\NewDocumentCommand{\tbinnermatrix}{m}{% \begin{tikzpicture}[baseline={([yshift=-0ex]current bounding box.center)}] \matrix(m) [ matrix of nodes, ampersand replacement=&, column sep=1ex, nodes in empty cells, nodes={ shape=rectangle, minimum height=3ex, anchor=center }, ]{ #1 }; \end{tikzpicture}% }

\ExplSyntaxOn

\NewDocumentCommand{\indexprod}{ O{16} m } % indices
{ \tedblack_indexprod:nn { #1 } { #2 } }

\cs_generate_variant:Nn \cs_set:Nn { NV } \cs_set_eq:NN \tedblack_innermatrix:n \tbinnermatrix \cs_generate_variant:Nn \tedblack_innermatrix:n { V }

\cs_new_protected:Nn \tedblack_indexprod:nn { % the indices \seq_set_from_clist:Nn \l_tmpa_seq { #2 } \seq_set_map:NNn \l_tmpa_seq \l_tmpa_seq { \tinybox{$\exp_not:n { ##1 }=####1$} } \tl_set:Nx \l_tmpa_tl { \seq_use:Nn \l_tmpa_seq { & } } \cs_set:NV __tedblack_temp:n \l_tmpa_tl \tl_clear:N \l_tmpa_tl \int_step_inline:nn { #1 } { \tl_put_right:Nx \l_tmpa_tl { __tedblack_temp:n { ##1 } \exp_not:N \ } } \tedblack_innermatrix:V \l_tmpa_tl }

\ExplSyntaxOff

\begin{document} [ \indexprod{i,j}\qquad \indexprod[5]{i,j,\mathbf{k}} ]

\end{document}

enter image description here

egreg
  • 1,121,712
  • What can I say? It always happens that mastering a subject means that the solutions given seem more than simple. – Ted Black Sep 02 '23 at 22:14
  • @TedBlack Look at the added code. – egreg Sep 02 '23 at 22:16
  • I guess \tinybox is robust because NewDocumentCommand is never expanded. – Ted Black Sep 02 '23 at 22:22
  • The added code uses \l_tmpb_seq and \l_tmpa_int before they are set. How is this possible? I understand that \prg_replicate creates multiple copies but in your code it is run after the \seq_set_map that uses them. – Ted Black Sep 02 '23 at 22:34
  • 1
    @TedBlack When in doubt, use \tl_show:N (or alike commands). – egreg Sep 03 '23 at 07:49
  • 2
    @TedBlack I added some comments – egreg Sep 03 '23 at 08:07
  • As usual I am reading latex3 code as if it was C code... You initially set \l_tmpb_seq to contain expressions that include \int_eval:n { \l_tmpa_int } ; these are just unexpanded tokens even though they contain int_eval. Then in the \prg_replicate loop, \l_tmpa_int is incremented and the contents of \l_tmpb_seq are expanded by using \tl_set:Nx. – Ted Black Sep 03 '23 at 09:03
  • @TedBlack Yes, that's it. I added another version that you can study. – egreg Sep 03 '23 at 16:07
  • The new version is a bit tricky... First, in the command \tinybox{$\exp_not: {##1}=####1$} ##1 is the item from seq_set_map but what is ####1? I understand that you use the variant of cs_set so that __tedblack_temp is the whole list of commands contained in l_tmpa_tl. But then in the int_step_inline loop somehow the same command takes an argument equal to the current value of #1 which is the set of letters that is the first input of ted_black_indexprod. How can ted_black_temp suddenly accept input parameters? – Ted Black Sep 04 '23 at 09:59
  • @TedBlack Do \cs_show:N \__tedblack_temp:n after setting it to see the magic! – egreg Sep 04 '23 at 10:43
  • Yes it works. I still don't understand how the ####1 part in \seq_set_map:NNn \l_tmpa_seq \l_tmpa_seq { \tinybox{$\exp_not:n { ##1 }=####1$} } gets converted to ##1 (in fact I dont understand how the program can accept something like ####1. Then as soon as you assign \cs_set:NV \__tedblack_temp:n \l_tmpa_tl as if by magic the token list contains #1 instead of ##1 !! – Ted Black Sep 04 '23 at 13:25
  • @TedBlack That's just how TeX and nesting works. Here's even a LaTeX3 example. And there's also Q&A about a l3seq usecase as well. – Qrrbrbirlbel Sep 04 '23 at 13:29
  • @Qrrbrbirlbel so after \seq_set_map:NNn \l_tmpa_seq ##1 is replaced by the content and ####1 becomes ##1 , and after \cs_set:NV \__tedblack_temp:n \l_tmpa_tl ##1 becomes #1? – Ted Black Sep 04 '23 at 13:48
  • @TedBlack Yes, that's the idea. – egreg Sep 04 '23 at 13:54
  • By the way what is ‘exp_not’ doing in ‘ $\exp_not:n { ##1 }=####1’ ? – Ted Black Sep 05 '23 at 09:07
  • @TedBlack defensive programming to allow for dangerous tokens in the argument – egreg Sep 05 '23 at 10:08
  • OK so ##1 is replaced by i or \mathbf{k} and \exp_not makes sure \mathbf is not expanded further... I would think that since \seq_set_map:NNn takes n as its third argument this is already taken care of. – Ted Black Sep 05 '23 at 10:16
  • @TedBlack But later we use \tl_put_right:Nx – egreg Sep 05 '23 at 12:28
1

I understand that this is an exercise in LaTeX3 but I want to offer my PGFKeys version of it.

Value-keys also just store tokens which we can alter with (amongst other)

  • \pgfkeyssetvalue{/key}{<value>} and
  • \pgfkeysaddvalue{/key}{<prefix>}{<suffix>}

or their handler alternatives (amongst other)

  • /key/.initial=<value> (or /key=<value> if the /key was initialized beofrehand)
  • /key/.add={<prefix>}{<suffix>},
  • /key/.prefix=<prefix> and
  • /key/.append=<suffix>.

An ungrouped PGFFor loop can be achieved via the .list handler which allows us to repeatedly use a key.

By nesting .list applications it is possible to build a matrix. We just need to make sure we put one less \pgfmatrixnextcell (the &/\& of it all) in than number of columns. This is done by not putting it in on the first cell of a row. That's why /tikz/matrix/create cell gets redefined in its own definition.

I've chosen anchor = base for the nodes inside the matrix so that their baselines align. The matrix itself is anchored at its center which means its center will be at y = 0pt which is the value chosen for the baseline.


Instead of using i, j, \mathbf{k} as the list, you can use

\indexprod[
  indexprod/rows=5,
  column 3/.style={set matrix macro={$\mathbf{##2}=##1$}}
]{i, j, k}

for the same effect. Of course, you can use another top-level interface that is less verbose than that.

Code

\documentclass{article}
\usepackage{tikz}
\tikzset{
  matrix node/.style 2 args={
    name=\tikzmatrixname-\the\pgfmatrixcurrentrow-\the\pgfmatrixcurrentcolumn},
  set matrix macro/.code=
    \protected\def\tikzmatrixcell##1##2{\node[matrix node={##1}{##2}]{#1};},
  set matrix macro'/.code=\protected\def\tikzmatrixcell##1##2{#1},
  create matrix/.code 2 args=% quicker https://tex.stackexchange.com/a/692610
    \pgfkeyssetvalue{/tikz/matrix/content}{}%
    \pgfkeysdef{/tikz/matrix/create rows}{%
      \pgfkeysvalueof{/tikz/matrix/reset create cell/.@cmd}\pgfeov
      \pgfkeysdef{/tikz/matrix/create columns}{%
        \pgfkeysvalueof{/tikz/matrix/create cell/.@cmd}{##1}{####1}\pgfeov}%
      \tikzset{matrix/create columns/.list={#2}}%
      \pgfkeysaddvalue{/tikz/matrix/content}{}{\pgfmatrixendrow}}%
    \tikzset{matrix/create rows/.list={#1},
      node contents=\pgfkeysvalueof{/tikz/matrix/content}},
  matrix/reset create cell/.code=
    \pgfkeysdefargs{/tikz/matrix/create cell}{##1##2}{%
      \pgfkeysaddvalue{/tikz/matrix/content}{}{\tikzmatrixcell{##1}{##2}}%
      \pgfkeysdefargs{/tikz/matrix/create cell}{####1####2}{%
        \pgfkeysaddvalue{/tikz/matrix/content}{}{%
          \pgfmatrixnextcell\tikzmatrixcell{####1}{####2}}}}}

\tikzset{ indexprod/rows/.initial=16, indexprod/list/.initial={1, ..., \pgfkeysvalueof{/tikz/indexprod/rows}}, every indexprod diagram/.style={ baseline=+0pt, column sep=1ex, set matrix macro={$##2 = ##1$}, every outer matrix/.style={shape=rectangle, inner sep=+0pt, outer sep=+0pt}, every matrix/.style={ anchor=center, nodes={shape=rectangle, anchor=base, minimum height=3ex}}}} \NewDocumentCommand{\indexprod}{O{} m}{% \tikz[every indexprod diagram,#1] \matrix[create matrix/.expanded= {\pgfkeysvalueof{/tikz/indexprod/list}}{\unexpanded{#2}}];} \begin{document} \indexprod{i, j} \quad \indexprod[indexprod/rows=5]{i, j, \mathbf{k}} \end{document}

Output

enter image description here

Qrrbrbirlbel
  • 119,821