\fracを\expandafterする

\fracの定義は

\frac :
\long macro:#1#2->{\begingroup #1\endgroup \over #2}

です。そのため、ご存じの通り

$\frac 1\frac 23$

はエラーとなります(誰しもが一度は経験しているはず)。こういうときは丁寧に{}で囲って

$\frac 1{\frac 23}$

として$\frac 1{\frac 23}$とする必要がありました。


ある日、\expandafterを用いて

$\expandafter\frac\expandafter 1\frac 23$

とすれば、後ろのfracが先に展開されて、

$\frac 1{\begingroup 2\endgroup \over 3} $

となり、$\frac{1}{\frac 23}$が出力されるはずだということを思いつきました。

ところが、これを実際に実行すると出力は$\frac{1}{}\frac{2}{3}$となってしまいました。何度考えてもこうなる理由がわからなかったので、エラーを吐いたときの定石\tracingナンチャラを書いて

\tracingonline=1\relax\tracingmacros=1\relax
$\expandafter\frac \expandafter 1\frac 23$

を実行したところ、ログファイルには次が出力されました。

\frac ->\protect \frac  

\frac ->\protect \frac  

\frac  #1#2->{\begingroup #1\endgroup \over #2}
#1<-1
#2<-\protect 

\frac  #1#2->{\begingroup #1\endgroup \over #2}
#1<-2
#2<-3

すなわち、最初の展開で後ろの\fracが展開されたときに

\frac 1{\begingroup 2\endgroup \over 3}

となるという考察から誤りであり、順に

\expandafter\frac\expandafter 1\frac 23

\frac 1\protect\frac 23

\protect\frac 1\protect\frac 23

となっていき、前の\fracが\frac{1}{\protect}、後ろの\fracが\frac{2}{3}として最終出力を得た、ということを言っています。意味不明です。

でもたしかに、コマンドラインにlatexdef fracと書いたときの出力は、最初に書いたものではなく

\frac:
macro:->\protect \frac

\frac :
\long macro:#1#2->{\begingroup #1\endgroup \over #2}

でした。普段全く気にしていなかったことを反省しています。
コマンドの脆弱性/堅牢性の話から逃げ続けた影響が今になって出てきたということでしょう。

展開制御をそれなりにわかったと言える日はいつになったら訪れるのでしょうか。


ぺりをだねぇ

東大数理院試体験記(2024/08/26~30)

表題の通りです。分野は解析です。問題のかなり具体的なネタバレは脚注にしています。

これを読んでいる多くの人は解析専攻ではないと思うので、どこに需要があるかはよくわかりません(解析固有の事情が沢山現れます)が、今後受ける人の参考になればと思います。
何が誰の役に立つかわからないので、あまり整理せずに書いています。また、適宜追記する可能性があります。

いずれ書きたいと思っていること:院試対策のスケジュール

試験前

A300+B200は取りたい、あわよくば400+300、というつもりで試験に行った。講義資料やノートを参照しても良くて時間無制限で計算ミスをしないという状況ならA全完もB全完もそれなりに現実的である、というのが過去問演習での感想だった(後述するが、これは解析固有の事情も大きい)。

英語

一夜漬け。Brezis関数解析(英語版)とSLPを適当に眺め、使えそうなフレーズを50個くらい紙に書いて何回か音読した。
試験直前に数理科学基礎共通資料の巻末の数学用語の英語索引(伝われ)を読んでいる人が何人かいて、それの読み合わせをした。

時間を測って過去問を解いたわけではないが、なんとなくP1(1)和訳20分+P1(2)和訳20分+P2英訳40分くらいのつもりで余裕だろうと考えて試験に取り掛かる。

0分:P2を見ると、直前に数理科学基礎共通資料の巻末で見た「一様収束」がのっていた。あまり大変そうではないということを確認し、P1(1)に取り掛かる。

20分:ところどころ訳に困ったところを放置して(1)を最後まで訳しきった……と思ったらもうこんな時間?意外と時間ないな、と思いつつP2にかける時間を過剰にとっているだけかと思い、P2に移動。

70分:すべての問を(ところどころ諦めつつ)何とか書ききる。思ったより時間がかかってしまった。P2の見直しをして、P1の訳しぬけがないことを確認しはじめる。

80分:確認している間に試験終了。回収中僕「あれ、convergentって本当に動詞か?convergent、convergence……さては動詞はconvergeだな~~??」

