Home
4768 words
24 minutes
【計算化学】自作pythonライブラリで遷移状態構造を求めてみる(バルバラン(bullvalene)のCope転位, NNP(UMA)使用)

最終更新:2026-01-06

概要#

本記事では、自作ライブラリ(MultiOptPy)で、バルバラン(bullvalene)のCope転位の素過程の遷移状態構造を算出してみる。計算レベルは、Meta社のFAIR Chemistryが開発したニューラルネットワークポテンシャル(NNP)であるUMA(Meta’s Universal Model for Atoms)とした。

MultiOptPyは電子状態計算ソフトウェアを用いた分子構造最適化手法の勉強を目的として作成したpythonライブラリである。

MultiOptPyのレポジトリ:https://github.com/ss0832/MultiOptPy

今回使用したニューラルネットワークポテンシャルについて:

BH9のデータセットについて:

  • J. Chem. Theory Comput. 2022, 18, 1, 151–166

https://doi.org/10.1021/acs.jctc.1c00694

この文献のSupporting Informationから、データセットの詳細を確認できる。

有機金属錯体が関わる反応を除いたさまざまなカテゴリの反応がまとめられたデータセットである。DFTの汎関数の電子エネルギーの精度の比較などのベンチマークに主に使われることを想定していると考えられる。

使用した自作ライブラリMultiOptPyのバージョン#

v1.20.4

Video Demo#

https://www.youtube.com/watch?v=AE61iY2HZ8Y

環境#

Windows 11

※Windows 11環境下でAnaconda PowerShell Promptを使用した。

Source codeのダウンロード(Unixコマンド)#

wget https://github.com/ss0832/MultiOptPy/archive/refs/tags/v1.20.4.zip
unzip v1.20.4.zip
cd MultiOptPy-1.20.4

https://github.com/ss0832/MultiOptPy/releases/tag/v1.20.4 にアクセスしてzipファイルをダウンロードする。Unixコマンドの場合とはディレクトリ名が異なるので都度読み替えていただけると良い。

移動先のディレクトリでrequirements.txtを参照することで、本ソースコードで必要なモジュールを把握することが出来る。導入方法は各自の状況に合わせて適宜LLMとの対話などで調べると良い。

次に述べる環境構築手順を使用する場合は、環境構築が終わった後、pip install -r requirements.txtで本自作モジュールが動作させるために最低限必要なモジュールを導入することが可能である。

環境構築手順#

今回は、Windows 11のPower Shellを使用した。初めに、NNPを使用できる環境が整ったAnaconda PowerShell Promptを用意する手順を説明する。

1, https://repo.anaconda.com/archive/ より、Anaconda3-2025.06-1-Windows-x86_64.exeでAnacondaをインストールする。

2, 検索機能を使い、スタートからAnaconda PowerShell Promptを開く。

3, 以下のコマンドを実行し、仮想環境を作成する。

conda create -n (任意の仮想環境名) python=3.12.7

4, 先ほど作成した仮想環境をconda activate (仮想環境名)で起動させる。

5, 以下のコマンドを実行し、必要なライブラリを導入する。

pip install ase==3.26.0 fairchem-core==2.7.1 torch==2.6.0
  • fairchem-coreは、FAIR Chemistryが管理しているNNPを動作させるために必要なライブラリである。
  • aseはNNPに電子エネルギーを算出したい分子構造を渡すために必要なインターフェイスの役割を果たすために必要なライブラリである。
  • torchはPyTorchライブラリを指す。これはニューラルネットワークなどの機械学習を行ったり、学習結果を扱ったりするために必須なライブラリである。

これで、Anaconda PowerShell Promptから仮想環境を立ち上げることで、NNPを使用する準備が整えることが出来る。

次に、NNPを使用するために必要なModelの情報が保存されている.ptファイルのダウンロードおよびNNPの自作ライブラリへの導入方法について説明する。

UMAを使用可能にするための手順#

1, 以下のサイトにアクセスして、uma-s-1p1.ptをダウンロードする。(使用許諾が下りていれば可能である。)

https://huggingface.co/facebook/UMA

2, ダウンロード後、MultiOptPy-1.20.4ディレクトリ内に存在するsoftware_path.confに対して、uma-s-1p1.ptの絶対パスを用いて以下を追記する。

uma-s-1p1::(uma-s-1p1.ptの絶対パス)

