定理与证明 (amsthm)

证明末尾那个空心方块并不是任何字体里的字符。LaTeX 的 amsthm 是用两条竖线和两条横线 当场画出来 的(amsthm.sty 中的 \openbox;实测宽 7.77786pt、高 6.75003pt,在 10pt 正文中即 0.77778em × 0.675em)。定理环境的思路完全一样。LaTeX 并不发给你一个做好的“定理”,而是给你 \newtheorem 这个 声明机制,让你自己决定什么算定理、编号怎么走。本页追踪 \newtheorem 究竟生成了什么、它的两个可选参数为何不能同时使用、\theoremstyle 真正改变了什么,以及让证明结束符号跑到错误行上的 \qedhere 问题——每一步都附上实际编译的结果。

\newtheorem 生成了什么:一个环境和一个计数器

\newtheorem{theorem}{Theorem} 会创建 一个环境和一个用于给它编号的计数器。日志中会出现 \c@theorem=\count196 之类的一行,说明计数器确实被分配了。第一个参数是你在 \begin{…} 中书写的环境名,第二个参数是标题中以粗体打印的词。关键在于两者是不同的东西:可以让环境名保持为朴素的 theorem,只把打印出来的词换成 SatzThéorème 或别的什么。

latex
\usepackage{amsthm}
\newtheorem{theorem}{Theorem}   % declares the environment AND allocates a counter

完成这条声明后,每次使用 theorem 环境都会得到粗体标题和连续编号——Theorem 1Theorem 2——其中的文字在默认样式下排为斜体。编号保存在 LaTeX 计数器中,所以在中间插入一个定理,后面的编号会自动调整。这正是人们搜索定理编号时真正想要的性质:只要你从不手写编号,编号就绝不会错位。

latex
\begin{theorem}
  There are infinitely many primes.
\end{theorem}

\begin{theorem}[Pythagoras]
  In a right triangle, $a^2 + b^2 = c^2$.
\end{theorem}

如第二个例子所示,在 \begin{theorem} 之后立即用方括号传入名称,它会以括号形式接在编号后面:“Theorem 2 (Pythagoras).”。这一点 与是否编号无关,在下文用 \newtheorem* 创建的无编号环境中同样适用。另外,\newtheorem 本身 属于标准 LaTeX2e。单独的 \newtheorem 就能创建带编号的环境,但接下来要讲的 \theoremstyle\newtheorem*proof 环境和 \qedhere 全都是 amsthm 的扩展,写下 \usepackage{amsthm} 之后才可用。

两个方括号:共享与从属不能同时使用

\newtheorem 只接受一个方括号,而 它放在哪里决定了它的含义。放在环境名正后方,表示“共享一个已有的计数器”;放在第二个参数之后,表示“让计数器从属于这个父计数器并随之重置”。如果定理和引理各自编号,就会出现“Theorem 1, Lemma 1, Theorem 2, Lemma 2…”这样重复的号码,读者容易混淆,所以多数数学写作选择前者,把编号排成一条线:“Theorem 1, Lemma 2, Theorem 3…”。

latex
\newtheorem{theorem}{Theorem}
\newtheorem{lemma}[theorem]{Lemma}        % bracket BEFORE: share theorem's counter
\newtheorem{definition}{Definition}[section]  % bracket AFTER: reset per section, "2.1"

后置的方括号里也可以放一个已有的定理环境,而不是 section。写成 \newtheorem{corollary}{Corollary}[theorem],推论编号就会 每出现一个定理便重置,于是“Corollary 3.1”读作“附属于 Theorem 3 的第一个推论”。

声明效果
\newtheorem{theorem}{Theorem}用自己的计数器编号 1, 2, 3, …
\newtheorem{lemma}[theorem]{Lemma}共享 theorem 的计数器,引理与定理连成一条编号序列
\newtheorem{theorem}{Theorem}[section]每遇 \section 重新开始,并以节号为前缀显示为 “2.1”
\newtheorem{corollary}{Corollary}[theorem]每出现一个 theorem 便重置,继承父定理编号,显示为 “3.1”
\newtheorem*{remark}{Remark}不创建计数器,只打印标题词而不编号(需要 amsthm)

那么 两个一起写 会怎样?把 \newtheorem{lemma}[theorem]{Lemma}[section] 交给 TeX Live 2024,确实会报错——但它 \newtheorem 只字未提。因为 \newtheorem 根本不读第二个方括号,[section]作为普通文字滞留在导言区,于是 LaTeX 认定正文已经开始。你看到的是这条消息。

log
! LaTeX Error: Missing \begin{document}.

l.4 \newtheorem{lemma}[theorem]{Lemma}[
                                       section]

也就是说,共享和从属 只能二选一。当你两者都想要时——“引理与定理共用编号,而这套编号每节重置”——就把 [section] 只写在父环境上。声明 \newtheorem{theorem}{Theorem}[section],那么通过 \newtheorem{lemma}[theorem]{Lemma} 共享它的引理会自动带上节号。既然只有一个计数器,这样就能毫无矛盾地同时满足两项要求。

当你遇到 Command \theorem already defined.

这个错误说明你 把同一个环境名声明了两次\newtheorem 是创建新环境的命令,因此不允许覆盖已存在的名字。在同一份导言区里写两次 \newtheorem{theorem}{Theorem},输出就是下面这样。

log
! LaTeX Error: Command \theorem already defined.
               Or name \end... illegal, see p.192 of the manual.

实际工作中很少是因为自己写了两遍。通常是 文档类或已加载的宏包已经替你声明了 theorem。会议模板、amsartelsarticle 等都可能预先准备好定理环境。有三种解法:删掉自己那一行;把自己的改名为 mytheorem 之类;或者沿用已有声明,只调整 \theoremstyle。如果你只是想给已存在的环境换一个标题词,与其动用 \newtheorem,不如去找该文档类自己的接口。

\theoremstyle 改变的是标题与正文的字体

amsthm\theoremstyle 切换的是标题(head)与正文(body)所用 字体的组合。内置三种:plaindefinitionremark。最关键的一点是,\theoremstyle{…} 只对写在它之后的 \newtheorem 生效。把样式放在声明后面不会追溯生效,所以导言区里要按样式把声明分组。什么都不写则为 plain

  • plain:标题为粗体,正文为斜体。用于需要强调陈述的命题:定理、引理、命题、推论。不设置时的默认样式。
  • definition:标题为粗体,正文为直立(罗马)体。用于希望按普通正文阅读的内容:定义、例子、问题、条件。当文字中含公式或篇幅较长时,直立体更易读。
  • remark标题为斜体,正文为直立体。用于补充性的文字:评注、注记、claim。因为连标题都变轻,所以不会打断周围正文的行文。
preamble
\usepackage{amsmath, amsthm}

\theoremstyle{plain}            % italic body
\newtheorem{theorem}{Theorem}[section]
\newtheorem{lemma}[theorem]{Lemma}
\newtheorem{corollary}[theorem]{Corollary}

\theoremstyle{definition}       % upright body
\newtheorem{definition}[theorem]{Definition}
\newtheorem{example}[theorem]{Example}

\theoremstyle{remark}           % italic head
\newtheorem*{remark}{Remark}

在这个配置中,theoremlemmacorollarydefinitionexample 五者共享按节重置的同一条编号序列(Theorem 2.1、Definition 2.2、Lemma 2.3…),前三者正文为斜体,后两者为直立体。只有 remark 没有编号,其 “Remark” 标题为斜体。之所以能共享编号却分开字体,是因为 计数器和样式是两条互相独立的轴

proof 环境,以及证明结束符号跑错行的问题

amsthmproof 环境会在开头放置斜体的 “Proof.”,并在末尾自动加上 \qedsymbol(默认为 □)。但 proof 试图把符号放在 最后一个段落行的行末,所以当证明以陈列公式结束时,符号无处可依,□ 就会 独自掉到下一行。这不是印象,而是可以量出来的。把同一个证明放进 \vbox 并测量高度,结果如下:

log
% \vbox{\hsize=8cm \begin{proof}Compute:\[a^2+b^2=c^2.\]\end{proof}}
without \qedhere   h = 66.94444pt
with    \qedhere   h = 47.77777pt      % 19.16667pt shorter: one whole line saved

差值是 19.16667pt,正好一行。\qedhere 是一条“把符号放在这里”的指令:写在陈列公式末尾,□ 就会贴到该公式行的右端,多出的那一行随之消失。\qedhereequationaligngather* 中都能用,在 enumerate 的最后一个条目里也能用(以上均已在 TeX Live 2024 上确认无错误通过)。当它无法妥善放置符号时,amsthm 会报出 Package amsthm Warning: The \qedhere command may not work correctly here,查看日志即可知道。

