1

I'm trying to write a package to provide some shortcut commands for common linear algebra operations (for my intro to linear algebra course at least). However, I'm getting weird errors under certain circumstances. Based on other posts I think I'm getting tripped by the fragile vs. robust command thing (which I don't completely understand), and I'm not sure how to fix it. I have tried changing my macros to \DeclareRobustCommand and adding \protects in various places, but none of those solved the issue. Specifically, I'm getting the following error:

Use of \\mat doesn't match its definition.

\@ifnextchar ... \reserved@d =#1\def \reserved@a {
                                                  #2}\def \reserved@b {#3}\f...
l.136 This \mat{\sqrt{2},0,0,0}
                                also doesn't work
If you say, e.g., `\def\a1{...}', then you must always
put `1' after `\a', since control sequence names are
made up of letters only. The macro here has not been
followed by the required stuff, so I'm ignoring it.

And here is the MRCE for the error (las stands for "linear algebra shortcut"):

\documentclass{article}
\usepackage{amsmath}
\usepackage{amssymb}
\usepackage{amsfonts}
\usepackage{tcolorbox}
\usepackage{etoolbox}
\usepackage{pgffor}
\usepackage{pgfmath}
\usepackage{calc}
\usepackage{intcalc}
\usepackage{calculator}
\usepackage{nicematrix}

\makeatletter
\setcounter{MaxMatrixCols}{25}

\gdef\las@defaultvec{row} 

\newcounter{las@matrow}
\newcounter{las@matcol}
\newcounter{las@listlen}
\newcounter{las@matsize}