これで、MultiOptPy-1.20.4がNNPuma-s-1p1を使用できるようになる。

※Linuxの場合#

すでにAnacondaの導入が終わっている前提で、環境の構築手段について述べる。以下を実行すると可能である。

## 1. Download and install MultiOptPy:
wget https://github.com/ss0832/MultiOptPy/archive/refs/tags/v1.20.4.zip
unzip v1.20.4.zip
cd MultiOptPy-1.20.4

## 2. Create and activate a conda environment:
conda env create -f environment.yml
conda activate test_mop

## 3. Copy the test configuration file and run the AutoTS workflow(任意で行う):
cp test/config_autots_run_xtb_test.json .
python run_autots.py aldol_rxn.xyz -cfg config_autots_run_xtb_test.json


#### Installation via pip (Linux)
conda create -n <env-name> python=3.12 pip
conda activate <env-name>
pip install git+https://github.com/ss0832/MultiOptPy.git@v1.20.4 
# or pip install multioptpy
wget https://github.com/ss0832/MultiOptPy/archive/refs/tags/v1.20.4.zip
unzip v1.20.4.zip
cd MultiOptPy-1.20.4

あとは、「UMAを使用可能にするための手順」と同じように進めれば問題なく導入可能である。

使用するNNPに関する具体的な説明#

今回使用するNNPについて具体的に説明する。

  • UMAのModel Checkpointはuma-s-1p1を使用した。
  • 小分子系のトレーニングセットであるOmol25(omol)を使用して学習したニューラルネットワークポテンシャルを使用する。

※自作ライブラリでの具体的な使用の仕方に関しては、ase_calculation_tools.py を参照すると良い。omol以外のモデルを使用したい場合は、現バージョンでは、multioptpy/Calculator/ase_tools/firechem.py内の、self.task_nameを編集することで対応可能である。

手順#

1. 初期構造の準備#

モデル反応系として、以下の構造を用意した。今回はファイルの名前をcope_rea_bullvalene.xyzとした。 初期構造は以下のものを使用した。

20
OptimizedStructure
C     -0.733916227901     -1.254484860783     -0.704382141231
C      0.766796819652     -1.100437641868     -0.899022203960
C      1.412246072000      0.205526103848     -1.101509671182
C     -1.650773767931     -0.104472438476     -0.716028335314
C      1.060500719300      1.352028469910     -0.524680093339
C     -1.395758679159      1.107178224677     -0.228512575192
C     -0.094500259137      1.482473073840      0.438936483707
H     -1.142002090772     -2.147826862817     -1.156898919335
H      1.213957282022     -1.905678443291     -1.465641809077
H      2.256473597045      0.208614003101     -1.781571567511
H     -2.615661758230     -0.281486455958     -1.177378737367
H      1.626574518672      2.247337315242     -0.751944120919
H     -2.157094603111      1.873720829438     -0.307483556189
H     -0.161113079626      2.524692463451      0.747151664247
C      0.137359307118      0.642352089070      1.671841800026
H      0.193836534390      1.155761044169      2.624244999115
C      0.275191225618     -0.680988273313      1.645617365104
H      0.439299883261     -1.199995133025      2.583100681343
C      0.215651353104     -1.535653656568      0.450274792735
H      0.352933153684     -2.588659850648      0.653885944338
初期経路を求めるための初期構造

2. 遷移状態構造最適化#

 run_autots.pyを適切に使用することで、自動的に遷移状態構造が得られる。以下にその手順を説明していく。

初期構造をMultiOptPy-1.20.4ディレクトリ内にcope_rea_bullvalene.xyzとして保存する。その後、同じディレクトリ内で、config_cope_rea_bullvalene.jsonを作成し、以下のように記述する。

config_cope_rea_bullvalene.json

{
  "work_dir": "cope_rea_bullvalene",
  "top_n_candidates": 3,
  
  "step1_settings": {
    "othersoft": "uma-s-1p1",
    "opt_method": ["rsirfo_block_fsb"],
    "use_model_hessian": "fischerd3",
    "spin_multiplicity": 1,
    "electronic_charge": 0, 
	"manual_AFIR": ["300", "15", "6"]
  },
  
  "step2_settings": {
    "othersoft": "uma-s-1p1",
    "NSTEP": 20,
    "use_model_hessian": "fischerd3",
    "save_pict": true,
    "node_distance": 0.25,
	"align_distances_energy_predicted": 2,
    "spin_multiplicity": 1,
    "electronic_charge": 0
  },
  
  "step3_settings": {
    "othersoft": "uma-s-1p1",
    "opt_method": ["rsirfo_block_bofill"],
    "calc_exact_hess": 5,
    "tight_convergence_criteria": true,
    "max_trust_radius": 0.2,
    "frequency_analysis": true,
    "spin_multiplicity": 1,
    "electronic_charge": 0
  },

  "step4_settings": {
    "othersoft": "uma-s-1p1",
	"opt_method": ["rsirfo_block_bofill"],
    "spin_multiplicity": 1,
    "electronic_charge": 0,
	"calc_exact_hess": 10,
    "tight_convergence_criteria": true,
    "frequency_analysis": true,
    
    "intrinsic_reaction_coordinates": ["0.5", "200", "lqa"],

    "step4b_opt_method": ["rsirfo_block_fsb"]
  }
}

その後、以下のコマンドを実行する。

python run_autots.py cope_rea_bullvalene.xyz -cfg config_cope_rea_bullvalene.json

これにより、これまでの似た内容の記事で行ってきたコマンドの操作をまとめ、遷移状態構造を求める処理を自動的に行う。

具体的な処理の流れは、

Step1. バイアスポテンシャルによるNEB法のための初期経路の作成

Step2. NEB法による経路の緩和

Step3. NEB法により得られた経路のエネルギー極大値を示す構造のうち、エネルギー値が上位の最大で3個
(`run_autots.py`にて、`--top_n X`で最大値を変更可能)の構造を初期構造とした遷移状態構造の算出

(Step4.得られた遷移状態構造に対するIRC計算とIRC経路の末端に存在する構造に対する構造最適化。
こちらは`--run_step4`をコマンドで追記しなければ行わない。)

となっている。

run_autots.pyのオプションの説明:

  • -cfg YYY.jsonは、workflowを実行するためのオプションが記されたJSONファイルの読み込み先を指定する。

これらの一連の結果は、(jsonファイルの"work_dir"にて指定した名前)のディレクトリの中に存在するファイルを開いて確認できる。

以下にすべてのstepで共通のオプションに関する説明を載せる。

  • "opt_method": ["rsirfo_block_fsb"]は準ニュートン法であるRS-I-RFO法を構造最適化に使用することを示す。初期のへシアンに関しては、特にオプションで指定しない限り、単位行列が使われる。(以前のHessian更新法とは細かな点で異なる方法を使用している。具体的には、複数の座標変位や勾配変位を用いてHessianの更新を行う。)
  • "spin_multiplicity": Zはスピン多重度の指定である。PySCFを使用するときは目的とするスピン多重度に1を引いた値を指定する。(デフォルトでは1が指定される。)
  • "electronic_charge": 0は形式電荷をMとすることを示す。(デフォルトでは0が指定される。)
  • "othersoft": "uma-s-1p1"は今回使用するNNPを指定している。これを使用する際にASEライブラリが必要である。
  • "use_model_hessian": "fischerd3"は、計算コストが非常に低い数式を使用して、近似したHessianを生成する機能を呼び出すオプションである。デフォルトではこの機能は使用されない。

※オプションの説明はMultiOptPy-v1.20.4/OPTION_README.mdにて示されている。

Step 1#

Step1では、omolのデータセットを使用したuma-s-1p1モデルのNNPで得たエネルギーに対して、指定した人工力ポテンシャルを加えた上で初期構造を構造最適化を行っている。

