2

In the MWE below, the theorems should be sorted as "1.2 and 2.1" rather than "2.1 and 1.2". The correct sorting behavior is obtained by replacing sibling=subsection with parent=section but of course then the theorem counter is not shared with subsection.

\documentclass{article}

\usepackage{thmtools}
\usepackage{cleveref}

\declaretheorem[sibling=subsection]{theorem}
%\declaretheorem[parent=section]{theorem}

\begin{document}

\section{}
\begin{theorem}
Blah.
\end{theorem}

\begin{theorem}\label{theorem}
Blah.
\end{theorem}

\section{}
\begin{theorem}\label{othertheorem}
Blah.
\end{theorem}

\Cref{theorem,othertheorem}.

\end{document}

I suppose the issue arises from a difference in how a subsection label is written to the aux file vs a new theorem counter, but how do I make this work correctly?

3
  • There are not subsections in your MWE. May 22, 2021 at 21:41
  • It seems I misunderstood your question. You already have a solution, but for some reason you want to use sibling=subsection ? Could you please clarify? May 23, 2021 at 20:27
  • Please see my updated answer. May 23, 2021 at 20:57

3 Answers 3

1

Note. The bug below will likely be fixed in the next release. https://github.com/muzimuzhi/thmtools/commit/0f9a1cf0ecdf26b4d1a5c6fd8fe27c912e919d5a

Actually, digging through the cleveref code a bit more, I am now convinced that this is a bug in thmtools.

Consider the following code:

\documentclass{article}
\usepackage{amsthm}
%\usepackage{thmtools}
\usepackage{cleveref}

\newtheorem{theorem}[subsection]{Theorem}


\begin{document}

\section{}
\begin{theorem}
Blah.
\end{theorem}

\begin{theorem}\label{theorem}
Blah.
\end{theorem}

\section{}
\begin{theorem}\label{othertheorem}
Blah.
\end{theorem}

\Cref{theorem,othertheorem}.

\end{document}

With thmtools not loaded, the aux file shows the correct output.

\newlabel{theorem}{{1.2}{1}}
\newlabel{theorem@cref}{{[theorem][2][1]1.2}{[1][1][]1}}
...
\newlabel{othertheorem}{{2.1}{1}}
\newlabel{othertheorem@cref}{{[theorem][1][2]2.1}{[1][1][]1}}

But if you uncomment the loading of thmtools, you get

\newlabel{theorem}{{1.2}{1}}
\newlabel{theorem@cref}{{[theorem][2][]1.2}{[1][1][]1}}
...
\newlabel{othertheorem}{{2.1}{1}}
\newlabel{othertheorem@cref}{{[theorem][1][]2.1}{[1][1][]1}}

As indicated by the fact that Simon Dispa's patch works, this seems that the inclusion of thmtools actually forcibly bypassed the code placed into cleveref that were designed to handle precisely the situation of sibling counters and checking for parents.

Suggestions:

  • Switch to using either amsthm or ntheorem, and drop thmtools.
  • Alternatively, send the maintainers of thmtools a note (maybe pointing to this answer). Something in their package is screwing things up, but I don't have the time to track it down now.

Updated Workaround

Below is a simpler workaround for your issue.

Use

\documentclass{article}
\usepackage{thmtools}
\usepackage{cleveref}

\declaretheorem[sibling=subsection]{theorem}

\counterwithin{theorem}{section} %%% <---- NEW LINE

\begin{document}
...

Brief explanation: with the LaTeX native \newtheorem defined in latex.ltx, as well as the implementations in ntheorem and in amsthm, the above would not work. The reason is that their when you declare a sibling counter, the underlying counter used for the environment is the sibling counter. So \newtheorem{theorem}[subsection]{Theorem} never creates a new theorem, it just internally marks that theorem should use subsection as the underlying counter.

The implementation in thmtools is different. To support autoref when you declare a sibling counter, what it does is create an alias for the sibling counter.

The neat thing that happens with this definition is that, for all intents and purposes it appears that there is a counter called theorem. It is just that underneath it the counter register is the same as that used for subsection. So that when you call \stepcounter{theorem}, you will see that the subsection counter also goes up by one. And you can also do things like \arabic{theorem} to show the arabic numeral representation of the subsection counter.

What thmtools doesn't do, however, is re-register the counter into the reset list of its sibling. Functionally for the purpose of resetting the counter, this is inconsequential. Registering both theorem and subsection into the reset list for section would just mean that when section is stepped, first subsection will reset to zero, then theorem (which is really subsection) will again reset to zero. So there's not much point of registering both.

However, from the point of view of cleveref, which reads the reset list to determine which counters are reset by which others, this makes a difference. Even though theorem is secretly subsection and hence is reset by section, it is not "obviously so" when looking at the reset list!

If would be nice if thmtools can be updated to help out cleveref and other packages that read the reset list by doing this registration. But since that is not the case, you can do it yourself. As mentioned before, theorem behaves pretty much like a normal counter. So you can just go ahead and call \counterwithin. In terms of actually resetting the counter, it makes no impact. But now cleveref can see the parent-child relation.

