Typst で Wick 縮約を書く

:::note info
この記事は[Typst Advent Calendar 2024](https://qiita.com/advent-calendar/2024/typst)の5日目の記事です。
昨日は[@gomazarashi](https://qiita.com/gomazarashi)さんでした。
明日は[@tomoyatajika](https://qiita.com/tomoyatajika)さんによる記事が公開される予定です。
:::

新進気鋭の組版ソフトウェアである Typst が最近流行っています。

Typst には[多くのパッケージ](https://typst.app/universe/search/?kind=packages)がありますが、~~Wick 縮約を書くパッケージは今のところ存在しないようです。~~

(2024年4月追記、この記事を公開した4日後に [wicked](https://typst.app/universe/package/wicked/) というパッケージが公開されていました。Typst の進歩は本当に日進月歩ですね！)

Wick 縮約（ウィック縮約）の詳しい説明は省きますが、場の量子論などで使われる次のような数式中の文字を線でつなぐもののことです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/603264/6e5bf9fe-cc2a-8c11-0023-480008c983d9.png)

この記事では pinit というパッケージを使って Wick 縮約を Typst で書く方法を紹介します。

## pinit
[pinit](https://github.com/OrangeX4/typst-pinit) はピンを置いて矢印や線を書くことができるパッケージです。
```typst
#import "@preview/pinit:0.2.2": *

文章にピン#pin(1)を置いて、それに矢印をつけることができます。

#pinit-point-from(1)[こんな感じです。]
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/603264/f099b334-5e8c-2cfd-4eb8-95f6679a601d.png)

2つのピンの中点から線を伸ばすことで Wick 縮約を書くことができます。
```typst
$
  #pin("1-l")psi#pin("1-r")phi#pin("2-l")psi#pin("2-r")
$
#pinit-line(("1-l", "1-r"), ("1-l", "1-r"), stroke: 0.5pt, start-dy: -0.9em, end-dy: -0.9em-5pt)
#pinit-line(("2-l", "2-r"), ("2-l", "2-r"), stroke: 0.5pt, start-dy: -0.9em, end-dy: -0.9em-5pt)
#pinit-line(("1-l", "1-r"), ("2-l", "2-r"), stroke: 0.5pt, start-dy: -0.9em-5pt, end-dy: -0.9em-5pt)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/603264/6e5bf9fe-cc2a-8c11-0023-480008c983d9.png)


## より便利な関数
上のような方法で Wick 縮約を書くのは面倒なので、TeX の [simpler-wick](https://ctan.org/pkg/simpler-wick) パッケージのような自動的に Wick 縮約を書いてくれるものが欲しいところです。

Typst では次のような簡単な関数を書くことで実現できます。

```typst
#import "@preview/pinit:0.2.2": *

#let wick-id = counter("wick-id")
#let wick-state = state("wick-state", ())

#let wick(sep: 5pt, offset-t: 0.9em, offset-b: 0.3em, stroke: 0.5pt, body) = {
  wick-id.step()
  wick-state.update(())
  body
  context {
    let name = "wick-" + str(wick-id.get().at(0)) + "-"
    let dict = (:)
    for (i, n) in wick-state.get().enumerate() {
      let p = (name + str(i) + "-l", name + str(i) + "-r")
      let offset = if n > 0 { -offset-t } else { offset-b }
      let dy = offset - sep * n
      pinit-line(p, p, stroke: stroke, start-dy: offset, end-dy: dy)
      let bef = dict.remove(str(n), default: none)
      if bef == none {
        dict.insert(str(n), p)
      } else {
        pinit-line(bef, p, stroke: stroke, start-dy: dy, end-dy: dy)
      }
    }
  }
}

#let c(n, body) = (
  context {
    let s = wick-state.get()
    pin("wick-" + str(wick-id.get().at(0)) + "-" + str(s.len()) + "-l")
    body
    pin("wick-" + str(wick-id.get().at(0)) + "-" + str(s.len()) + "-r")
    wick-state.update(s => { s.push(n); s })
  }
)
```

関数`c`で Wick 縮約をする項と線の高さを決め、関数`wick`の中にある同じ高さの線をつないでいます。これを使えば、先ほどの例は次のように簡単に書くことができます。

```typst
#wick($
  #c(1, $psi$)phi#c(1, $psi$)
$)
```

もっと複雑な例も書くことができます。
```typst
#wick($
  #c(1, $A$)#c(2, $B$)#c(1, $A$)#c(2, $B$) + #c(-1, $C$)#c(1, $D$)#c(-1, $C$)#c(1, $D$)
$)

#v(1em)

#wick($
  #c(1.5, $A$)integral (d x)/x#c(1.5, $A$)
$)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/603264/2960c258-5d18-cfc0-480c-aa42ba2fbfed.png)

## おわりに
Typst は様々な便利なパッケージがあり、プログラミングも簡単です。Wick 縮約を書く関数も上のように簡単に書くことができます。