# Obstacles to simulating an amsmath matrix by a TiKZ matrix of math nodes

I'm trying to figure out a TiKZ-based solution to this problem involving arrows between matrix rows. To this end, I would like to at least be able to make a matrix of math nodes simulate the behaviour of the matrix environment from amsmath.

I have encountered several problems in doing so. One of them is the appearance of the matrices themselves and their relation to the surrounding text. Consider the following example:

\documentclass{article}
\usepackage{amsmath,tikz}
\usetikzlibrary{matrix}

\newlength\mtxrowsep   \setlength\mtxrowsep{2ex}
\newlength\mtxcolsep   \setlength\mtxcolsep{\arraycolsep}

\begin{document}
\begin{gather*}
4 \times
\left[\begin{matrix} a & b \\ c & d \end{matrix}\right]
4 \times
\left[\begin{tikzpicture}
\matrix [matrix of math nodes, ampersand replacement=\&,
inner sep=0pt, column sep=\mtxcolsep, row sep=\mtxrowsep]
{ a \& b \\ c \& d \\ };
\end{tikzpicture}\right]
4 \times
\left[\begin{minipage}{1.7em}
{}\hfill\begin{tikzpicture}
\matrix [matrix of math nodes, ampersand replacement=\&,
inner sep=0pt, column sep=\mtxcolsep, row sep=\mtxrowsep]
{ a \& b \\ c \& d \\   };
\end{tikzpicture}\hfill{}
\end{minipage}\right]
\end{gather*}
\end{document}


This produces the following output:

The genuine amsmath matrix is on the left. The matrix in the middle suffers from the fact that the matrix of math nodes in the tikzpicture environment is apparently sitting on top of the baseline of the surrounding math. The matrix on the right is properly aligned because it is sitting inside of a minipage, whose width was manually specified. (Not pictured is anything using left delimiter or right delimiter, which has spacing issues of its own and suffers from the same vertical alignment issues.)

Main questions.