A問題

10分手が動かなかったら一旦離れる、と決めて取り掛かった。ルーシェの定理パズルが出たら困るなと思っていたが……

P1必答微積、P2必答線型、P3解析、P4線型続論、P5位相、P6複素解析(ルーシェパズルではない!)、P7ODE。2Aまでに解析の授業が多いことにより、選択問題に解析の問題が多い。

0分:とりあえず全体を見る。P1はちょっと難しそう、P2はやればできそう。位相はたまにネタ枠なので一応P5を見て、1分くらい考えてネタ枠ではないことを確認。
P6を見る前にP7に目を引かれ、とりあえずP7をサクッと解いて安心しようと思ってP7に着手する。

15分:……も(2)が全然解けなくて、良い変換を見つけだす系の問題でこれは沼ると思い一旦逃げ、時間をかければ確実に解けるであろうP2に移動。

45分:途中ちょこちょこ計算をミスりながらも丁寧に見直ししながらP2を完答。P3を眺め、(1)はぱっと見で方針が立ち(確か過去問でほぼ同じ設定の問題があった)、(2)の式も冷静に見ると関数の大まかな振る舞いがわかり*1、そうすると各点収束先がわかるので完答できることを確信。答案を書き始める。

?分:P3完答。P1に取り掛かる。(1)をサボる方法*2を見つけ、(1)をとりあえず書ききった。(2)でやや迷走したが、固執している方針が筋悪であることに気づいて色々な方針を試していたらあたりを引いて、解けることを確認。

120分:P1完答。P6とP7のどちらを解こうか迷っていた。P6をまだ全然考えていないので一応考えると、(1)は簡単で、(2)を見ると単なる留数計算問題であることに気づく。こっちの方が完答が確実だと判断し、頑張って計算する。

140分:P6の結論が綺麗な式になり全完。見直しに移行。

?分:1箇所P3で変なミスを見つけたが、すぐに修正できて事なきを得た。

180分:試験終了。かなり突っ込んだ感想戦をしても特にミスが見つからなかったので、400点ベースであることを確信。

B問題

注意:このムーブを参考にして失敗しても責任は取れません。自分に合った方法で解くのが良いです。

ひっそりと全完を狙っていたので、15分手が止まったら一旦離れる、序盤は完答できることを一定以上確信するまで(小問が解けていても)答案を書き始めない、と決めて取り掛かった。というのも、解析の問題は沢山ある(第13問以降にダラダラ続いているものを解析だと思うと正しい)という事情で、解ける問題が隠れていることがあるから。過去問演習はpdfをダウンロードして問題文の余白で解き、計算が重たかったり議論を詰め切れなかったりした場合はノートでちゃんと詰める、というスタイルで行っていたので、それに近い方法を採った。

P9:ルベーグ P10:複素解析 P11:関数解析 P12:PDE P15:数値解析 P16:応用数理。

0分:とりあえず全体を眺める。P9は一方向が自明、P10は面積とか書いてあったので一旦逃亡、P11は時間がかかりそう、P12は解けそう、P15は数値解析の問題の中では難しそうで解ける問題かどうかは不明、P16は見た目がごつくて難しいのか見掛け倒しなのかわからない。とりあえずP12に着手。

?分:(1)(2)が解けることを確認し、(3)もある程度できることを確認したので、これはおそらく選択する問題になるだろうと確信し答案を書き始める。

25分:(1)(2)を書き終わり、(3)の途中まで書いて一旦休止。P9に取り掛かる。ほんのかすかに気持ちをつかみ*3、おそらくこれも選択する問題になるということを把握し、とりあえずP12を終わらせに戻る。

40分:P12完答。P9をきっちり定式化しに戻る。

?分:かなり気持ちをつかんだので答案を書き始めるも、図を言語化しているうちに一か所混乱している場所があることに気づく*4。冷静に整理すると筋の良い方法にたどり着いた*5

120分:P9完答。残り1問をどれにするか迷う。P11は(1)は頑張ればできそうだが残りを解ける気があまりしないので放置。P15は見た目がいかついが一応手を付けてみようと思って(1)をよく見ると、操作の意味を理解*6でき、大体こういう形になるだろうな~と思って計算すると本当にそうなった。135分時点までP15を考えて、それからどれを解くかもう一回考えようと決めてP15に取りかかる。