2
  • The issue is now tracked in github.com/muzimuzhi/thmtools/issues/22.
    – ronno
    May 25, 2021 at 10:43
  • 1
    @ronno: I did a bit more diving to help the maintainer track down the issue. Which also helped me come up with probably the simplest workaround in this situation. See the update. May 26, 2021 at 1:22
2

UPDATED

This patch will solve the issue as presented in the MWE using \declaretheorem[sibling=subsection]{theorem} by adding an ordering reference number.

Previously, Theorem 1.2 was referenced to as Theorem #2 of nothing and Theorem 2.1 as Theorem #1 of nothing. Naturally, they were arranged in the wrong order, with second in the first place.

Adding the ordering reference number, now the reference to Theorem 1.2 is Theorem #2 of 1 and to Theorem 2.1 Theorem #1 of 2, so the sort order is correct.

z

\documentclass{article}

\usepackage{thmtools}
\usepackage{cleveref}

\declaretheorem[sibling=subsection]{theorem}

%%%************************************************* added
\makeatletter
\def\refstepcounter@noarg#1{% 
    \cref@old@refstepcounter{#1}%
    \cref@constructprefix{#1}{\cref@result}%
    \@ifundefined{cref@#1@alias}%
    {\def\@tempa{#1}}%
    {\def\@tempa{\csname cref@#1@alias\endcsname}}%
    \ifx\cref@result\@empty%
    \def\cref@result{\thesection}%
    \fi%
    \protected@edef\cref@currentlabel{%
        [\@tempa][\arabic{#1}][\cref@result]%
        \csname p@#1\endcsname\csname the#1\endcsname}}%
\makeatother
%%%*************************************************


\begin{document}
    
    \section{}
    \begin{theorem}
        Blah.
    \end{theorem}
    
    \begin{theorem}\label{theorem}
        Blah.
    \end{theorem}
    
    \section{}
    \begin{theorem}\label{othertheorem}
        Blah.
    \end{theorem}
    
    \Cref{theorem,othertheorem}.
    
\end{document}
5
  • I don't see why that should matter. I think you switched the theorem definition to use parent=section which (as I mention in the question) works fine. If you take the code in your answer and switch the theorem declaration then in the output you get "Theorems 2.1, 1.2 and 2.4".
    – ronno
    May 23, 2021 at 2:37
  • @ronno I updated the answer using \declaretheorem[sibling=subsection]{theorem} May 23, 2021 at 21:48
  • Thanks for the update, can you explain what your \def is doing? Is it making every label/ref be compared first by the section number?
    – ronno
    May 25, 2021 at 0:49
  • The code patches the same code from cleveref. The original code calls ...constructprefix... to store the parent counter value into \cref@result, and writes it to the aux file. The updated code adds a check: whenever \cref@result returns empty (meaning cleveref cannot find a parent counter), the added code sets it to default to section. May 25, 2021 at 4:01
  • Actually, it is surprising to me that @Simon's patch works: it seems to indicate that thmtools doesn't work as advertised. With amsthm or ntheorem and the sibling counter, cleveref should never visit the noarg branch of \refstepcounter. It should use the version with the optional argument, as cleveref patches the theorem creation code in amsthm and ntheorem so that they call \refstepcounter[theorem]{subsection} instead. May 25, 2021 at 4:12
1

Workaround: make subsection track theorem, instead of the other way around.

\documentclass{article}

\usepackage{thmtools}
\usepackage{cleveref}

\let\originalsubsection\subsection
\renewcommand\subsection[1]{%
        \setcounter{subsection}{\value{theorem}}%
        \stepcounter{theorem}%
        \originalsubsection{#1}}

\declaretheorem[parent=section]{theorem}

\begin{document}

\section{}
\begin{theorem}
Blah.
\end{theorem}

\subsection{Blank}

\begin{theorem}\label{theorem}
Blah.
\end{theorem}


\section{Test}
\begin{theorem}\label{othertheorem}
Blah.
\end{theorem}

\Cref{theorem,othertheorem}.

\end{document}

enter image description here

Brief diagnosis:

thmtools+cleveref writes stuff like

\newlabel{othertheorem@cref}{{[theorem][1][2]2.1}{[1][1][]1}}

into the aux field. Inside the second set of braces are instructions on how cleveref is supposed to interpet the label. First bracket is the counter type (usually counter name, but with thmtools / amsthm / ntheorem it tracks the name of the environment instead, since they frequently share the same counter), the second bracket is the counter value, and the third is the parent counter value. If you use thmtools to declare a sibling counter, and the counter is not one of the theorem counters, somehow the parent/numberwithin information is lost.

There may be a way to fix this by patching cleveref and/or amsthm/thmtools. But the above workaround is cheaper.

3
  • Guess following your diagnosis: maybe thmtools+cleveref cannot tell if a general counter provided as sibling is numberwithined some other counter and take care of that?
    – ronno
    May 25, 2021 at 0:54
  • It is a bit more complicated than that. TeX internals does not have a good way to look up what are the counters that resets a given counter. Internally what is kept, for each counter, is a list of the counters that the given counter resets. To look up the reverse information you have to search through all counters, and check whether your counter is reset by it. (Also I think the resetting-by may be non-unique.) Cleveref only checks whether your counter is reset by the standard sectioning commands. May 25, 2021 at 3:22
  • See my second answer for some additional thoughts. May 25, 2021 at 4:17

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .