TERRYのブログ

最近は競技プログラミングにお熱。

AHCテストケース実行ツールpahcerの使い方

AHC (AtCoder Heuristic Contest) では、実装→評価→考察→改善のサイクルを繰り返して自分のコードをブラッシュアップしていくことが非常に重要ですが、この評価というものはなかなか曲者です。特に初心者~中級者が陥りがちな罠として、

  • seed=0の1ケースだけを見て判断してしまう(seed=0は改善していても、他のケースは悪化しているかもしれない)
  • 数個程度のケースを手で実行して評価している(そのときの運に左右されて正しい評価が難しくなる)
  • AtCoderに提出して評価している(提出制限に引っかかったり、どのケースが悪いのか分からなかったりする)

といったものが挙げられるでしょう。AHCの改善サイクルを正しく高速に回すには、「正しい評価」「正しい分析」「高速な実行」が必要です。なぜなら、

  • 正しいスコア評価ができない → 誤った方針に繋がってしまう
  • スコアが出ていないケースを特定できない → 対策を立てるのが難しくなる
  • 実行に時間がかかる → 改善サイクルを回す回数が少なくなる

ためです。AHCの成績を上げたいのであれば、「正しい評価」「正しい分析」「高速な実行」が実現できるローカル実行環境の整備は必須といって良いでしょう。とはいえ、これができるスクリプトを作成するのは競プロ以外のプログラミングに慣れていないと少々難しいです。

そこで *1 私は pahcer *2 というコマンドラインツールを作成して公開しています。この記事ではpahcerの使い方についてユースケース別に紹介していきたいと思います。pahcerのバージョンはv0.3.1を対象にしています。

このページに書かれていない細かい内容については、以下の公式リポジトリをご参照ください。

github.com

導入

インストール方法

まずはインストールしないと始まらないでしょう。pahcerの導入にはRustが必要なので、Rust公式サイトを参考にインストールしてください。

Rustのバージョンが古い場合は上手くpahcerをインストールできない場合があります。以下のコマンドを実行して予めアップデートしておきましょう。

# インストール済みRustのアップデート
rustup update

以下のコマンドを実行するとpahcerをインストールできます。

# pahcerのインストール
cargo install pahcer

# 上手くインストールできない場合、以下を試すと解決するかも
cargo install pahcer --locked

バージョンアップ

cargo-update を使うのがおすすめです。以下のコマンドを実行してアップデートしてください。

cargo install cargo-update  # 初回のみ必要
cargo install-update pahcer

アンインストール

以下のコマンドを実行すればアンインストールできます。

cargo uninstall pahcer

基本的な使い方チュートリアル

まずは基本的な使い方を押さえましょう。この手順をなぞれば一通り使えるようになるはずです。

ディレクトリ構成

以下のようにファイルを配置してください。*3 この配置でなくても動作しますが、その場合は設定ファイルを編集する必要があります。

v0.3.1時点では、C++/Python/Rust/Goの4言語に対応しています。その他の言語の方は手動で設定ファイルを編集する必要があります。*4

[C++]
ahc000       (プロジェクトルート)
├ main.cpp   (解答プログラムのコード)
└ tools      (AtCoder提供の公式ローカルテストツール)
  ├ src
  └ in

[Python]
ahc000       (プロジェクトルート)
├ main.py    (解答プログラムのコード)
└ tools      (AtCoder提供の公式ローカルテストツール)
  ├ src
  └ in

[Rust]
ahc000       (プロジェクトルート)
├ src
│ └ main.rs  (解答プログラムのコード)
├ targets    (ビルド成果物フォルダ)
├ Cargo.toml
└ tools      (AtCoder提供の公式ローカルテストツール)
  ├ src
  └ in

[Go]
ahc000       (プロジェクトルート)
├ main.go   (解答プログラムのコード)
└ tools      (AtCoder提供の公式ローカルテストツール)
  ├ src
  └ in

初期設定

各プロジェクトごとに、以下を実行して初期設定を行ってください。

コマンドを実行すると、設定ファイル ./pahcer_config.toml と、実行結果サマリなどが格納される ./pahcer ディレクトリが生成されます。

pahcer init -p <PROBLEM_NAME> -o <OBJECTIVE> -l <LANGUAGE> [-i]

オプションの説明は以下の通りです。

  • <PROBLEM_NAME> : コンテスト名
  • <OBJECTIVE>: スコアを最大化するか最小化するか
    • max: スコアが大きい方が良い
    • min: スコアが小さい方が良い
  • <LANGUAGE>: 使用言語
    • cpp: C++
    • python: Python
    • rust: Rust
    • go: Go
  • -i: インタラクティブ問題かどうか

コマンド例は以下の通りです。

# AHC039 (非インタラクティブ問題、スコア最大化)でC++を使用
pahcer init -p ahc039 -o max -l cpp

# AHC030 (インタラクティブ問題、スコア最小化)でPythonを使用
pahcer init -p ahc030 -o min -l python -i

