Revtex's tensor macro interferes with nested amsmath split and align environments

The problem is shown if you

{\let\protect\show\tensor}

(which lets us see how a protected command is implemented). We get

> \tensor =\long macro:
#1->\@ontopof {#1}{\leftrightarrow }{1.15}\mathord {\box 2}

The key is \box 2. What is happening is \tensor is inserting whatever happens to be in that box. Presumably this is normally perfectly safe, but the same box is being used by align, which you can see with something like

\begin{align}
  A &= B  \\
  \begin{split}
    C &= D \\
      &= E
  \end{split}
\end{align}
\copy2

This looks like a bug in REVTeX. You can empty box 2 to deal with the issue. I guess this should be done as part of \tensor, so something like

\documentclass[12pt,prd,amsmath]{revtex4}
\usepackage{letltxmacro}
\LetLtxMacro{\savedtensor}{\tensor}
\DeclareRobustCommand{\tensor}{%
  \setbox2=\hbox{}%
  \savedtensor
}
\begin{document}
Here is a split inside an align
\begin{align}
  A &= B  \\
  \begin{split}
    C &= D \\
      &= E
  \end{split}
\end{align}
And now we write a tensor $\tensor{T}$ which grabs the second line.
\end{document}

would be a sensible workaround.


\tensor is defined in package revsymb, loaded by class revtex4:

\DeclareRobustCommand\tensor[1]{\@ontopof{#1}{\leftrightarrow}{1.15}\mathord{\box2}}

Macro \@ontopof uses \mathchoice to get the current math style and configures the math style for the double arrow that is put on top of #1:

\def\@ontopof#1#2#3{%
 {%
  \mathchoice
    {\@@ontopof{#1}{#2}{#3}\displaystyle     \scriptstyle      }%
    {\@@ontopof{#1}{#2}{#3}\textstyle        \scriptstyle      }%
    {\@@ontopof{#1}{#2}{#3}\scriptstyle      \scriptscriptstyle}%
    {\@@ontopof{#1}{#2}{#3}\scriptscriptstyle\scriptscriptstyle}%
 }%
}%

The actual work is then done by \@@ontopof.

\def\@@ontopof#1#2#3#4#5{%
  \setbox\z@\hbox{$#4#1$}%
  \setbox\f@ur\hbox{$#5#2$}%

Box 0 contains the base symbol #1 and box 4 contains the double arrow that will be put on box 0.

  \setbox\tw@\null\ht\tw@\ht\z@ \dp\tw@\dp\z@

Box 2 remembers the height and depth of box 0, the base symbol.

  \@ifdim{\wd\z@>\wd\f@ur}{%
    \setbox\f@ur\hb@xt@\wd\z@{\hss\box\f@ur\hss}%
    \mathord{\rlap{\raise#3\ht\z@\box\f@ur}\box\z@}%
  }{%
    \setbox\f@ur\hb@[email protected]\wd\f@ur{\hss\box\f@ur\hss}%
    \setbox\z@\hb@xt@\wd\f@ur{\hss$#4\relax#1$\hss}%
    \mathord{\rlap{\copy\z@}\raise#3\ht\z@\box\f@ur}%
  }%
}%

The problem is that the assigment for box 2 is done locally inside a group (BTW, even numbered registers 0, 2, 4, 6, and 8 are for local assignments, odd numbered 1, 3, 5, 7, and 9 for global assignments). Thus after \@ontopof has done its work, the setting of box 2 is lost and the previous contents of box 2 is used in \tensor.

I assume that the idea was that the composite symbol keeps the original height and depth of the base symbol. This avoids ruining the line spacing by symbols with large heights, but at the risk of clashes.

The following example implements this idea

  • \m@th is added in the box settings to reset \mathsurround to avoid unwanted space additions, if \mathsurround is not zero.
  • The complexity of the box structure is simplified.
  • Fix for the case the base symbol's width is between 0.9 and 1 of the width of the top symbol.
  • There are other macros besides \tensor that use \@ontopof with \box2.

Guessed explanation for using 90% of the width of the top symbol:
Depending on the width of the symbols the smaller symbol is horizontally centered in a box with the width of the wider symbol. Whereas the full width of the base symbol is taken, only the real width of the top symbol is taken. The full width consists of the real glyph width and the left and right side bearings. The code assumes 5% for each side and estimates the glyph width to 90% of the character box width.

\documentclass[12pt,prd,amsmath]{revtex4}

\makeatletter
\DeclareRobustCommand\REV@dddot[1]{\@ontopof{#1}{\cdots}{1.0}}
\DeclareRobustCommand\tensor[1]{\@ontopof{#1}{\leftrightarrow}{1.15}}
\DeclareRobustCommand\overstar[1]{\@ontopof{#1}{\ast}{1.15}}
\DeclareRobustCommand\loarrow[1]{\@ontopof{#1}{\leftarrow}{1.15}}
\DeclareRobustCommand\roarrow[1]{\@ontopof{#1}{\rightarrow}{1.15}}

\def\@@ontopof#1#2#3#4#5{%
  \setbox\z@\hbox{$\m@th#4#1$}%
  \setbox\f@ur\hbox{$\m@th#5#2$}%
  \@ifdim{\wd\z@>.9\wd\f@ur}{%   
    \setbox\f@ur\hb@xt@\wd\z@{%  
      \hss
      \raise#3\ht\z@\box\f@ur
      \hss
    }%
    \wd\f@ur\z@  \ht\f@ur\z@  \dp\f@ur\z@
    \setbox\z@\hbox{%
      \box\f@ur
      \box\z@  
    }%
  }{% 
    \setbox\f@ur\hb@[email protected]\wd\f@ur{%
      \hss \raise#3\ht\z@\box\f@ur \hss
    }%
    \setbox\z@\hbox to \wd\f@ur{%
      \wd\f@ur\z@  \ht\f@ur\z@  \dp\f@ur\z@
      \box\f@ur
      \hss \box\z@ \hss
    }%
  }%  
  \box\z@
}%
\makeatother

\begin{document}

\setlength{\fboxsep}{0pt}
\setlength{\fboxrule}{.1pt}
\newcommand*{\test}[1]{%   
  \fbox{$#1{T}$}, \fbox{$#1{\mathrm{I}}$}
}
\begin{gather*}
  \test\dddot\\
  \test\tensor\\
  \test\overstar\\
  \test\loarrow\\ 
  \test\roarrow   
\end{gather*}     

Here is a split inside an align
\begin{align}
  A &= B  \\ 
  \begin{split}
    C &= D \\  
      &= E  
  \end{split}  
\end{align}    
And now we write a tensor $\tensor{T}$ which grabs the second line.
\end{document}

Result