以下のJSON内で記述したバイアスポテンシャルで、次の経路緩和アルゴリズムの初期経路として用いるトラジェクトリーを生成する。

  • "manual_AFIR": ["yyy", "a", "b]:yyykJ/molの活性化障壁を超えうるペア同士を近づける力を原子のラベル番号aとbのペアに構造最適化時に加えることを示す。
  • "shape_conditions": ["yyy", "lt", "a,b"]: 構造最適化中に、ラベル番号aの原子とラベル番号bの原子の間の距離yyy(Å)よりも大きくなった時に構造最適化を途中で打ち切る。“lt”を”gt”にすると、yyy(Å)よりも小さくなった時に途中で打ち切る。
  • "shape_conditions": ["yyy", "lt", "a,b,c"]: 構造最適化中に、角度の中心がラベル番号bの原子で、ラベル番号aの原子とラベル番号cで作る角度yyy(degrees)よりも大きくなった時に構造最適化を途中で打ち切る。“lt”を”gt”にすると、yyy(degrees)よりも小さくなった時に途中で打ち切る。
  • "shape_conditions": ["yyy", "lt", "a,b,c,d"]: 構造最適化中に、ラベル番号bとラベル番号cを軸とした、ラベル番号aの原子とラベル番号dの原子がなす二面角yyy(degrees)よりも大きくなった時に構造最適化を途中で打ち切る。“lt”を”gt”にすると、yyy(degrees)よりも小さくなった時に途中で打ち切る。
  • "keep_pot": ["c", "d", "a,b"]:力の定数c a.u.で、平行距離dÅの調和ポテンシャルをa番とb番の原子ペアにかける。初期経路生成時に開裂を防ぎたい結合を保持するとき等に使用する。

Step1が正常終了していれば作成されたwork_dirディレクトリ中に、cope_rea_bullvalene_step1_traj.xyzが存在する。必要に応じて確認し、目的に沿った初期経路が得られているか確認する。もし想定とは異なる場合は、プロセスをkillして再度設定を見直してやり直す。

cope_rea_bullvalene_step1_traj.xyzは構造最適化の過程をAvogadro(公式ページ:https://avogadro.cc/ )等で可視化して確認できるようにしている。このcope_rea_bullvalene_step1_traj.xyzはStep2のNEB計算に使用している。

cope_rea_bullvalene_step1_traj.xyzをアニメーションとして表示したい場合は、[https://github.com/ss0832/molecule_movie] を使うと良い。

Step 2#

Step2では、NEB法を用いることで、先ほど得られたcope_rea_bullvalene_step1_traj.xyz全体のエネルギーを下げることができる。これにより、パスのエネルギー極大値を持つ構造を遷移状態構造に近づける。(この時点ではまだ正確な遷移状態構造は求められていない。)

Step2固有のオプションについて以下に示す。

  • "NSTEP": nはn回分NEB法による経路の緩和を行うことを示す。

  • "save_pict": trueは緩和中のパスのエネルギープロファイルや各ノードの勾配のRMS値をmatplotlibで可視化するオプションである。

  • "node_distance": yyy: 入力された経路を経路座標上でyyy(Å)間隔で線形補間により構造を再配置して初期経路とする。

  • "align_distances_energy_predicted": a:経路緩和a回に1回、経路座標上で等間隔に線形補間により構造を置きなおす。エネルギー極大値を示す構造に対しては前後のノードの情報を使って経路座標を変数とした多項式を作り、真のエネルギー極大値の場所を連続最適化により推定し、再配置する。

MultiOptPy-v1.20.4/"work_dir"と同じディレクトリ内に、NEBという名前を含むディレクトリが生成されている。 そのディレクトリ内のenergy_plot.csvを確認し、緩和後のパスのエネルギー極大値を示す構造を確認する。

経路の緩和後の各ノードのエネルギー一覧(単位) (energy_plot.csvに保存されている。)

NEB計算の結果の可視化
NEB計算の結果の可視化

bias_force_rms.csvにて、各Iterationごとのすべてのノードの勾配のRMS値を確認できる。

経路緩和の結果、以下の構造がstep3の初期構造として自動的に用いられた。“work_dir”内のcope_rea_bullvalene_step3_TS_Opt_Inputs内に保存されたcope_rea_bullvalene_ts_guess_X.xyzにて確認が可能である。ts_guessの番号が小さい順にエネルギー値が高い構造を示すようになっている。

※こちら[https://ss0832.github.io/molecule_viewer/] を使うことでも可視化は可能である。

cope_rea_bullvalene_ts_guess_1.xyz

20
0 1
C      -0.730206968831     -1.145745912257     -0.976701939520
C       0.761814716106     -1.098566087258     -0.889067652189
C       1.486779765140      0.172761842656     -1.169989973899
C      -1.607829321545     -0.125444564788     -0.637737437281
C       1.185810147647      1.342314700377     -0.628243187046
C      -1.301164084565      1.057076006813      0.017866184112
C       0.065413403847      1.567040477257      0.329348753962
H      -1.150613443095     -2.065442116719     -1.362125137619
H       1.197223356237     -1.918544740493     -1.450995290024
H       2.317248672922      0.109387806109     -1.863288042670
H      -2.663084147565     -0.350775916858     -0.740086329497
H       1.776799726038      2.213487471264     -0.885333409657
H      -2.121051552530      1.697222724105      0.316366822966
H       0.021969752634      2.610025438020      0.626064360423
C      -0.088098298947      0.683678563099      1.522390603858
H      -0.431822879838      1.167085629615      2.427204525175
C       0.200273834948     -0.671461153500      1.584511130617
H       0.001316968512     -1.157278510259      2.532897833716
C       0.501080719350     -1.512954521330      0.523037587116
H       0.578139633535     -2.573867135851      0.723880597459
NEB法により緩和した経路から得られた遷移状態構造を求めるための初期構造 (No.1)

Step 3#

step3のオプションで、追加での説明を要するものを以下に示す。

  • "opt_method": ["rsirfo_block_bofill"]は遷移状態構造の最適化向けのoptimizerを指定することを意味する。準ニュートン法であるRS-I-RFO法を使用する。今回は-fcで正確なHessianを計算するようにしているので、初期Hessianは正確なHessianを使用するようになっている。(Bofill法によるHessianの更新法を細かい点で変更している。具体的には、複数の座標変位や勾配変位を用いてHessianの更新を行う。)
  • "saddle_order": 1は一次の鞍点を求めることを指定する。(step3のデフォルトでは一次の鞍点を指定する。それ以外の値の指定は、プログラムの使用目的上想定していないので、行わないことを勧める。)
  • "calc_exact_hess": 5は5回の反復回数当たり1回正確なHessianを計算することを指定する。
  • "frequency_analysis": trueは収束条件を満たした後に基準振動解析を行うことを示す。(自前で実装しているため、あくまで目安として使用することを推奨する。各振動モードをvibration_animation内のxyzファイルで可視化できる。)UMAモデルから算出されるHessianは数値微分により求めているため、原子数Zが多いとZの二乗オーダーで計算コストが急増する。
  • "tight_convergence_criteria": trueは収束条件を厳しくすることを示す。(Gaussianのtightと同等)
  • "max_trust_radius": Dは一回の反復計算ごとの計算されるステップ幅の最大値をDÅ以下にすることを示す。デフォルトでは、"saddle_order": 1を指定すると0.1Åが指定される。
  • "detect_negative_eigenvalues": trueは、初めの計算時(ITR. 0)に、任意の次数の鞍点(遷移状態構造等)を求める際に、正確なへシアンから算出した固有値に1つも負の固有値がない場合、計算を打ち切るオプションである。

実行して得られた正確な遷移状態構造と思われる構造を以下に示す。

(実行して得られた正確な遷移状態構造は計算開始時に、MultiOptPy-1.20.4/"work_dir"ディレクトリ内に生成された新規ディレクトリ内のcope_rea_bullvalene_ts_final_X.xyzとして保存されている。)

cope_rea_bullvalene_ts_final_1.xyz

20
OptimizedStructure
C     -0.733929043401     -1.138749654326     -0.993316346631
C      0.753520400130     -1.101744096838     -0.881437159792
C      1.481608355002      0.170581958336     -1.165716419195
C     -1.610012937613     -0.123267735665     -0.642740648532
C      1.180426856573      1.340829899932     -0.626037934734
C     -1.307461683886      1.066509798532     -0.000098477278
C      0.058991827349      1.563060268905      0.334554026231
H     -1.156752851932     -2.058868924444     -1.374977489370
H      1.194297769798     -1.918197304282     -1.446042946302
H      2.310660271370      0.106514239694     -1.860829785274
H     -2.664197091279     -0.349974271366     -0.750253959914
H      1.771390322578      2.211133806996     -0.886704834728
H     -2.129735768108      1.702054019549      0.302765712588
H      0.020657290402      2.608006749720      0.628285037322
C     -0.068028632621      0.683425761947      1.533095602182
H     -0.420640286035      1.163280103025      2.436682418101
C      0.204816305010     -0.674554399856      1.588546250531
H      0.007967767545     -1.158141041952      2.538100514417
C      0.516798686433     -1.515420947125      0.532275754396
H      0.589622442683     -2.576478230780      0.733850685982
遷移状態構造 (No.1)

3回の反復計算により、停留点に収束した構造が得られた。"frequency_analysis": trueオプションにより生成されたnormal_modes.txtvibration_animationディレクトリ内の振動モードのアニメーションを確認した。

以下に"frequency_analysis": trueオプションで生成されたnormal_modes.txtの一部を示す。

Mode                                 0                   1                   2
Freq [cm^-1]                     -433.7243            217.2218            294.0353
Reduced mass [au]                   5.6051              2.5013              3.4952
Force const [Dyne/A]               -0.6212              0.0695              0.1780
Char temp [K]                       0.0000            312.5337            423.0512
Normal mode                   x         y         z            x         y         z            x         y         z     
       C                -0.04547    0.04799   -0.10976   -0.04640    0.00007    0.00068    0.03492    0.05579   -0.07539
       C                -0.04553   -0.01036    0.03363   -0.04398    0.01333   -0.05467    0.03148   -0.01023    0.03897
       C                -0.01188   -0.01743    0.00522    0.06290   -0.01936    0.07596    0.06270   -0.01897    0.07617
       C                 0.00452   -0.01868   -0.01108   -0.06644   -0.03678    0.04316   -0.00092    0.00264    0.00145
       C                 0.01661   -0.00125   -0.01395    0.06202   -0.01985    0.07654   -0.06197    0.01939   -0.07668
       C                 0.04392   -0.03319    0.11655   -0.04193   -0.01928   -0.00674   -0.04189   -0.03435    0.08188
       C                 0.03789    0.03700   -0.02252   -0.04441    0.01309   -0.05439   -0.03210    0.00987   -0.03854
       H                -0.05288    0.05380   -0.12271   -0.02972   -0.02033    0.03118    0.04361    0.08845   -0.16630
       H                -0.03808   -0.00694    0.02964   -0.06813    0.01954   -0.08262    0.03680   -0.01053    0.04366
       H                -0.01825   -0.04452    0.00042    0.22777   -0.06818    0.27694    0.10736   -0.03298    0.13058
       H                -0.00458    0.01341    0.01307   -0.06652   -0.07016    0.11700    0.00956   -0.03866   -0.01317
       H                 0.03111   -0.01650   -0.03279    0.22477   -0.06988    0.27894   -0.10654    0.03346   -0.13117
       H                 0.04967   -0.04307    0.14751   -0.03118   -0.02271    0.02962   -0.04759   -0.08531    0.17015
       H                 0.03174    0.03270   -0.01735   -0.06651    0.02045   -0.08370   -0.03518    0.01145   -0.04474
       C                -0.10817    0.01239   -0.06756    0.00634    0.03002   -0.03482    0.09725    0.01926   -0.01357
       H                -0.12078    0.01525   -0.07704    0.04040    0.01949   -0.01603    0.18818    0.04316    0.01030
       C                 0.00297   -0.01956   -0.01004    0.06465    0.03765   -0.04507    0.00047   -0.00290   -0.00115
       H                 0.00190    0.01710    0.00869    0.14198    0.04824   -0.02335   -0.01353    0.03640    0.01584
       C                 0.10404    0.00094    0.07609    0.01251    0.01164   -0.04340   -0.08935   -0.04017    0.00646
       H                 0.13311    0.00430    0.09134    0.04090    0.01823   -0.01893   -0.18962   -0.04936   -0.01049
       
(...snip...)

その結果、虚振動が1つであることが確認できた。つまりこの構造は遷移状態構造である。

次に、vibration_animation内の虚振動を示す分子振動が示されたxyzファイル(mode_1_XXXi_wave_number.xyz)をAvogadroで確認すると、求められた遷移状態構造の中に、想定される反応系と生成系をつなぐ方向に振動している構造が存在することを確認できた。

終わりに#

   自作ライブラリで、UMAモデルのニューラルネットワークポテンシャル(NNP, uma-s-1p1)を用いて、バルバラン(bullvalene)のCope転位の反応のある1つの遷移状態構造を算出する手順を説明した。

参考#

【計算化学】自作pythonライブラリで遷移状態構造を求めてみる(バルバラン(bullvalene)のCope転位, NNP(UMA)使用)
https://ss0832.github.io/posts/20260106_mop_usage_cope_rea_bullvalene/
Author
ss0832
Published at
2026-01-06