immut/context 设计
设计目标
ContextPolynomial[A] 让用户以具名变量书写多项式,并通过求值、部分求值和代换对其进行变换,而算术仍是按位置进行的,并复用项表示和稀疏表示。它也是 luna-poly 与 Luna-Flow/type_theory 的交汇处:代换可以以 type_theory 名字为键,但规范形式、存储和系数算术仍归本包所有。
数学背景
上下文上的多项式
VariableContext 为变量 命名。上下文多项式是一个二元组 ,其中
诸如 [(x, 1), (y, 2), (x, 1)] 的具名项是名字上自由交换幺半群中的一个字;上下文通过把每个变量的指数加到其下标处,将它映射为指数向量。该映射是幺半群同态(字的连接对应指数向量相加),因此同一变量的重复因子合并为 ,置换因子得到同一个单项式。
代换即泛性质
设 是交换的。对任意选取的多项式 ,恰好存在一个固定 且把 的环同态 ,即
它是同态。 可加性由构造即成立。在单项式上,
并且 两边关于 都是双线性的,因此该等式从单项式推广到所有多项式。
它是唯一的。 固定 的同态在 上由乘法性确定,在和上由可加性确定,因此它在 上的值决定了它。
代换 为变量的子集 指定替换项。它确定像
其中标量 视为常数多项式 ,从而确定同态 。substitute(σ) 完全按照公式逐项计算 :每个因子 变为 (未替换)、常数 (标量)或 (多项式)。
设计决策
同时、一遍式代换
问题。 给定 , 应该变为 还是 ?
选择。 同时代换:每个 按给定原样取自 ,替换多项式本身不再被代入。这就是上面的同态 ,因此
顺序的解读是两个同态的复合,而复合法则
之所以成立,是因为两边都是固定 且在每个 上取值一致的同态。要顺序代换,调用 substitute 两次:。同时代换之所以作为基本操作,是因为它与顺序无关:置换 的条目不会改变结果。
重复条目是错误,而不是覆盖
把同一变量映射两次的列表并不定义一个函数 。substitute_checked 不会选取第一个或最后一个条目,而是返回 None(substitute 则中止)。同样的规则在名字解析之后也适用,因此文本相同的两个不同 Name 值,或同一名字出现两次,都会被拒绝。
部分求值保留上下文
问题。 在 中赋值 之后,结果不再依赖于 。它可以属于 ,也可以留在 中。
选择。 eval_partial 是仅以标量为像的代换,因此其结果属于具有相同上下文的 。被赋值的变量只是次数为 。保留 意味着结果可以与 上的其他多项式相加、相乘以及相互代入,而无需任何上下文改造,并且使部分求值能与完全求值复合。记 为 上的部分赋值, 为其余变量的赋值。那么
因为两边都是固定 的同态 ,并且在生成元上,对 有 ,否则有 。 给已赋值变量的任何值都无关紧要,这就是示例用 x = 0 对部分结果求值的原因。
来自 type_theory 的名字先解析为变量
substitute_names 和 eval_partial_named 用 VariableContext::variable_by_type_theory_name 把每个 Name 映射到变量,然后调用基于变量的形式。名字往返保证在同一上下文内这是无损的:解析 v.to_type_theory_name() 会得回 v。未知名字会使整个调用失败,因此拼写错误绝不会悄悄地让某个变量未被替换。由于多项式没有绑定子,不会发生变量捕获。
什么使调用无效
每个带检查的操作恰好在以下情形返回 None:
| 操作 | 拒绝条件 |
|---|---|
from_named_terms_*_checked, variable_checked | 变量不包含在上下文中 |
eval_named_checked | 变量在上下文之外;下标小于 arity() 的变量未被赋值或被赋值两次 |
substitute_checked, eval_partial_checked | 变量在上下文之外或被列出两次;替换多项式的上下文不同 |
*_names_checked | 此外,名字不在上下文中 |
add_checked, mul_checked | 上下文不同 |
Option 结果仅记录调用失败这一事实。会中止的形式检查相同的条件。
存储在构造时选定,结果与存储无关
多项式以项数组或稀疏映射的形式保存在一个私有枚举之后,由 from_named_terms_as_terms / _as_sparse 选定,或通过绑定已有的 TermPolynomial / SparsePolynomial 选定。两种存储满足相同的规范形式不变量,因此每个可观察结果(作为集合的项、求值、代换)都相同;只有代价和 to_terms() 的顺序不同。二元运算在两个操作数存储一致时保持该存储,混合操作数时使用稀疏存储,并转换以项存储的一方。代换以及构造器 constant 和 variable 产生稀疏存储。
上下文必须相等,而不是合并
二元运算要求上下文相等。自动合并 和 需要把两者单射到一个并上下文中,并对每个指数向量重新编号,而当同一名字出现在不同位置时,并并不唯一。要求相等使每个运算都是同一个环 中的普通运算。由于上下文按结构比较,由分别创建但相同的上下文构建的多项式可以自由组合。
正确性 / 不变量
- 上下文不变量。 上下文多项式的每个指数向量长度至多为 。具名构造器保证这一点。
from_term_polynomial和from_sparse_polynomial不检查它;违反它的多项式会使eval_named(_checked)和to_string中止。调用者必须确保polynomial.arity() <= context.size()。 - 代换对交换系数计算 ,即满足 的唯一环自同态;结果是规范的(零项消失,如代换 时)。
- 部分求值满足 。
- 具名求值等于在按下标列出的值处的按下标求值。
- 算术即底层存储的算术,并继承其定律。
- 代价。 代换把每一项按幂的乘积求值并加到累加器中;每次加法都会重新规范化累加器。对 项且结果为 项的情形,仅加法就需 ,另加多项式乘积的代价。具名查找与上下文大小成线性关系。
被否决的替代方案
- 顺序代换。 其结果依赖于条目的顺序,并且可以表示为两次同时代换。
- 投影掉已赋值的变量。 这会改变结果的上下文,并破坏与 上其他多项式的复合。
- 重复条目以最后一个为准。 这会掩盖错误;拒绝重复可使 保持为函数。
- 使用
type_theory的代换机制。 其避免捕获的代换解决的是多项式不存在的问题,而且其项不会携带多项式规范形式。 - 隐式上下文并。 见上文:不唯一,而且会使每个二元运算都要对指数重新编号。
边界
- 没有相等性实例;请显式比较
context()和项列表。 - 没有稠密一元存储;上下文多项式总是多元的。
- 不能从上下文中消去变量,也不能对上下文重命名或重排。
- 代换需要交换系数才是同态;代码不检查交换性。
- 错误不携带原因(
Option),并且from_term_polynomial/from_sparse_polynomial信任其元数。