1. Is there any way native to tikz to change the vertical alignment of the tikzpicture for the matrix in the middle? (I didn't find anything whilst scanning through the manual...)

2. Is there any way to extract the width of an image automatically, to allow automatic resizing of the minipage for the matrix on the right?

-
how about text height and text depth? –  flying sheep Aug 29 '11 at 17:09
i'm not a tikz adept, but is the \ after the c \& d in the tikz version necessary? –  barbara beeton Aug 29 '11 at 17:42
Yes --- the TiKZ documentation emphasizes that every row must terminate in \\, which while unfortunate is something one can hope to take care of with a wrapper environment of some sort. –  Niel de Beaudrap Aug 29 '11 at 17:48
Thought you might be interested in the following. I've reused this code in tex.stackexchange.com/q/23431/86 and added code so that TikZ draws the delimiters rather than using "standard" delimiters. –  Loop Space Sep 14 '11 at 19:34

Okay, here's my first attempt. It needs a little tweaking with spacing either side, and I've only tested it with the 2x2 matrix given and without any font size changes, so some of the choices for lengths might be amiss.

The vertical centring is handled by setting the baseline option on the enclosing tikzpicture. We want the actual centre of the matrix to be a bit above the baseline (about the middle of an =, I guess). Trial and error gave me a height of .7ex. Next is the delimiters. TikZ can handle them itself, so I figured it best to let it deal with them rather than putting stuff in boxes or minipages (the node text is already in boxes and so forth so putting in more seems overkill). I found the delimiters to be vertically a little off, so I shifted them down a point, and also a little far out. I didn't want to touch the inner sep as that changes both vertical and horizontal, so I simply shifted them in a little. These are the every (left|right|) delimiter styles. I wanted a little inner sep on the matrix itself but not on the entries themselves. I also adjusted the row and column separations a little to better match the AMS styles.

Next was the ampersand. I'm a little wary of this, but what I do is to change it to an active character. It checks to see if it is inside a TikZ matrix. If it is, then it produces \pgfmatrixnextcell and if not, it produces the old ampersand. This could be made a little more robust by only checking for if we're in one of our special TikZ matrices. I don't know if this is a good solution or not.

After that, it's simply a matter of wrapping it all up in an environment.

Let's start with the result. In each row, the first is a tikzpicture, the second is the same but wrapped in to an environment, the third is the AMS original.

And now the code:

\documentclass{standalone}
%\url{http://tex.stackexchange.com/q/26866/86}
\usepackage{amsmath,tikz}
\usetikzlibrary{matrix}

\newlength\mtxrowsep   \setlength\mtxrowsep{1.5ex}
\newlength\mtxcolsep   \setlength\mtxcolsep{2\arraycolsep}

\tikzset{
ams/.style={
baseline=-.7ex,
every delimiter/.style={yshift=-1pt},
every left delimiter/.style={xshift=2pt},
every right delimiter/.style={xshift=-2pt},
every node/.style={inner sep=0pt},
},
ams matrix/.style={
inner sep=1pt,
column sep=\mtxcolsep,
row sep=\mtxrowsep,
%    ampersand replacement=\&,
matrix of math nodes,
},
bmatrix/.style={
ams,
every matrix/.style={
ams matrix,
left delimiter={[},
right delimiter={]},
}
},
Bmatrix/.style={
ams,
every matrix/.style={
ams matrix,
left delimiter={\lbrace},
right delimiter={\rbrace},
}
},
pmatrix/.style={
ams,
every matrix/.style={
ams matrix,
left delimiter={(},
right delimiter={)},
}
},
vmatrix/.style={
ams,
every matrix/.style={
ams matrix,
left delimiter={|},
right delimiter={|},
}
},
Vmatrix/.style={
ams,
every matrix/.style={
ams matrix,
left delimiter={\|},
right delimiter={\|},
}
},
}

\let\matamp=&

\catcode\&=13
\makeatletter
\def&{\iftikz@is@matrix
\pgfmatrixnextcell
\else
\matamp
\fi}
\makeatother

%\usepackage{environ}
\def\endtikzmatrix{\\\egroup;\end{tikzpicture}}
\foreach \mtype in {b,B,p,v,V} {

\expandafter\xdef\csname tikz\mtype matrix\endcsname{%
\noexpand\begin{tikzpicture}[\mtype matrix]
\noexpand\matrix \noexpand\bgroup}
\expandafter\global\expandafter\let\csname endtikz\mtype matrix\endcsname=\endtikzmatrix
}

\begin{document}
\foreach \mtype in {b,B,p,v,V} {
\edef\metype{\mtype matrix}
\edef\tmetype{tikz\mtype matrix}
\begin{gather*}
\begin{tikzpicture}[\metype]
\matrix (m) {
a & b \\ c & d \\};
\end{tikzpicture}
\begin{\tmetype} a & b \\ c & d \end{\tmetype}
\begin{\metype} a & b \\ c & d \end{\metype}
\end{gather*}
}

\end{document}


(The \foreach loops are just to save me cut-and-pasting a lot.)

-
This is a very, very good effort! I notice only a slight difference in the row-spacing between the two TiKZ variants and the AMS original in each case; and no significant differences otherwise, for these 2x2 examples. (One would hope that a suitable study of amsmath.sty, latex.ltx, or even more primordial source files might yield the precise row-spacing in any case.) Interestingly, I also do not notice any significant difference in the examples given between the first two matrices of each type. I also appreciate the effort taken into crafting wrapper environments! –  Niel de Beaudrap Aug 29 '11 at 23:59
With experimentation, I found some minor improvements: row-spacing should be set to {\mtxrowsep,between borders} in order for the spacing to be correct at different typeface sizes; and the value of \mtxrowsep should be changed to \arraystretch\baselineskip as the first step in the wrapper environments to get the correct row-spacing. I found that inner ysep=0.7ex and inner xsep=0 to be better than a symmetric inner sep. Finally, depending on the font size, yshift values ranging from 0 to 1ex may be needed. This gives essentially robust results for different matrix dimensions. –  Niel de Beaudrap Aug 30 '11 at 19:07
@Niel: I think you should feel free to edit in any improvements that you figure out. By the way, I think I forgot to say that this idea (but not any details) has been something I've thought "I wonder if that would be possible" for some time so I think it's a great question. I often find myself (in lectures) wanting to do TikZy stuff to a matrix and this idea would be a great way to make that much, much easier to do. –  Loop Space Aug 30 '11 at 19:25

To modify the alignment of the picture, the problem is that Tikz aligns things with respect to the bounding box it computes. It is possible to manually set the bounding box, then align objects with respect to the bounding box. For example,

\begin{tikzpicture}

\path[use as bounding box] (0,0) rectangle (1,1);
%shift matrix with respect to bounding box
\matrix [matrix of math nodes, ampersand replacement=\&,
inner sep=0pt, column sep=\mtxcolsep, row sep=\mtxrowsep,
left delimiter={[},right delimiter={]},shift={(0,1)}] (m)
{ a \& b \\ c \& d \\ };

\end{tikzpicture}


or

\begin{tikzpicture}

%shift bounding box
\path[use as bounding box] (0,1) rectangle (1,2);

\matrix [matrix of math nodes, ampersand replacement=\&,
inner sep=0pt, column sep=\mtxcolsep, row sep=\mtxrowsep,
left delimiter={[},right delimiter={]}] (m)
{ a \& b \\ c \& d \\ };

\end{tikzpicture}


The result is

-
The vertical alignment is improper. –  egreg Aug 29 '11 at 17:00
I removed delimiter stuff, added alignment information. –  Frédéric Aug 29 '11 at 17:30
I'm not sure entirely what's going on with these examples; but if I leave in the \fill (0,0) circle [radius=2pt]; commands, it doesn't finish compiling in any short time, and if I remove that command I get a squashed matrix either well above or well below the baseline (i.e. even more poorly aligned than previously). Is there something missing? –  Niel de Beaudrap Aug 29 '11 at 17:46
I compiled them again and I had no problem. I added the image. –  Frédéric Aug 29 '11 at 17:55

I don't know much about TikZ, but

\left[\vcenter{\hbox{\begin{tikzpicture}
<TikZ code>\end{tikzpicture}}}\right]


gives "almost correct" brackets.

-

An easy way would be using bmatrix which does both vertical centering and the square brackets for you, around the tikzpicture:

\begin{bmatrix}\begin{tikzpicture}
\matrix [matrix of math nodes, ampersand replacement=\&,
inner sep=0pt, column sep=\mtxcolsep, row sep=\mtxrowsep]
{ a \& b \\ c \& d \\ };
\end{tikzpicture}\end{bmatrix}

-
While a slightly more complex solution, this seems to get the vertical spacing almost exactly the way that matrix does (improving slightly over the vertical positioning of egreg's answer relative to the arms of the square brackets). Do you have any insights as to how to get the row/column spacing exactly the same as in the array environment (which lengths to use)? –  Niel de Beaudrap Aug 29 '11 at 17:14
You could start working with column sep=\arraycolsep and getting row sep from \baselineskip. However it might not give exactly the same. At last, you could figure out a value for row and column sep measured in ex or em resp. which does resizing with the font. –  Stefan Kottwitz Aug 29 '11 at 17:24
Hum, you've edited your answer. If you change bmatrix to just matrix, you probably get an answer which is similar to the one you had before with array; and certainly the result is about the same. (I want to be able to simulate matrix without delimiters as well.) –  Niel de Beaudrap Aug 29 '11 at 17:26
By cargo-cult coding with experimentation, it would seem that the column separation should be \arraycolsep plus the width of a single space, and the row separation is \arraystretch times the combined height and depth of \strutbox, divided by 2. I'll have to find out if there's any serious basis for these measures. I'll check again tomorrow. –  Niel de Beaudrap Aug 29 '11 at 17:38
@Niel: yes, I changed it, because using (b)matrix is easier than array with @{}c@{}` option. –  Stefan Kottwitz Aug 29 '11 at 17:38