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...

1
  • 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 Commented Sep 2, 2023 at 22:02

2 Answers 2

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

18
  • What can I say? It always happens that mastering a subject means that the solutions given seem more than simple.
    – Ted Black
    Commented Sep 2, 2023 at 22:14
  • @TedBlack Look at the added code. 😀
    – egreg
    Commented Sep 2, 2023 at 22:16
  • I guess \tinybox is robust because NewDocumentCommand is never expanded.
    – Ted Black
    Commented Sep 2, 2023 at 22:22
  • 1
    @TedBlack When in doubt, use \tl_show:N (or alike commands).
    – egreg
    Commented Sep 3, 2023 at 7:49
  • 2
    @TedBlack I added some comments
    – egreg
    Commented Sep 3, 2023 at 8:07
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

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .