longmath の括弧の高さを自動調整する新しい方法を使ってみよう

<!-- # longmath の括弧の高さを自動調整する新しい方法を使ってみよう -->

[longmath][] パッケージは括弧の高さ自動調整するためのまったく新しいコマンドを提供します。

[longmath]: https://ctan.org/pkg/longmath "CTAN: Package longmath"

:::note info
longmath パッケージ内部で Lua を利用しているため、LuaLaTeX のみで動作します。

また、本記事はバージョン 1.0 (2024-03-04) です。
:::

## longmath の新しい left・right

LaTeX がデフォルトで提供する `\left`・`\middle`・`\right` のように、longmath パッケージでは基本となる 3 つのコマンドがあります。

- `\lleft`
- `\mmiddle`
- `\rright`

基本的な使い方はまったく同じですが、決定的に違う点として、それぞれを独立に利用できるという点があります。

これまで `\left`・`\right` は独立して利用できず、必ず組で使う必要がありました。加えて、`\middle` はこの組の間になければなりませんでした。

```latex
%% \middle が外側にあるような書き方は許されなかった
\frac{ \left ( a + 1 \right) \middle / x }{a}
```

longmath パッケージから提供されるコマンドではこのようなグルーピングは行わないため、それぞれを独立して利用できます。

```latex
%% longmath パッケージでは \middle が外側にあっても許される
\frac{ \lleft ( a + 1 \rright) \middle / x }{a}
```

そのため、邪悪な気もしますが `\vphantom` を代替するような使い方も可能になっています。（[後述][sec-vphantom]）

[sec-vphantom]: #vphantom-的な使い方 "\vphantom 的な使い方"

また、`\lleft`・`\rright` は `\left`・`\right` における丸括弧周囲の謎スペースを生じさせません。

![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/590705/54be1cc3-92d8-6292-f904-be0d78130e22.png)

### 数式クラスに対応した `\middle`

`\middle` では、これに続く記号がどの数式クラスに属するかは指定されていませんでした。

例えば集合における内包的記法では、以下のように `\middle|` とすることで `\left\{`・`\right\}` の高さと合わせるようにします。

```latex
G =
  \left\{
    (x,y) \in \mathbb{N}^2
    \middle|
    Z - \frac{1}{2} \leq x^2 + y^2 \leq Z + \frac{1}{2}
  \right\}
```

このときの `\middle|` はいずれの数式クラスにも当てはまりません。（[参考][zr1334151482]）

[zr1334151482]: https://zrbabbler.hatenablog.com/entry/20120411/1334151482 "\middle であり \mid であるもの - マクロツイーター"

longmath パッケージでは `\mmiddle` に加えて、数式クラスに対応したコマンドが存在します。

|  コマンド  | 数式クラス                               |
| :--------: | :--------------------------------------- |
|  `\mmord`  | 通常 (e.g. `a`, `\alpha`)                |
|  `\mmrel`  | 関係演算子 (e.g. `=`, `\coloneq`)        |
|  `\mmbin`  | 二項演算子 (e.g. `+`, `-`)               |
|  `\mmop`   | 前に置かれる作用素 (e.g. `\int`, `\sum`) |
| `\mmpunct` | 後置記号 (eg. `.`, `!`)                  |

上で挙げた参考の記事内では、`\mathrel{}\middle|\mathrel{}` とすることで `\middle|` を関係演算子の扱いにしていますが、longmath パッケージでは `\mmrel|` とすることで関係演算子として扱うことが出来ます。

```latex
G =
  \lleft\{
    (x,y) \in \mathbb{N}^2
    \mmrel|
    Z - \frac{1}{2} \leq x^2 + y^2 \leq Z + \frac{1}{2}
  \rright\}
```

![01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/590705/bac6e0ef-1a2e-4eed-197a-70fc763b9c86.png)

### 数式環境内で括弧の高さを揃える

例えば以下のような数式を考えたとき、`\sum` を含む 1 つ目の組と `\int` を含む 2 つ目の組では丸括弧の高さが若干異なります。

```latex
\lleft( A + \sum B \rright)
\lleft( C + \int D \rright)
```

![02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/590705/911553c5-c104-5f7a-8004-9f7ec3f9f496.png)

longmath パッケージでは、これを解消するために 2 つのコマンドが用意されています。

- `\mleft`
- `\mright`

これらを利用することで、環境内（グループ内）にある最も高いデリミタに調整されます。

```latex
\mleft( A + \sum B \mright)
\mleft( C + \int D \mright)
```

![03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/590705/e00a2c55-cba2-2b39-4cdd-1af4476a0d69.png)

この高さの統一は TeX のグループに左右されます。そのため、各組を `{}` で囲う等すると高さは揃いません。

```latex
%% これでは各組の丸括弧の高さが揃わない
{ \mleft( A + \sum B \mright) }
{ \mleft( C + \int D \mright) }
```

### 手動のスケール調整

ここまでに紹介した 10 つのコマンドは数値によって手動でスケールを調整することも出来ます。

`\lleft` の後に数値を置くことでスケールを調整します。

```latex
X =
  \lleft (
    \sum_k^\infty A
    \mmbin 900 | \sum_k^\infty B
    \mmbin 700 | \sum_k^\infty C
    \mmbin 500 | \sum_k^\infty D
    \mmbin 300 | \sum_k^\infty E
  \rright )
```

![04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/590705/15169c5d-a6b8-5bac-c20e-d3fffaea16f0.png)

<!-- スケールのデフォルト値は＊＊です。 -->

### タグ付け

:::note warn
この機能は複数回のタイプセットが必要になります。
:::

例えば次のような式を考えたとき、それぞれの項で丸括弧とスラッシュの高さは異なります。

```latex
  \frac{ \lleft ( a + 1      \rright) \mmord / x }{a}
+ \frac{ \lleft ( \sum_k b^k \rright) \mmord / y }{b}
+ \frac{ \lleft ( c^2 + 1    \rright) \mmord / z }{c}
```

![05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/590705/6b05af47-6388-90fd-454e-1846ddb365e8.png)

longmath パッケージでは、`\lleft` コマンド等に続いてタグ付けすることで高さを揃えることが出来ます。

```latex
  \frac{ \lleft {f5} ( a + 1      \rright) \mmord / x }{a}
+ \frac{ \lleft {f5} ( \sum_k b^k \rright) \mmord / y }{b}
+ \frac{ \lleft {f5} ( c^2 + 1    \rright) \mmord / z }{c}
```

![06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/590705/2bee2388-5429-9c28-65fb-1ba968f130f9.png)

このとき、`{f5}` のようなタグ付けは `\lleft`・`\rright` どちらのコマンドに付随していても同じ結果を得ます。
また、`\mleft`・`\mright` を使っても同じ結果を得ます。

あるいはこのとき、`\mmord` にタグ付けをすると、スラッシュの高さのみが全体で揃うようになります。

```latex
  \frac{ \lleft ( a + 1      \rright) \mmord {f6} / x }{a}
+ \frac{ \lleft ( \sum_k b^k \rright) \mmord {f6} / y }{b}
+ \frac{ \lleft ( c^2 + 1    \rright) \mmord {f6} / z }{c}
```

![07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/590705/c3efe40f-4b93-ce8f-6774-d9c321ebe0b5.png)

このタグ付けは、次のような角括弧と丸括弧の高さを各々で揃えたいとき等にも有効です。

```latex
  F\lleft {Fx} [ X   \rright] \lleft {Pw} ( 1 + 2^2 \rright) \lleft {Pw} ( 1 + 2^{2^2} \rright)
+ F\lleft {Fx} [ X^2 \rright] \lleft {Pw} ( 1 + 3^3 \rright) \lleft {Pw} ( 1 + 3^{3^3} \rright)
+ F\lleft {Fx} [ X^3 \rright] \lleft {Pw} ( 1 + 5^5 \rright) \lleft {Pw} ( 1 + 5^{5^5} \rright)
```

![08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/590705/7bad4374-ee0a-416b-6184-884d1323d37a.png)

:::note info
このタグ付けは相互参照のように固有のタグ名である必要があります。

一意のタグを作成する必要があるため、タグの命名が非常に面倒になるかも知れませんが、`\delimiterprefix` を構成することで比較的容易に一意に構成することが出来ます。

例えば以下のように構成すると、すべてのタグ名の先頭に `\theequation.` が挿入されます。（この例では数式毎にタグ名が固有になります）

```latex
\delimiterprefix{\theequation.}
```

このタグ名は相互参照のラベル名とは干渉しません。
:::

#### 改行や `&` をまたいで高さを揃える

split 環境内で高さを調整する括弧が改行を含む場合でも、タグを使用して紐づけることができます。

```latex
\begin{equation}
  \begin{split}
    a \lleft( \sum_{k=1}^N k + \sum_{k=1}^N \frac{1}{k} \rright)
    & = a
      %% ここから
      \lleft {be} (
        \frac{1}{1} + \frac{1}{2} + \frac{1}{3} + \frac{1}{4} + \frac{1}{5} + \cdots
    \\
    & \quad 1 + 2 + 3 + 4 + 5 + \cdots
      \rright {be} )
      %% ここまで
  \end{split}
\end{equation}
```

![09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/590705/d6e16118-dbdb-bcf0-3a55-7c99f1fe2de6.png)

あるいは、`\pushdelimiter` と `\pulldelimiter` を利用して以下のように構成することも出来ます。

```latex
\begin{equation}
  \begin{split}
    a \lleft( \sum_{k=1}^N k + \sum_{k=1}^N \frac{1}{k} \rright)
    & = a
      %% ここから
      \lleft(\pushdelimiter
        \frac{1}{1} + \frac{1}{2} + \frac{1}{3} + \frac{1}{4} + \frac{1}{5} + \cdots
    \\
    & \quad 1 + 2 + 3 + 4 + 5 + \cdots
      \pulldelimiter\rright)
      %% ここまで
  \end{split}
\end{equation}
```

#### 数式環境をまたいで高さを揃える

タグ付けして高さを揃えることが出来るため、数式環境をまたいで高さを揃えることも可能です。

```latex
Equations that start with
\[
  \lleft(
    \int\limits_1^\infty f(x) \, \mathrm{d}x
  \rright)
  %% ここから
  \lleft {longeq} (
    \sum_k a_k + 1000 + 999 + \cdots
\]
often go on for pages and end with
\[
  \cdots + 1
  \rright {longeq} )
  %% ここまで
  = 0 .
\]
```

![10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/590705/7ebd6c76-c28d-8489-61ba-f6113f5c2416.png)

`\delimiterprefix` を構成している場合、このような数式環境をまたいで高さを揃えられない場合があります。

#### `\vphantom` 的な使い方

例えば次のような根号を含む式を考えたとき、それぞれの項の根号記号の高さは異なります。

```latex
  \sqrt{ a + 1 }
+ \sqrt{ \sum\nolimits_k b_k }
+ \sqrt{ c^2 + 1 }
```

![11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/590705/e2856b6d-3ec9-471b-19cf-627b7fa3e0be.png)

このとき、高さを揃えようとすると `\vphantom` を使った非常に面倒な操作が必要です。また、高さが最大となる項を事前に知っている必要もあります。

longmath パッケージから提供されるコマンドは独立して利用できるため、以下のようにすることで高さを揃えるように機能させることが出来ます。

```latex
  \sqrt{ \mleft {sq8} . a + 1 }
+ \sqrt{ \mleft {sq8} . \sum\nolimits_k b_k }
+ \sqrt{ \mleft {sq8} . c^2 + 1 }
```

![12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/590705/40012237-02f1-bfff-adcf-e8151a74733a.png)

## ネストの深さ応じた自動的な括弧の判定

:::note warn
この機能は複数回のタイプセットが必要になります。
:::

これまで紹介した高さを自動調整するコマンドに `*` を与えることで、ネストの深さに応じて自動的に括弧を変更することが出来ます。

`\autodelimiters` を使って、以下のようにネストされる括弧の順序を指定します。（対応する括弧の中央に `*` を置きます）

```latex
\autodelimiters{ \lfloor [ \{ ( * ) \} ] \rceil }
```

数式内では、`\lleft*`・`\rright*` を使います。

```latex
\lleft* A + \lleft* B + \lleft* C + \lleft*
D + \frac{ \lleft* X + Y \rright* \times Z }{ W }
\rright* \cdot E \rright* \cdot F \rright* \cdot G \rright*
```

![13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/590705/50ec908c-0879-e121-f8c9-eb9ae4ccb3e0.png)

これによって、自動的にネストに深さに応じて括弧を変更されます。

加えて、タグ付けを行うとネストをそろえることも出来ます。

```latex
  \lleft {qx} *
    \frac{ n+1 }{ n^2 }
  \rright * ^2
+ \lleft {qx} *
  \frac{ n }{ \lleft * n - 1 \rright *^2 }
  \rright * ^2
```

