2

I'm currently working on writing something in latex that requires many usages of diagonal matrices of form

$\begin{bmatrix*}
    \lambda_1 & 0 & 0\\
    0 & \lambda_2 & 0\\
    0 & 0 & \lambda_3\\ 
\end{bmatrix*}$

with a variable number of parameters \lambda.

Previously, I've found a script that helped me create vectors with multiple parameters and therefore save me from rewriting the matrix for them:

\newcommand{\vect}[1]{%
\begin{bmatrix} #1\checknextarg}
\newcommand{\checknextarg}{\@ifnextchar\bgroup{\gobblenextarg}{ \end{bmatrix}}}
\newcommand{\gobblenextarg}[1]{ \\ #1\@ifnextchar\bgroup{\gobblenextarg}{ \end{bmatrix}}}

While this script works for vectors, I can't think of a generalized way to make something similar to this for diagonal matrices. Finding a script like that will save me a ton of time. I've tries using lualatex for this but I'm inexperienced with it and this doesn't help with trying to have a variable amount of parameters and a corresponding amount of 0s. What I hope for is for the example above, I would simply be able to do something like $\diagmat{\lambda_1}{\lambda_2}{\lambda_3}$ for any amount of parameters, similar to what I can do with \vect. While I'm not sure if this is possible, anything that can save me time and space in writing these matrices would be appreciated.

user213059
  • 21
  • 1

4 Answers4

4

If you want to use LuaLaTeX, there's a solution for fun:

%!TEX program = lualatex
\documentclass{standalone}
\usepackage{amsmath}
\usepackage{mathtools}
\usepackage{luacode}
%More than 10 columns
\setcounter{MaxMatrixCols}{20}
\begin{luacode*}
function diagonal_matrix (n)
    local matrix = {}
    for i = 1,n do
    matrix[i] = {}
        for j = 1, n do
            if i == j then
                matrix[i][j] = [[\lambda_{]]..tostring(i)..[[}]]
            else
                matrix[i][j] = "0"
            end             
        end
    end
    tex.print([[\begin{bmatrix*}]])
    for i=1,n do
        tex.print(table.concat(matrix[i], [[ & ]]))
        tex.print([[\\]])
    end
    tex.print([[\end{bmatrix*}]])
end
\end{luacode*}
\newcommand{\diagmatrix}[1]{\directlua{diagonal_matrix(#1)}}
\usepackage{amsmath}
\begin{document}
$\diagmatrix{15}$
\end{document}

enter image description here

2

This is a job for expl3.

\documentclass{article}
\usepackage{amsmath,xparse}

\ExplSyntaxOn

\NewDocumentCommand{\diagonal}{O{b}m}
 {% #1 = fences, #2 = entries, comma separated
  \egreg_diagonal:nn { #1 } { #2 }
 }

\seq_new:N \l__egreg_diagonal_entries_seq
\seq_new:N \l__egreg_diagonal_row_seq

\cs_new_protected:Nn \egreg_diagonal:nn
 {
  \seq_set_from_clist:Nn \l__egreg_diagonal_entries_seq { #2 }
  \begin{#1matrix}
  \int_step_function:nN { \seq_count:N \l__egreg_diagonal_entries_seq } \__egreg_diagonal:n
  \end{#1matrix}
 }

\cs_new_protected:Nn \__egreg_diagonal:n
 {% #1 = row number
  \seq_clear:N \l__egreg_diagonal_row_seq
  \int_step_inline:nn { \seq_count:N \l__egreg_diagonal_entries_seq }
   {
    \int_compare:nTF { #1 == ##1 }
     {
      \seq_put_right:Nx \l__egreg_diagonal_row_seq
       {
        \seq_item:Nn \l__egreg_diagonal_entries_seq { #1 }
       }
     }
     {
      \seq_put_right:Nn \l__egreg_diagonal_row_seq { 0 }
     }
   }
  \seq_use:Nn \l__egreg_diagonal_row_seq { & } \\
 }

\ExplSyntaxOff

\begin{document}

\[
\diagonal{1} \qquad \diagonal[p]{1,2} \qquad
\diagonal[B]{1,2,3} \qquad \diagonal[v]{1,2,3,4}
\]
\[
\diagonal{\lambda_1,\lambda_2,\lambda_3,\lambda_4,\lambda_5,
  \lambda_6,\lambda_7,\lambda_8,\lambda_9}
\]

\end{document}

Here I exploit the fact that \int_step_function:nN delivers its result “all at once”, so we can build the whole matrix starting in its first cell.

The optional argument states the shape of the fences, in the same style as amsmath: b (default) for brackets, p for parentheses, B for braces, v for vertical lines and V for double vertical lines.

The mandatory argument is a comma separated list of the diagonal entries.

The outer loop builds a row at each step. It calls an inner loop that adds entries to a sequence: 0 if we're not at the diagonal, the right entry otherwise. Next the sequence is delivered with & between each item and \\ for ending the row.

enter image description here

Warning: the empty diagonal matrix cannot be produced. A test whether the list of entries is empty can be added: change the main function to

\cs_new_protected:Nn \egreg_diagonal:nn
 {
  \seq_set_from_clist:Nn \l__egreg_diagonal_entries_seq { #2 }
  \begin{#1matrix}
  \int_compare:nTF { \seq_count:N \l__egreg_diagonal_entries_seq == 0 }
   {
    {\:}
   }
   {
    \int_step_function:nN { \seq_count:N \l__egreg_diagonal_entries_seq } \__egreg_diagonal:n
   }
  \end{#1matrix}
 }

and the call \diagonal{} would produce

enter image description here

A different implementation where one can choose whether to input explicit diagonal elements or just follow a pattern.

\documentclass{article}
\usepackage{amsmath,xparse}

\ExplSyntaxOn

\NewDocumentCommand{\diagonal}{m}
 {
  \group_begin:
  \keys_set:nn { egreg/diagonal } { #1 }
  \egreg_diagonal:
  \group_end:
 }

\keys_define:nn { egreg/diagonal }
 {
  type .tl_set:N = \l__egreg_diagonal_type_tl,
  type .initial:n = b,
  entries .clist_set:N = \l_egreg_diagonal_entries_clist,
  pattern .code:n = \cs_set_protected:Nn \__egreg_diagonal_pattern:n { #1 },
  items .int_set:N = \l__egreg_diagonal_items_int,
 }

\seq_new:N \l__egreg_diagonal_entries_seq
\seq_new:N \l__egreg_diagonal_row_seq
\cs_generate_variant:Nn \seq_set_from_clist:Nn { NV }

\cs_new_protected:Nn \egreg_diagonal:
 {
  \cs_if_exist:NTF \__egreg_diagonal_pattern:n
   {
    \int_step_inline:nn { \l__egreg_diagonal_items_int }
     {
      \seq_put_right:Nn \l__egreg_diagonal_entries_seq { \__egreg_diagonal_pattern:n { ##1 } }
     }
   }
   {
    \seq_set_from_clist:NV \l__egreg_diagonal_entries_seq \l_egreg_diagonal_entries_clist
   }
  \begin{\l__egreg_diagonal_type_tl matrix}
  \int_compare:nTF { \seq_count:N \l__egreg_diagonal_entries_seq == 0 }
   {
    {\:}
   }
   {
    \int_step_function:nN { \seq_count:N \l__egreg_diagonal_entries_seq } \__egreg_diagonal:n
   }
  \end{\l__egreg_diagonal_type_tl matrix}
 }

\cs_new_protected:Nn \__egreg_diagonal:n
 {% #1 = row number
  \seq_clear:N \l__egreg_diagonal_row_seq
  \int_step_inline:nn { \seq_count:N \l__egreg_diagonal_entries_seq }
   {
    \int_compare:nTF { #1 == ##1 }
     {
      \seq_put_right:Nx \l__egreg_diagonal_row_seq { \seq_item:Nn \l__egreg_diagonal_entries_seq { #1 } }
     }
     {
      \seq_put_right:Nn \l__egreg_diagonal_row_seq { 0 }
     }
   }
  \seq_use:Nn \l__egreg_diagonal_row_seq { & } \\
 }

\ExplSyntaxOff

\begin{document}

\[
\diagonal{entries=} \qquad
\diagonal{entries=1} \qquad \diagonal{type=p,entries={1,2}} \qquad
\diagonal{type=B,entries={1,2,3}} \qquad \diagonal{type=v,entries={1,2,3,4}}
\]
\[
\diagonal{pattern=\lambda_{#1},items=9}
\]

\end{document}

The output is the same as before.

egreg
  • 1,121,712
2

It's very easy to do that with nicematrix:

\documentclass{article}
\usepackage{nicematrix}
\usepackage{ifthen}

\NewDocumentCommand \DiagMatrix {m}
  {
    \pAutoNiceMatrix{#1-#1}
      { \ifthenelse{\value{iRow}=\value{jCol}} { \lambda_{\arabic{iRow}}} { 0 } }
  }

\begin{document}
$\DiagMatrix{5}$
\end{document}

Result of the above code

F. Pantigny
  • 40,250
1

I can offer a \romannumeral-expansion-driven tail-recursive loop which does without Lua-/ε-TeX-extensions.

The syntax differs from what you ask for:

You suggest: \diagmat{\lambda_1}{\lambda_2}..{\lambda_N}.

My loop does: \diagmat{{\lambda_1}{\lambda_2}..{\lambda_N}}.

With my loop, the \diagmat-macro processes a single argument which contains the ⟨list of diagonal values⟩ which in turn can be of arbitrary length.


Some Pseudo-Code:

\diagmat{⟨list of diagonal values⟩}

→ \romannumeral0\@diagmat{⟨list of diagonal values⟩}% {⟨list of zeros for next row⟩}% initialized empty {⟨list of rows⟩}% initialized empty {⟨new list of rows⟩}% initialized empty {⟨amount of diagonal values⟩}% initialized 0

\@diagmat is a \romannumeral-expansion driven tail-recursive loop which modifies/(ex)changes its own arguments from one iteration to the next/from one call to itself to the next call to itself in order to work as follows:

Initialize:

⟨list of diagonal values⟩ := User-provided list of undelimited arguments; ⟨list of zeros for next row⟩ := empty; ⟨list of rows⟩ := empty; ⟨new list of rows⟩ := empty; ⟨amount of diagonal values⟩ := 0;

STEP 1:
If ⟨list of diagonal values⟩ is blank, i.e., is empty or contains only spaces, THEN // -- Comment: The loop for attaching "\\" to the end of each table-row: IF ⟨list of rows⟩ is empty, THEN IF ⟨new list of rows⟩ is empty, THEN Terminate \romannumeral-expansion; ELSE Terminate \romannumeral-expansion and deliver \begingroup \c@MaxMatrixCols=⟨amount of diagonal values⟩\relax $\begin{bmatrix*}⟨new list of rows⟩\end{bmatrix*}$% \endgroup; ENDIF; ELSE ⟨new list of rows⟩ := ⟨new list of rows⟩ + first element of ⟨list of rows⟩ + "\\" ; ⟨list of rows⟩ := result of removing first element from ⟨list of rows⟩ ; GOTO STEP 1; ENDIF; ELSE IF ⟨list of rows⟩ is empty, THEN // -- Comment: Attach another matrix-row ⟨list of rows⟩ := ⟨new list of rows⟩ + "{" IF ⟨list of zeros for next row⟩ is not empty, THEN + ⟨list of zeros for next row⟩ + "&" ENDIF; + first element of ⟨list of diagonal values⟩ + "}"; IF ⟨list of zeros for next row⟩ is empty, THEN ⟨list of zeros for next row⟩ := "0"; ELSE ⟨list of zeros for next row⟩ := ⟨list of zeros for next row⟩ + "&0"; ENDIF; ⟨list of diagonal values⟩ := result of removing first element from ⟨list of diagonal values⟩; ⟨new list of rows⟩ := empty; ⟨amount of diagonal values⟩ := ⟨amount of diagonal values⟩ + 1; ELSE // -- Comment: The loop for attaching "&0" to the end of each matrix-row as there will be another matrix-row: ⟨new list of rows⟩ := ⟨new list of rows⟩ + "{" + first element of ⟨list of rows⟩ + "&0}"; ⟨list of rows⟩ := result of removing first element from ⟨list of rows⟩; ENDIF; GOTO STEP 1; ENDIF;


Here is the example:

\documentclass[a4paper]{article}
\usepackage{amsmath}
\usepackage{mathtools}

%===================[adjust margins/layout for the example]==================== \csname @ifundefined\endcsname{pagewidth}{}{\pagewidth=\paperwidth}% \csname @ifundefined\endcsname{pdfpagewidth}{}{\pdfpagewidth=\paperwidth}% \csname @ifundefined\endcsname{pageheight}{}{\pageheight=\paperheight}% \csname @ifundefined\endcsname{pdfpageheight}{}{\pdfpageheight=\paperheight}% \textwidth=\paperwidth \oddsidemargin=1.25cm \marginparsep=.2\oddsidemargin \marginparwidth=\oddsidemargin \advance\marginparwidth-2\marginparsep \advance\textwidth-2\oddsidemargin \advance\oddsidemargin-1in \evensidemargin=\oddsidemargin \textheight=\paperheight \topmargin=1.25cm \footskip=.5\topmargin {\normalfont\global\advance\footskip.5\ht\strutbox}% \advance\textheight-2\topmargin \advance\topmargin-1in \headheight=0ex \headsep=0ex \pagestyle{plain} \parindent=0ex \parskip=0ex \topsep=0ex \partopsep=0ex %==================[eof margin-adjustments]====================================

\makeatletter %%----------------------------------------------------------------------------- %% Exchange things in the token-stream: %%............................................................................. \newcommand\UD@PassFirstToSecond[2]{#2{#1}}% \newcommand\UD@Exchange[2]{#2#1}% \newcommand\UD@firstoftwo[2]{#1}% \newcommand\UD@secondoftwo[2]{#2}% @ifdefinable\UD@stopromannumeral{\chardef\UD@stopromannumeral=`^^00}% %%----------------------------------------------------------------------------- %% Check whether argument is empty: %%............................................................................. %% \UD@CheckWhetherNull{<Argument which is to be checked>}% %% {<Tokens to be delivered in case that argument %% which is to be checked is empty>}% %% {<Tokens to be delivered in case that argument %% which is to be checked is not empty>}% %% %% The gist of this macro comes from Robert R. Schneck's \ifempty-macro: %% <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J> \newcommand\UD@CheckWhetherNull[1]{% \romannumeral\expandafter\UD@secondoftwo\string{\expandafter \UD@secondoftwo\expandafter{\expandafter{\string#1}\expandafter \UD@secondoftwo\string}\expandafter\UD@firstoftwo\expandafter{\expandafter \UD@secondoftwo\string}\expandafter\UD@stopromannumeral\UD@secondoftwo}{% \expandafter\UD@stopromannumeral\UD@firstoftwo}% }% %%----------------------------------------------------------------------------- %% Check whether argument is blank (empty or only spaces): %%----------------------------------------------------------------------------- %% -- Take advantage of the fact that TeX discards space tokens when %% "fetching" _un_delimited arguments: -- %% \UD@CheckWhetherBlank{<Argument which is to be checked>}% %% {<Tokens to be delivered in case that %% argument which is to be checked is blank>}% %% {<Tokens to be delivered in case that argument %% which is to be checked is not blank>}% \newcommand\UD@CheckWhetherBlank[1]{% \romannumeral\expandafter\expandafter\expandafter\UD@secondoftwo \expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo#1{}{}}% }% %%----------------------------------------------------------------------------- %% Extract first inner undelimited argument: %% %% \UD@ExtractFirstArg{ABCDE} yields {A} %% %% \UD@ExtractFirstArg{{AB}CDE} yields {{AB}} %% %% Due to \romannumeral-expansion the result is delivered after two %% expansion-steps/after "hitting" \UD@ExtractFirstArg with \expandafter %% twice. %% %% \UD@ExtractFirstArg's argument must not be blank. %% This case can be cranked out via \UD@CheckWhetherBlank before calling %% \UD@ExtractFirstArg. %% %% Use frozen-\relax as delimiter for speeding things up. %% I chose frozen-\relax because David Carlisle pointed out in %% <https://tex.stackexchange.com/a/578877> %% that frozen-\relax cannot be (re)defined in terms of \outer and cannot be %% affected by \uppercase/\lowercase. %% %% \UD@ExtractFirstArg's argument may contain frozen-\relax: %% The only effect is that internally more iterations are needed for %% obtaining the result. %% %%............................................................................. @ifdefinable\UD@RemoveTillFrozenrelax{% \expandafter\expandafter\expandafter\UD@Exchange \expandafter\expandafter\expandafter{% \expandafter\expandafter\ifnum0=0\fi}% {\long\def\UD@RemoveTillFrozenrelax#1#2}{{#1}}% }% \expandafter\UD@PassFirstToSecond\expandafter{% \romannumeral\expandafter \UD@PassFirstToSecond\expandafter{\romannumeral \expandafter\expandafter\expandafter\UD@Exchange \expandafter\expandafter\expandafter{% \expandafter\expandafter\ifnum0=0\fi}{\UD@stopromannumeral#1}% }{% \UD@stopromannumeral\romannumeral\UD@ExtractFirstArgLoop }% }{% \newcommand\UD@ExtractFirstArg[1]% }% \newcommand\UD@ExtractFirstArgLoop[1]{% \expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}% {\expandafter\UD@stopromannumeral\UD@firstoftwo#1{}}% {\expandafter\UD@ExtractFirstArgLoop\expandafter{\UD@RemoveTillFrozenrelax#1}}% }% %------------------------------------------------------------------------------ % Expandable incrementing of natural number formed by a sequence of % explicit catcode-12-character-tokens-from-the-set {0,1,2,3,4,5,6,7,8,9} %.............................................................................. % \Increment{<natural number k as sequence of explicit catcode-12-character- % tokens from the set 0123456789>} % -> % <natural number (k+1) as sequence of explicit catcode-12-character-tokens % from the set 0123456789> % In expansion-contexts the result is delivered after two expansion-steps/is % obtained by "hitting" \Increment with \expandafter twice. %------------------------------------------------------------------------------ \newcommand\Increment[1]{% \romannumeral \UD@IncrementReverse{\UD@IncrementFork{}}{\relax}{}#1\relax }% \newcommand\UD@IncrementReverse[4]{% \ifx\relax#4% \expandafter\UD@firstoftwo \else \expandafter\UD@secondoftwo \fi {#1#3#2}{\UD@IncrementReverse{#1}{#2}{#4#3}}% }% @ifdefinable\UD@IncrementSelect{% \long\def\UD@IncrementSelect#10123456789\relax#2#3\relax\relax\relax{#2}% }% \newcommand\UD@IncrementFork[2]{% \UD@IncrementSelect #2123456789\relax{\UD@IncrementReverse{\UD@stopromannumeral}{}{}#11}% 0#223456789\relax{\UD@IncrementReverse{\UD@stopromannumeral}{}{}#12}% 01#23456789\relax{\UD@IncrementReverse{\UD@stopromannumeral}{}{}#13}% 012#2456789\relax{\UD@IncrementReverse{\UD@stopromannumeral}{}{}#14}% 0123#256789\relax{\UD@IncrementReverse{\UD@stopromannumeral}{}{}#15}% 01234#26789\relax{\UD@IncrementReverse{\UD@stopromannumeral}{}{}#16}% 012345#2789\relax{\UD@IncrementReverse{\UD@stopromannumeral}{}{}#17}% 0123456#289\relax{\UD@IncrementReverse{\UD@stopromannumeral}{}{}#18}% 01234567#29\relax{\UD@IncrementReverse{\UD@stopromannumeral}{}{}#19}% 012345678#2\relax{\UD@IncrementFork{#10}}% 0123456789#2{\UD@IncrementReverse{\UD@stopromannumeral}{}{}#11\relax}% 0123456789\relax{\UD@IncrementReverse{\UD@stopromannumeral}{}{}#11#2}% \relax\relax\relax }% %%----------------------------------------------------------------------------- %% \diagmat which initiates the \romannumeral0-expansion-driven %% @diagmat-tail-recursive loop. %%............................................................................. \newcommand\diagmat[1]{% \romannumeral@diagmat{#1}{}{}{}{0}% }% \newcommand@diagmat[5]{% % #1 = <List of diagonal values> % #2 = <List of zeros for next row if present> % #3 = <List of rows> % #4 = <New list of rows> % #5 = <Amount of diagonal values> \UD@CheckWhetherBlank{#1}{% \UD@CheckWhetherNull{#3}{% \UD@CheckWhetherNull{#4}{\UD@stopromannumeral}{% \UD@stopromannumeral \begingroup\c@MaxMatrixCols=#5\relax$\begin{bmatrix}#4\end{bmatrix}$\endgroup %\def\tempa{{#5}--#4}\show\tempa }% }{% \expandafter\expandafter\expandafter\UD@PassFirstToSecond \expandafter\expandafter\expandafter{% \expandafter\UD@Exchange \expandafter{% \romannumeral \expandafter\expandafter\expandafter\UD@stopromannumeral \UD@ExtractFirstArg{#3}\% }{#4}% }{% \expandafter\UD@PassFirstToSecond\expandafter{\UD@firstoftwo{}#3}{% @diagmat{#1}{#2}% }% }% {#5}% }% }{% \UD@CheckWhetherNull{#3}{% \expandafter\expandafter\expandafter\UD@PassFirstToSecond \expandafter\expandafter\expandafter{% \Increment{#5}% }{% \expandafter\expandafter\expandafter\UD@PassFirstToSecond \expandafter\expandafter\expandafter{% \expandafter\UD@PassFirstToSecond\expandafter{% \romannumeral \expandafter\UD@Exchange \expandafter{% \romannumeral \expandafter\expandafter\expandafter\UD@stopromannumeral \UD@ExtractFirstArg{#1}% }{% \UD@CheckWhetherNull{#2}{\UD@stopromannumeral}{\UD@stopromannumeral#2&}% }% }{#4}% }{% \UD@CheckWhetherNull{#2}{% \UD@PassFirstToSecond{0}% }{% \UD@PassFirstToSecond{#2&0}% }% {% \expandafter@diagmat\expandafter{\UD@firstoftwo{}#1}% }% }{}% }% }{% \expandafter\expandafter\expandafter\UD@PassFirstToSecond \expandafter\expandafter\expandafter{% \expandafter\UD@PassFirstToSecond \expandafter{% \romannumeral \expandafter\expandafter\expandafter\UD@stopromannumeral \UD@ExtractFirstArg{#3}&0% }{#4}% }{% \expandafter\UD@PassFirstToSecond\expandafter{\UD@firstoftwo{}#3}{% @diagmat{#1}{#2}% }% }% {#5}% }% }% }% \makeatother

\begin{document}

\verb|\diagmat{}|:%

\diagmat{}%

\smallskip\hrule\smallskip

\verb|\diagmat{{\lambda_1}}|:

\diagmat{{\lambda_1}}%

\smallskip\hrule\smallskip

\verb|\diagmat{{\lambda_1}{\lambda_2}}|:

\diagmat{{\lambda_1}{\lambda_2}}%

\smallskip\hrule\smallskip

\verb|\diagmat{{\lambda_1}{\lambda_2}{\lambda_3}}|:

\diagmat{{\lambda_1}{\lambda_2}{\lambda_3}}%

\smallskip\hrule\smallskip

\begin{verbatim} \diagmat{ {\lambda_1} {\lambda_2} {\lambda_3} {\lambda_4} {\lambda_5} {\lambda_6} {\lambda_7} {\lambda_8} {\lambda_9} {\lambda_{10}} {\lambda_{11}} {\lambda_{12}} {\lambda_{13}} {\lambda_{14}} {\lambda_{15}} {\lambda_{16}} {\lambda_{17}} {\lambda_{18}} {\lambda_{19}} {\lambda_{20}} } \end{verbatim}

\diagmat{ {\lambda_1} {\lambda_2} {\lambda_3} {\lambda_4} {\lambda_5} {\lambda_6} {\lambda_7} {\lambda_8} {\lambda_9} {\lambda_{10}} {\lambda_{11}} {\lambda_{12}} {\lambda_{13}} {\lambda_{14}} {\lambda_{15}} {\lambda_{16}} {\lambda_{17}} {\lambda_{18}} {\lambda_{19}} {\lambda_{20}} }

\end{document}


enter image description here

Ulrich Diez
  • 28,770