\newcommand{\las@length}[2]{ % \len{list}{counter}
    \setcounter{#2}{0}
    \foreach\@e in {#1} {
        \stepcounter{#2}
    }
}

\newcommand{\las@createMatrix}[3]{ % dims, rows
    \setcounter{las@matrow}{#1}
    \setcounter{las@matcol}{#2}
    \setcounter{las@listlen}{0}
    \las@length{#3}{las@listlen} % get the length of the list after expansion
     \setcounter{las@matsize}{\thelas@matrow*\thelas@matcol}
    \ifnum\thelas@matsize=\thelas@listlen % if neq
    \else 
        \PackageError{LAS}{Attempt to define a \arabic{las@matrow} by \arabic{las@matcol} matrix with \arabic{las@listlen} elemets}{}
    \fi
    \xdef\las@resMatrix{}
    \foreach \elem[count=\@i] in {#3}{
        % \xdef\las@resMatrix{\las@resMatrix \elem \\}
        \ifnum\@i=\thelas@matsize
            \xdef\las@resMatrix{\las@resMatrix\elem}
        \else
            \ifnum\intcalcMod{\@i}{\thelas@matcol}=0
                \xdef\las@resMatrix{\las@resMatrix \elem \\}
            \else
                \xdef\las@resMatrix{\las@resMatrix \elem &}
            \fi
        \fi
    }
}

\newcommand{\las@vec}[2]{
    \las@length{#2}{las@listlen}
    \ifstrequal{#1}{row}{
        \las@createMatrix{1}{\thelas@listlen}{#2}
    }{
        \ifstrequal{#1}{col}{
            \las@createMatrix{\thelas@listlen}{1}{#2}
        }{
            \PackageError{LAS}{Unknown option "#1". Valid options are "col"  and "row"}{}
        }
    }
}

\newcommand{\las@mat}[2]{
    \setcounter{las@matrow}{1}
    \setcounter{las@matcol}{1}
    \ifstrequal{#1}{0,0}{
        \las@length{#2}{las@listlen}
        \SQRT{\thelas@listlen}{\@dim}
        \FLOOR{\@dim}{\@dim}
        \ifnum\intcalcSqr\@dim=\thelas@listlen
        \else
            \PackageError{LAS}{Not a square matrix. Please specify the dimensions}{}
        \fi
        \setcounter{las@matrow}{\@dim}
        \setcounter{las@matcol}{\@dim}
    }{
        \foreach\elem[count=\@i] in {#1} {
            \ifnum\@i=1
                \setcounter{las@matrow}{\elem}
            \else
                \ifnum\@i=2
                    \setcounter{las@matcol}{\elem}
                \else 
                    \PackageError{LAS}{Too many options given to mat}{}
                \fi
            \fi
        }
    }
    \las@createMatrix{\thelas@matrow}{\thelas@matcol}{#2}
}

\renewcommand{\vec}[2][col]{ % overrides \vec in amsmath 
    \ensuremath{
        \las@vec{#1}{#2}
        \begin{bNiceMatrix}
            \las@resMatrix
        \end{bNiceMatrix}
        \ifstrequal{#1}{row}{^T}{}
    }
} 


\newcommand{\mat}[2][0,0]{
    \ensuremath{
        \las@mat{#1}{#2}
        \begin{bNiceMatrix}
            \las@resMatrix
        \end{bNiceMatrix}
    }
}

\makeatother
\begin{document}

This works
\begin{equation*}
    \mat{1,...,9}
\end{equation*}

This \mat{\frac{1}{3},\frac{2}{4},0,0} also works

Vectors \vec{1,...,4} are fine too 

% This doesn't work
% \begin{align*} 
%     \mat{1,...,9}
% \end{align*}

% This \mat{\sqrt{2},0,0,0} also doesn't work
% 
% \vec{\sqrt{2},0,0,0} doesn't work either

\end{document}

The above code is from a bigger package I'm making, but for the ease of reproducing the error I have merged them into the same document. The document compiles fine as it currently stands, however uncommenting anything in the document marked as not working will result in the error. I haven't figured out what exactly triggered the error, but I know from experience that using either \vec or \mat inside the align environment will not work. Using \sqrt inside any of these macro doesn't work either.

There was one related question about this package here.

0

2 Answers 2

3

The problem is, as already explained by Don Hosek, in \xdef.

I suggest a different loop management based on expl3 that needs no \foreach and no \xdef.

\documentclass{article}
\usepackage{amsmath}

\ExplSyntaxOn

\NewDocumentCommand{\mat}{ o m }
 {
  \IfNoValueTF { #1 }
   { \las_mat_compute_order:n { #2 } }
   { \las_mat_shape:nn { #1 } { #2 } }
  \las_mat:n { #2 }
 }

\RenewDocumentCommand{\vec}{ O{} m }
 {
  \keys_set:nn { las/vec } { row , #1 }
  \int_set:Nn \l_las_mat_cols_int { \clist_count:n { #2 } }
  \las_mat:n { #2 }
  \bool_if:NT \l_las_mat_colvec_bool { ^T }
 }

\keys_define:nn { las/vec }
 {
  col .bool_set:N = \l_las_mat_colvec_bool,
  row .bool_set_inverse:N = \l_las_mat_colvec_bool,
  col .default:n = true,
  row .default:n = true,
 }

\int_new:N \l_las_mat_cols_int
\int_new:N \l_las_mat_rows_int
\seq_new:N \l_las_mat_entries_seq

\cs_new_protected:Nn \las_mat_compute_order:n
 {
  \fp_compare:nF
   {% is the number of items a square?
    floor ( sqrt ( \clist_count:n { #1 } ) )
    =
    sqrt ( \clist_count:n { #1 } )
   }
   {% no, it isn't
    \PackageError{LAS}{Not~a~square~matrix}{Please~specify~the~dimensions}
   }
  \int_set:Nn \l_las_mat_cols_int { \fp_eval:n { ceil ( sqrt ( \clist_count:n { #1 } ) ) } }
%  \int_set:Nn \l_las_mat_rows_int { \l_las_mat_cols_int }
 }

\cs_new_protected:Nn \las_mat_shape:nn
 {
  \int_set:Nn \l_las_mat_rows_int { \clist_item:nn { #1 } { 1 } }
  \int_set:Nn \l_las_mat_cols_int { \clist_item:nn { #1 } { 2 } } 
  \int_compare:nF
   {% do we have enough entries?
    \l_las_mat_cols_int * \l_las_mat_rows_int = \clist_count:n { #2 }
   }
   {% no, we don't
    \PackageError{LAS}
     {
      Not~enough~entries~`\clist_item:nn { #1 } { 1 }~*~\clist_item:nn { #1 } { 1 }~
      !=~\clist_count:n { #2 }'
     }
     {
      Attempt~to~define~a~\clist_item:nn { #1 } { 1 }~by~\clist_item:nn { #1 } { 2 }~
      matrix~with~\clist_count:n { #1 }~elements
     }
   }
 }

\cs_new_protected:Nn \las_mat:n
 {
  \seq_set_from_clist:Nn \l_las_mat_entries_seq { #1 }
  \begin{bmatrix}
  \seq_map_indexed_function:NN \l_las_mat_entries_seq \l__mat_entry_add:nn
  \end{bmatrix}
 }

\cs_new:Nn \l__mat_entry_add:nn
 {
  #2 % the entry
  \int_compare:nTF { \int_mod:nn { #1 } { \l_las_mat_cols_int } = 0 }
   { \\ }
   { & }
 }

\ExplSyntaxOff

\begin{document}

\[
\mat{1,0,0,1}\ne
\mat{1,2,3,4,5,6,7,8,9}\ne
\mat[2,3]{1,2,3,4,5,6}\ne
\vec{1,2,3}\ne\vec[col]{1,2,3}
\]

Errors

\[
\mat{1,0,0}\qquad
\mat[3,2]{1,2,3,4,5}
\]

\end{document}

enter image description here

2

First, a tip for TeX/LaTeX debugging. LaTeX likes to hide the context of errors for the sake of user-friendliness, but unfortunately, the practice is programmer-hostile. When debugging macros, I would add

\errorcontextlines=5

to your file so you can get a "traceback" through the macro expansion to see where the problem is.

You were correct in suspecting that it's a fragile command/moving argument thing. Because you're doing \xdef here, you've created your own moving argument in your macro. LaTeX, unfortunately, is not magic, so the primitive \xdef doesn't know how to protect robustified commands like \sqrt. The solution to this is to replace your \xdefs with \protected@xdefs instead.

Here's the revised version of your \las@createMatrix command which fixes the problems:

\newcommand{\las@createMatrix}[3]{ % dims, rows
    \setcounter{las@matrow}{#1}
    \setcounter{las@matcol}{#2}
    \setcounter{las@listlen}{0}
    \las@length{#3}{las@listlen} % get the length of the list after expansion
     \setcounter{las@matsize}{\thelas@matrow*\thelas@matcol}
    \ifnum\thelas@matsize=\thelas@listlen % if neq
    \else 
        \PackageError{LAS}{Attempt to define a \arabic{las@matrow} by \arabic{las@matcol} matrix with \arabic{las@listlen} elemets}{}
    \fi
    \xdef\las@resMatrix{}
    \foreach \elem[count=\@i] in {#3}{
        % \xdef\las@resMatrix{\las@resMatrix \elem \\}
        \ifnum\@i=\thelas@matsize
            \protected@xdef\las@resMatrix{\las@resMatrix\elem}
        \else
            \ifnum\intcalcMod{\@i}{\thelas@matcol}=0
                \protected@xdef\las@resMatrix{\las@resMatrix \elem \\}
            \else
                \protected@xdef\las@resMatrix{\las@resMatrix \elem &}
            \fi
        \fi
    }
}

And, for completeness's sake, here's the improved output when I boosted \errorcontextlines:

ERROR: Use of \\mat doesn't match its definition.

--- TeX said ---
\@ifnextchar ... \reserved@d =#1\def \reserved@a {
                                                  #2}\def \reserved@b {#3}\f...

\las@resMatrix ->\protect \sqrt 
                                {2}&0\\0&
\pgffor@body ...ef \las@resMatrix {\las@resMatrix 
                                                  \elem } \else \ifnum \intc...
<to be read again> 
                   \pgffor@endhook 
\pgffor@invokebody ...pgffor@body \pgffor@endhook 
                                                  \ifx \pgffor@assign@after@...

\pgffor@next ...=\pgffor@value \pgffor@invokebody 
                                                  \pgffor@scan 
\pgffor@values ->\sqrt {2},0,0,0,
                                           \pgffor@stop ,
\las@createMatrix ...@resMatrix \elem &} \fi \fi }
                                                   
\las@mat ...{\thelas@matrow }{\thelas@matcol }{#2}
                                                   
\\mat [#1]#2-> \las@mat {#1}{#2}
                                 \ensuremath { \begin {bNiceMatrix} \[email protected]  This \mat{\protect\sqrt{2},0,0,0}
                                         also doesn't work
1
  • Thanks for the suggestion. It's very helpful and now \sqrt works fine inside my \mat. However, it seems that the problem still persists when I use \mat inside \begin{align}. How would you suggest me to fix that? I feel it's not because it moved anything this time because all the fragile commands work inside align just fine
    – Mia
    Apr 3, 2021 at 5:33

You must log in to answer this question.

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