テストケース実行

以下のコマンドを実行するとテストケースが並列で実行されます。

pahcer run

pahcer run 実行例

表の各列の説明

  • Progress : テストケース実行の進行状況
  • Seed : 実行したテストケースのseed値*5
  • Case Score : 当該テストケースのスコア
    • Score : 実スコア*6
    • Relative : ローカルでのベストスコアを100としたときの相対スコア *7
  • Average Score : その時点までの平均スコア
    • Score : 実スコアの平均値
    • Relative : 相対スコアの平均値
  • Exec. Time : 実行時間(ミリ秒表示)

サマリ情報の説明

  • Average Score : 実スコアの平均値
  • Average Score (log10) : 実スコアの対数を取った値の平均値
  • Average Relative Score : 相対スコアの平均値
  • Accepted : Acceptされたケース数
  • Max Execution Time : 実行時間の最大値

また、./tools/out ディレクトリに各ケースの標準出力が、 ./tools/err ディレクトリに各ケースの標準エラー出力がそれぞれ出力されます。

実行結果確認

過去の実行結果を表示するには、以下のコマンドを実行してください。最新の実行結果10件が表示されます。

pahcer list

pahcer list 実行例

表の各列の説明

  • Time: 実行した日時
  • AC/All: AC数/実行数
  • Avg Score: 実スコアの平均値(最も良いスコアは緑字で表示されます)
  • Avg Rel.: 相対スコアの平均値(最も良いスコアは緑字で表示されます)*8
  • Max Time: 実行時間の最大値
  • Tag: 実行に紐付くgitタグ(後述)
  • Comment: 実行に付けたコメント(後述)

もしくは、./pahcer/summary.md に実行結果が追記されていくので、そちらを閲覧してもOKです。

お疲れ様でした!これで基本的な使い方については完了です。

応用的な使い方

ここからは応用的な使い方について、ユースケース別に説明していきます。

ヘルプを見る

-h もしくは --help オプションを付けると、各コマンドのヘルプを見ることができます。

オプションを忘れてしまったときなどに使いましょう。

# pahcer 全体のヘルプ
pahcer --help

# 各コマンドのヘルプ
pahcer <COMMAND> --help

ヘルプ

テストケース実行数の変更

pahcer_config.toml を修正してあげましょう。デフォルトだと以下のような記述となっていると思います。

[general]
version = "0.3.1"

[problem]
problem_name = "ahc001"
objective = "Max"
score_regex = '(?m)^\s*Score\s*=\s*(?P<score>\d+)\s*$'

[test]
start_seed = 0
end_seed = 100
threads = 0
out_dir = "./pahcer"

# 後略

ここの start_seedend_seed を変更することで、実行するseedを start_seed 以上 end_seed 未満に変更することができます。半開区間であることに注意してください。

# 実行するseedを0以上49以下の50ケースに変更
[test]
start_seed = 0
end_seed = 50
threads = 0
out_dir = "./pahcer"

実行ケース数を x 倍に増やすとスコアのばらつき(標準偏差)はだいたい 1/\sqrt x 倍になるため安定しますが、当然実行時間も x 倍になります。短期コンテストでは評価のばらつきを小さくするより、素早く確認して改善サイクルをたくさん回した方が良いことも多いです。

テストケース数の基準が分からないという方は以下を目安にしてみてください。プログラムの実行時間やお使いのPCの性能(CPUコア数)に大きく依存するので参考程度に。

  • 短期コンテストで素早く確認したい: 20~50ケースくらい
  • 長期コンテストである程度正確に評価したい: 100~200ケースくらい
  • 長期コンテストで最後にチューニング・バグチェックしたい: 500~3000ケースくらい

並列実行数の変更

並列実行数の変更も、ケース数変更と同様に pahcer_config.toml を修正すればOKです。

threads が並列実行数を表すので、ここの値を変更してください。デフォルトは threads = 0 となっていて、 0 に指定されている場合は自動的にCPUの物理コア数と同じ値になります。もし並列実行数が多すぎてPCが固まってしまうなどであれば、例えば threads = 2 などに減らすなどして様子を見てみてください。

# 並列実行数を5に変更
[test]
start_seed = 0
end_seed = 100
threads = 5
out_dir = "./pahcer"

実行にコメントを付ける

各実行で何を変更したか分かるように、コメントを付けることができます。以下のように -c オプションを付与してください。

pahcer run -c <comment>

# 例
pahcer run -c パラメータ調整

付与したコメントは pahcer list 実行時に表示されるほか、./pahcer/summary.md にも出力されます。

コメント付与例

実行とコードを紐付ける

パラメータ調整などでテストケース実行を何度も繰り返していると、どの実行がどのコードに紐付いているか分からなくなりがちです。 pahcer では、そのようなケースを想定してgitと連携してテスト実行結果とコードを紐付ける機能があります。