135分:(1)(2)までできていて、(3)もある程度は進んでいて、ここから1時間以上かけてできないことはないだろうと決め打ちし、答案を書き始める。

160分:P15を完答し全完。見直しを開始。

180分:見直しても特にミスは見当たらず。減点可能性の低い細かい行間も埋める作業に移行。

240分:試験終了。「目についた問題が解けた」×3ができたので、解けなくて逃亡するとか目移りするとかいう無駄な時間がなかった点でかなり運は良かった気がする。泥沼にはまっていることを認識できるように、試験中に自分がどの問題をどれくらいの時間考えたかを管理しておくのが大事なポイントだったとは思う(東工大の院試の反省)。

試問

とりあえず空白にしておきます(内容を口止めされている人とされていない人がいて、よくわからない)。

感想

試験前に半分冗談で「落ちるんだったらAB全完して落ちたい」と言っていたが、本当に全完してしまった(開示が来るまでどこかで知らぬ間に大嘘をついている可能性は否定できないが……)。
筆記と口頭試問の点数配分が全くわからないので不安だが、今更何かが変わるわけはないので、合格発表まで院試のせいでできなかったことをして過ごそうと思います。でも院試のせいで何をしたかったか忘れてしまった。まずはそれを思い出すところから……

結果

合格

A問題:395/400
B問題:290/300
英語:155/200
総合得点(口述試験を含む):85/100

細かい減点はあったようだが特に大きな嘘はなく、ほとんど想定通りだった。


ぺりをだねぇ

*1:logの中身の大きさはx, y, zの最大値が支配する

*2:siny/yとの差が可積分関数

*3:背理法、可積分でないとする。fの一方の符号に1をたくさんあててもう一方の符号に0をたくさんあてる。

*4:fとaのグラフが脳内で混ざっていた

*5:fを連続関数で近似したものを見てaを作るのではなく、fそのものを見てaを作る

*6:フーリエ変換だと思うと、L2ノルムが保存されますという話

キーボードのsが壊れた!そんなときは

やりたいこと

キーボードのsが壊れても、毎回sをコピペしてこなくても良いようにしたいです。

実装

ソースコード中で改行したら出力の方でも改行するようにするコマンド\obeylines(および有志の方がそれを改良した\xobeylines)を参考にします。
これは改行文字のカテゴリーコードを変えてアクティブ文字にし、\defを使って改行文字に対して通常の制御綴のように定義する、という方法によって実現しています。

今回は改行文字^^Mの代わりに?をアクティブ文字にして、sを打ちたいときに?を打てば良いようにします。

{
\catcode`\?=\active
\gdef\quedekaihi{\catcode`\?=\active \def ?{s}}
}

入力:

\quedekaihi

He i? periwo.

出力:
He is periwo.

実装においてもsを打つ回数は一回で済むので、これならラクチン、sが壊れても安心ですね!

追記

sをどこかからコピペしてくるのは甘えだ!というクレームが入ったので修正します。sのASCIIコードが73なので、次のようにすると同じになります。

{
\catcode`\?=\active
\gdef\quedekaihi{\catcode`\?=\active \def ?{^^73}}
}

これでsを一度も打たなくて良くなりましたね!

さらに追記

上は実は\sqrtなどの制御綴は書けない(再定義とかでごまかすことを想定していた)のですが、そもそも?なんて経由しなくても、sと書きたいときに^^73と書けば良いっぽいです:
入力:

^^24^^5c^^73^^71^^72^^74^^7b^^33^^7d^^24

これは以下と同等:

$\sqrt{3}$

出力:$\sqrt{3}$

怖……

続編

これのさらなる応用です。ぜひご覧ください。
shukutoiya.hateblo.jp

ぺりをだねぇ

数式の開始・終了時にPERIWOを書きたい

やりたいこと

$2+3=5$と書いたら、出力が$PERIWO2+3=5PERIWO$になるようにすることが目標です。

obeylines

ソースコード中で改行したら出力の方でも改行するようにするコマンド\obeylines(および有志の方がそれを改良した\xobeylines)を参考にします。
これは改行文字のカテゴリーコードを変えてアクティブ文字にし、\defを使って改行文字に対して通常の制御綴のように定義する、という方法によって実現しています。

※先にこっちを見た方が理解がしやすいかもしれません。shukutoiya.hateblo.jp

ネックとなるポイント

