Python で書く代数 - pyarith よ, 私は帰ってきた

1年ぶりの pyarith の記事です. 実は Python で書く代数 を書いてから, 追加した実装があるのでそれについて書きます.

書いてあること

  • pyarith のおさらい
  • 環について
  • 環の公理とテストケースの対応

おさらい

pyarith とは Python でどこまで代数的対象が表現できるかの実験ライブラリです. Python コードは読めるけど代数学のことは知らない人に, 代数的概念を説明することを目的としています. 今のところライブラリとしての実用性は目指していません. おそらくパフォーマンスも出ません.

前々回の記事 で「群」の解説をし, 前回の記事 で群であることを確かめるテストについて解説しました. このテストでは pytest というライブラリを使用し, パラメータ化されたテストを行いました.

この記事を書いたおかげで pytest に興味を持ってくれた人もいたようで, ちょっと嬉しいです.

前回扱ったのは「群」という概念でしたが, 今回は「環」を取り上げます.

群はある規則を満たす集合でした. 環は, 群にさらに満たすべき規則を追加した集合です.

群と環にどんな差分があるかは環を実装した Ring クラスの実装を見てみましょう. https://bitbucket.org/cocoatomo/pyarith/src/dfbb010c50390e5dece00b92558a5c3461199507/tests/test_ring.py ここから分かるのは「環は群に無い __mul__ というメソッドを持つ」ということです.

この Ring クラスと __mul__ というメソッドに課せられた制限はテストケースで表すのでした.

テストケースとその意味
test_mul_welldefined_right 右からの乗算が well-defined であること
test_mul_welldefined_left 左からの乗算が well-defined であること
test_ONE (乗法に関する) 単位元の存在
test_mul_ONE 単位元の性質
test_mul_associative_law 乗法の結合律

乗法は可換とは限らない演算なので, 可換性に関するテストはありません.

また加算と乗算が両方登場する性質を表すテストケースとして以下のものがあります.

テストケースとその意味 (2)
test_distributing_law 分配法則

小学生の頃に分配法則という言葉が出てきましたが, 当時はここまで重要な性質だとは思いませんでした.

環の公理一覧については http://ja.wikipedia.org/wiki/%E7%92%B0_(%E6%95%B0%E5%AD%A6) を参照してください.

Python で書く代数 - pytest で test class 編

前回 は pytest を使ってパラメータ化されたテストを実行しました.

実際に使ったテスト対象コードは, 群を Python で実装したコードでした. ABC モジュールと pytest によるテストを使って, 代数の公理的な定義を擬似的に表現しました.

ただし前回のテストコードではテスト関数が多く, 分類したい気持ちになってきます.

pytest のドキュメント を眺めると, 2.4.5 Parametrizing test methods through per-class configuration にクラスを使ってテスト関数をまとめる方法が書いてあります. 早速これを使います.

test hook

ここで使用するのはテストの前に実行される pytest_generate_tests 関数です. この関数でテスト実行前に, モジュールの中にあるクラスとそのメソッドからパラメータ化されたテストを生成しています.

def pytest_generate_tests(metafunc):
    """Collect and parameterize classes in this module as test cases.

    This test-case collecting function ignores module level functions.

    c.f. 2.4.5 Parametrizing test methods through per-class configuration
    """

    if metafunc.cls is None: # ignore module level functions
        return
    funcarglist = metafunc.cls.params[metafunc.function.__name__]
    argnames = list(funcarglist[0])
    metafunc.parametrize(argnames, [[funcargs[name] for name in argnames]
                                    for funcargs in funcarglist])

テストクラスには, テストコードを記述した関数をメソッドとして記述します.

class TestEquality:
    params = {
        'test_equals_reflexive_law': single_params,
        'test_equals_symmetric_law': double_params,
        'test_equals_transitive_law': triple_params
        }

    def test_equals_reflexive_law(self, a):
        assert a == a

    def test_equals_symmetric_law(self, a, b):
        if a == b:
            assert b == a

    def test_equals_transitive_law(self, a, b, c):
        if a == b and b == c:
            assert a == c

パラメータ化されたテストを実装するためにテストクラスには仕掛けが施されています. その仕掛けは params というクラスフィールドで, テストメソッドとそれが使用するパラメータの対応付けが辞書で設定されています. params 辞書の値は辞書のリストになっていて, 個々のテストケースが列挙されています.

single_params = [{'a': a} for a
                 in integer_values +
                 rational_values +
                 intint_values]

(ここでは single_params の書き方だけを載せましたが, モジュール全体は BitBucket のレポジトリ を参照してください)

前回の pytest.mark.parametrize デコレータとはパラメータ値の指定の方法が異るので注意してください.

pytest_generate_tests やその引数 metafunc の裏側については, また調べてブログに書くことにします.

それでは.

Licenses