latex
\begin{proof}
  Rearranging both sides gives
  \[
    a^2 + b^2 = c^2. \qedhere
  \]
\end{proof}

标题词和结束符号都可以替换。单个证明可以接一个方括号,如 \begin{proof}[Proof of Theorem 1];全文的默认词则来自 \proofname,重定义它即可(已验证 \renewcommand{\proofname}{Demonstration} 会得到 “Demonstration.” 的标题)。结束符号则是重定义 \qedsymbol。如开头所述,默认的 \qedsymbol\openbox,也就是用线条画出的空心方块——因此换成 $\blacksquare$ 会得到实心方块,重定义为空则完全去掉这个标记。注意 \blacksquareamssymb 的符号(amssymb.sty 第 48 行),只加载 amsthm 会得到 ! Undefined control sequence.——别忘了 \usepackage{amssymb}

preamble
\usepackage{amssymb}                         % \blacksquare lives here, not in amsthm
\renewcommand{\proofname}{Demonstration}     % heading word for every proof
\renewcommand{\qedsymbol}{$\blacksquare$}    % filled square instead of the hollow one
% \renewcommand{\qedsymbol}{}                % no end-of-proof marker at all

thmtools:用 key=value 取代方括号的位置

当声明超过十来条时,\newtheorem 那套“含义由方括号位置决定”的写法就变得难读了。thmtools(TeX Live 2024 内附 2023/05/04 v0.76)是架在 amsthm(或 ntheorem)之上的高层接口,通过 \declaretheorem 让同样的设置改用 key=value 书写。它本身不拥有编号机制,只是在底层调用 amsthm\newtheorem,因此要与 amsthm 一起加载。

preamble
\usepackage{amsmath, amsthm, thmtools, thm-restate}

\declaretheorem[numberwithin=section]{theorem}      % same as [section] after arg 2
\declaretheorem[sibling=theorem]{lemma}             % same as [theorem] after arg 1
\declaretheorem[style=definition, sibling=theorem]{definition}
\declaretheorem[numbered=no, name=Remark]{remark}

键的对应很直白。从属于节是 numberwithin=(同义词 parent=within=),共享计数器是 sibling=(同义词 numberlike=sharecounter=),无编号是 numbered=no,标题词是 name=(同义词 title=heading=),外观是 style=。把上面的导言区实际排出来,Theorem 1.1 之后紧接着就是 Lemma 1.2——输出确认了 sibling= 确实建立了同一条编号序列。

真正值得引入 thmtools 的理由,是两个用 amsthm 单打独斗会很麻烦的功能。第一个是 重述(restatable。加载随附的 thm-restate,把定理写在 restatable 环境里,其内容就会保存为一个宏,之后可以 以同样的编号 再排一次。当你想在正文中带编号地陈述结论、而把证明放到附录时,这就是标准做法。实际排版后,正文中的 Theorem 1.1 在附录里原样重现为 Theorem 1.1。

latex
\begin{restatable}[Euclid]{theorem}{firsteuclid}
  \label{thm:euclid}
  There are infinitely many primes.
\end{restatable}

% later, e.g. in an appendix — same number, references still point at the original
\firsteuclid*

restatable 的第一个参数(可省略)是名称,第二个是要使用的定理环境,第三个是 记住内容的宏名。之后展开 \firsteuclid 就会再排一次该定理,编号仍固定在最初出现处。加星的 \firsteuclid* 在重述时让 \label/\ref 继续指向 原声明处而非副本。另一个功能是 定理列表:写 \listoftheorems 会生成类似目录的列表,用 ignoreall 配合 show={…} 可缩小到指定环境。还有 onlynamed,只收录通过 \begin{…}[name] 命名过的定理;若同时加载 hyperref,每一项都会成为通向定理本体的链接。

latex
\listoftheorems                                     % everything
\listoftheorems[ignoreall, show={theorem, lemma}]   % only these two
\listoftheorems[ignoreall, show={theorem}, onlynamed]  % only the ones you named

想自定义外观时,用 \declaretheoremstyle 定义一个样式,再从 \declaretheorem[style=…]{…} 调用。键可以设定标题字体 headfont、正文字体 bodyfont、上下间距 spaceabove/spacebelow、名称外的括号 notebraces、标题与正文之间的间距 postheadspace、结束符号 qed 等等。判断上:若只需要少数几条声明,单用 amsthm 就够了;等到数 \newtheorem 的方括号开始让你烦躁,或者你想要 restatable 和定理列表时,再加上 thmtools