immut/context の設計
設計目標
ContextPolynomial[A] を使うと、名前付き変数で多項式を書き、評価・部分評価・代入によって変換できます。一方で算術は位置ベースのままで、項表現と疎表現を再利用します。ここは luna-poly が Luna-Flow/type_theory と接する場所でもあります。代入は type_theory の名前をキーにできますが、正規形、格納、係数の算術はこのパッケージが引き続き担います。
数学的背景
コンテキスト上の多項式
VariableContext は変数 に名前を付けます。コンテキスト多項式は次を満たす組 です。
[(x, 1), (y, 2), (x, 1)] のような名前付きの項は、名前の上の自由可換モノイドの語です。コンテキストは各変数の指数をそのインデックスに加算することで、これを指数ベクトルに写します。この写像はモノイド準同型(語の連結は指数ベクトルの和)なので、同じ変数の繰り返しの因子は のようにまとまり、因子を並べ替えても同じ単項式になります。
代入は普遍性そのものである
を可換とします。任意の多項式 に対し、 を固定し と送る環準同型 がちょうど 1 つ存在し、それは次のものです。
準同型であること。 加法性は構成から成り立ちます。単項式については
であり、 の両辺は について双線形なので、この等式は単項式からすべての多項式に拡張されます。
一意であること。 を固定する準同型は、 上では乗法性によって、和の上では加法性によって決まるので、 での値によって決定されます。
代入 は変数の部分集合 に置換先を割り当てます。これにより次の像が決まります。
ここでスカラー は定数多項式 と見なし、これにより準同型 が定まります。substitute(σ) は式のとおりに項ごとに を計算します。各因子 は (置換なし)、定数 (スカラー)、または (多項式)になります。
設計上の判断
同時・1 パスの代入
問題。 のとき、 は になるべきか、 になるべきか。
選択。 同時代入です。各 は で与えられたとおりに取り、置換先の多項式自体にはさらに代入しません。これは上の準同型 であり、したがって
逐次的な解釈は 2 つの準同型の 合成 であり、合成則
が成り立ちます。両辺はいずれも を固定し、すべての 上で一致する準同型だからです。逐次的に代入するには substitute を 2 回呼びます:。同時代入を基本操作とするのは順序に依存しないからです。 のエントリを並べ替えても結果は変わりません。
重複エントリは上書きではなくエラー
同じ変数を 2 回写すリストは関数 を定義しません。最初または最後のエントリを選ぶ代わりに、substitute_checked は None を返します(substitute は中断します)。同じ規則が名前解決の後にも適用されるため、同じテキストを持つ 2 つの異なる Name 値や、同じ名前の 2 回の指定は拒否されます。
部分評価はコンテキストを保つ
問題。 で を代入すると、結果は に依存しません。結果は に置くことも、 に留めることもできます。
選択。 eval_partial はスカラーの像だけを使う代入なので、結果は同じコンテキストの に属します。代入された変数は単に次数 になります。 を保つことで、結果をコンテキストを手術することなく 上の他の多項式と足し、掛け、代入でき、部分評価と完全な評価を合成できます。 上の部分割り当てを 、残りの変数の割り当てを と書くと、
が成り立ちます。両辺は を固定する準同型 であり、生成元上で なら 、そうでなければ となるからです。既に割り当てた変数に が与える値は無関係で、そのため例では部分評価の結果を x = 0 で評価しています。
type_theory の名前はまず変数に解決される
substitute_names と eval_partial_named は、すべての Name を VariableContext::variable_by_type_theory_name で変数に写してから、変数ベースの形を呼びます。名前の往復 により、1 つのコンテキスト内ではこれは情報を失いません。v.to_type_theory_name() を解決すると v に戻ります。未知の名前があると呼び出し全体が失敗するので、タイプミスによって変数が黙って置換されずに残ることはありません。多項式には束縛子がないので、捕獲は起こりえません。
呼び出しが無効になる条件
各チェック付き演算は、ちょうど次の状況で None を返します。
| 演算 | 拒否される条件 |
|---|---|
from_named_terms_*_checked, variable_checked | 変数がコンテキストに含まれていない |
eval_named_checked | 変数がコンテキスト外である。インデックスが arity() 未満の変数が未割り当てまたは 2 回割り当てられている |
substitute_checked, eval_partial_checked | 変数がコンテキスト外であるか 2 回指定されている。置換先の多項式のコンテキストが異なる |
*_names_checked | 加えて、名前がコンテキストにない |
add_checked, mul_checked | コンテキストが異なる |
Option の結果は呼び出しが失敗したことだけを記録します。中断する形も同じ条件を検査します。
格納は構築時に選び、結果は格納に依存しない
多項式は非公開の列挙型の背後に項配列または疎なマップとして保持され、from_named_terms_as_terms / _as_sparse によって、または既存の TermPolynomial / SparsePolynomial を束ねることで選ばれます。どちらの格納も同じ正規形の不変条件を満たすため、観測可能な結果(集合としての項、評価、代入)はすべて同じで、異なるのはコストと to_terms() の順序だけです。二項演算は両オペランドの格納が一致すればそれを保ち、混在する場合は項格納側を変換して疎な格納を使います。代入と、コンストラクタ constant および variable は疎な格納を生成します。
コンテキストは統合せず、等しいことを要求する
二項演算は等しいコンテキストを要求します。 と を自動的に統合するには、両方を和コンテキストに埋め込み、すべての指数ベクトルを番号付け直す必要があり、同じ名前が異なる位置に現れる場合には和は一意ではありません。等しさを要求すれば、すべての演算は 1 つの環 における単純な演算のままです。コンテキストは構造的に比較されるので、別々に作られた同一のコンテキストから構築した多項式は自由に組み合わせられます。
正しさ / 不変条件
- コンテキストの不変条件。 コンテキスト多項式のすべての指数ベクトルの長さは 以下です。名前付きコンストラクタはこれを保証します。
from_term_polynomialとfrom_sparse_polynomialは検査しないため、これに違反する多項式ではeval_named(_checked)とto_stringが中断します。呼び出し側はpolynomial.arity() <= context.size()を保証しなければなりません。 - 代入 は、可換な係数に対して となる唯一の環自己準同型 を計算します。結果は正規形です( を代入したときのように零の項は消えます)。
- 部分評価 は を満たします。
- 名前付き評価 は、インデックス順に並べた値でのインデックス評価に等しくなります。
- 算術 は基礎となる格納のものであり、その法則を受け継ぎます。
- コスト。 代入は各項を累乗の積として評価してアキュムレータに加え、各加算でアキュムレータを再正規化します。項が 個で結果が 項のとき、加算だけで多項式の積に加えて かかります。名前の検索はコンテキストのサイズに比例します。
採用しなかった代替案
- 逐次代入。 結果がエントリの順序に依存し、2 回の同時代入で表現できます。
- 代入済み変数の射影による除去。 結果のコンテキストが変わり、 上の他の多項式との合成が壊れます。
- 重複時に最後のエントリを優先する方式。 誤りを隠してしまいます。重複を拒否すれば は関数のままです。
type_theoryの代入機構の利用。 その捕獲回避代入は多項式には存在しない問題を解くものであり、その項は多項式の正規形を持ちません。- 暗黙のコンテキスト和。 上記のとおり一意ではなく、すべての二項演算で指数の番号付け直しが必要になります。
境界
- 等価性のインスタンスはありません。
context()と項のリストを明示的に比較してください。 - 密な一変数の格納はありません。コンテキスト多項式は常に多変数です。
- コンテキストからの変数の消去や、コンテキストの名前変更・並べ替えはありません。
- 代入が準同型になるには係数が可換である必要がありますが、コードは可換性を検査しません。
- エラーは理由を持たず(
Option)、from_term_polynomial/from_sparse_polynomialは引数のアリティをそのまま信頼します。