0

I have a small custom style file/package which contains a bunch of macros useful for a subfield of Computer Science I operate in (see here for the latest complete version of the package). A major part of the writing that I do involves setting out numbered "rules". For that, I use an array, wrapped in a newenvironment (which itself is wrapped in a newfloat to get floating behaviour, but I doubt that's relevant here).

Each rule is itself written out using another macro, to ensure that they are all specified the same way, and provide me with automatic sequential numbering of the rules. I would like to be able to label and cross-reference the rules like many other types of countable things in LaTeX. I tried including an optional parameter for the rules macro, and writing a label if the optional parameter was non-empty (you can see this in the MWE below). This seemingly worked well at first when I was trying to create a minimal working example, but I did not see the same behaviour when applying it to some of my completed old papers. Instead, I got error messages that I didn't really understand.

Eventually, after hours of trying to get the MWE and an old paper to produce the same outputs, I discovered that the problem appears to be with amsmath. Specifically, when amsmath is included via usepackage, it doesn't like my attempt at custom labelling. I guess that this is to do with how the rules environment works using an array, and that amsmath somehow redefines array in a way that makes the labelling stop working. Given that amsmath is pretty much a certainty for inclusion in any paper my package is likely to be relevant for, this obviously is not an acceptable situation.

The MWE is below, and was more or less constructed by copy-pasting the relevant bits out of the .sty file and wrapping them in makeatletter/makeatother. I have also included all the packages that are included in the sty file using RequirePackage, just in case that makes any difference.

\documentclass[10pt,a4paper]{article}

\usepackage{amsmath,newfloat,array,framed,etoolbox,perfectcut,trimspaces,changepage}
\usepackage{hyperref}
\usepackage{cleveref}

\makeatletter

\newcounter{cpsystems@RuleNum}
\DeclareFloatingEnvironment[name=Ruleset,within=none]{cprulesetfloat}

\newenvironment{cpruleset}
{\begin{framed}\begin{adjustwidth}{-1.0em}{-1.0em}
\renewcommand{\arraystretch}{1.1}\[\begin{array}{lllllr}}
{\end{array}\]\end{adjustwidth}\end{framed}}

\newcommand{\cprule}[6][]{
    \refstepcounter{cpsystems@RuleNum}
    \notblank{#1}{\label{#1}}{}
    \cpsystems@basecprule{#2}{#3}{#4}{#5}{#6}{\hspace{1.5em}(\arabic{cpsystems@RuleNum})}
}

\newcommand{\cpsystems@basecprule}[6]{
    \trim@spaces@noexp{#1 & #2 & \rightarrow_{#3} & #4 & #5  & #6\\}
}

\newcommand*{\cpfunc}[2]{
\trim@spaces@noexp{#1\perfectunary{IncreaseHeight}{(}{)}{#2}}
}

\makeatother

\begin{document}

\section{Intro}
See Ruleset~\ref{rules}.

\section{The rules}

\begin{cprulesetfloat}
\begin{cpruleset}

\cprule[rule1]{s_1}{\cpfunc{a}{B}}{{\scriptstyle 1}}{s_2}{\cpfunc{f}{G}}

\cprule{s_2}{\cpfunc{c}{D}}{{\scriptstyle +}}{s_3}{\cpfunc{h}{I}}

\cprule[rule2]{s_2}{\cpfunc{c}{D}}{{\scriptstyle +}}{s_3}{\cpfunc{h}{I}}

\end{cpruleset}
\caption{\label{rules}My fancy ruleset}
\end{cprulesetfloat}

I'm particularly proud of rule~\ref{rule3}. That's \ref{rule3}.

\begin{cprulesetfloat}
\begin{cpruleset}

\cprule[rule3]{s_1}{\cpfunc{a}{B}}{{\scriptstyle 1}}{s_2}{\cpfunc{f}{G}}

\cprule{s_2}{\cpfunc{c}{D}}{{\scriptstyle +}}{s_3}{\cpfunc{h}{I}}

\cprule{s_2}{\cpfunc{c}{D}}{{\scriptstyle +}}{s_3}{\cpfunc{h}{I}}

\end{cpruleset}
\caption{\label{rules2}My fancy ruleset 2}
\end{cprulesetfloat}

\end{document}

A correctly built ruleset environment should look something like: enter image description here

There are two problems with the MWE. Firstly, there's a compilation error if both of the optional parameters (intended labels) in the first cpruleset are left in. This seems to be the issue with amsmath - it apparently thinks they're both labels for the array or something, and so complains that there are multiple labels defined for the same thing. Secondly, and for reasons entirely unclear to me, no matter what I try the ref command I use to reference the rules (after ensuring there is only label per cpruleset), it always gives back the number 2.

My question is: How should I change the definition of the cpruleset environment and/or the cprule macro, to enable labelling and cross-referencing of rule numbers? I have tried everything I can think of, and at this point I am completely stumped. My best guess is that I need to move to a different environment from array, but despite looking around a bit (at, e.g. align, gather, etc. in the amsmath and mathtools packages), I couldn't find anything that seemed to enable me to achieve the same results without regard to cross-referencing. It is preferable, but not vital, that any solution plays nicely with hyperref and/or cleveref and would enable something like \autoref{rule2} or \cref{rule2} to get output similar to "Rule 2".

1 Answer 1

1

amsmath redefines \label so that it complains if there are two \label commands in a display math.

It stores the original \label command as \ltx@label so you could use that instead---but not with cleveref, as cleveref too redefines \label (at begin document, based on the amsmath definition) and then you get lots of warnings about missing and multiple labels.

To get around the mess you could define your own \ruleref command based on zref:

\documentclass[10pt,a4paper]{article}

\usepackage{amsmath,newfloat,array,framed,etoolbox,perfectcut,trimspaces,changepage}

\usepackage{hyperref}
\usepackage{cleveref}
\usepackage[user]{zref}
\makeatletter
\zref@newprop{destname}[Doc-Start]{\@currentHref}
\zref@addprop{main}{destname}
\newcommand\ruleref[1]{\hyperlink{\zref@extract{#1}{destname}}{\zref{#1}}}

\newcounter{cpsystems@RuleNum}
\DeclareFloatingEnvironment[name=Ruleset,within=none]{cprulesetfloat}

\newenvironment{cpruleset}
{\begin{framed}\begin{adjustwidth}{-1.0em}{-1.0em}
\renewcommand{\arraystretch}{1.1}\[\begin{array}{lllllr}}
{\end{array}\]\end{adjustwidth}\end{framed}}

\newcommand{\cprule}[6][]{
    \refstepcounter{cpsystems@RuleNum}
    \notblank{#1}{\zlabel{#1}}{}
    \cpsystems@basecprule{#2}{#3}{#4}{#5}{#6}{\hspace{1.5em}(\arabic{cpsystems@RuleNum})}
}

\newcommand{\cpsystems@basecprule}[6]{
    \trim@spaces@noexp{#1 & #2 & \rightarrow_{#3} & #4 & #5  & #6\\}
}

\newcommand*{\cpfunc}[2]{
\trim@spaces@noexp{#1\perfectunary{IncreaseHeight}{(}{)}{#2}}
}

\makeatother

\begin{document}

\section{Intro}
See Ruleset~\ref{rules}.

\section{The rules}

\begin{cprulesetfloat}
\begin{cpruleset}

\cprule[rule1]{s_1}{\cpfunc{a}{B}}{{\scriptstyle 1}}{s_2}{\cpfunc{f}{G}}

\cprule{s_2}{\cpfunc{c}{D}}{{\scriptstyle +}}{s_3}{\cpfunc{h}{I}}

\cprule[rule2]{s_2}{\cpfunc{c}{D}}{{\scriptstyle +}}{s_3}{\cpfunc{h}{I}}

\end{cpruleset}
\caption{\label{rules}My fancy ruleset}
\end{cprulesetfloat}

I'm particularly proud of rule~\ruleref{rule3}. That's \ruleref{rule3}.

\begin{cprulesetfloat}
\begin{cpruleset}

\cprule[rule3]{s_1}{\cpfunc{a}{B}}{{\scriptstyle 1}}{s_2}{\cpfunc{f}{G}}

\cprule{s_2}{\cpfunc{c}{D}}{{\scriptstyle +}}{s_3}{\cpfunc{h}{I}}

\cprule{s_2}{\cpfunc{c}{D}}{{\scriptstyle +}}{s_3}{\cpfunc{h}{I}}

\end{cpruleset}
\caption{\label{rules2}My fancy ruleset 2}
\end{cprulesetfloat}

\end{document}
1
  • 🤯 I must admit, I hadn't come across the zref package before. I'll have to take a look at how hyperref and cleveref do their thing at some point. This definitely seems to do the trick for my purposes, though. Thanks! :)
    – Jarak
    Aug 14, 2021 at 23:57

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.