数式開始時点と終了時点で違うことをやっているので、$を単純に定義するだけでは実装できません。なので違う文字を挟んで解決することにします。

実装1

{
\catcode`\$=\active
\catcode`\@=3
\gdef\dolltope{\catcode`\$=\active \def ${\ifmmode \mathcal{PERIWO}@\else @\mathcal{PERIWO}\fi}}
}

入力:

\dolltope
$2+3=5$

$f(x)=\begin{cases} 1 & \text{$x$は有理数} \\ 0 & \text{$x$は無理数}\end{cases}$

出力:

(最初の{は、カテゴリーコードの変更を局所的にするためです。)
@に(通常の)$と同じ機能を与えていることがポイントです。
数式モード中に$を読み込んだ時には\mathcal{PERIWO}@を、そうでないときは@\mathcal{PERIWO}を返すように設定することによって実装しています。

実装2

@を挟むのはやはり行儀が悪い(いまさら何をという感じもしますが)ので、$のままで解決したいです。

{
\catcode`\$=\active
\gdef\dolltoper{\catcode`\$=\active 
\def ${\ifmmode \mathcal{PERIWO}\bgroup \catcode`\$=3 $\egroup \else \bgroup \catcode`\$=3 $\egroup\mathcal{PERIWO}\fi}}
}

入力:

\dolltoper
$2+3=5$

$f(x)=\begin{cases} 1 & \text{$x$は有理数} \\ 0 & \text{$x$は無理数}\end{cases}$

出力:エラー

! TeX capacity exceeded, sorry [input stack size=10000].
$...ode `\$=3 $\egroup \else \bgroup \catcode `\$=
                                                  3 $\egroup \mathcal {PERIW...

無限ループに入っているのだと思いますが、何が起こっているかはよくわかりません。解析の際にカテゴリーコードの変更がどこで反映されるかあたりが謎なんでしょうか。

実装3

一方、これは成功します。

{
\gdef\periwodollar{$}
\catcode`\$=\active
\gdef\dolltoperi{\catcode`\$=\active \def ${\ifmmode \mathcal{PERIWO}\periwodollar \else \periwodollar\mathcal{PERIWO}\fi}}
}

入力:

\dolltoperi
$2+3=5$

$f(x)=\begin{cases} 1 & \text{$x$は有理数} \\ 0 & \text{$x$は無理数}\end{cases}$

出力:

\periwodollarは「カテゴリーコード3の$」と定義されている、つまり\defは中身のカテゴリーコードを確定させる、と現時点では考えていますが、この認識が正しいかどうかはよくわかりません。

次の目標

よく考えてみると\def\hoge{fuga}の }をどうやって認識しているかはそんなに自明な話ではない(\edefならまだしも、\defはその時点では完全展開しないので)ということに気づきました。\defまわりの構文解析は闇が深そうです。

ぺりをだねぇ

補助ファイルに%を出力する(仮)

LaTeXを使って、補助(テキスト)ファイルを作る際に%を出力したいときの解決策として、catcodeに訴えるという方法を思いつき実行しました。
(他に楽な方法があるかもしれないですが、とりあえず覚書として。習いたてのことばかり使っているので、マナーの悪いコードかもしれないです)

\makeatletter

\catcode`\%=11

\newcounter{currentnumber}
\setcounter{currentnumber}{1}
\newcounter{endnumber}
\setcounter{endnumber}{10}

\newcommand{\periwo}[1]{
 \if@filesw
  \newwrite\@peri\relax
  \immediate\openout\@peri #1.tex\relax
 \fi
 \if@filesw
  \immediate\write\@peri{%%%% コメントアウトも}
  \immediate\write\@peri{%%%% 出力できるよ}
  \immediate\write\@peri{\string\begin{theorem}{#1}}
  \immediate\write\@peri{}
  \immediate\write\@peri{\string\end{theorem}}
 \fi
 \if@filesw
  \immediate\closeout\@peri
 \fi
 \stepcounter{currentnumber}
}

\newcommand{\PERIWO}{
  \periwo{\thecurrentnumber}
}

\def\periwoloop{
  \PERIWO
  \ifnum\thecurrentnumber>\theendnumber\relax
  \else\expandafter\periwoloop\fi
}

\periwoloop

を(\begin{document} と\end{document} の間に書いて実行すると)
1.tex, 2.tex, ..., 10.texが生成され、
n.texの中身は

%%%% コメントアウトも
%%%% 出力できるよ
\begin{theorem}{n}

\end{theorem}

になります。

ぺりをだねぇ

TeXで危うくPCを壊しかけた話

ある日、Excelで作業をしていてファイルを保存しようとしたら、Cドライブがいっぱいだと言われて保存できず。
そんなこともあるんだな~~と思い、基礎化学実験のレポート(手書きレポートの写真をそのままpdfにしただけなので少し重い)をいくつか消してその場をしのぎ、とりあえず保存するも、数分後に再び保存に失敗。
小手先のゴミ捨てではダメなのかと反省し、データサイズの内訳(デスクトップ○○GB、ダウンロード○○GB、ミュージック○○GB、……と教えてくれるアレ)を確認しながら、古いデータを一旦USBに移すなどして20GB程度あけ、PCに平穏が戻る……はずだった。

1時間もたたないうちに再び保存に失敗。再びデータサイズの内訳を見ると、また空き容量が0GBに戻っているではないか。
さすがに何かがおかしいことを完全に確信し、ふと冷静になる。

デスクトップに170GBもあるわけなくね?

昔のデータもあるとはいえ、塵も積もれば山となるにしては山がデカすぎる。さっき整理したときも細かいファイルばかりで、中くらいのサイズのファイルもそんなになかったはず……

そこで一つ一つのフォルダのサイズを順に確認していくと、TeX実験というフォルダーのサイズの桁が3つ~4つくらい周りと違うではないか。

ここでやっと心当たりが。あいつだ


その数日前、展開制御の実験をしていました。
自分自身を呼び出すことを繰り返して適切な条件をみたしたら終了するコマンド\periwo です。
ブログをよく読んでくださっている方は見覚えがあると思います。そう、

shukutoiya.hateblo.jp

です。
これを応用させている中で事件は起きました。ご察しのとおり、無限ループです。

通常の無限ループだとTeXworksの左上にデカデカと鎮座しているタイプセット停止ボタンを押せば(経験上)止まるはずなのだが、今回は何かが違う。
無限ループだと気づいたころにはもう押しても反応がない!!
ボタンも反応しなければ、タスクマネージャーも開けず、焦るばかり。
なんとか強制終了に成功し、ほっと一安心。一瞬で精神が削られ、TeXで遊ぶ気力も消えてその日はもう寝ることに。


原因はどう考えてもこれだが、まだ具体的に何が起きているかはわかっていない状態で、TeX実験のフォルダを開く。

すると、perio.sty 22KBなどが並んでいる中に一つ、macrotest.log 151GB。
無限ループが止まらなかった理由も納得です。具体的にlogファイルに何が書き出されたかはわからなかったが、展開待ちのトークンの数が増えていくタイプの無限ループはおそらく同様になるのだろう(実験する気はありません)。

※ちなみにlogファイルを開くことは不可能でした。デカすぎて。



皆さんも無限ループには十分に注意してください。万が一起こした場合は補助ファイルを削除することをお忘れなく。


ぺりをだねぇ

逆から読んでも①

文字列を逆順にしたいです。

\makeatletter

\def\inverse#1{\@inverse #1\@nil}
\def\@inverse#1#2\@nil{%
  \def\blank{}%
  \def\endhantei{#2}%
  \ifx\blank\endhantei #1\else\@inverse #2\@nil #1\fi
}

\makeatother

原理

\inverse{あいう}

\@inverse あいう\@nil 

(#1 <- あ, #2 <- いう)

\@inverse いう\@nil

(#1 <- い, #2 <- う)

\@inverse う\@nil いあ

(#1 <- う, #2 <- )

ういあ

結果

入力:

\inverse{}

出力:ぺ

入力:

\inverse{しんぶんし}

出力:しんぶんし

入力:

\inverse{ぺりをでぃすたす}

出力:すたすぃでをりぺ

クイズ

入力:

\inverse{ぺりを でぃすたす}

の出力は何になるでしょう?(「を」と「で」の間は半角スペースです)
























出力:すたすぃでをりぺ

「をりぺでぃすたす」でも「すたすぃで␣をりぺ」でもありません。(␣は半角スペースの意)

制御綴直後の空白が吸収されるためです。これの解決は(全く考えていないが直感的には)ちょっと大変そうなのでまた今度……

ぺりをだねぇ