以下のコマンドを実行すると、テストケース実行と同時に、その時点でのリポジトリのコードに自動でgitタグが付与されます。gitがインストールされている必要があるため注意してください。

pahcer run -t

gitタグの付与

以下のように、 pahcer/******** という名前でタグが生成されます。*9

git graph

コードにgitタグが付与されているため、 git cherry-pick などで変更を取り込むことができます。以下はパラメータ調整をいくつか試して、相対スコアが一番良かったものを取り込む例です。

git cherry-pick で変更を取り込む例

取り込み後のgit graph

一番良かったものを取り込んだ後はgitタグは不要になるでしょう。その際は、以下のコマンドを実行すると pahcer/ で始まるタグを全て削除することができます。*10

pahcer prune

pahcer prune 実行例

一連の流れを改めて書くとこんな感じです。

# パラメータ調整などをしながら複数回pahcer runを実行
pahcer run -t -c ○○を改善

...

# 結果をリスト表示
pahcer list

# pahcer list で表示されたタグ名を指定して取り込み
git cherry-pick pahcer/abc12345

# 作成したタグの一括消去
pahcer prune

pahcer list の表示数を変更する

結果の表示数を変更する場合は、以下のオプションを付与してください。

# COUNT件表示する
pahcer list -n <COUNT>

# 全件表示する
pahcer list -a

5件だけ表示した例

GUIツールとの連携

yunixさんがGUIツールである pahcer-studio を作成されています。

テストケースごとの分析もできる高機能なツールとなっていますので、よろしければこちらもお試しください。

github.com

マニアックな使い方

ここからはマニアックな使い方の説明です。普通に使う分にはあまり触ることがないかもしれません。

出力先ディレクトリを変更する

デフォルトでは、 ./pahcer ディレクトリ内に各ケースの最高スコアや結果サマリなどが出力されます。

出力先を変更したい場合は、 pahcer_config.toml ファイルの [test] セクションを編集してください。

[test]
start_seed = 0
end_seed = 100
threads = 0
out_dir = "./pahcer"    # これを書き換えると出力先ディレクトリが変更される

テストケース実行コマンドを変更する

テストケース実行コマンドがうまく動かなかったり、カスタマイズしたりしたい場合は、 pahcer_config.toml ファイルの [[test.compile_steps]] および [[test.test_steps]] セクションを編集してください。

前者はコンパイル処理で、 pahcer run 実行時に1回だけ実行されます。Pythonのようにコンパイル不要な言語では空になっています。

後者は各テストケースの実行コマンドで、テストケースごとに実行されます。非インタラクティブ問題ではユーザーコード実行 → vis 実行、インタラクティブ問題では tester にユーザーコードの実行コマンドを渡すというような処理がデフォルトで記述されています。

各ステップで設定できるオプションの詳細はREADMEをご参照ください。

実行コマンドの例

pahcer run の実行オプション

pahcer run にはいくつか実行オプションがあります。詳細はREADMEを参照いただければと思いますが、比較的よく使われると思われるものを挙げます。

オプション 機能説明 想定用途
-c, --comment テストケースにコメントを付与 コード変更内容のメモなど
-t, --tag テストケースにgitタグを付与 実行とコードの紐付け
--setting-file 設定ファイルをデフォルトから変更 少数ケース実行と大量ケース実行を使い分けたいときなど
--freeze-best-scores ベストスコアの更新を行わないようにする 相対スコアの基準値を固定して、過去の実行との比較を正確に行いたいときなど

上記のオプションも入れて全部盛りにするとこんな感じになります。

pahcer run -c 焼きなまし高速化バージョン -t --setting-file settings.toml --freeze-best-scores

Optunaと連携してハイパーパラメータチューニングを行う

Optunaの評価関数に pahcer を使い、ハイパーパラメータチューニングを行うことができます。

githubに簡単なチュートリアルを乗せているので、そちらをご参照ください。

github.com

タイプミス防止

何度も実行してると慣れます。 aliasabbr などを使ってください。

おわりに

pahcer が手軽なテストケース実行の一助となれば幸いです。

githubリポジトリが以下のリンク先にありますので、ご要望・コントリビューション等お待ちしております!

github.com

*1:といっても9割方自分の欲しい機能を詰め込んだだけですが。

*2:ペーサーと読みます。pacer (伴走者) + ahc から来ています。

*3:"AtCoder提供の公式ローカルテストツール"とは、Windows用のコンパイル済みバイナリではなく、Rust言語で書かれたソースファイルの方を指します。

*4:pull requestもお待ちしております!

*5:実行順は不定なので注意してください。

*6:0点の場合はWA扱いとなります。

*7:OBJECTIVE = max のときは 100 * YOURS / BEST 、 OBJECTIVE = min のときは 100 * BEST / YOURS で計算されます。

*8:相対スコアは pahcer list 実行時における最新のベストスコアを用いて再計算されます。

*9:内部的には、git commit → git tag → git reset を順に行っています。

*10:もし誤って消してしまった場合は、git reflogなどで頑張ってください。