![14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/590705/fa519aa5-a685-5ab5-ad9d-6d926cee03a3.png)

----------

実際に使う場合、`\lleft*`・`\rright*` とするのは疲れるので、以下のように定義しておくと便利でしょう。

```latex
\NewDocumentCommand{\ad}{ o m }{%% Auto Delimiter
  \IfNoValueTF{#1}%
    {\lleft* #2 \rright*}%
    {\lleft {#1} * #2 \rright*}%
}
```

これを利用することで、上で示した例は以下のように書き替えることが出来ます。

```latex
\ad{ A + \ad{ B + \ad{ C + \ad{
D + \frac{ \ad{ X + Y } \times Z }{ W }
} \cdot E } \cdot F } \cdot G }
```

```latex
  \ad[qx]{
    \frac{ n+1 }{ n^2 }
  } ^2
+ \ad[qx]{
  \frac{ n }{ \ad{ n - 1 }^2 }
  } ^2
```

## 自動的に改行する split 環境

longmath パッケージでは、自動的に改行する split 環境に準じた longmath 環境と longmath* 環境を提供します。

これらの 2 つの環境では、minipage 環境と同じように幅を指定することで自動的に数式を改行するようになります。（longmath* 環境の方がちょっとだけタイトな結果になるようです）

オプション引数は、aligned や gathered 環境と同じ `t`・`c`・`b` を受け付けます。

```latex
Q =
  \begin{longmath}[t]{70mm}
    1 + \frac{1}{2} + \frac{1}{3} + \frac{1}{4} + \frac{1}{5} + \frac{1}{6} + \frac{1}{7} + \frac{1}{8} + \frac{1}{9} + \frac{1}{10}
    + \frac{1}{11} + \frac{1}{12} + \frac{1}{13} + \frac{1}{14} + \frac{1}{15} + \frac{1}{16} + \frac{1}{17} + \frac{1}{18} + \frac{1}{19} + \frac{1}{20}
    + \frac{1}{21} + \frac{1}{22} + \frac{1}{23}
    + \cdots
  \end{longmath}
```

![15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/590705/f3d8672b-a156-98ce-7190-3face215d7bc.png)

見た目としては mathtools パッケージの multlined 環境に似たものになります。

幅の大きさは `70mm` のような絶対的な数値の他にディスプレイ可能な寸法の比（`0.6` など）による表現も可能です。

加えて、幅の値に `+` を続けてズレ幅を指定することも出来ます。（当然ですが、足された値が幅になります）

```latex
Q =
  \begin{longmath}[t]{70mm+20mm}
    1 + \frac{1}{2} + \frac{1}{3} + \frac{1}{4} + \frac{1}{5} + \frac{1}{6} + \frac{1}{7} + \frac{1}{8} + \frac{1}{9} + \frac{1}{10}
    + \frac{1}{11} + \frac{1}{12} + \frac{1}{13} + \frac{1}{14} + \frac{1}{15} + \frac{1}{16} + \frac{1}{17} + \frac{1}{18} + \frac{1}{19} + \frac{1}{20}
    + \frac{1}{21} + \frac{1}{22} + \frac{1}{23}
    + \cdots
  \end{longmath}
```

![16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/590705/ee3e53cf-19bb-e908-0b8a-4eb565abe78a.png)

----------

ところで、個人的に関係演算子の右側で改行されるのは奇妙に感じていますが、これは数学的に一般的な表現方法ですか…？

## 余談

個人的に longmath パッケージで有用だと感じている機能は [ネストの深さに応じて自動的に括弧を判定するやつ][sec-autobrace] です。

[sec-autobrace]: #ネストの深さ応じた自動的な括弧の判定 "ネストの深さに応じた自動的な括弧の判定"

多くの場合、丸括弧 ⇒ 角括弧 ⇒ 波括弧の順で括ると思われるので、`\autodelimiters{ \{ [ ( * ) ] \} }` として利用すると便利だと思います。

----------

投稿日の今日は 2024 年 7 月 7 日の七夕ですが、多くの地点の [暑さ指数][wbgt] は 30 を超え熱中症に気を付けなければならない気温となっています。まじであつい :hot_face:

[wbgt]: https://www.wbgt.env.go.jp/wbgt_data.php "環境省熱中症予防情報サイト 暑さ指数"

LaTeX をするときくらいはなるべく楽な方法を探してクールに過ごしましょう。