3

I'm (relatively) new to LaTeX, and I'm having a syntactic problem using the wonderful code here, which allows arbitrary matrix operations in LaTeX.

I wrote a macro to generate certain matrices. E.g.:

\def\GetRotationMatrixZ#1{
    cos(#1),-sin(#1),0,
    sin(#1), cos(#1),0,
          0,       0,1
}

Now I would like to use it with the linked code's \matnew, \matset commands. Everything obvious I tried, however, failed miserably. I tried many variations on the macro's syntax, but I'm stuck.

EDIT: the main usage case would look something like:

\matnew \transformMatrixA
\matset \transformMatrixA {\GetRotationMatrixY\whatever}
% . . .
\matmul \transformMatrix \transformMatrixA \transformMatrixB

I've tried similar things; I don't remember exactly which. This particular example gives:

Unexpected comma: extra arguments ignored.
  • In what way do you want to use it with \matnew and \matset? Could you include in your post what you've tried...? Perhaps that'll make things more clear. – Werner Nov 6 '13 at 17:52
  • @Werner: I don't remember all the things I've tried exactly, but I added an example of one of them. – imallett Nov 6 '13 at 18:52
4

LaTeX3 people will give more authoritative answers (particularly they know how to generate variants to \matset which will expand its argument). In the mean time, perhaps this helps (it seems the issues were that you did not use semi-colons to end rows, and there was a problem of expansion).

TeX is a macro language, where the key word is expansion. Even if you use higher abstraction levels like LaTeX2 (which tries to its best to \emph{hide} from the user the programming possibilities) or LaTeX3 (which to the contrary has a very well-thought and sophisticated scheme to make it easier to code, while at the same time clearly separating programmer tasks from end user tools), you can not escape that. It seems that the \matset macro from the code you linked too does not expand its argument. If you try to use it as \matset\MatrixA {\macro{3}}, it will receive \macro{3} and not the semi-colon separated rows it seems to expect. You first need to tell TeX to expand \macro{3} to that list or rows. It would be immediate for a \LaTeX3 programmer to generate the variant of \matset which first expands its argument, as a work-around, \expandafter\matset\expandafter\MatrixA \expandafter{\macro{3}} would do it. Notice that the brace { is part of the input stream and must be jumped over by \expandafter.

%% (to be embedded in the code from [there](https://tex.stackexchange.com/a/112683/34359))

\def\GetRotationMatrix #1{%
    cos(#1), -sin(#1), 0;
    sin(#1),  cos(#1), 0;
          0,        0, 1 }

\expandafter\matset\expandafter\transformMatrixA \expandafter
 {\GetRotationMatrix {3.141592653}}

 \begin{equation}
   \mattypeset \transformMatrixA
 \end{equation}

Here embedded at the end of the code of Bruno Le Floch

% Programming-level functions: \fpm_new:N, \fpm_set:Nn, \fpm_gset:Nn,
% \fpm_add:NNN, \fpm_sub:NNN, \fp_neg:NN, \fp_transpose:NN, \fp_mul:NNN.
%
% Expandable programming-level functions: \fpm_lines:N, \fpm_columns:N,
% \fpm_get:Nnn.
%
% Document-level functions: \matnew, \matset, \matgset, \matadd,
% \matsub, \matmul, \mattypeset.
%
\RequirePackage{expl3}
{
  \ExplSyntaxOn
  %
  % Programming-level code, for adding, multiplying, matrices.  A matrix
  % of size |MxN| is stored as a token list of the form
  %
  % \s__fpm { M } { N } { {a11} ... {a1N} } ... { {aM1} ... {aMN} } ;
  %
  % where |\s__fpm| is a marker used to recognize matrices, |M| and |N|
  % are non-negative integers, and |aij| are floating point numbers.
  %
  % (1) Variables.
  %
  \cs_new_eq:NN \s__fpm \scan_stop: % A marker.
  \tl_const:Nn \c_empty_fpm { \s__fpm { 0 } { 0 } ; }
  \cs_new_eq:NN \l__fpm_tmpa_fpm \c_empty_fpm
  \seq_new:N \l__fpm_lines_seq
  \int_new:N \l__fpm_lines_A_int
  \int_new:N \l__fpm_lines_B_int
  \int_new:N \l__fpm_columns_A_int
  \int_new:N \l__fpm_columns_B_int
  \tl_new:N \l__fpm_matrix_A_tl
  \tl_new:N \l__fpm_matrix_B_tl
  \tl_new:N \l__fpm_matrix_C_tl
  \seq_new:N \l__fpm_matrix_A_seq
  \seq_new:N \l__fpm_matrix_B_seq
  \seq_new:N \l__fpm_one_line_A_seq
  \seq_new:N \l__fpm_one_line_B_seq
  \tl_new:N \l__fpm_one_line_A_tl
  \int_new:N \l__fpm_tmpa_int
  %
  % (2) Variants and generic helpers.
  %
  %
  % (3) Storing matrices.
  %
  \cs_new_protected:Npn \fpm_new:N #1
    { \cs_new_eq:NN #1 \c_empty_fpm }
  \cs_new_protected_nopar:Npn \fpm_set:Nn
    { \__fpm_set:NNn \tl_set:Nx }
  \cs_new_protected_nopar:Npn \fpm_gset:Nn
    { \__fpm_set:NNn \tl_gset:Nx }
  \cs_new_protected:Npn \__fpm_set:NNn #1#2#3
    {
      \seq_set_split:Nnn \l__fpm_lines_seq { ; } {#3}
      \seq_set_filter:NNn \l__fpm_lines_seq \l__fpm_lines_seq
        { ! \tl_if_empty_p:n {##1} }
      %
      % Now all lines are non-empty.
      %
      \tl_clear:N \l__fpm_matrix_A_tl
      \int_zero:N \l__fpm_lines_A_int
      \int_zero:N \l__fpm_columns_A_int
      \seq_map_inline:Nn \l__fpm_lines_seq
        {
          \int_incr:N \l__fpm_lines_A_int
          \seq_set_from_clist:Nn \l__fpm_one_line_A_seq {##1}
          \int_set:Nn \l__fpm_tmpa_int { \seq_count:N \l__fpm_one_line_A_seq }
          \int_compare:nNnT \l__fpm_columns_A_int = \c_zero
            { \int_set_eq:NN \l__fpm_columns_A_int \l__fpm_tmpa_int }
          \int_compare:nNnF \l__fpm_tmpa_int = \l__fpm_columns_A_int
            { \seq_map_break:n { \msg_error:nn { fpm } { invalid-size } } }
          \tl_put_right:Nx \l__fpm_matrix_A_tl
            { { \seq_map_function:NN \l__fpm_one_line_A_seq \__fpm_set_aux:n } }
        }
      #1 #2
        {
          \s__fpm
          { \int_use:N \l__fpm_lines_A_int }
          { \int_use:N \l__fpm_columns_A_int }
          \l__fpm_matrix_A_tl
          ;
        }
    }
  \cs_new:Npn \__fpm_set_aux:n #1 { { \fp_to_tl:n {#1} } }
  %
  % (4) Extracting the size of a matrix, and its contents.
  % |#1| is the matrix, |#2|, |#3| integer variables receiving the
  % number of lines and of columns, and |#4| a token list receiving the
  % contents of the matrix.
  %
  \cs_new_protected:Npn \__fpm_get_parts:NNNN #1#2#3#4
    { \exp_after:wN \__fpm_get_parts:NnnwNNN #1 #2 #3 #4 }
  \cs_new_protected:Npn \__fpm_get_parts:NnnwNNN \s__fpm #1#2#3 ; #4#5#6
    {
      \int_set:Nn #4 {#1}
      \int_set:Nn #5 {#2}
      \tl_set:Nn #6 {#3}
    }
  %
  % (5) Some expandable functions: getting one entry, getting the size.
  %
  \cs_new:Npn \fpm_lines:N #1
    { \exp_after:wN \__fpm_lines:NnnwN #1 \use_i:nn }
  \cs_new:Npn \fpm_columns:N #1
    { \exp_after:wN \__fpm_lines:NnnwN #1 \use_ii:nn }
  \cs_new:Npn \__fpm_lines:NnnwN \s__fpm #1#2#3 ; #4 { #4 {#1} {#2} }
  \cs_new:Npn \fpm_get:Nnn #1#2#3
    { \exp_after:wN \__fpm_get:Nnnwnn #1 #2 #3 }
  \cs_new:Npn \__fpm_get:Nnnwnn \s__fpm #1#2#3 ; #4#5
    { \exp_args:Nf \tl_item:nn { \tl_item:nn {#3} {#4} } {#5} }
  %
  % (6) Summing matrices
  %
  \cs_new_protected_nopar:Npn \fpm_add:NNN { \__fpm_add:NNNN + }
  \cs_new_protected_nopar:Npn \fpm_sub:NNN { \__fpm_add:NNNN - }
  \cs_new_protected:Npn \__fpm_add:NNNN #1#2#3#4
    {
      \tl_set:Nn \l__fpm_sign_tl {#1}
      \__fpm_get_parts:NNNN #3
        \l__fpm_lines_A_int \l__fpm_columns_A_int \l__fpm_matrix_A_tl
      \__fpm_get_parts:NNNN #4
        \l__fpm_lines_B_int \l__fpm_columns_B_int \l__fpm_matrix_B_tl
      \int_compare:nNnTF \l__fpm_lines_A_int = \l__fpm_lines_B_int
        {
          \int_compare:nNnTF \l__fpm_columns_A_int = \l__fpm_columns_B_int
            { \__fpm_add:N #2 }
            { \msg_error:nn { fpm } { invalid-size } }
        }
        { \msg_error:nn { fpm } { invalid-size } }
    }
  \cs_new_protected:Npn \__fpm_add:N #1
    {
      \seq_set_split:NnV \l__fpm_matrix_A_seq { } \l__fpm_matrix_A_tl
      \seq_set_split:NnV \l__fpm_matrix_B_seq { } \l__fpm_matrix_B_tl
      \tl_clear:N \l__fpm_matrix_C_tl
      \seq_mapthread_function:NNN
        \l__fpm_matrix_A_seq
        \l__fpm_matrix_B_seq
        \__fpm_add_lines:nn
      \tl_set:Nx #1
        {
          \s__fpm
          { \int_use:N \l__fpm_lines_A_int }
          { \int_use:N \l__fpm_columns_A_int }
          \l__fpm_matrix_C_tl
          ;
        }
    }
  \cs_new_protected:Npn \__fpm_add_lines:nn #1#2
    {
      \seq_set_split:Nnn \l__fpm_one_line_A_seq { } {#1}
      \seq_set_split:Nnn \l__fpm_one_line_B_seq { } {#2}
      \tl_put_right:Nx \l__fpm_matrix_C_tl
        {
          {
            \seq_mapthread_function:NNN
              \l__fpm_one_line_A_seq
              \l__fpm_one_line_B_seq
              \__fpm_add_entries:nn
          }
        }
    }
  \cs_new:Npn \__fpm_add_entries:nn #1#2
    { { \fp_to_tl:n { #1 \l__fpm_sign_tl #2 } } }
  %
  % (7) Negating all entries.
  %
  \cs_new_protected:Npn \fpm_neg:NN #1#2
    { \tl_set:Nx #1 { \exp_after:wN \__fpm_neg:Nnnw #2 } }
  \cs_new:Npn \__fpm_neg:Nnnw \s__fpm #1#2#3 ;
    { \s__fpm {#1} {#2} \tl_map_function:nN {#3} \__fpm_neg_aux:n ; }
  \cs_new:Npn \__fpm_neg_aux:n #1
    { { \tl_map_function:nN {#1} \__fpm_neg_auxii:n } }
  \cs_new:Npn \__fpm_neg_auxii:n #1
    { { \fp_to_tl:n { - #1 } } }
  %
  % (8) Transposing a matrix.
  %
  \cs_new_protected:Npn \fpm_transpose:NN #1#2
    {
      \__fpm_get_parts:NNNN #2
        \l__fpm_lines_A_int \l__fpm_columns_A_int \l__fpm_matrix_A_tl
      \seq_set_split:NnV \l__fpm_matrix_A_seq { } \l__fpm_matrix_A_tl
      \tl_clear:N \l__fpm_matrix_B_tl
      \prg_replicate:nn { \l__fpm_columns_A_int }
        {
          \tl_put_right:Nx \l__fpm_matrix_B_tl
            { { \seq_map_function:NN \l__fpm_matrix_A_seq \__fpm_wrap_head:n } }
          \seq_set_map:NNn \l__fpm_matrix_A_seq \l__fpm_matrix_A_seq
            { \tl_tail:n {##1} }
        }
      \tl_set:Nx #1
        {
          \s__fpm
          { \int_use:N \l__fpm_columns_A_int }
          { \int_use:N \l__fpm_lines_A_int }
          \l__fpm_matrix_B_tl
          ;
        }
    }
  \cs_new:Npn \__fpm_wrap_head:n #1 { { \tl_head:n {#1} } }
  %
  % (9) Multiplying matrices.
  %
  \cs_new_protected:Npn \fpm_mul:NNN #1#2#3
    {
      \int_compare:nNnTF { \fpm_columns:N #2 } = { \fpm_lines:N #3 }
        {
          \fpm_transpose:NN \l__fpm_tmpa_fpm #3
          \__fpm_get_parts:NNNN #2
            \l__fpm_lines_A_int \l__fpm_columns_A_int \l__fpm_matrix_A_tl
          \__fpm_get_parts:NNNN #3
            \l__fpm_lines_B_int \l__fpm_columns_B_int \l__fpm_matrix_B_tl
          \tl_set:Nx #1
            {
              \s__fpm
              { \int_use:N \l__fpm_lines_A_int }
              { \int_use:N \l__fpm_columns_B_int }
              \tl_map_function:NN \l__fpm_matrix_A_tl \__fpm_mul_line:n
              ;
            }
        }
        { \msg_error:nn { fpm } { invalid-size } }
    }
  \cs_new:Npn \__fpm_mul_line:n #1
    { { \exp_after:wN \__fpm_mul_line:Nnnwn \l__fpm_tmpa_fpm {#1} } }
  \cs_new:Npn \__fpm_mul_line:Nnnwn \s__fpm #1#2#3 ; #4
    { \__fpm_mul_line:nn {#4} #3 \q_recursion_tail \q_recursion_stop }
  \cs_new:Npn \__fpm_mul_line:nn #1#2
    {
      \quark_if_recursion_tail_stop:n {#2}
      {
        \fp_to_tl:n
          {
            \__fpm_mul_one:nwn #1 \use_none_delimit_by_q_stop:w
              \q_mark #2 \q_nil \q_stop
            0
          }
      }
      \__fpm_mul_line:nn {#1}
    }
  \cs_new:Npn \__fpm_mul_one:nwn #1#2 \q_mark #3
    { #1 * #3 + \__fpm_mul_one:nwn #2 \q_mark }
  %
  %
  % Messages.
  %
  \msg_new:nnn { fpm } { invalid-size }
    { Sizes~of~matrices~or~lines~don't~match. }
}
\RequirePackage{amsmath, siunitx}
{
  \ExplSyntaxOn
  %
  % Turning matrices into arrays for display.
  %
  \cs_new_protected:Npn \fpm_to_array:N #1
    {
      \begin{pmatrix}
        \exp_after:wN \__fpm_to_array:Nnnw #1
      \end{pmatrix}
    }
  \cs_new_eq:NN \__fpm_newline: ? % Dummy def.
  \cs_new_protected:Npn \__fpm_to_array:Nnnw \s__fpm #1#2#3 ;
    {
      \cs_gset_nopar:Npn \__fpm_newline:
        { \cs_gset_nopar:Npn \__fpm_newline: { \\ } }
      \tl_map_inline:nn {#3}
        {
          \__fpm_newline:
          \seq_set_split:Nnn \l__fpm_one_line_A_seq { } {##1}
          \seq_set_map:NNn \l__fpm_one_line_A_seq \l__fpm_one_line_A_seq
            { \__fpm_to_array_entry:n {####1} }
          \seq_use:Nnnn \l__fpm_one_line_A_seq { & } { & } { & }
        }
    }
  \cs_new_protected:Npn \__fpm_to_array_entry:n #1
    {
      \str_case:nnn {#1}
        {
          { nan } { \text{nan} }
          { inf } { \infty }
          { -inf } { -\infty }
        }
        { \num{#1} }
    }
}

\RequirePackage{xparse}
\ExplSyntaxOn
%
% Document-level functions.
%
\NewDocumentCommand { \matnew } { m } { \fpm_new:N #1 }
\NewDocumentCommand { \matset } { mm } { \fpm_set:Nn #1 {#2} }
\NewDocumentCommand { \matgset } { mm } { \fpm_gset:Nn #1 {#2} }
\NewDocumentCommand { \matadd } { mmm } { \fpm_add:NNN #1 #2 #3 }
\NewDocumentCommand { \matsub } { mmm } { \fpm_sub:NNN #1 #2 #3 }
\NewDocumentCommand { \matneg } { mm } { \fpm_neg:NN #1 #2 }
\NewDocumentCommand { \mattranspose } { mm } { \fpm_transpose:NN #1 #2 }
\NewDocumentCommand { \matmul } { mmm } { \fpm_mul:NNN #1 #2 #3 }
\NewDocumentCommand { \mattypeset } { m }
  { \fpm_to_array:N #1 }
\ExplSyntaxOff

\documentclass{article}
\begin{document}
  \matnew \X
  \matnew \Y
  \matnew \Z
  \matset \X { 1 , 2 + 3 ; 4 , 3.4e22 }
  \matset \Y { 3 , 4 ; -5 , 6 }
  \begin{align}
    \matadd \Z \X \Y
    \mattypeset \Z & = \mattypeset \X + \mattypeset \Y \\
    \matsub \Z \X \Y
    \mattypeset \Z & = \mattypeset \X - \mattypeset \Y \\
    \matmul \Z \X \Y
    \mattypeset \Z & = \mattypeset \X \times \mattypeset \Y \\
    \matmul \Z \Y \X
    \mattypeset \Z & = \mattypeset \Y \times \mattypeset \X
  \end{align}
\def\GetRotationMatrix #1{%
    cos(#1), -sin(#1), 0;
    sin(#1),  cos(#1), 0;
          0,        0, 1 }

\expandafter\matset\expandafter\transformMatrixA \expandafter
 {\GetRotationMatrix {3.141592653}}

 \begin{equation}
   \mattypeset \transformMatrixA
 \end{equation}
\end{document}

The first four equations are the ones from the original code

matrices

  • Why don't you make it a complete document please? – user11232 Nov 6 '13 at 22:53
  • @jfbu: That fixes the problem (+1). I couldn't parse what all the "\expandafter"s do. I read the manual page, but this is a lot more complicated. If you explain it, I'll accept as answer. Thanks! – imallett Nov 6 '13 at 23:52
  • I guess one option would be to change the definition of \matset to \NewDocumentCommand { \matset } { mm } { \fpm_set:Nx #1 {#2} } and define the variant \cs_generate_variant:Nn \fpm_set:Nn { Nx }. The \fpm_set:Nx function then performs \edef expansion on #2, rather than a single step of expansion as the \expandafter that jfbu suggests. I am not sure what is the best approach here (or rather, I know that the best approach would require some changes to l3fp, which I do not yet have time to code). – Bruno Le Floch Nov 7 '13 at 17:56
  • @BrunoLeFloch in xint I use \romannumeral-`0 systematically to expand the arguments. I can not do \edef as the xint macros want to be completely expandable. – user4686 Nov 7 '13 at 18:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.