简介
oCAS(Open Computer Algebra System)是一个用 Rust 编写的高性能计算机代数系统(CAS),采用 LGPL-3.0-or-later 许可证开源。项目目标是在核心符号计算性能上达到或超越 Symbolica 与 SageMath,同时通过 Python(PyO3)和 C/C++(cbindgen)绑定提供多语言接口。
oCAS 采用分层 crate 架构,从底层 arena 分配器到顶层语言绑定共 13 个 workspace 成员,依赖方向严格向下。所有系数域(任意精度整数/有理数、有限域、实数球、复数、双精度浮点)通过统一的 Domain trait 抽象,多项式代数、Gröbner 基、符号微积分、ODE 求解等模块共享同一表达式基础设施。
版本特性对照表
下表列出 oCAS 各版本的关键新增特性(0.1 → 0.30):
| 版本 | 日期 | 关键新增 |
|---|---|---|
| 0.1.0 | 2026-06-29 | 工作空间搭建、Arena 分配器、统一错误类型、C ABI 最小示例、跨平台 CI |
| 0.2.0 | 2026-06-30 | 表达式树 Atom(hash consing)、基于 logos/chumsky 的解析器、规范化器 |
| 0.3.0 | 2026-06-30 | 系数域(Integer/Rational/FiniteField/RealBall/Complex)、稠密/稀疏多项式、可选 GMP/FLINT 后端 |
| 0.4.0 | 2026-07-01 | 重写引擎(通配符模式、AC 匹配、Rule/simplify)、可选 egg e-graph 等式饱和 |
| 0.5.0 | 2026-07-01 | 符号微分 diff、Taylor 展开 taylor、启发式积分 integrate |
| 0.6.0 | 2026-07-08 | 稳定 prelude API、rustdoc 示例、proptest 属性测试、Criterion 基准、SymPy 对比工具 |
| 0.7.0 | 2026-07-01 | 根隔离(Sturm 定理)、线性方程组求解器(ℚ/ℤ)、丢番图方程、Buchberger Gröbner 基 |
| 0.8.0 | 2026-07-02 | 求值引擎(栈式 VM、CSE 优化、SIMD 向量化、Cranelift JIT 后端) |
| 0.9.0 | 2026-07-02 | Python 绑定(PyO3 Expression/ExpressionEvaluator)、C 微积分 API、C++ RAII 包装、33 项 pytest |
| 0.10.0 | 2026-07-02 | Python Polynomial/Matrix 类、Bareiss 行列式、FiniteField 实现 EuclideanDomain、mdBook 文档站 |
| 0.11.0 | 2026-07-03 | 正确性对比框架(16 模块 82 测试)、ℤ/𝔽_p 完整因式分解(Cantor–Zassenhaus + Hensel)、数论原语、多元 GCD |
| 0.12.0 | 2026-07-04 | RationalPolynomial 有理函数类型、Brown PRS 结式、Karatsuba 快速乘法、部分分式分解 apart/together |
| 0.13.0 | 2026-07-06 | F4 Gröbner 基算法(Gebauer–Moeller 筛选、SimpCache、ℤ_p i64 快速路径) |
| 0.14.0 | 2026-07-18 | Risch 符号积分(Hermite 约化、RDE、三角/特殊函数)、FGLM 换序、Hilbert 界 |
| 0.15.0 | 2026-07-20 | 多输出 JIT、f32 混合精度、流式求值(恒定内存)、Arena reset + workspace 池、原生 i64 F4 管线 |
| 0.16.0 | 2026-07-21 | 任意多元因式分解(Wang EEZ)、$n$ 元 GCD(稠密递归求值–插值) |
| 0.17.0 | 2026-07-22 | 代数数域因式分解(Trager 范数算法)、$\mathrm{GF}(p^d)$ 构造 |
| 0.18.0 | 2026-07-23 | 资源控制 Fuel、超对偶数自动微分 HyperDual、Vegas 蒙特卡洛积分、张量基础(指标槽/缩并/对称化) |
| 0.19.0 | 2026-07-23 | F5 签名约简算法(cyclic-6 ℤ₁₃ 加速 ~1400×至 2.63s)、统一 Gröbner 分发入口、Rust 1.97 工具链 |
| 0.20.0 | 2026-07-27 | ODE 求解器(5 种一阶 + 2 种二阶 + 幂级数)、ODE 分类引擎 classify_ode |
| 0.20.1 | 2026-07-27 | 积分因子、常数变易法(VOP)、待定系数法扩展(共振/三角/叠加)、降阶法 |
| 0.21.0 | 2026-07-30 | BPSW 素性判定、整数分解(Pollard rho/p−1/p+1/ECM)、离散对数、模多项式 GCD(Brown)、12 个数论 Python 绑定 |
| 0.22.0 | 2026-08-02 | McKay 图同构规范标号引擎、张量表达式规范化、Young 投影子、回溯 AC 匹配、多模式替换 |
| 0.23.0 | 2026-08-02 | 理想运算(成员/和/积/商/饱和/交集)、消元序、零维求解、准素分解、Hilbert 级数、有理根定理 |
| 0.24.0 | 2026-08-03 | 启发式积分(LIATE 分部积分/三角换元/Weierstrass)、DoubleF64 双精度浮点(~31 位有效数字) |
| 0.25.0 | 2026-08-04 | ℚ 上多素数 Gröbner 基(并行幸运素数 F5、CRT + 有理重构、无迹 p-adic Hensel 提升)、模 GCD 并行化、Algorithm::MultiModular 与 Auto 路由 |
| 0.26.0 | 2026-08-04 | u128 SWAR 打包单项式 F5 快通道、免克隆两阶段 echelon、grevlex 基准变体;修复分次单项式序的度方向反置(预存在错误基) |
| 0.27.0 | 2026-09-06 | 符号积分广度:规则表引擎(A–H 族)、符号常数有理后端、Weierstrass 线性变元、有界展开重试、三角积化和差;Rubi 1892 题覆盖率 5.87% → 9.62% |
| 0.27.1 | 2026-09-10 | 积分广度机制攻坚:Chebyshev 二项微分、三角分母幂递推、exp-log 核代换、一般二次根式引擎 + Euler III、反三角核、单三角核有理式;覆盖率 9.62% → 16.44%;修复两组错案 |
| 0.27.2 | 2026-09-11 | 已验证覆盖率口径(独立数值 oracle:5 点中心差分 + 椭圆定义积分)、阶段打点与 33 例悬挂归因、确定性阶段预算、有界展开预通道、双曲闭式族、有理导数核代换、三角相位归一、逆函数复合消去、exp(逆函数) 代数化、半幂前端 + 椭圆 Legendre 约化(EllipticF/E);修复六组错案(已验证覆盖率 93.1%、mismatches 0、超时 33 → 13、墙钟 −22%) |
| 0.27.3 | 2026-09-12 | 0.27 线收尾:特殊函数导数表 + oracle 扩头(Ei/Ei(n,z)/Si/Ci/Shi/Chi/Fresnel)、特殊函数归约族(多项式 × F、Ei(n,·)、F/xᵐ、F²)、精确线性平方折叠 p²+2pq+q² → (p+q)²、半幂前端仿射变元与两支片层因子;Rubi 覆盖率 18.45% → 19.56%(新解 21、回归 0、mismatches 0、超时 13 → 12) |
| 0.28.0 | 2026-09-13 | 积分机制正确性地基:符号证书引擎(结构/初等域 + I² = −1 约化 + 依赖原子合并,确定性预算)、三值 Outcome 与 integrate_outcome(Python/C 同步暴露)、表达式级循环检测(预算感知入口键 + 独立残项预算)、正则塔依赖生成元合并、Risch 结果塔内精确校验(抓到并兜住一个潜伏错案)、双曲前端因墙钟代价默认关闭;Rubi 覆盖率 19.56% → 19.61%(371 solved,+1 新解 0 回归),mismatches 0,超时 12 持平,墙钟 −6.0%;certified_rate = 28.8%(目标 1.0 未达,如实记录) |
| 0.29.0 | 2026-10-01 | 超越 Risch 补全:Risch 微分方程的有理解(完整有理片段:弱规范化、法/特殊分母界、次数界、SPDE、抵消递归)、各塔层的 Rothstein–Trager 对数部分(结果式值插值、ℚ 有理根筛 + 完备性判定、逐根 gcd 经 Πvᵢ == d1 自证)、耦合微分系统基础设施 integral/coupled.rs(未挂接)、塔随机通用性测试(凡无残项答案必须过证书门,该族 certified_rate = 1.0);修复指数层 Laurent 拆分的潜伏错案;Rubi 覆盖率 19.61% → 19.98%(378 solved,新解 7 / 回归 0)、verified 351/378、mismatches 0、超时 12 持平、墙钟 439.6 s,certified_rate 28.8% → 29.1% |
| 0.30.0 | 2026-10-25 | 代数扩张与反函数代换:RT 代数常量根(基层实/复二次因子与双二次四次因子 Az⁴+Bz²+C,塔层复二次因子,均在扩域内自证)、证书根式层(CertMethod::RadicalField:sqrt(S) / S^(p/q) 统一为 r^q = S 三角列约化)、多根式基归约(integral/multi_radical.rs:MR-L Möbius 有理化、带门同根合并、MR-G/R/I/P)、反函数代换引擎(integral/inverse_trig.rs M4:11 头 × 仿射变元 × 整数核幂 × 多项式权);Rubi solved 378 → 451(coverage 23.84%)、verified 410/451、certified_rate 29.1% → 48.8%(220/451)、mismatches 0、超时 12 持平、墙钟 489.5 s、0 回归、证书假阳性 0;radical 桶 51 → 84、inverse-trig-hyper 8 → 22(≥48/146 口径未达,如实记录) |
模块架构
oCAS 由 13 个 crate 组成,依赖关系如下(箭头 A → B 表示 A 依赖 B;
ocas-py/ocas-c 还依赖 ocas-eval、ocas-rewrite 等核心 crate,
图中仅示代表边;虚线表示可选依赖):
graph TD
CORE["ocas-core<br/>Arena, Fuel, 线程池"]
DOMAIN["ocas-domain<br/>系数域, 数论, 代数数"]
POLY["ocas-poly<br/>多项式, 矩阵, Gröbner 基"]
ATOM["ocas-atom<br/>表达式树 Atom, 张量"]
PARSE["ocas-parse<br/>解析器"]
REWRITE["ocas-rewrite<br/>模式匹配, 化简"]
CALC["ocas-calc<br/>微积分, ODE, 积分"]
EVAL["ocas-eval<br/>求值, JIT, 数值积分"]
GPL["ocas-gpl<br/>GPL 后端隔离"]
TOP["ocas<br/>顶层 crate + prelude"]
PY["ocas-py<br/>Python 绑定 (PyO3)"]
C["ocas-c<br/>C/C++ 绑定 (cbindgen)"]
TESTS["ocas-tests<br/>正确性对比, pytest"]
DOMAIN --> CORE
ATOM --> CORE
EVAL --> CORE
EVAL --> DOMAIN
EVAL --> ATOM
PARSE --> ATOM
REWRITE --> ATOM
REWRITE --> PARSE
CALC --> ATOM
CALC --> DOMAIN
CALC --> POLY
CALC --> REWRITE
POLY --> DOMAIN
TOP --> DOMAIN
TOP --> ATOM
TOP --> POLY
TOP --> CALC
TOP --> EVAL
TOP --> REWRITE
TOP --> PARSE
TOP --> CORE
TESTS --> TOP
TOP -.-> PY
TOP -.-> GPL
PY --> CALC
C --> CALC
style CORE fill:#e8f4fd,stroke:#2196F3
style DOMAIN fill:#e8f4fd,stroke:#2196F3
style POLY fill:#fff3e0,stroke:#FF9800
style ATOM fill:#e8f5e9,stroke:#4CAF50
style CALC fill:#fce4ec,stroke:#E91E63
style EVAL fill:#f3e5f5,stroke:#9C27B0
style TOP fill:#fffde7,stroke:#FDD835
style PY fill:#efebe9,stroke:#795548
style C fill:#efebe9,stroke:#795548
crate 一览:
| crate | 职责 |
|---|---|
ocas-core | Arena 分配器、Fuel 资源控制、ThreadPool、GMP 类型别名 |
ocas-domain | 系数域 trait(Domain/EuclideanDomain)、Integer/Rational/FiniteField/RealBall/Complex/DoubleF64/AlgebraicExtension、数论函数、假设系统 |
ocas-poly | 稠密一元/稀疏多元多项式、RationalPolynomial、矩阵、GCD、因式分解、Gröbner 基(Buchberger/F4/F5)、FGLM、理想运算 |
ocas-atom | Atom/Arena/Symbol 表达式树、hash consing、张量(图规范化/Young 投影) |
ocas-parse | 基于 logos/chumsky 的表达式解析器 |
ocas-rewrite | 通配符模式匹配(AC 回溯)、Rule/simplify/transform、可选 egg e-graph |
ocas-calc | 符号微分 diff、分层积分管线(有理→Risch→三角→特殊函数→启发式)、Taylor 展开、ODE 求解器(一阶/二阶/级数解/Laplace/系统)、部分分式 |
ocas-eval | 栈式 VM 解释器、SIMD 向量化求值、Cranelift JIT、流式求值、Vegas 蒙特卡洛数值积分 |
ocas | 顶层 re-export crate + prelude::* |
ocas-py | PyO3 Python 绑定(25 类 + 32 函数) |
ocas-c | C/C++ FFI 绑定(91 个 #[no_mangle] 导出)+ ocas.hpp RAII 包装 |
ocas-gpl | GPL 许可后端隔离(GMP/MPFR/FLINT) |
ocas-tests | 正确性对比框架(SymPy/SageMath/Symbolica)、pytest 套件、C API 集成测试 |
关键特性
- 13 个 crate —— 从 arena 运行时到语言绑定的分层 Rust 架构,依赖严格向下。
- 7 种系数域 —— 任意精度整数/有理数、有限域 $\mathbb{F}_p$、实数球、复数、双精度浮点(DoubleF64 ~31 位)、代数数域 $\mathbb{Q}(\alpha)$ / $\mathrm{GF}(p^d)$。
- 多项式代数 —— 稠密一元/稀疏多元多项式、Karatsuba 乘法、GCD(模 GCD Brown 算法)、多元 GCD、因式分解(Cantor–Zassenhaus + Hensel + Wang EEZ)、代数数域分解(Trager)。
- Gröbner 基 —— Buchberger / F4 / F5 三种算法、FGLM 换序、理想运算(成员/和/积/商/饱和/交集/消元/根式/准素分解)、Hilbert 级数。
- 符号微积分 —— 微分、Taylor 展开、分层积分管线(有理 → Risch → 三角 → 特殊函数 → 启发式 → 未求值形式)。
- ODE 求解 —— 一阶(可分离/线性/Bernoulli/恰当/齐次/积分因子)、二阶(常系数/Cauchy–Euler/降阶/待定系数/参数变易)、幂级数与 Frobenius 级数解、Laplace IVP、2×2 系统。
- 求值引擎 —— 栈式 VM 解释器、SIMD 批量求值(f64x4)、Cranelift JIT(f64/f32)、流式求值(恒定内存)。
- 自动微分 —— 超对偶数
HyperDual<T>前向模式,一阶及高阶偏导数。 - 张量代数 —— 指标槽、显式缩并、McKay 图同构规范化(1-WL 细化 + 路径不变量剪枝)、Young 投影子。
- 数值积分 —— 自适应蒙特卡洛(Vegas),支持高维定积分。
- 数论 —— BPSW 素性判定、整数分解(试除/Pollard rho/p−1/p+1/ECM)、离散对数(BSGS + Pohlig–Hellman)、Euler φ / Möbius / 除数函数。
- 重写与化简 —— AC 回溯模式匹配、通配符(
x__/x___)、不动点化简、fuel 受限化简、可选 egg e-graph 等式饱和。 - 三语言绑定 —— Rust prelude API、Python(PyO3,25 类 + 32 函数)、C/C++(cbindgen,91 个导出 + RAII 包装)。
- 正确性框架 —— 与 SymPy、SageMath、Symbolica 跨多个数学模块的交叉验证(1550 个
#[test]/#[tokio::test]标注,截至 0.28.0)。 - 可选数值后端 —— GMP/MPFR/FLINT 隐藏在 feature flag 后,GPL 后端隔离在
ocas-gpl。
快速导航
| 主题 | 说明 | 入口 |
|---|---|---|
| Rust API 参考 | 全部公共类型与函数的逐项文档 | API 参考总览 |
| Python API 参考 | PyO3 绑定的签名、参数、异常、示例 | Python API |
| C/C++ API 参考 | FFI 原型、错误码、内存管理、示例 | C/C++ API |
| 数学基础 | 从多项式代数到 Risch 算法的渐进式讲解 | 数学基础总览 |
| 算法详解 | Gröbner 基、因式分解、数论、积分、ODE 的实现细节 | 算法 |
| 求解器 | 线性/丢番图/多项式系统/ODE 求解器 | 求解器 |
| 重写与化简 | 模式匹配、规则化简、e-graph | 重写 |
| 求值与 JIT | VM 解释器、SIMD、Cranelift JIT | 求值 |
| 数值积分 | Vegas 自适应蒙特卡洛 | 数值积分 |
| 自动微分 | 超对偶数前向模式 AD | 自动微分 |
| 张量 | 指标、缩并、规范化、Young 投影 | 张量 |
| 基准与性能 | Criterion 基准、SymPy/SageMath 对比 | 性能 |
| 正确性 | 跨系统交叉验证测试 | 正确性 |
| 构建指南 | 后端选择、Windows 构建、贡献 | 构建 |
项目状态
oCAS 当前版本为 0.30.0。核心符号引擎、多项式代数、Gröbner 基与代数几何、ODE 求解、JIT 求值、三语言绑定与正确性对比框架已达到功能完备。通往稳定 1.0 的路线见 路线图。
快速上手
本章帮助你在 5 分钟内完成 oCAS 的安装与首次运行。oCAS 0.24 支持 Rust、Python 和 C/C++ 三种语言接口。
系统要求
语言 最低版本 备注 Rust 1.97+ (edition 2024) 推荐通过 rustup 安装 Python 3.10+ 使用 pip install或从源码构建C/C++ C11 / C++17 需要 cargo构建 C 库
Rust
安装
在你的 Cargo.toml 中添加 oCAS:
[dependencies]
ocas = "0.24"
如需可选后端,在 features 中开启:
[dependencies]
ocas = { version = "0.24", features = ["gmp", "jit"] }
常用 feature 一览:
| Feature | 说明 |
|---|---|
gmp | 使用 GMP 任意精度算术(替代纯 Rust num-bigint) |
mpfr | 使用 MPFR 球算术(实数区间算术) |
jit | Cranelift JIT 编译求值器 |
simd | SIMD 向量化批量求值 |
egg | e-graph 等式饱和引擎 |
ntt | NTT 快速多项式乘法(𝔽_p) |
mimalloc | 使用 mimalloc 分配器 |
system-libs | 链接系统预装的 GMP/MPFR(Windows/MinGW 必需) |
完整 feature 列表见 Rust API 参考。
验证安装
创建一个最小项目来确认一切正常:
cargo new ocas-test && cd ocas-test
在 Cargo.toml 中添加依赖:
[dependencies]
ocas = "0.24"
在 src/main.rs 中写入:
use ocas::prelude::*; fn main() { let arena = Arena::new(); let ctx = AtomArena::new(&arena); let expr = parse(&ctx, "x^2 + 2*x + 1").unwrap(); let d = diff(&ctx, expr, Symbol::new("x")); println!("d/dx(x² + 2x + 1) = {}", d); // 输出:d/dx(x² + 2x + 1) = 2 + 2*x }
运行:
cargo run
若输出 2 + 2*x,说明安装成功。
基本用法
符号微分与化简
use ocas::prelude::*; use ocas::ocas_rewrite::rules::default_rules; fn main() { let arena = Arena::new(); let ctx = AtomArena::new(&arena); // 解析表达式 let expr = parse(&ctx, "x^2").unwrap(); // 微分 let d = diff(&ctx, expr, Symbol::new("x")); println!("微分: {}", d); // 输出:2*x // 化简(默认规则集:恒等元移除、常量折叠) let e2 = parse(&ctx, "x + 0 + y*0 + z*1").unwrap(); let rules = default_rules(&ctx, &()); let s = simplify(&ctx, e2, &rules, 100); println!("化简: {}", s); // 输出:x + z }
多项式运算
use ocas::prelude::*; use ocas_poly::dense::DenseUnivariatePolynomial; use ocas_domain::rational::RationalDomain; use ocas_domain::Rational; fn main() { let domain = RationalDomain; // 1 + 2x + x² let p = DenseUnivariatePolynomial::from_coeffs( domain, vec![Rational::new(1, 1), Rational::new(2, 1), Rational::new(1, 1)], ); // 1 + x let q = DenseUnivariatePolynomial::from_coeffs( domain, vec![Rational::new(1, 1), Rational::new(1, 1)], ); let (quot, rem) = p.div_rem(&q).unwrap(); // DenseUnivariatePolynomial 未实现 Display,直接断言系数 assert_eq!(quot.coeffs(), &[Rational::new(1, 1), Rational::new(1, 1)]); // 1 + x assert_eq!(rem.coeffs(), &[]); // 余 0 }
求解线性方程组
use ocas_calc::solve::solve_linear_rational; fn main() { // 求解 2x + y = 5, x - y = 1 let a = vec![vec![2, 1], vec![1, -1]]; let b = vec![5, 1]; let solution = solve_linear_rational(&a, &b).unwrap(); println!("x = {}/{}, y = {}/{}", solution[0].0, solution[0].1, solution[1].0, solution[1].1); // 输出:x = 2/1, y = 1/1 }
数论
use ocas_domain::number_theory::{ is_prime, factor::factor_integer, functions::euler_phi, }; fn main() { println!("is_prime(97) = {}", is_prime(&97.into())); // 输出:is_prime(97) = true let factors = factor_integer(&360.into()); println!("360 = {}", factors.iter() .map(|(p, e)| format!("{}^{}", p, e)) .collect::<Vec<_>>() .join(" × ")); // 输出:360 = 2^3 × 3^2 × 5^1 println!("φ(360) = {}", euler_phi(&360.into())); // 输出:φ(360) = 96 }
更多 Rust API 详见:
- 表达式系统 — Arena、Symbol、Atom、模式匹配
- 系数域 — Integer、Rational、FiniteField 等
- 多项式 — 一元/多元多项式、单项式序
- 矩阵 — 行列式、求解、乘法
- 微积分 — diff、integrate、taylor
- 求解器 — 线性方程、丢番图、ODE
- 数论 — 素性、分解、离散对数
Python
安装
pip install ocas
要求 Python ≥ 3.10(使用 PyO3 abi3 稳定 ABI)。
验证安装
python -c "import ocas; e = ocas.Expression('x^2'); print(e.diff('x'))"
若输出 2*x,说明安装成功。如需在 Python 交互环境中验证:
>>> import ocas
>>> e = ocas.Expression("x^2 + 2*x + 1")
>>> e.diff("x")
2 + 2*x
>>> e.simplify()
1 + 2*x + x^2
基本用法
符号微分与化简
import ocas
e = ocas.Expression("x^2")
# 微分
d = e.diff("x")
print(f"微分: {d}")
# 输出:微分: 2*x
# 化简(默认规则集:恒等元移除、常量折叠)
e2 = ocas.Expression("x + 0 + y*0 + z*1")
s = e2.simplify()
print(f"化简: {s}")
# 输出:化简: x + z
多项式运算
import ocas
# 1 + 2x + x²
p = ocas.Polynomial([1, 2, 1])
print(f"次数: {p.degree()}") # 次数: 2
print(f"p(2) = {p.eval(2)}") # p(2) = 9
# 多项式 GCD
q = ocas.Polynomial([1, 1]) # 1 + x
g = p.gcd(q)
print(f"gcd = {g}") # gcd = 1 + x
# 因式分解
factors = p.factor()
print(f"因式分解: {factors}") # 因式分解: [(1 + x, 2)]
有限域运算
import ocas
gf5 = ocas.FiniteField(5)
q = ocas.Polynomial([1, 2, 1], domain=gf5)
print(q.eval(3)) # (1 + 6 + 9) mod 5 = 16 mod 5 = 1
矩阵运算
import ocas
m = ocas.Matrix([[1, 2], [3, 4]])
print(f"行列式: {m.determinant()}") # 行列式: -2
print(f"转置: {(m.transpose()).rows()}") # 转置: [[1, 3], [2, 4]]
print(f"m × m: {(m @ m).rows()}") # m × m: [[7, 10], [15, 22]]
数论
import ocas
print(ocas.isprime(97)) # True
print(ocas.factorint(360)) # [("2", 3), ("3", 2), ("5", 1)]
print(ocas.totient(360)) # 96
print(ocas.nextprime(100)) # 101
print(ocas.mobius(30)) # -1
print(ocas.divisor_count(360)) # 24
ODE 求解
import ocas
e = ocas.Expression("Derivative(y(x), x) + y(x)") # y' + y = 0
# 分类可用的求解方法
methods = ocas.classify_ode(e, "y", "x")
print(methods)
# 求解 y' + y = 0
sol = ocas.dsolve(e, "y", "x")
print(sol) # y = C1*exp(-x)
更多 Python API 详见 Python API 参考。
C/C++
构建 C 库
oCAS 的 C 库通过 cargo 构建。从源码仓库克隆并编译:
git clone https://github.com/charleshzh/ocas.git
cd ocas
cargo build -p ocas-c --release
构建产物位于 target/release/:
| 文件 | 说明 |
|---|---|
libocas_c.so(Linux)/ ocas_c.dll(Windows)/ libocas_c.dylib(macOS) | 动态库 |
libocas_c.a(Linux/macOS)/ ocas_c.lib(Windows) | 静态库 |
头文件 ocas.h 位于 ocas-c/include/ 目录。C++ 用户还可以使用同目录下的 ocas.hpp(RAII 包装,覆盖表达式、数论和张量域)。
Windows 用户:推荐使用 MSYS2 MINGW64 环境。详见 在 Windows 上构建。
验证安装
创建一个最小测试文件 test_ocas.c:
#include <stdio.h>
#include "ocas.h"
int main(void) {
struct ocas_OcasExpr *e = ocas_expr_parse("x^2 + 2*x + 1", NULL);
if (!e) {
fprintf(stderr, "解析失败\n");
return 1;
}
struct ocas_OcasExpr *d = ocas_expr_diff(e, "x", NULL);
char *s = ocas_expr_to_string(d, NULL);
printf("d/dx(x² + 2x + 1) = %s\n", s);
/* 输出:d/dx(x² + 2x + 1) = 2 + 2*x */
ocas_string_free(s);
ocas_expr_free(d);
ocas_expr_free(e);
return 0;
}
编译并运行(以 Linux 为例,调整路径以匹配你的环境):
# 假设 oCAS 已构建在 /path/to/ocas
gcc test_ocas.c -I/path/to/ocas/ocas-c/include \
-L/path/to/ocas/target/release -locas_c -lm -ldl -lpthread -o test_ocas
LD_LIBRARY_PATH=/path/to/ocas/target/release ./test_ocas
若输出 2 + 2*x,说明安装成功。
基本用法
符号微分
#include <stdio.h>
#include "ocas.h"
int main(void) {
struct ocas_OcasExpr *e = ocas_expr_parse("x^2", NULL);
struct ocas_OcasExpr *d = ocas_expr_diff(e, "x", NULL);
char *s = ocas_expr_to_string(d, NULL);
printf("微分: %s\n", s);
/* 输出:微分: 2*x */
ocas_string_free(s);
ocas_expr_free(d);
ocas_expr_free(e);
return 0;
}
数论
#include <stdio.h>
#include "ocas.h"
int main(void) {
char *result;
/* 素性测试 */
int prime = ocas_ntheory_isprime("97", NULL);
printf("is_prime(97) = %s\n", prime ? "true" : "false");
/* 输出:is_prime(97) = true */
/* 整数分解 */
result = ocas_ntheory_factorint("360", NULL);
printf("360 = %s\n", result);
/* 输出:360 = 2:3,3:2,5:1 */
ocas_string_free(result);
/* Euler φ 函数 */
result = ocas_ntheory_totient("360", NULL);
printf("φ(360) = %s\n", result);
/* 输出:φ(360) = 96 */
ocas_string_free(result);
return 0;
}
C++ RAII 包装
如果你使用 C++,可以利用 ocas.hpp 中的 RAII 包装避免手动管理内存:
#include <iostream>
#include "ocas.h"
#include "ocas.hpp"
int main() {
ocas::Expression e("x^2 + 2*x + 1");
auto d = e.diff("x");
std::cout << "d/dx = " << d.to_string() << std::endl;
// 输出:d/dx = 2 + 2*x
// 数论(无需手动 free)
std::cout << "is_prime(97) = " << ocas::ntheory::isprime("97") << std::endl;
std::cout << "360 = " << ocas::ntheory::factorint("360") << std::endl;
std::cout << "φ(360) = " << ocas::ntheory::totient("360") << std::endl;
return 0;
}
更多 C/C++ API 详见 C/C++ API 参考。
常见问题
Rust 相关
Q:编译很慢怎么办?
oCAS 包含大量泛型代码,首次编译可能较慢。建议:
- 开发时使用
cargo build(dev profile,已对依赖开opt-level = 3) - 发布时使用
cargo build --release(启用 LTO) - 利用
sccache或cargo-nextest加速增量编译
Q:gmp feature 报错找不到 GMP 库?
在 Linux/macOS 上,确保已安装 GMP 开发包:
# Ubuntu/Debian
sudo apt install libgmp-dev
# macOS (Homebrew)
brew install gmp
在 Windows(MSYS2 MINGW64)上:
pacman -S mingw-w64-x86_64-gmp
或使用 system-libs feature 链接系统预装库。详见 在 Windows 上构建。
Q:flint feature 在 Windows 上不可用?
上游 flint3-sys 仅支持 POSIX 平台,Windows 上暂不支持 flint feature。
Python 相关
Q:pip install ocas 失败?
确认 Python 版本 ≥ 3.10:
python --version
如需从源码构建:
pip install maturin
maturin develop --release -m ocas-py/Cargo.toml
Q:Python 绑定中的函数名为什么有 py_ 前缀?
部分 Gröbner 函数(如 ocas.py_groebner_basis)保留了 py_ 前缀,这是因为 PyO3 绑定尚未添加 #[pyo3(name=...)] 注解。实际调用时使用带前缀的名称即可。
C/C++ 相关
Q:链接时报 undefined reference?
确保链接顺序正确——libocas_c 放在源文件之后:
gcc test.c -locas_c -lm -ldl -lpthread
并确保 LD_LIBRARY_PATH(Linux)或 PATH(Windows)包含库文件所在目录。
Q:ocas.h 中缺少某些函数?
仓库中附带的 ocas.h 可能不是最新的。构建时 cbindgen 会在 target/release/build/ocas-c-*/out/ 下生成最新的头文件。以该文件为准。
Q:C++ 包装 ocas.hpp 覆盖了哪些功能?
ocas.hpp 目前覆盖三个域:表达式(ocas::Expression)、数论(ocas::ntheory::*)和张量(ocas::tensor::*)。其余域(多项式、Gröbner、ODE 等)需直接使用 C API。
通用问题
Q:数值结果不精确?
默认使用纯 Rust num-bigint 有理数算术,完全精确。若需高精度浮点(区间算术),启用 mpfr feature:
ocas = { version = "0.24", features = ["mpfr"] }
Q:如何查看 oCAS 版本?
cargo metadata --format-version=1 | jq '.packages[] | select(.name=="ocas") | .version'
下一步
- Rust API 参考 — 完整的 Rust 模块、类型与函数文档
- Python API 参考 — 所有 Python 类与函数的详细说明
- C/C++ API 参考 — C 导出函数、C++ RAII 包装、内存管理
- 架构 — oCAS 的整体设计与模块关系
- Gröbner 基实现 — Buchberger / F4 / F5 算法详解
- ODE 求解 — 常微分方程分类与求解方法
架构
oCAS 由 12 个公共 crate(外加用于集成测试与基准测试的 ocas-tests)
组成的 Cargo workspace,层间依赖严格向下。每个 crate 只能依赖更底层的 crate,
不允许反向或循环依赖。
| 层级 | crate | 职责 |
|---|---|---|
| 5 绑定 | ocas、ocas-py、ocas-c | Rust、Python、C/C++ 公共 API |
| 4 应用 | ocas-calc、ocas-eval、ocas-parse | 微积分、求值、解析 |
| 3 符号引擎 | ocas-atom、ocas-rewrite | Atom、转换器、模式匹配、e-graph |
| 2 代数核 | ocas-domain、ocas-poly | 域、多项式、数论 |
| 1 数值后端 | ocas-core | GMP 封装(FLINT 后端位于 ocas-poly) |
| 0 运行时 | ocas-core | arena、错误、线程池、FFI |
重点模块
ocas-poly::factor — 多项式因式分解,使用 Hensel 提升与有限域技术。
支持无平方因子分解以及整数域与有理数域上的完全因式分解。
ocas-poly::groebner — Gröbner 基计算,提供三种算法:Buchberger、F4(生产默认)和 F5(基于签名)。支持可配置的单项式序(Lex、Grevlex、WeightOrder、BlockOrder)以及零维理想的 FGLM 转换。
ocas-poly::roots — 单变量多项式实根隔离。
ocas-tests::correctness — 交叉验证框架,包含 201 项 #[test] 测试,覆盖 19 个数学模块(截至 0.24.0),将 oCAS 结果与 SymPy、SageMath、Symbolica 进行对比。
完整设计文档见仓库中的 ARCHITECTURE_CN.md。
Rust API 参考
oCAS 的 Rust API 采用分层 crate 架构,顶层 ocas crate 通过 prelude 模块统一导出所有常用类型和函数。用户只需一行导入即可开始使用:
#![allow(unused)] fn main() { use ocas::prelude::*; }
模块层级
graph TD
OC["ocas<br/>顶层统一入口 + prelude"]
CORE["ocas-core<br/>Arena、错误、燃料"]
DOMAIN["ocas-domain<br/>系数域、数论、代数数域"]
ATOM["ocas-atom<br/>表达式树、张量"]
PARSE["ocas-parse<br/>字符串解析"]
POLY["ocas-poly<br/>多项式、矩阵、Gröbner 基"]
REWRITE["ocas-rewrite<br/>模式匹配、化简、重写"]
CALC["ocas-calc<br/>微积分、ODE、求解器"]
EVAL["ocas-eval<br/>求值器、JIT、数值积分"]
OC --> CORE
OC --> DOMAIN
OC --> ATOM
OC --> PARSE
OC --> POLY
OC --> REWRITE
OC --> CALC
OC --> EVAL
ATOM --> CORE
ATOM --> DOMAIN
PARSE --> CORE
PARSE --> ATOM
POLY --> CORE
POLY --> DOMAIN
REWRITE --> CORE
REWRITE --> ATOM
REWRITE --> PARSE
CALC --> CORE
CALC --> DOMAIN
CALC --> ATOM
CALC --> POLY
CALC --> REWRITE
EVAL --> CORE
EVAL --> ATOM
EVAL --> DOMAIN
style OC fill:#4a9eff,stroke:#2d7ad4,color:#fff
style CORE fill:#6c757d,stroke:#545b62,color:#fff
style DOMAIN fill:#28a745,stroke:#1e7e34,color:#fff
style ATOM fill:#17a2b8,stroke:#117a8b,color:#fff
style PARSE fill:#6f42c1,stroke:#59359a,color:#fff
style POLY fill:#fd7e14,stroke:#ca6510,color:#fff
style REWRITE fill:#e83e8c,stroke:#b82d6e,color:#fff
style CALC fill:#20c997,stroke:#17a085,color:#fff
style EVAL fill:#ffc107,stroke:#d39e00,color:#333
| Crate | 职责 |
|---|---|
ocas-core | Arena bump 分配器、统一错误类型 OcasError、燃料计数 Fuel |
ocas-domain | 系数域 trait(Domain、EuclideanDomain)及实现(整数、有理数、有限域、实数球、复数、双精度浮点、代数数域)、数论函数库、对偶数自动微分 |
ocas-atom | 表达式树(Atom、AtomNode、AtomArena)、hash-consing、张量系统 |
ocas-parse | 字符串 → Atom 解析器 |
ocas-poly | 多项式(稠密一元、稀疏多元、有理分式域)、矩阵、Gröbner 基算法(Buchberger/F4/F5)、理想运算、FGLM 换序 |
ocas-rewrite | 模式匹配(AC 匹配)、化简(不动点迭代)、自底向上变换、E-graph(可选) |
ocas-calc | 符号微分、积分(Risch 管线)、Taylor 展开、ODE 求解、丢番图/线性/多项式系统求解器 |
ocas-eval | 栈 VM 解释器、JIT 编译(Cranelift)、SIMD 批量求值、Vegas 蒙特卡洛数值积分 |
prelude 导入内容清单
use ocas::prelude::* 导入以下所有项。同时也可通过 ocas::TypeName 直接访问最常用的类型。
表达式树
| 名称 | 类型 | 说明 |
|---|---|---|
Atom | struct | Copy 句柄,指向 arena 中的表达式节点 |
AtomArena | struct | hash-consing 构造器,从 Arena 创建表达式 |
AtomNode | enum | 节点变体:Num/Var/Fun/Add/Mul/Pow |
Symbol | struct | interned 字符串标识符,Copy |
normalize | fn | 规范化表达式(排序、合并同类项) |
parse | fn | 字符串解析为 Atom |
ParseError | enum | 解析错误 |
系数域
| 名称 | 类型 | 说明 |
|---|---|---|
Domain | trait | 系数域基本算术(zero/one/add/mul/sub/neg 等) |
EuclideanDomain | trait | 欧几里得域(+ div_rem/gcd/lcm) |
Integer | struct | 任意精度整数 |
IntegerDomain | struct | 整数域实现 |
Rational | struct | 任意精度有理数 |
RationalDomain | struct | 有理数域实现 |
FiniteField | struct | 𝔽ₚ 有限域 |
FiniteFieldElement | struct | 有限域元素 |
RealBall | struct | 实数球算术(需 mpfr feature) |
RealBallDomain | struct | 实数球域实现 |
Complex | struct | 复数 |
ComplexDomain | struct | 复数域实现 |
DoubleF64 | struct | 双精度浮点(~31 位有效数字) |
DoubleF64Domain | struct | 双精度浮点域实现 |
AlgebraicExtension | struct | 代数数域 ℚ(α) 或 GF(p^d) |
AlgebraicElement | struct | 代数数域元素 |
AlgebraicNumberField | struct | 代数数域构造器 |
Assumption | enum | 假设变体(Positive/Negative/Integer/Real/Complex 等 13 种) |
Assumptions | struct | 假设集合 |
SymbolAssumptions | struct | 符号级假设管理 |
多项式
| 名称 | 类型 | 说明 |
|---|---|---|
DenseUnivariatePolynomial | struct | 稠密一元多项式 |
SparseMultivariatePolynomial | struct | 稀疏多元多项式 |
RationalPolynomial | struct | 多项式分式域元素 p/q |
RootInterval | struct | 恰好包含一个实根的区间(low/high 边界) |
MonomialOrder | trait | 单项式序 trait |
Lex | struct | 字典序 |
Grlex | struct | 分次字典序 |
Grevlex | struct | 分次反字典序 |
WeightOrder | struct | 加权序 |
BlockOrder | struct | 分块消元序 |
SubOrder | struct | 子序 |
monomial_divides | fn | 单项式整除测试 |
monomial_lcm | fn | 单项式最小公倍 |
monomial_are_coprime | fn | 单项式互素测试 |
矩阵
| 名称 | 类型 | 说明 |
|---|---|---|
Matrix | struct | 域上矩阵(Bareiss 行列式、高斯消元求解) |
MatrixError | enum | 矩阵运算错误 |
Gröbner 基
| 名称 | 类型 | 说明 |
|---|---|---|
GroebnerBasis | struct | Gröbner 基结果(含多项式列表与元数据) |
buchberger | fn | Buchberger 算法入口 |
f4 | fn | F4 算法入口(矩阵行阶梯批处理) |
微积分
| 名称 | 类型 | 说明 |
|---|---|---|
diff | fn | 符号微分 |
integrate | fn | 符号积分(分层管线) |
integrate_outcome | fn | 认证积分:三值输出 + 符号证书(0.28.0) |
integrate_outcome_with_options | fn | 带选项的认证积分(0.28.0) |
integrate_heuristic | fn | 启发式积分(不含 Risch) |
integrate_with_fuel | fn | 带燃料限制的积分 |
taylor | fn | Taylor 展开 |
substitute | fn | 表达式替换 |
apart | fn | 部分分式分解 |
求解器
| 名称 | 类型 | 说明 |
|---|---|---|
solve_linear_rational | fn | ℚ 上线性方程组 Ax=b |
solve_linear_integer | fn | ℤ 上线性方程组 Ax=b |
solve_diophantine | fn | 丢番图方程 ax+by=c |
DiophantineSolution | struct | 丢番图解(特解 + 步长) |
SolveError | enum | 求解错误 |
solve_polynomial_system | fn | 多项式系统求解(零维/正维/空解集) |
classify_ode | fn | ODE 类型分类 |
dsolve | fn | 符号 ODE 求解 |
dsolve_ivp | fn | ODE 初值问题(Laplace) |
dsolve_system | fn | 2×2 ODE 系统求解 |
ODE | struct | ODE 描述 |
ODESolution | enum | ODE 解(显式/隐式/参数/级数/系统/未解) |
ODEType | enum | ODE 类型枚举 |
重写与化简
| 名称 | 类型 | 说明 |
|---|---|---|
Pattern | enum | 匹配模式(Literal/Wildcard/Add/Mul/Pow/Fun) |
Rule | struct | 重写规则 |
Bindings | struct | 模式绑定结果 |
MatchError | enum | 匹配错误(含 BudgetExhausted) |
WildcardLevel | enum | 通配符级别(Single/Sequence/NullSequence) |
match_pattern | fn | AC 模式匹配 |
simplify | fn | 不动点化简 |
simplify_with_fuel | fn | 带燃料限制的化简 |
transform | fn | 自底向上变换 |
求值与 JIT
| 名称 | 类型 | 说明 |
|---|---|---|
ExpressionEvaluator | struct | 栈 VM 解释器 |
FunctionMap | struct | 自定义函数注册表 |
EvaluationDomain | trait | 求值域约束 |
EvaluationError | enum | 求值错误 |
EvalTree | struct | 求值树 |
Instr | enum | 指令枚举 |
Instruction | struct | 指令详情 |
Slot | enum | 操作数槽(Param/Const/Temp) |
PowfExtension | trait | 浮点幂扩展 |
VectorEvaluator | struct | SIMD 批量求值(需 simd feature) |
JitEngine | struct | JIT 编译引擎(需 jit feature) |
JitCompiledFunction | struct | JIT 编译结果 |
数值积分
| 名称 | 类型 | 说明 |
|---|---|---|
Vegas | struct | Vegas 蒙特卡洛积分器 |
VegasOptions | struct | Vegas 选项(bin 数、样本数、迭代数等) |
IntegrateResult | struct | 积分结果(值 + 误差) |
Integrator | struct | 积分器接口 |
StatisticsAccumulator | struct | 逆方差加权累加器 |
integrate_1d | fn | 一维数值积分便捷函数 |
自动微分
| 名称 | 类型 | 说明 |
|---|---|---|
DualShape | struct | 导数布局描述 |
HyperDual | struct | 超对偶数 |
DualCoeff | trait | 对偶数系数约束 |
new_first_order | fn | 一阶对偶便捷构造 |
张量
| 名称 | 类型 | 说明 |
|---|---|---|
Tensor | struct | 命名指标张量 |
IndexSlot | struct | 指标槽(标签 + 位置) |
IndexPosition | enum | Upper(逆变)/ Lower(协变) |
Symmetry | enum | None/Symmetric/Antisymmetric |
Contracted | enum | 缩并结果(Scalar/Product) |
TensorProduct | struct | 张量积 |
contract | fn | 指标缩并 |
symmetrise_sign | fn | 反对称化符号 |
运行时
| 名称 | 类型 | 说明 |
|---|---|---|
Arena | struct | bump 分配器 |
OcasError | enum | 统一错误类型 |
Result | type | Result<T, OcasError> |
Fuel | struct | 燃料计数(防无限循环) |
Feature Flags 速查表
在 Cargo.toml 中通过 features 启用可选后端和加速功能:
[dependencies]
ocas = { version = "0.24", features = ["gmp", "jit"] }
| Feature | 默认 | 说明 | 依赖的子 crate feature |
|---|---|---|---|
gmp | 否 | 使用 GMP 任意精度算术(显著加速大整数运算) | ocas-domain/gmp, ocas-poly/gmp, ocas-core/gmp |
mpfr | 否 | 启用 MPFR 实数球算术(RealBall 类型) | ocas-domain/mpfr |
flint | 否 | 使用 FLINT 数论库加速多项式运算 | ocas-poly/flint |
python | 否 | 启用 Python 绑定(PyO3) | dep:ocas-py |
gpl | 否 | 启用 GPL 许可的功能模块 | dep:ocas-gpl |
egg | 否 | 启用 E-graph 化简(egg_simplify 函数) | ocas-rewrite/egg |
jit | 否 | 启用 Cranelift JIT 编译(compile_jit 方法) | ocas-eval/jit |
simd | 否 | 启用 SIMD 批量求值(VectorEvaluator) | ocas-eval/simd |
mimalloc | 否 | 使用 mimalloc 全局分配器(加速大量小分配场景) | dep:mimalloc |
system-libs | 否 | 链接系统预装的 GMP/MPFR/MPC(MinGW 必需) | ocas-domain/system-libs |
ntt | 否 | NTT 数论变换加速 𝔽ₚ 上多项式乘法 | ocas-poly/ntt |
sprs | 否 | 稀疏矩阵后端(用于 F4 准备阶段) | ocas-poly/sprs |
fast-poly | 否 | fast_polynomial Estrin 求值加速 | ocas-eval/fast-poly |
提示:
default = [],所有后端默认关闭以保持最大可移植性(包括 Windows MSVC)。推荐开发配置:features = ["gmp", "jit"]。
子页面索引
| 主题 | 文件 | 内容概述 |
|---|---|---|
| 表达式系统 | rust-expressions | Arena、Atom、AtomArena、Symbol、模式匹配、解析 |
| 系数域 | rust-domains | Domain trait、整数/有理数/有限域/实数球/复数/双精度/代数数域、假设系统 |
| 多项式 | rust-polynomials | 稠密一元、稀疏多元、有理分式域、单项式序 |
| 矩阵 | rust-matrix | Matrix(Bareiss 行列式、高斯消元) |
| 微积分 | rust-calculus | diff、integrate、taylor、积分管线分层 |
| 求解器 | rust-solvers | 线性方程组、丢番图、多项式系统、ODE 求解 |
| 重写与化简 | rust-rewrite | Pattern、Rule、simplify、transform、E-graph |
| 求值与 JIT | rust-evaluation | ExpressionEvaluator、JIT、SIMD、FunctionMap |
| 自动微分 | rust-autodiff | DualShape、HyperDual、DualCoeff |
| 张量 | rust-tensors | Tensor、缩并、对称性、规范化、Young 投影 |
| 数论 | rust-ntheory | 素性、分解、离散对数、CRT、数论函数、二次剩余 |
| Gröbner 基与理想 | rust-groebner | Buchberger/F4/F5、FGLM、理想运算、准素分解 |
| 因式分解 | rust-factoring | 一元/多元 ℤ[x]/𝔽ₚ[x] 因式分解、代数数域、有理函数 |
| 数值积分 | rust-numeric-integration.md | Vegas 蒙特卡洛、integrate_1d |
快速开始
use ocas::prelude::*; use ocas_core::arena::Arena; fn main() { // 1. 创建 arena 和表达式上下文 let arena = Arena::new(); let ctx = AtomArena::new(&arena); // 2. 构造表达式 sin(x) let x = ctx.var("x"); let expr = ctx.fun("sin", &[x]); // 3. 符号微分(diff 接受 Symbol 作为求导变量) let deriv = diff(&ctx, expr, Symbol::new("x")); assert_eq!(deriv.to_string(), "cos(x)"); // 4. 化简(空规则集保持表达式不变) let simplified = simplify(&ctx, expr, &[], 100); assert_eq!(simplified.to_string(), "sin(x)"); println!("d/dx sin(x) = {}", deriv); }
参见
- 架构概览 — 整体设计与数据流
- 数学基础总览 — 背景数学知识与学习路径
- Python API 参考 — Python 绑定
- C/C++ API 参考 — C/C++ 绑定
Rust API 参考:表达式系统
本章记录 oCAS 表达式系统的核心类型与函数,涵盖 Arena 分配器、符号、表达式节点、构造上下文、解析、规范化以及模式匹配。
核心类型概览
表达式系统的核心类型层次如下:
Arena (bump 分配器)
└── AtomArena (hash-consing 构造器)
└── Atom (Copy 句柄)
└── AtomNode (enum: Num/Var/Fun/Add/Mul/Pow)
└── Symbol (interned 字符串)
设计不变量:Atom 是一个 Copy 的 Arena 句柄,指向 Arena 中分配的 AtomNode。由于 AtomArena 使用 hash-consing(相同结构的子表达式在同一个 AtomArena 中返回同一个指针),结构相等等价于指针相等——== 的结果与指针比较一致。注意实现细节:Atom 的 PartialEq 是派生的结构比较(递归比较 AtomNode),hash-consing 保证其真值与指针相等一致,而非直接比较指针。减法和除法在解析时脱糖:$x - y$ 变为 Add([x, Mul([Num(-1), y])]),$x / y$ 变为 Mul([x, Pow(y, Num(-1))])。
Arena
#![allow(unused)] fn main() { use ocas_core::arena::Arena; }
Arena 是 bump 分配器,为表达式节点提供批量内存管理。所有表达式节点分配在 Arena 中,Arena 销毁时一次性释放全部内存。当前版本不运行析构函数,因此仅安全存储 Copy 类型。
Arena::new
#![allow(unused)] fn main() { pub fn new() -> Self }
功能:创建使用默认块大小(64 KiB)的 Arena。
参数:无。
返回值:Arena。
示例:
#![allow(unused)] fn main() { use ocas_core::arena::Arena; let arena = Arena::new(); let value = arena.allocate_with(|| 42); assert_eq!(*value, 42); }
Arena::with_capacity
#![allow(unused)] fn main() { pub fn with_capacity(block_size: usize) -> Self }
功能:创建使用指定块大小的 Arena。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
block_size | usize | 每个内存块的字节数。 |
返回值:Arena。
Arena::allocate_with
#![allow(unused)] fn main() { pub fn allocate_with<T>(&self, init: impl FnOnce() -> T) -> &mut T }
功能:在 Arena 中分配一个值,通过闭包构造,返回与 Arena 生命周期绑定的可变引用。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
init | impl FnOnce() -> T | 构造值的闭包。 |
返回值:&mut T。
错误:若 T 的大小为零则 panic。
示例:
#![allow(unused)] fn main() { use ocas_core::arena::Arena; let arena = Arena::new(); let value = arena.allocate_with(|| "hello"); assert_eq!(*value, "hello"); }
Arena::allocate_slice
#![allow(unused)] fn main() { pub fn allocate_slice<T: Copy>(&self, values: &[T]) -> &[T] }
功能:将一个切片复制到 Arena 中,返回与 Arena 生命周期绑定的切片引用。空切片返回 &[]。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
values | &[T] | 待分配的切片,T: Copy。 |
返回值:&[T]。
示例:
#![allow(unused)] fn main() { use ocas_core::arena::Arena; let arena = Arena::new(); let data = [1, 2, 3]; let slice = arena.allocate_slice(&data); assert_eq!(slice, &[1, 2, 3]); }
Arena::reset
#![allow(unused)] fn main() { pub fn reset(&self) }
功能:重置 Arena——保留第一个内存块并重置偏移量,释放其余块。重置后,之前分配的任何引用不得继续使用。
参数:无。
返回值:无。
示例:
#![allow(unused)] fn main() { use ocas_core::arena::Arena; let arena = Arena::new(); let _ = arena.allocate_with(|| 1); arena.reset(); let value = arena.allocate_with(|| 2); assert_eq!(*value, 2); }
Arena::chunk_count
#![allow(unused)] fn main() { pub fn chunk_count(&self) -> usize }
功能:返回 Arena 当前持有的内存块数量。
参数:无。
返回值:usize。
Symbol
#![allow(unused)] fn main() { use ocas_atom::Symbol; }
#![allow(unused)] fn main() { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct Symbol(&'static str); }
Symbol 是全局 interned 的符号名称,用于变量名、函数名和常量名。相同内容的 Symbol 在进程中指向同一块静态内存,比较为 $O(1)$ 指针比较。Symbol 实现了 Copy。
Symbol::new
#![allow(unused)] fn main() { pub fn new(name: &str) -> Self }
功能:创建或查找一个已存在的 Symbol。首次调用时 intern 字符串,后续调用返回已有的 Symbol。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
name | &str | 符号名称。 |
返回值:Symbol。
示例:
#![allow(unused)] fn main() { use ocas_atom::Symbol; let x = Symbol::new("x"); let also_x = Symbol::new("x"); assert_eq!(x, also_x); assert_eq!(x.as_str(), "x"); }
Symbol::as_str
#![allow(unused)] fn main() { pub fn as_str(&self) -> &str }
功能:返回符号的字符串切片。
参数:无。
返回值:&str。
Atom
#![allow(unused)] fn main() { use ocas_atom::Atom; }
#![allow(unused)] fn main() { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct Atom<'a>(&'a AtomNode<'a>); }
Atom 是指向 Arena 中 AtomNode 的轻量 Copy 句柄。复制 Atom 仅复制一个指针,不复制底层数据。由于 hash-consing,a == b 的真值与指针比较一致(同一 AtomArena 内);其实现是派生的结构比较。
Atom 实现了 Display,输出格式为:
Num(42)→42Var("x")→xFun("sin", [x])→sin(x)Add([x, y])→x + y(子表达式若非叶子节点则加括号)Mul([x, y])→x*yPow(x, n)→x^n(底数/指数若非叶子则加括号)
Atom::node
#![allow(unused)] fn main() { pub fn node(&self) -> &'a AtomNode<'a> }
功能:获取底层节点数据的引用。用于模式匹配表达式结构。
参数:无。
返回值:&'a AtomNode<'a>。
示例:
#![allow(unused)] fn main() { use ocas_atom::{AtomArena, AtomNode}; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let x = ctx.var("x"); assert!(matches!(x.node(), AtomNode::Var(_))); }
Atom::children
#![allow(unused)] fn main() { pub fn children(&self) -> &'a [Atom<'a>] }
功能:返回直接子表达式的切片(从左到右)。Num 和 Var 返回空切片;Fun、Add、Mul 返回参数切片;Pow 返回空切片(需使用 binary_children 获取两个操作数)。
参数:无。
返回值:&'a [Atom<'a>]。
示例:
#![allow(unused)] fn main() { use ocas_atom::AtomArena; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let x = ctx.var("x"); let y = ctx.var("y"); let sum = ctx.add(&[x, y, ctx.num(1)]); assert_eq!(sum.children().len(), 3); assert_eq!(x.children().len(), 0); }
Atom::binary_children
#![allow(unused)] fn main() { pub fn binary_children(&self) -> Option<(Atom<'a>, Atom<'a>)> }
功能:若此 Atom 是 Pow 节点,返回 (base, exp);否则返回 None。
参数:无。
返回值:Option<(Atom<'a>, Atom<'a>)>。
示例:
#![allow(unused)] fn main() { use ocas_atom::AtomArena; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let x = ctx.var("x"); let y = ctx.var("y"); let power = ctx.pow(x, y); let (base, exp) = power.binary_children().unwrap(); assert_eq!(base.to_string(), "x"); assert_eq!(exp.to_string(), "y"); }
AtomNode
#![allow(unused)] fn main() { use ocas_atom::AtomNode; }
#![allow(unused)] fn main() { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum AtomNode<'a> { Num(i64), Var(Symbol), Fun(Symbol, &'a [Atom<'a>]), Add(&'a [Atom<'a>]), Mul(&'a [Atom<'a>]), Pow(Atom<'a>, Atom<'a>), } }
表达式树中每个节点的具化数据。Atom 句柄通过 node() 方法获取 AtomNode 引用。
变体说明
| 变体 | 说明 |
|---|---|
Num(i64) | 64 位有符号整数字面量。 |
Var(Symbol) | 命名变量或常量。 |
Fun(Symbol, &'a [Atom<'a>]) | 函数应用。第一个参数是函数名,第二个是参数列表(至少一个元素)。 |
Add(&'a [Atom<'a>]) | 加法。参数列表至少一个元素。 |
Mul(&'a [Atom<'a>]) | 乘法。参数列表至少一个元素。 |
Pow(Atom<'a>, Atom<'a>) | 幂运算。第一个参数是底数,第二个是指数。 |
设计说明:
- 减法/除法在解析时脱糖:$x - y$ 变为
Add([x, Mul([Num(-1), y])]),$x / y$ 变为Mul([x, Pow(y, Num(-1))])。AST 中不存在独立的减法/除法节点。 Add和Mul的参数列表通过normalize后是排序的,确保结构相等的表达式产生相同的 AST。
示例:
#![allow(unused)] fn main() { use ocas_atom::{AtomArena, AtomNode}; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let x = ctx.var("x"); match x.node() { AtomNode::Var(s) => assert_eq!(s.as_str(), "x"), _ => panic!("expected variable"), } }
AtomArena
#![allow(unused)] fn main() { use ocas_atom::AtomArena; }
#![allow(unused)] fn main() { pub struct AtomArena<'a> { arena: &'a Arena, cons_table: RefCell<FastHashMap<AtomNode<'a>, Atom<'a>>>, } }
AtomArena 是构造 Atom 的唯一入口。它封装了一个 Arena 引用和一个 hash-consing 表(内部可变性通过 RefCell 实现)。所有构造方法从调用者角度看是不可变的。相同结构的子表达式在同一个 AtomArena 中始终返回同一个 Atom 句柄——这使得结构相等等价于指针相等。
AtomArena::new
#![allow(unused)] fn main() { pub fn new(arena: &'a Arena) -> Self }
功能:创建以给定 Arena 为后端的 AtomArena。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
arena | &'a Arena | bump 分配器引用。 |
返回值:AtomArena<'a>。
示例:
#![allow(unused)] fn main() { use ocas_atom::AtomArena; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let n = ctx.num(42); assert_eq!(n.to_string(), "42"); }
AtomArena::num
#![allow(unused)] fn main() { pub fn num(&self, value: i64) -> Atom<'a> }
功能:创建整数字面量 Atom。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
value | i64 | 整数值。 |
返回值:Atom<'a>——Num(value) 节点。
示例:
#![allow(unused)] fn main() { use ocas_atom::AtomArena; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let n = ctx.num(7); assert_eq!(n.to_string(), "7"); }
AtomArena::var
#![allow(unused)] fn main() { pub fn var(&self, name: &str) -> Atom<'a> }
功能:创建变量 Atom。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
name | &str | 变量名称。 |
返回值:Atom<'a>——Var(Symbol) 节点。
示例:
#![allow(unused)] fn main() { use ocas_atom::AtomArena; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let x = ctx.var("x"); assert_eq!(x.to_string(), "x"); }
AtomArena::fun
#![allow(unused)] fn main() { pub fn fun(&self, name: &str, args: &[Atom<'a>]) -> Atom<'a> }
功能:创建函数应用 Atom。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
name | &str | 函数名。 |
args | &[Atom<'a>] | 参数列表。debug 模式下为空会 panic。 |
返回值:Atom<'a>——Fun(Symbol, &[Atom]) 节点。
示例:
#![allow(unused)] fn main() { use ocas_atom::AtomArena; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let x = ctx.var("x"); let f = ctx.fun("sin", &[x]); assert_eq!(f.to_string(), "sin(x)"); }
AtomArena::add
#![allow(unused)] fn main() { pub fn add(&self, args: &[Atom<'a>]) -> Atom<'a> }
功能:创建加法 Atom。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
args | &[Atom<'a>] | 操作数列表。debug 模式下为空会 panic。 |
返回值:Atom<'a>——Add(&[Atom]) 节点。
示例:
#![allow(unused)] fn main() { use ocas_atom::AtomArena; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let x = ctx.var("x"); let y = ctx.var("y"); let sum = ctx.add(&[x, y]); assert_eq!(sum.to_string(), "x + y"); }
AtomArena::mul
#![allow(unused)] fn main() { pub fn mul(&self, args: &[Atom<'a>]) -> Atom<'a> }
功能:创建乘法 Atom。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
args | &[Atom<'a>] | 操作数列表。debug 模式下为空会 panic。 |
返回值:Atom<'a>——Mul(&[Atom]) 节点。
示例:
#![allow(unused)] fn main() { use ocas_atom::AtomArena; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let x = ctx.var("x"); let y = ctx.var("y"); let product = ctx.mul(&[x, y]); assert_eq!(product.to_string(), "x*y"); }
AtomArena::pow
#![allow(unused)] fn main() { pub fn pow(&self, base: Atom<'a>, exp: Atom<'a>) -> Atom<'a> }
功能:创建幂运算 Atom。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
base | Atom<'a> | 底数。 |
exp | Atom<'a> | 指数。 |
返回值:Atom<'a>——Pow(base, exp) 节点。
示例:
#![allow(unused)] fn main() { use ocas_atom::AtomArena; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let x = ctx.var("x"); let p = ctx.pow(x, ctx.num(3)); assert_eq!(p.to_string(), "x^3"); }
AtomArena::slice
#![allow(unused)] fn main() { pub fn slice(&self, atoms: &[Atom<'a>]) -> &'a [Atom<'a>] }
功能:将 Atom 切片分配到 Arena 中并返回引用。用于需要共享 Arena 生命周期的多结果场景(如 ODE 系统的分量解)。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
atoms | &[Atom<'a>] | 待分配的 Atom 切片。 |
返回值:&'a [Atom<'a>]。
函数
parse
#![allow(unused)] fn main() { use ocas_parse::parse; }
#![allow(unused)] fn main() { pub fn parse<'a>(ctx: &'a AtomArena<'a>, input: &str) -> Result<Atom<'a>, ParseError> }
功能:将数学表达式字符串解析为 Atom 树。支持整数、变量、函数调用(f(x))、加减乘除幂运算和括号。减法脱糖为加法+负系数,除法脱糖为乘法+负指数。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
ctx | &'a AtomArena<'a> | 用于分配节点的 arena 上下文。 |
input | &str | 待解析的表达式字符串。 |
返回值:Result<Atom<'a>, ParseError>。
错误:
| 变体 | 说明 |
|---|---|
ParseError::Lex(LexError) | 输入包含非法字符。 |
ParseError::UnexpectedEof | 输入意外结束(如 "x +")。 |
ParseError::UnexpectedToken | 遇到意外的 token(如 "*x")。 |
示例:
#![allow(unused)] fn main() { use ocas_atom::AtomArena; use ocas_core::arena::Arena; use ocas_parse::parse; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let expr = parse(&ctx, "x^2 + 2*x + 1").unwrap(); assert_eq!(expr.to_string(), "((x^2) + (2*x)) + 1"); }
参见:normalize
normalize
#![allow(unused)] fn main() { use ocas_atom::normalize::normalize; }
#![allow(unused)] fn main() { pub fn normalize<'a>(ctx: &AtomArena<'a>, atom: Atom<'a>) -> Atom<'a> }
功能:将表达式规范化为确定性标准形式。具体操作:
- 展平嵌套的
Add和Mul(如Add([Add([x, y]), z])→Add([x, y, z])) - 对操作数排序
- 合并数值系数(如
Add([x, Num(2), Num(3)])→Add([Num(5), x]))
结果分配在与输入相同的 arena 中。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
ctx | &AtomArena<'a> | 用于分配结果的 arena 上下文。 |
atom | Atom<'a> | 待规范化的表达式。 |
返回值:Atom<'a>——规范化后的表达式。
示例:
#![allow(unused)] fn main() { use ocas_atom::normalize::normalize; use ocas_atom::AtomArena; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let x = ctx.var("x"); let y = ctx.var("y"); let z = ctx.var("z"); let inner = ctx.add(&[x, y]); let outer = ctx.add(&[inner, z, ctx.num(2), ctx.num(3)]); let result = normalize(&ctx, outer); assert_eq!(result.to_string(), "5 + x + y + z"); }
substitute
#![allow(unused)] fn main() { use ocas_calc::series::substitute; }
#![allow(unused)] fn main() { pub fn substitute<'a>( ctx: &'a AtomArena<'a>, expr: Atom<'a>, var: Symbol, replacement: Atom<'a>, ) -> Atom<'a> }
功能:将表达式中所有出现的变量 var 替换为 replacement。基于 transform 实现的便捷函数。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
ctx | &'a AtomArena<'a> | arena 上下文。 |
expr | Atom<'a> | 待替换的表达式。 |
var | Symbol | 被替换的变量名。 |
replacement | Atom<'a> | 替换表达式。 |
返回值:Atom<'a>——替换后的表达式。
示例:
#![allow(unused)] fn main() { use ocas_atom::{AtomArena, Symbol}; use ocas_calc::series::substitute; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let x = ctx.var("x"); let y = ctx.var("y"); let expr = ctx.add(&[ctx.pow(x, ctx.num(2)), ctx.num(1)]); let result = substitute(&ctx, expr, Symbol::new("x"), y); assert_eq!(result.to_string(), "(y^2) + 1"); }
参见:transform
transform
#![allow(unused)] fn main() { use ocas_rewrite::transform; }
#![allow(unused)] fn main() { pub fn transform<'a, F>(ctx: &'a AtomArena<'a>, atom: Atom<'a>, mut f: F) -> Atom<'a> where F: FnMut(Atom<'a>) -> Option<Atom<'a>>, }
功能:自底向上遍历表达式树并对每个节点应用变换函数 f。f 在子节点已被变换之后调用。返回 Some(atom) 替换原节点,返回 None 保留原节点(已变换子节点的版本)。这是 oCAS 规则引擎和化简器使用的标准重写遍历模式。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
ctx | &'a AtomArena<'a> | arena 上下文。 |
atom | Atom<'a> | 待变换的表达式。 |
f | FnMut(Atom<'a>) -> Option<Atom<'a>> | 变换函数。 |
返回值:Atom<'a>——变换后的表达式。
示例:
#![allow(unused)] fn main() { use ocas_atom::{Atom, AtomArena, AtomNode}; use ocas_core::arena::Arena; use ocas_rewrite::transform; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let x = ctx.var("x"); let y = ctx.var("y"); let sum = ctx.add(&[x, y]); let result = transform(&ctx, sum, |a| { if let AtomNode::Add(args) = a.node() { if args.len() == 2 && args[0] == x && args[1] == y { return Some(ctx.add(&[y, x])); } } None }); assert_eq!(result.to_string(), "y + x"); }
collect_funs
#![allow(unused)] fn main() { use ocas_atom::walk::collect_funs; }
#![allow(unused)] fn main() { pub fn collect_funs<'a>(atom: Atom<'a>) -> Vec<(Symbol, Atom<'a>)> }
功能:收集表达式中所有函数应用,按后序(最内层优先)返回,去重(hash-consing 保证结构相同的应用只出现一次)。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
atom | Atom<'a> | 待遍历的表达式。 |
返回值:Vec<(Symbol, Atom<'a>)>——(函数名, 函数应用节点) 列表。
示例:
#![allow(unused)] fn main() { use ocas_atom::{AtomArena, walk::collect_funs}; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let x = ctx.var("x"); let sin_x = ctx.fun("sin", &[x]); let expr = ctx.fun("cos", &[sin_x]); let funs = collect_funs(expr); assert_eq!(funs.len(), 2); assert_eq!(funs[0].0.as_str(), "sin"); assert_eq!(funs[1].0.as_str(), "cos"); }
参见:collect_vars
collect_vars
#![allow(unused)] fn main() { use ocas_atom::walk::collect_vars; }
#![allow(unused)] fn main() { pub fn collect_vars(atom: Atom) -> Vec<Symbol> }
功能:收集表达式中所有不同的变量名,按首次出现顺序(深度优先、从左到右)返回。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
atom | Atom | 待遍历的表达式。 |
返回值:Vec<Symbol>——变量名列表。
示例:
#![allow(unused)] fn main() { use ocas_atom::{AtomArena, walk::collect_vars}; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let x = ctx.var("x"); let y = ctx.var("y"); let expr = ctx.add(&[ctx.mul(&[x, y]), x]); let vars = collect_vars(expr); assert_eq!(vars.len(), 2); assert_eq!(vars[0].as_str(), "x"); assert_eq!(vars[1].as_str(), "y"); }
参见:collect_funs
模式匹配
模式匹配系统允许用带通配符的模式(Pattern)匹配 Atom 表达式树,支持结合律/交换律(AC)匹配和回溯预算控制。
Pattern
#![allow(unused)] fn main() { use ocas_rewrite::pattern::Pattern; }
#![allow(unused)] fn main() { #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Pattern<'a> { Literal(Atom<'a>), Wildcard(Symbol, WildcardLevel), Add(Vec<Pattern<'a>>), Mul(Vec<Pattern<'a>>), Pow(Box<(Pattern<'a>, Pattern<'a>)>), Fun(Symbol, Vec<Pattern<'a>>), } }
模式镜像了 AtomNode 的结构,但增加通配符节点。Add 和 Mul 的匹配支持结合律-交换律(AC):模式中的子模式可以匹配参数列表的任意子集。
| 变体 | 说明 |
|---|---|
Literal(Atom) | 精确匹配给定的 Atom。 |
Wildcard(Symbol, WildcardLevel) | 通配符匹配,名称和级别决定绑定行为。 |
Add(Vec<Pattern>) | 匹配 Add 节点,参数列表 AC 匹配。 |
Mul(Vec<Pattern>) | 匹配 Mul 节点,参数列表 AC 匹配。 |
Pow(Box<(Pattern, Pattern)>) | 匹配 Pow 节点,底数和指数分别匹配。 |
Fun(Symbol, Vec<Pattern>) | 匹配 Fun 节点,函数名和参数列表分别匹配。 |
Pattern::from_atom
#![allow(unused)] fn main() { pub fn from_atom(_ctx: &'a impl PatternAlloc<'a>, atom: Atom<'a>) -> Pattern<'a> }
功能:将 Atom 转换为 Pattern。名称以 _ 结尾(或以 _ 开头)的变量被视为通配符:
x_→Wildcard(Symbol("x"), Single)x__→Wildcard(Symbol("x"), Sequence)x___→Wildcard(Symbol("x"), NullSequence)
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
_ctx | &'a impl PatternAlloc<'a> | 模式分配器(通常传 &())。 |
atom | Atom<'a> | 待转换的表达式。 |
返回值:Pattern<'a>。
示例:
#![allow(unused)] fn main() { use ocas_atom::AtomArena; use ocas_core::arena::Arena; use ocas_rewrite::pattern::{Pattern, WildcardLevel}; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let x = ctx.var("x_"); let pat = Pattern::from_atom(&(), x); assert!(matches!(pat, Pattern::Wildcard(s, WildcardLevel::Single) if s.as_str() == "x")); }
参见:WildcardLevel、match_pattern
WildcardLevel
#![allow(unused)] fn main() { use ocas_rewrite::pattern::WildcardLevel; }
#![allow(unused)] fn main() { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum WildcardLevel { Single, Sequence, NullSequence, } }
通配符的匹配范围。
| 变体 | 命名约定 | 说明 |
|---|---|---|
Single | 名称以单个 _ 结尾(如 x_;以 _ 开头亦可,如 _x) | 匹配恰好一个 Atom。 |
Sequence | 名称以 __ 结尾(如 x__;以 __ 开头亦可,如 __x) | 匹配 Add/Mul/Fun 参数列表中的一个或多个 Atom。 |
NullSequence | 名称以 ___ 结尾(如 x___;以 ___ 开头亦可,如 ___x) | 匹配零个或多个 Atom。 |
示例:
#![allow(unused)] fn main() { use ocas_rewrite::pattern::WildcardLevel; assert!(matches!(WildcardLevel::Single, WildcardLevel::Single)); assert!(matches!(WildcardLevel::Sequence, WildcardLevel::Sequence)); assert!(matches!(WildcardLevel::NullSequence, WildcardLevel::NullSequence)); }
match_pattern
#![allow(unused)] fn main() { use ocas_rewrite::matcher::match_pattern; }
#![allow(unused)] fn main() { pub fn match_pattern<'a>(pattern: Pattern<'a>, atom: Atom<'a>) -> Result<Bindings<'a>, MatchError> }
功能:尝试将 pattern 与 atom 匹配,成功时返回绑定集合。使用默认回溯预算(DEFAULT_MAX_BACKTRACKS = 10_000)。Add/Mul 使用 AC 匹配(全回溯搜索),Fun 使用有序匹配(支持序列通配符)。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
pattern | Pattern<'a> | 匹配模式。 |
atom | Atom<'a> | 被匹配的表达式。 |
返回值:Result<Bindings<'a>, MatchError>。
示例:
#![allow(unused)] fn main() { use ocas_atom::{AtomArena, Symbol}; use ocas_core::arena::Arena; use ocas_rewrite::pattern::{Pattern, WildcardLevel}; use ocas_rewrite::matcher::match_pattern; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let x = ctx.var("x"); let sum = ctx.add(&[x, ctx.num(1)]); let pat = Pattern::Add(vec![ Pattern::Wildcard(Symbol::new("a"), WildcardLevel::Single), Pattern::Literal(ctx.num(1)), ]); let bindings = match_pattern(pat, sum).unwrap(); }
match_pattern_with_budget
#![allow(unused)] fn main() { use ocas_rewrite::matcher::match_pattern_with_budget; }
#![allow(unused)] fn main() { pub fn match_pattern_with_budget<'a>( pattern: Pattern<'a>, atom: Atom<'a>, max_backtracks: usize, ) -> Result<Bindings<'a>, MatchError> }
功能:与 match_pattern 相同,但允许自定义回溯预算,防止病态输入导致指数时间爆炸。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
pattern | Pattern<'a> | 匹配模式。 |
atom | Atom<'a> | 被匹配的表达式。 |
max_backtracks | usize | 最大回溯次数。 |
返回值:Result<Bindings<'a>, MatchError>。
错误:若回溯次数超限,返回 MatchError::BudgetExhausted。
Bindings
#![allow(unused)] fn main() { use ocas_rewrite::matcher::Bindings; }
#![allow(unused)] fn main() { #[derive(Debug, Clone, Default)] pub struct Bindings<'a> { /* 私有字段 */ } }
成功匹配后返回的通配符绑定集合。
Bindings::new
#![allow(unused)] fn main() { pub fn new() -> Self }
功能:创建空的绑定集合。
Bindings::get
#![allow(unused)] fn main() { pub fn get(&self, name: Symbol) -> Option<&MatchValue<'a>> }
功能:按通配符名称查找绑定值。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
name | Symbol | 通配符名称(不含尾部下划线)。 |
返回值:Option<&MatchValue<'a>>。
示例:
#![allow(unused)] fn main() { use ocas_atom::{AtomArena, Symbol}; use ocas_core::arena::Arena; use ocas_rewrite::pattern::{Pattern, WildcardLevel}; use ocas_rewrite::matcher::match_pattern; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let x = ctx.var("x"); let y = ctx.var("y"); let sum = ctx.add(&[x, y]); let pat = Pattern::Add(vec![ Pattern::Wildcard(Symbol::new("a"), WildcardLevel::Single), Pattern::Wildcard(Symbol::new("b"), WildcardLevel::Single), ]); let bindings = match_pattern(pat, sum).unwrap(); let val = bindings.get(Symbol::new("a")).unwrap(); }
MatchValue
#![allow(unused)] fn main() { use ocas_rewrite::matcher::MatchValue; }
#![allow(unused)] fn main() { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum MatchValue<'a> { Single(Atom<'a>), Sequence(&'a [Atom<'a>]), } }
通配符绑定的值类型。
| 变体 | 说明 |
|---|---|
Single(Atom<'a>) | Single 级别通配符绑定到单个 Atom。 |
Sequence(&'a [Atom<'a>]) | Sequence 或 NullSequence 级别通配符绑定到一个 Atom 切片。 |
MatchError
#![allow(unused)] fn main() { use ocas_rewrite::matcher::MatchError; }
#![allow(unused)] fn main() { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum MatchError { NoMatch, InconsistentBinding, BudgetExhausted, } }
模式匹配错误。
| 变体 | Display | 说明 |
|---|---|---|
NoMatch | "pattern did not match" | 模式不匹配目标表达式。 |
InconsistentBinding | "inconsistent wildcard binding" | 同一通配符名称在不同位置绑定到不同值。 |
BudgetExhausted | "backtrack budget exhausted" | AC 匹配的回溯预算耗尽。表示输入可能导致指数时间匹配,应增大预算或简化模式。 |
参见
- 重写与化简——
Rule、simplify、replace_all等完整重写引擎 - 微积分——
diff、integrate、taylor等基于表达式系统的运算 - 系数域——
Domaintrait 和各种域类型
Rust API 参考:系数域
系数域是 oCAS 所有多项式、矩阵和求解器算法的基础抽象。每个域描述一组元素及其算术运算,通过 Domain 和 EuclideanDomain trait 统一接口。
模块路径:ocas_domain
导入方式:
#![allow(unused)] fn main() { use ocas_domain::{ Domain, EuclideanDomain, Integer, IntegerDomain, Rational, RationalDomain, FiniteField, FiniteFieldElement, RealBall, RealBallDomain, Complex, ComplexDomain, DoubleF64, DoubleF64Domain, AlgebraicExtension, AlgebraicNumberField, AlgebraicElement, }; use ocas_domain::assumptions::{Assumption, Assumptions, SymbolAssumptions}; }
Domain trait
签名:
#![allow(unused)] fn main() { pub trait Domain: Clone + PartialEq + Eq + std::fmt::Debug + Sized { type Element: Clone + PartialEq + Eq + std::fmt::Debug + 'static; fn zero(&self) -> Self::Element; fn one(&self) -> Self::Element; fn add(&self, a: &Self::Element, b: &Self::Element) -> Self::Element; fn sub(&self, a: &Self::Element, b: &Self::Element) -> Self::Element; fn neg(&self, a: &Self::Element) -> Self::Element; fn mul(&self, a: &Self::Element, b: &Self::Element) -> Self::Element; fn div(&self, a: &Self::Element, b: &Self::Element) -> Option<Self::Element>; fn inv(&self, a: &Self::Element) -> Option<Self::Element>; fn is_zero(&self, a: &Self::Element) -> bool; fn is_one(&self, a: &Self::Element) -> bool; fn mul_assign(&self, a: &mut Self::Element, b: &Self::Element); fn sub_mul_assign(&self, a: &mut Self::Element, b: &Self::Element, c: &Self::Element); fn pow(&self, a: &Self::Element, n: u64) -> Self::Element; fn cast_u64(&self, n: u64) -> Self::Element; } }
功能:系数域的核心 trait。域对象本身可以携带参数(如有限域的模数),因此所有运算通过 &self 进行。这与 Flint、SymPy Domain 等 CAS 库的"域对象"模式一致。
关联类型:
| 关联类型 | 约束 | 说明 |
|---|---|---|
Element | Clone + PartialEq + Eq + Debug + 'static | 域中元素的类型 |
方法:
| 方法 | 签名 | 说明 |
|---|---|---|
zero | fn zero(&self) -> Self::Element | 加法单位元 |
one | fn one(&self) -> Self::Element | 乘法单位元 |
add | fn add(&self, a, b) -> Self::Element | 加法 |
sub | fn sub(&self, a, b) -> Self::Element | 减法 |
neg | fn neg(&self, a) -> Self::Element | 取反 |
mul | fn mul(&self, a, b) -> Self::Element | 乘法 |
div | fn div(&self, a, b) -> Option<Self::Element> | 除法。b 为零或除法不精确时返回 None |
inv | fn inv(&self, a) -> Option<Self::Element> | 乘法逆元。a 为零时返回 None |
is_zero | fn is_zero(&self, a) -> bool | 判断是否为加法单位元(默认 *a == self.zero()) |
is_one | fn is_one(&self, a) -> bool | 判断是否为乘法单位元(默认 *a == self.one()) |
mul_assign | fn mul_assign(&self, a: &mut E, b: &E) | 原地乘法 *a *= b。默认创建新元素;GMP 等高性能域可覆盖 |
sub_mul_assign | fn sub_mul_assign(&self, a: &mut E, b: &E, c: &E) | 融合减乘 *a -= b * c。F4 行阶梯化中广泛使用 |
pow | fn pow(&self, a, n: u64) -> Self::Element | 非负整数次幂。默认使用二进制快速幂;有限域覆盖为 modpow |
cast_u64 | fn cast_u64(&self, n: u64) -> Self::Element | 将 u64 转换为域元素。默认逐次加一 |
示例:
#![allow(unused)] fn main() { use ocas_domain::{Domain, Integer, IntegerDomain}; let domain = IntegerDomain; let a = Integer::from(3); let b = Integer::from(5); assert_eq!(domain.add(&a, &b), Integer::from(8)); assert_eq!(domain.mul(&a, &b), Integer::from(15)); assert_eq!(domain.pow(&a, 3), Integer::from(27)); // 输出:所有断言通过 }
EuclideanDomain trait
签名:
#![allow(unused)] fn main() { pub trait EuclideanDomain: Domain { fn div_rem(&self, a: &Self::Element, b: &Self::Element) -> Option<(Self::Element, Self::Element)>; fn gcd(&self, a: &Self::Element, b: &Self::Element) -> Self::Element; fn extended_gcd(&self, a: &Self::Element, b: &Self::Element) -> (Self::Element, Self::Element, Self::Element); } }
功能:支持带余除法的欧几里得域。Domain 的扩展,额外提供 div_rem(带余除法)、gcd(最大公因子)和 extended_gcd(扩展欧几里得算法)。
方法:
div_rem
签名:fn div_rem(&self, a: &Self::Element, b: &Self::Element) -> Option<(Self::Element, Self::Element)>
功能:带余除法,返回 (商, 余数),满足 a = 商 * b + 余数,且 余数 == 0 或 deg(余数) < deg(b)。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
a | &Self::Element | 被除数 |
b | &Self::Element | 除数 |
返回值:Some((quotient, remainder)),b 为零时返回 None。
示例:
#![allow(unused)] fn main() { use ocas_domain::{EuclideanDomain, Integer, IntegerDomain}; let domain = IntegerDomain; let a = Integer::from(17); let b = Integer::from(5); let (q, r) = domain.div_rem(&a, &b).unwrap(); assert_eq!(q, Integer::from(3)); assert_eq!(r, Integer::from(2)); // 输出:17 = 3 × 5 + 2 }
gcd
签名:fn gcd(&self, a: &Self::Element, b: &Self::Element) -> Self::Element
功能:计算最大公因子。默认实现使用欧几里得算法。
返回值:gcd(a, b)。对于域(如 FiniteField),两个非零元素的 GCD 退化为 1。
示例:
#![allow(unused)] fn main() { use ocas_domain::{EuclideanDomain, Integer, IntegerDomain}; let domain = IntegerDomain; let a = Integer::from(12); let b = Integer::from(8); let g = domain.gcd(&a, &b); assert_eq!(g, Integer::from(4)); // 输出:gcd(12, 8) = 4 }
extended_gcd
签名:fn extended_gcd(&self, a: &Self::Element, b: &Self::Element) -> (Self::Element, Self::Element, Self::Element)
功能:扩展欧几里得算法,返回 (g, x, y) 满足 g = gcd(a, b) = a * x + b * y。
返回值:三元组 (g, x, y)。
示例:
#![allow(unused)] fn main() { use ocas_domain::{EuclideanDomain, Integer, IntegerDomain}; let domain = IntegerDomain; let a = Integer::from(35); let b = Integer::from(15); let (g, x, y) = domain.extended_gcd(&a, &b); // g = 5, x = 1, y = -2,满足 5 = 35×1 + 15×(-2) assert_eq!(g, Integer::from(5)); }
参见:Domain
Integer / IntegerDomain
IntegerDomain
签名:pub struct IntegerDomain;
功能:整数域 $\mathbb{Z}$,元素类型为 Integer。实现 Domain 和 EuclideanDomain。
特征:
div要求精确除法(余数为零),否则返回Noneinv仅对 $\pm 1$ 返回结果,其余返回Nonepow使用二进制快速幂
示例:
#![allow(unused)] fn main() { use ocas_domain::{Domain, EuclideanDomain, Integer, IntegerDomain}; let domain = IntegerDomain; let a = Integer::from(10); let b = Integer::from(3); // 精确除法:10/3 不精确 assert!(domain.div(&a, &b).is_none()); // 带余除法 let (q, r) = domain.div_rem(&a, &b).unwrap(); assert_eq!(q, Integer::from(3)); assert_eq!(r, Integer::from(1)); // 逆元:只有 ±1 有逆 assert!(domain.inv(&Integer::from(1)).is_some()); assert!(domain.inv(&Integer::from(5)).is_none()); // 输出:所有断言通过 }
Integer
签名:pub struct Integer(BigInt);
功能:任意精度整数。默认构建使用 num-bigint 的 BigInt;启用 gmp feature 时使用 GMP 后端。
Derive:Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash
构造与转换
Integer::new
签名:pub fn new<T: Into<BigInt>>(value: T) -> Self
功能:从机器整数或 BigInt 创建任意精度整数。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
value | T: Into<BigInt> | 整数值(i32、i64、u64、BigInt 等) |
示例:
#![allow(unused)] fn main() { use ocas_domain::Integer; let a = Integer::new(42); let b = Integer::new(100_i64); assert_eq!(a.to_string(), "42"); // 输出:42 }
Integer::from
签名:impl From<i64> for Integer / impl From<BigInt> for Integer
功能:从 i64 或 BigInt 转换。
示例:
#![allow(unused)] fn main() { use ocas_domain::Integer; let a = Integer::from(42); assert_eq!(a.to_string(), "42"); // 输出:42 }
访问器
Integer::inner
签名:pub fn inner(&self) -> &BigInt
功能:访问底层 BigInt 引用。
Integer::to_bigint
签名:pub fn to_bigint(&self) -> BigInt
功能:克隆为 BigInt(不论后端)。
Integer::to_i64
签名:pub fn to_i64(&self) -> Option<i64>
功能:尝试转换为 i64。溢出时返回 None。
运算方法
Integer::pow_u32
签名:pub fn pow_u32(&self, exp: u32) -> Self
功能:计算 $n^{exp}$。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
exp | u32 | 非负指数 |
示例:
#![allow(unused)] fn main() { use ocas_domain::Integer; let a = Integer::from(2); assert_eq!(a.pow_u32(10).to_string(), "1024"); // 输出:1024 }
Integer::modpow
签名:pub fn modpow(&self, exp: &Integer, modulus: &Integer) -> Integer
功能:模幂运算 $self^{exp} \bmod modulus$。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
exp | &Integer | 指数(非负) |
modulus | &Integer | 模数(正) |
示例:
#![allow(unused)] fn main() { use ocas_domain::Integer; let base = Integer::from(3); let exp = Integer::from(100); let modulus = Integer::from(7); let result = base.modpow(&exp, &modulus); assert_eq!(result.to_string(), "4"); // 输出:3^100 mod 7 = 4 }
Integer::mod_floor
签名:pub fn mod_floor(&self, modulus: &Integer) -> Integer
功能:地板模运算,结果 $r$ 满足 $0 \leq r < |modulus|$。
示例:
#![allow(unused)] fn main() { use ocas_domain::Integer; let a = Integer::from(-7); let m = Integer::from(3); assert_eq!(a.mod_floor(&m).to_string(), "2"); // 输出:-7 mod_floor 3 = 2 }
Integer::div_rem
签名:pub fn div_rem(&self, other: &Integer) -> (Integer, Integer)
功能:带余除法 (商, 余数)。注意:与 EuclideanDomain::div_rem 不同,此方法不要求 other 非零(行为由底层 BigInt 决定)。
Integer::is_even
签名:pub fn is_even(&self) -> bool
功能:判断是否为偶数。
Integer::is_negative
签名:pub fn is_negative(&self) -> bool
功能:判断是否为负数。
Integer::is_zero
签名:pub fn is_zero(&self) -> bool
功能:判断是否为零。
Integer::is_one
签名:pub fn is_one(&self) -> bool
功能:判断是否为一。
Integer::abs
签名:pub fn abs(&self) -> Integer
功能:绝对值。
Integer::sqrt
签名:pub fn sqrt(&self) -> Integer
功能:整数平方根(向下取整)。
示例:
#![allow(unused)] fn main() { use ocas_domain::Integer; let a = Integer::from(10); assert_eq!(a.sqrt().to_string(), "3"); // 输出:sqrt(10) = 3 }
运算符
Integer 实现了以下标准运算符 trait(支持所有引用组合:owned × owned、owned × &、& × owned、& × &):
| Trait | 运算 |
|---|---|
Add | + |
Sub | - |
Mul | * |
Div | /(整数除法,截断向零) |
Rem | % |
Neg | 一元 - |
Shr<u32> / ShrAssign<u32> | 右移 |
AddAssign<&Integer> / SubAssign<&Integer> / MulAssign<&Integer> / DivAssign<&Integer> | 复合赋值 |
Rational / RationalDomain
RationalDomain
签名:pub struct RationalDomain;
功能:有理数域 $\mathbb{Q}$,元素类型为 Rational。实现 Domain 和 EuclideanDomain。
特征:
div对所有非零除数精确返回结果inv对所有非零元素返回结果EuclideanDomain的div_rem对有理数域退化:余数恒为零
示例:
#![allow(unused)] fn main() { use ocas_domain::{Domain, Rational, RationalDomain}; let domain = RationalDomain; let a = Rational::new(1, 2); let b = Rational::new(1, 3); let sum = domain.add(&a, &b); assert_eq!(sum, Rational::new(5, 6)); // 输出:1/2 + 1/3 = 5/6 }
Rational
签名:pub struct Rational(BigRational);
功能:任意精度有理数。默认构建使用 num-rational 的 BigRational;启用 gmp feature 时使用 GMP 后端。
Derive:Debug, Clone, PartialEq, Eq, Hash
构造
Rational::new
签名:pub fn new(numer: i64, denom: i64) -> Self
功能:从分子和分母(i64)创建有理数。自动约简至最简分数。分母为零时行为由底层库决定。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
numer | i64 | 分子 |
denom | i64 | 分母(非零) |
示例:
#![allow(unused)] fn main() { use ocas_domain::Rational; let a = Rational::new(3, 6); assert_eq!(a.to_string(), "1/2"); // 输出:3/6 自动约简为 1/2 }
Rational::from_bigints
签名:pub fn from_bigints(numer: BigInt, denom: BigInt) -> Self
功能:从任意精度整数分子和分母创建有理数。
Rational::from_integer
签名:pub fn from_integer(n: Integer) -> Self
功能:从整数创建有理数(分母 = 1)。
访问器
Rational::inner
签名:pub fn inner(&self) -> &BigRational
功能:访问底层 BigRational 引用。
Rational::numer
签名:pub fn numer(&self) -> Integer
功能:返回分子(作为 Integer)。
Rational::denom
签名:pub fn denom(&self) -> Integer
功能:返回分母(作为 Integer,始终为正)。
示例:
#![allow(unused)] fn main() { use ocas_domain::Rational; let r = Rational::new(3, 4); assert_eq!(r.numer().to_string(), "3"); assert_eq!(r.denom().to_string(), "4"); // 输出:分子 = 3,分母 = 4 }
运算符
Rational 实现了以下标准运算符 trait:
| Trait | 运算 |
|---|---|
Add | + |
Sub | - |
Mul | * |
Div | / |
Neg | 一元 - |
AddAssign / SubAssign / MulAssign / DivAssign | 复合赋值 |
FiniteField / FiniteFieldElement
FiniteField
签名:pub struct FiniteField { /* prime: BigInt, ... */ }
功能:素数有限域 $\mathbb{Z}/p\mathbb{Z}$。算术使用任意精度整数,支持大素数。实现 Domain 和 EuclideanDomain。
构造
FiniteField::new
签名:pub fn new(prime: BigInt) -> Self
功能:以给定素数为模创建有限域。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
prime | BigInt | 模数(要求 $\geq 2$,不验证素性) |
错误:debug 模式下若 prime < 2 会 panic。
示例:
#![allow(unused)] fn main() { use num_bigint::BigInt; use ocas_domain::{Domain, FiniteField}; let f = FiniteField::new(BigInt::from(7)); let a = f.element(3); let b = f.element(5); assert_eq!(f.add(&a, &b), f.element(1)); // 3 + 5 = 1 (mod 7) assert_eq!(f.mul(&a, &b), f.element(1)); // 3 × 5 = 1 (mod 7) // 输出:所有断言通过 }
元素构造
FiniteField::element
签名:pub fn element(&self, value: impl Into<BigInt>) -> FiniteFieldElement
功能:从任意整数创建域元素。值自动规约到 $[0, p-1]$ 范围。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
value | impl Into<BigInt> | 整数值(可超出 $[0, p-1]$,自动取模) |
示例:
#![allow(unused)] fn main() { use num_bigint::BigInt; use ocas_domain::{Domain, FiniteField}; let f = FiniteField::new(BigInt::from(7)); let a = f.element(10); assert_eq!(a.value().to_string(), "3"); // 输出:10 mod 7 = 3 }
FiniteField::from_i64
签名:pub fn from_i64(&self, val: i64) -> FiniteFieldElement
功能:从 i64 创建域元素(规约 mod p)。
访问器
FiniteField::prime
签名:pub fn prime(&self) -> &BigInt
功能:返回域的模数。
FiniteField::prime_u64
签名:pub fn prime_u64(&self) -> u64
功能:将模数作为 u64 返回。
错误:若素数不适用 u64 表示则 panic。
FiniteField::to_i64
签名:pub fn to_i64(&self, a: &FiniteFieldElement) -> i64
功能:将域元素转换为 i64(范围 $[0, p)$)。
错误:若素数不适用 u64 表示则 panic。
Domain 实现
div通过 Fermat 小定理求逆:$a^{-1} \equiv a^{p-2} \pmod{p}$inv同上,零元素返回Nonepow使用modpow优化(远快于默认二进制幂)
EuclideanDomain 实现
div_rem:域中除法精确,余数恒为零gcd:域中 GCD 退化——两者均为零时返回 0,否则返回 1
FiniteFieldElement
签名:pub struct FiniteFieldElement { value: BigInt }
功能:素数有限域的元素。值始终在 $[0, p-1]$ 范围内。
Derive:Debug, Clone, PartialEq, Eq, Hash
FiniteFieldElement::value
签名:pub fn value(&self) -> &BigInt
功能:返回 $[0, p-1]$ 范围内的标准代表元。
示例:
#![allow(unused)] fn main() { use num_bigint::BigInt; use ocas_domain::FiniteField; let f = FiniteField::new(BigInt::from(7)); let a = f.element(-3); assert_eq!(a.value().to_string(), "4"); // 输出:-3 mod 7 = 4 }
参见:FiniteField
RealBall / RealBallDomain
RealBallDomain
签名:pub struct RealBallDomain;
功能:实数球(区间)域。元素类型为 RealBall。仅实现 Domain(不实现 EuclideanDomain,因为实数球不支持精确带余除法)。
注意:默认构建使用轻量 f64 球,适合模板和演示。启用 mpfr feature 后使用 rug::Float 和有向舍入产生严格区间。
RealBall
签名:pub struct RealBall { mid: f64, rad: f64 }(默认构建)
功能:实数球:中点 ± 半径。真实值保证包含在 $[mid - rad, mid + rad]$ 中。
Derive:Debug, Clone, PartialEq, Eq;默认构建额外实现 Copy。
构造
RealBall::new
签名(默认):pub fn new(mid: f64, rad: f64) -> Self
功能:从中点和半径创建球。半径被钳制为非负。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
mid | f64(或 rug::Float) | 中点 |
rad | f64(或 rug::Float) | 半径($\geq 0$) |
RealBall::from_f64
签名:pub fn from_f64(value: f64) -> Self
功能:从 f64 值创建零半径球(精确值)。
示例:
#![allow(unused)] fn main() { use ocas_domain::RealBall; let ball = RealBall::from_f64(3.14); assert_eq!(ball.mid(), 3.14); assert_eq!(ball.rad(), 0.0); // 输出:精确球 }
访问器
RealBall::mid
签名:pub fn mid(&self) -> f64(默认) / pub fn mid(&self) -> &rug::Float(mpfr)
功能:返回中点。
RealBall::rad
签名:pub fn rad(&self) -> f64(默认) / pub fn rad(&self) -> &rug::Float(mpfr)
功能:返回半径。
RealBall::lower
签名:pub fn lower(&self) -> f64(默认) / pub fn lower(&self) -> rug::Float(mpfr)
功能:返回保守下界 $mid - rad$(mpfr 版本使用向下舍入)。
RealBall::upper
签名:pub fn upper(&self) -> f64(默认) / pub fn upper(&self) -> rug::Float(mpfr)
功能:返回保守上界 $mid + rad$(mpfr 版本使用向上舍入)。
RealBall::precision
签名:pub fn precision(&self) -> u32(仅 mpfr feature)
功能:返回 MPFR 后端的精度(位数)。
Domain 实现
add:$(a \pm r_a) + (b \pm r_b) = (a+b) \pm (r_a + r_b)$(mpfr 版本额外加舍入误差)sub:类似加法,半径相加mul:四角法——计算四个极端乘积 $a_lo \cdot b_lo$、$a_lo \cdot b_hi$ 等,取最小/最大值得到新区间div:$a / b = a \cdot b^{-1}$,当球包含零时返回Noneinv:$1/(mid \pm rad)$,当球包含零时返回None
示例:
#![allow(unused)] fn main() { use ocas_domain::{Domain, RealBall, RealBallDomain}; let domain = RealBallDomain; let a = RealBall::from_f64(2.0); let b = RealBall::from_f64(3.0); let prod = domain.mul(&a, &b); assert!(prod.lower() <= 6.0 && 6.0 <= prod.upper()); // 输出:乘积球包含真值 6.0 }
参见:Domain
Complex / ComplexDomain
ComplexDomain
签名:pub struct ComplexDomain<D: Domain> { base: D, ... }
功能:在任意基域 $D$ 上构造的复数域。元素类型为 Complex<D>。仅实现 Domain。
构造
ComplexDomain::new
签名:pub fn new(base: D) -> Self
功能:在基域 base 上创建复数域。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
base | D: Domain | 基域(如 IntegerDomain、RationalDomain) |
ComplexDomain::base
签名:pub fn base(&self) -> &D
功能:返回基域引用。
ComplexDomain::real_element
签名:pub fn real_element(&self, re: D::Element) -> Complex<D>
功能:创建纯实数元素。
ComplexDomain::imag_element
签名:pub fn imag_element(&self, im: D::Element) -> Complex<D>
功能:创建纯虚数元素。
Domain 实现
mul:$(a+bi)(c+di) = (ac-bd) + (ad+bc)i$div:$(a+bi)/(c+di) = \frac{(ac+bd)+(bc-ad)i}{c^2+d^2}$,分母为零时返回Noneinv:通过div(one, a)实现
示例:
#![allow(unused)] fn main() { use ocas_domain::{Complex, ComplexDomain, Domain, Integer, IntegerDomain}; let domain = ComplexDomain::new(IntegerDomain); let a = Complex::new(Integer::from(1), Integer::from(2)); let b = Complex::new(Integer::from(3), Integer::from(4)); let sum = domain.add(&a, &b); assert_eq!(*sum.re(), Integer::from(4)); assert_eq!(*sum.im(), Integer::from(6)); // 输出:(1+2i) + (3+4i) = 4+6i }
参见:Domain
Complex
签名:pub struct Complex<D: Domain> { inner: NumComplex<D::Element> }
功能:复数,实部和虚部属于基域 $D$。
Derive:Debug, Clone, PartialEq, Eq, Hash
构造与访问
Complex::new
签名:pub fn new(real: D::Element, imag: D::Element) -> Self
功能:从实部和虚部创建复数。
Complex::re
签名:pub fn re(&self) -> &D::Element
功能:返回实部引用。
Complex::im
签名:pub fn im(&self) -> &D::Element
功能:返回虚部引用。
Complex::inner
签名:pub fn inner(&self) -> &NumComplex<D::Element>
功能:返回底层 num_complex::Complex 引用。
DoubleF64 / DoubleF64Domain
DoubleF64Domain
签名:pub struct DoubleF64Domain;
功能:双精度浮点域。元素类型为 DoubleF64。仅实现 Domain。
DoubleF64
签名:
#![allow(unused)] fn main() { pub struct DoubleF64 { pub hi: f64, // 高阶分量(主值) pub lo: f64, // 低阶分量(误差项) } }
功能:双精度浮点数,表示为 $hi + lo$,满足 $|lo| \leq 0.5 \cdot \text{ulp}(hi)$。提供约 31 位十进制有效数字(~84 位二进制),约为单个 f64 精度的两倍。
Derive:Debug, Clone, Copy, PartialEq;实现 Eq。
算术基于 Dekker 和 Knuth 的无误差变换算法(TwoSum、TwoProd),显著快于 MPFR 等任意精度替代方案。
常量
| 常量 | 值 | 说明 |
|---|---|---|
DoubleF64::ZERO | { hi: 0.0, lo: 0.0 } | 零 |
DoubleF64::ONE | { hi: 1.0, lo: 0.0 } | 一 |
构造与转换
DoubleF64::new
签名:pub fn new(hi: f64, lo: f64) -> Self
功能:从高阶和低阶分量创建。调用者需确保 $|lo| \leq 0.5 \cdot \text{ulp}(hi)$ 以保证正确性。
DoubleF64::from_f64
签名:pub fn from_f64(x: f64) -> Self
功能:从单个 f64 创建($lo = 0$)。
DoubleF64::to_f64
签名:pub fn to_f64(self) -> f64
功能:提取高阶分量。
From<f64>
签名:impl From<f64> for DoubleF64
功能:等价于 from_f64。
无误差变换
DoubleF64::quick_two_sum
签名:pub fn quick_two_sum(a: f64, b: f64) -> Self
功能:当 $|a| \geq |b|$ 时的无误差求和。比 two_sum 快但有前提条件。
DoubleF64::two_sum
签名:pub fn two_sum(a: f64, b: f64) -> Self
功能:Dekker TwoSum——无误差求和,舍入误差精确捕获到 lo 分量。
示例:
#![allow(unused)] fn main() { use ocas_domain::DoubleF64; let s = DoubleF64::two_sum(1.0, f64::EPSILON); assert_eq!(s.hi, 1.0 + f64::EPSILON); // s.lo 捕获了舍入误差 }
查询
| 方法 | 签名 | 说明 |
|---|---|---|
abs | pub fn abs(self) -> Self | 绝对值 |
is_nan | pub fn is_nan(self) -> bool | 是否为 NaN |
is_infinite | pub fn is_infinite(self) -> bool | 是否为无穷 |
is_finite | pub fn is_finite(self) -> bool | 是否有限 |
算术运算
DoubleF64::add / sub / mul / div
签名:
#![allow(unused)] fn main() { pub fn add(self, other: Self) -> Self pub fn sub(self, other: Self) -> Self pub fn mul(self, other: Self) -> Self pub fn div(self, other: Self) -> Self }
功能:双精度加减乘除。内部使用 TwoSum/TwoProd 捕获舍入误差。
DoubleF64::powi
签名:pub fn powi(self, n: i64) -> Self
功能:整数次幂,使用二进制快速幂。支持负指数。
示例:
#![allow(unused)] fn main() { use ocas_domain::DoubleF64; let x = DoubleF64::from_f64(3.0); assert_eq!(x.powi(3).hi, 27.0); assert_eq!(x.powi(-1).hi, 1.0 / 3.0); // 输出:3^3 = 27, 3^(-1) = 0.333... }
超越函数
| 方法 | 签名 | 说明 |
|---|---|---|
sqrt | pub fn sqrt(self) -> Self | Newton 迭代平方根。负数返回 NaN |
exp | pub fn exp(self) -> Self | 指数函数,Taylor 级数 + 参数规约($\ln 2$ 缩减) |
ln | pub fn ln(self) -> Self | 自然对数,Newton 迭代。非正数返回 NaN |
sin | pub fn sin(self) -> Self | 正弦,Taylor 级数 + $[-\pi, \pi]$ 规约 |
cos | pub fn cos(self) -> Self | 余弦,$\cos(x) = \sin(\pi/2 - x)$ |
tan | pub fn tan(self) -> Self | 正切,$\sin(x)/\cos(x)$ |
示例:
#![allow(unused)] fn main() { use ocas_domain::DoubleF64; let x = DoubleF64::from_f64(1.0); let e = x.exp(); // e ≈ 2.718281828... assert!((e.hi - std::f64::consts::E).abs() < 1e-15); let pi = DoubleF64::from_f64(std::f64::consts::PI); let s = pi.sin(); assert!(s.hi.abs() < 1e-15); // sin(π) ≈ 0 // 输出:exp(1) ≈ e, sin(π) ≈ 0 }
运算符
DoubleF64 实现了 Add、Sub、Mul、Div、Neg、AddAssign、SubAssign、MulAssign、DivAssign、PartialOrd、Zero(num_traits)。
Display
当 lo == 0 时显示 hi 值;否则以 31 位科学计数法显示 $hi + lo$ 的和。
AlgebraicExtension / AlgebraicNumberField / AlgebraicElement
AlgebraicExtension
签名:pub struct AlgebraicExtension<D: Domain> { base: D, min_poly: Vec<D::Element> }
功能:代数扩张 $D[\alpha]/(m(\alpha))$,其中 $m$ 是首一多项式。当基域 $D$ 是域且 $m$ 不可约时,商环是域:
AlgebraicExtension<RationalDomain>= 代数数域 $\mathbb{Q}(\alpha)$AlgebraicExtension<FiniteField>= Galois 域 $\mathrm{GF}(p^d)$
元素是剩余类,由次数小于 $\deg(m)$ 的唯一多项式代表表示。逆元使用基域上的扩展欧几里得算法。
⚠️ 注意:不检查极小多项式的不可约性。在可约模下,环有零因子,Domain::inv 对非单位返回 None。
实现:Domain、EuclideanDomain
构造
AlgebraicExtension::new
签名:pub fn new(base: D, min_poly: Vec<D::Element>) -> Self
功能:从基域和首一极小多项式创建代数扩张。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
base | D: Domain | 基域 |
min_poly | Vec<D::Element> | 极小多项式,升序排列,首一(最高次系数为 1),次数 $\geq 1$ |
错误:debug 模式下检查:(1) 次数 $\geq 1$(即 min_poly.len() >= 2);(2) 首一性。
示例:
#![allow(unused)] fn main() { use ocas_domain::{AlgebraicExtension, Domain, Rational, RationalDomain}; // ℚ(√2):极小多项式 α² − 2 let two = Rational::new(2, 1); let neg_two = RationalDomain.neg(&two); let field = AlgebraicExtension::new( RationalDomain, vec![neg_two, Rational::new(0, 1), Rational::new(1, 1)], ); let sqrt2 = field.alpha(); // √2 · √2 = 2 assert_eq!(field.mul(&sqrt2, &sqrt2), field.from_base(two)); // 输出:α² = 2 }
访问器
AlgebraicExtension::base_domain
签名:pub fn base_domain(&self) -> &D
功能:返回基域引用。
AlgebraicExtension::min_poly
签名:pub fn min_poly(&self) -> &[D::Element]
功能:返回极小多项式系数(升序,首一)。
AlgebraicExtension::extension_degree
签名:pub fn extension_degree(&self) -> usize
功能:返回扩张次数 $\deg(m)$。
元素构造
AlgebraicExtension::from_base
签名:pub fn from_base(&self, c: D::Element) -> AlgebraicElement<D::Element>
功能:将基域常量嵌入扩张域。
AlgebraicExtension::alpha
签名:pub fn alpha(&self) -> AlgebraicElement<D::Element>
功能:返回扩张的生成元 $\alpha$。
AlgebraicExtension::element
签名:pub fn element(&self, coeffs: Vec<D::Element>) -> AlgebraicElement<D::Element>
功能:从系数(升序)创建元素,自动规约模极小多项式。
Domain 实现
mul:先多项式乘法,再规约模 $m(\alpha)$inv:使用扩展欧几里得算法——若 $\gcd(a, m) = 1$(常数),则 $a^{-1} = s \bmod m$div:$a/b = a \cdot b^{-1}$is_zero:系数向量为空
EuclideanDomain 实现
div_rem:域上除法精确,余数恒为零gcd:域中退化——两者均零返回 0,否则返回 1
示例(Galois 域 $\mathrm{GF}(3^2)$):
#![allow(unused)] fn main() { use ocas_domain::{AlgebraicExtension, Domain}; use ocas_domain::FiniteField; use num_bigint::BigInt; let base = FiniteField::new(BigInt::from(3)); let field = AlgebraicExtension::new( base.clone(), vec![base.element(1), base.element(0), base.element(1)], // α² + 1 ); let alpha = field.alpha(); // α² = −1 = 2 (mod 3) assert_eq!(field.mul(&alpha, &alpha), field.from_base(base.element(2))); // 乘法群阶为 8:(1+α)⁸ = 1 let a = field.add(&field.one(), &alpha); assert_eq!(field.pow(&a, 8), field.one()); // 输出:α² = 2, (1+α)⁸ = 1 }
AlgebraicNumberField
签名:pub type AlgebraicNumberField = AlgebraicExtension<RationalDomain>;
功能:$\mathbb{Q}(\alpha)$ 的类型别名。
AlgebraicElement
签名:pub struct AlgebraicElement<E> { coeffs: Vec<E> }
功能:代数扩张的元素——$\alpha$ 的多项式剩余类,次数小于扩张次数。系数升序存储,尾随零被裁剪。零元素的系数向量为空。
Derive:Debug, Clone, PartialEq, Eq, Hash
AlgebraicElement::coeffs
签名:pub fn coeffs(&self) -> &[E]
功能:返回系数切片(升序,尾随零已裁剪)。
Display
显示格式:0(零元素)、c(纯常数)、(c1)·α + c0、(c2)·α^2 + (c1)·α + c0 等(常数项不加括号,各次项之间以 + 连接)。
示例:
#![allow(unused)] fn main() { use ocas_domain::{AlgebraicExtension, Domain, Rational, RationalDomain}; // ℚ(i):极小多项式 α² + 1 let field = AlgebraicNumberField::new( RationalDomain, vec![Rational::new(1, 1), Rational::new(0, 1), Rational::new(1, 1)], ); let i = field.alpha(); let one_plus_i = field.add(&field.one(), &i); // (1+i)⁻¹ = (1-i)/2 let inv = field.inv(&one_plus_i).unwrap(); assert_eq!(inv.coeffs(), &[Rational::new(1, 2), Rational::new(-1, 2)]); // 输出:1/(1+i) = (1/2) - (1/2)·α }
Assumptions 系统
假设系统用于声明符号变量的性质(如"x 是实数"、"n 是正整数")。求解器和化简器通过假设来选择算法和验证解。
模块路径:ocas_domain::assumptions
Assumption
签名:
#![allow(unused)] fn main() { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Assumption { Real, Complex, Integer, Rational, Positive, Negative, NonNegative, NonPositive, NonZero, Finite, Even, Odd, Prime, } }
功能:可对符号变量声明的单一谓词。假设相互独立——一个变量可同时携带多个假设(如 Positive | Integer)。
变体说明
| 变体 | 含义 | Display |
|---|---|---|
Real | 实数 | "real" |
Complex | 复数 | "complex" |
Integer | 整数 | "integer" |
Rational | 有理数 | "rational" |
Positive | 严格正($> 0$) | "positive" |
Negative | 严格负($< 0$) | "negative" |
NonNegative | 非负($\geq 0$) | "non-negative" |
NonPositive | 非正($\leq 0$) | "non-positive" |
NonZero | 非零 | "non-zero" |
Finite | 有限(非 $\pm\infty$) | "finite" |
Even | 偶数 | "even" |
Odd | 奇数 | "odd" |
Prime | 素数 | "prime" |
Assumption::implied
签名:pub fn implied(&self) -> &'static [Assumption]
功能:返回此假设逻辑蕴含的其他假设列表。插入假设时,其蕴含会自动传播。
蕴含关系表:
| 假设 | 蕴含 |
|---|---|
Positive | NonNegative, NonZero, Real |
Negative | NonPositive, NonZero, Real |
NonNegative | Real |
NonPositive | Real |
Integer | Rational, Real |
Rational | Real |
Complex | Real |
Even | Integer |
Odd | Integer |
Prime | Integer, Positive |
Real, NonZero, Finite | (无额外蕴含) |
Assumption::conflicts
签名:pub fn conflicts(&self) -> &'static [Assumption]
功能:返回与之矛盾的假设列表。同时包含一个假设及其冲突项的集合是不一致的。
冲突关系表:
| 假设 | 与之冲突 |
|---|---|
Positive | Negative, NonPositive |
Negative | Positive, NonNegative |
NonNegative | Negative |
NonPositive | Positive |
Even | Odd |
Odd | Even |
其他假设无冲突项。
BitOr 支持
Assumption | Assumption 返回 Assumptions:
#![allow(unused)] fn main() { use ocas_domain::assumptions::{Assumption, Assumptions}; let a = Assumption::Positive | Assumption::Integer; assert!(a.implies(Assumption::Real)); // 输出:Positive | Integer 蕴含 Real }
参见:Assumptions、SymbolAssumptions
Assumptions
签名:pub struct Assumptions { inner: Vec<Assumption> }
功能:关于一个符号变量的假设集合。内部以排序、去重向量存储以优化小规模效率。操作在逻辑蕴含下封闭——插入 Positive 会使 NonNegative 和 Real 可用。
Derive:Debug, Clone, PartialEq, Eq, Default
构造
Assumptions::new
签名:pub fn new() -> Self
功能:创建空假设集。
Assumptions::single
签名:pub fn single(a: Assumption) -> Self
功能:创建包含单个假设(及其蕴含)的集合。
查询
Assumptions::len
签名:pub fn len(&self) -> usize
功能:返回显式存储的假设数量。
Assumptions::is_empty
签名:pub fn is_empty(&self) -> bool
功能:是否为空集。
Assumptions::contains
签名:pub fn contains(&self, a: Assumption) -> bool
功能:检查假设是否被此集合蕴含(直接成员或逻辑蕴含)。
Assumptions::implies
签名:pub fn implies(&self, other: Assumption) -> bool
功能:检查此集合是否逻辑蕴含 other。
示例:
#![allow(unused)] fn main() { use ocas_domain::assumptions::{Assumption, Assumptions}; let mut a = Assumptions::new(); a.insert(Assumption::Positive); a.insert(Assumption::Integer); assert!(a.contains(Assumption::Real)); // Positive 蕴含 Real assert!(a.implies(Assumption::NonZero)); // Positive 蕴含 NonZero assert!(!a.implies(Assumption::Even)); // 不蕴含 Even // 输出:所有断言通过 }
修改
Assumptions::insert
签名:pub fn insert(&mut self, a: Assumption) -> bool
功能:插入假设及其所有逻辑蕴含。返回 false 若插入导致矛盾(不一致假设仍被存储,应检查 is_consistent)。
Assumptions::remove
签名:pub fn remove(&mut self, a: Assumption)
功能:移除假设。不移除由被移除假设所蕴含的其他假设(它们可能仍由其他存储的假设蕴含)。
Assumptions::is_consistent
签名:pub fn is_consistent(&self) -> bool
功能:检查集合是否一致(无矛盾假设)。
Assumptions::iter
签名:pub fn iter(&self) -> impl Iterator<Item = Assumption> + '_
功能:遍历存储的假设。
运算符
Assumption | Assumption→AssumptionsAssumptions | Assumption→AssumptionsAssumptions | Assumptions→Assumptions(并集)FromIterator<Assumption>→Assumptions
示例:
#![allow(unused)] fn main() { use ocas_domain::assumptions::{Assumption, Assumptions}; let a: Assumptions = [Assumption::Positive, Assumption::Integer].into_iter().collect(); assert!(a.is_consistent()); let b = Assumption::Positive | Assumption::Negative; assert!(!b.is_consistent()); // 输出:Positive + Integer 一致;Positive + Negative 不一致 }
参见:Assumption、SymbolAssumptions
SymbolAssumptions
签名:pub struct SymbolAssumptions { entries: Vec<(String, Assumptions)> }
功能:符号名到假设的映射。求解器和化简器用此确定合法变换。例如 $\sqrt{x^2} \to x$ 仅在 $x$ 被假设为 NonNegative 时有效。
Derive:Debug, Clone, PartialEq, Eq, Default
构造
SymbolAssumptions::new
签名:pub fn new() -> Self
功能:创建空映射。
查询与修改
SymbolAssumptions::set
签名:pub fn set(&mut self, symbol: &str, assumptions: Assumptions)
功能:为符号设置假设(替换已有条目)。
SymbolAssumptions::get
签名:pub fn get(&self, symbol: &str) -> Option<&Assumptions>
功能:获取符号的假设。
SymbolAssumptions::remove
签名:pub fn remove(&mut self, symbol: &str)
功能:移除符号的假设。
SymbolAssumptions::check
签名:pub fn check(&self, symbol: &str, assumption: Assumption) -> bool
功能:检查符号是否满足特定假设。
SymbolAssumptions::len
签名:pub fn len(&self) -> usize
功能:返回有假设的符号数量。
SymbolAssumptions::is_empty
签名:pub fn is_empty(&self) -> bool
功能:是否无符号有假设。
SymbolAssumptions::iter
签名:pub fn iter(&self) -> impl Iterator<Item = &(String, Assumptions)>
功能:遍历所有 (符号名, 假设集) 对。
示例:
#![allow(unused)] fn main() { use ocas_domain::assumptions::{Assumption, Assumptions, SymbolAssumptions}; let mut sa = SymbolAssumptions::new(); sa.set("x", Assumptions::single(Assumption::Positive)); sa.set("n", Assumption::Integer | Assumption::Positive); assert!(sa.check("x", Assumption::Real)); // Positive 蕴含 Real assert!(sa.check("n", Assumption::NonZero)); // Positive 蕴含 NonZero assert!(!sa.check("x", Assumption::Integer)); // 不蕴含 Integer // 输出:所有断言通过 }
Rust API 参考:多项式
本章记录 oCAS 的多项式系统,涵盖三类核心数据结构:
DenseUnivariatePolynomial<D>— 稠密一元多项式SparseMultivariatePolynomial<D, O>— 稀疏多元多项式RationalPolynomial<D, O>— 多项式分式域元素
以及单项式序 trait MonomialOrder 和多种序的实现。
模块路径:ocas_poly
目录
单项式序
MonomialOrder trait
签名:
#![allow(unused)] fn main() { pub trait MonomialOrder: Clone + PartialEq + Eq + std::fmt::Debug + Default { fn cmp(&self, lhs: &[usize], rhs: &[usize]) -> std::cmp::Ordering; } }
功能:定义单项式之间的全序关系。多项式的首项、排序和 Gröbner 基计算都依赖于此。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
self | &Self | 序的实例(零大小类型或带参数的类型) |
lhs | &[usize] | 左侧单项式的指数向量 $[\alpha_1, \alpha_2, \dots]$ |
rhs | &[usize] | 右侧单项式的指数向量 |
返回值:std::cmp::Ordering — Less 表示 lhs 在序中排在 rhs 之前(即 lhs 更大),Greater 表示 lhs 更小。
设计说明:简单序(Lex、Grevlex、Grlex)是零大小类型,无运行时开销。参数化序(WeightOrder、BlockOrder、MatrixOrder)在构造时存储配置。
参见:Lex、Grevlex、Grlex、WeightOrder、BlockOrder、MatrixOrder
Lex
签名:
#![allow(unused)] fn main() { #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub struct Lex; }
功能:字典序。从左到右逐分量比较指数向量。
比较规则:lhs > rhs 当且仅当存在 $i$ 使得 $\alpha_j = \beta_j$ 对所有 $j < i$ 且 $\alpha_i > \beta_i$。
示例:
#![allow(unused)] fn main() { use ocas_poly::sparse::{Lex, MonomialOrder}; let a = [2, 1]; // x^2 y let b = [1, 1]; // x y assert_eq!(Lex.cmp(&a, &b), std::cmp::Ordering::Greater); // a 在 Lex 序中更大(首分量 2 > 1) }
Grlex
签名:
#![allow(unused)] fn main() { #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub struct Grlex; }
功能:分次字典序。先按总次数降序排列,同次再按字典序。
比较规则:
- 总次数 $\sum \alpha_i$ 更大者排在前面
- 总次数相同时,按字典序比较
示例:
#![allow(unused)] fn main() { use ocas_poly::sparse::{Grlex, MonomialOrder}; let a = [2, 0]; // x^2, 次数 2 let b = [1, 1]; // xy, 次数 2 let c = [0, 3]; // y^3, 次数 3 // c 次数最高,排在最前 assert_eq!(Grlex.cmp(&c, &a), std::cmp::Ordering::Less); // a 和 b 次数相同,按字典序 a > b assert_eq!(Grlex.cmp(&a, &b), std::cmp::Ordering::Greater); }
Grevlex
签名:
#![allow(unused)] fn main() { #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub struct Grevlex; }
功能:分次反字典序。先按总次数降序排列,同次再按反向字典序比较(从最后一个分量开始,方向反转)。
比较规则:
- 总次数更大者排在前面
- 总次数相同时,从最后一个分量开始比较,更小者排在前面
特点:Grevlex 序在 Gröbner 基计算中通常产生最小的中间矩阵,是默认序。
示例:
#![allow(unused)] fn main() { use ocas_poly::sparse::{Grevlex, Lex, MonomialOrder}; let a = [2, 1]; let b = [1, 1]; assert_eq!(Lex.cmp(&a, &b), std::cmp::Ordering::Greater); assert_eq!(Grevlex.cmp(&a, &b), std::cmp::Ordering::Less); // Grevlex 下 a < b(反向字典序反转了方向) }
WeightOrder
签名:
#![allow(unused)] fn main() { #[derive(Debug, Clone, PartialEq, Eq)] pub struct WeightOrder { weights: SmallVec<[i64; 4]>, } }
功能:加权序。按 $\sum_i w_i \cdot \alpha_i$ 降序排列。适用于无法用零大小类型表达的消元序。
构造方法:
#![allow(unused)] fn main() { pub fn new(weights: SmallVec<[i64; 4]>) -> Self pub fn from_slice(weights: &[i64]) -> Self }
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
weights | SmallVec<[i64; 4]> 或 &[i64] | 每个变量的权重,长度应等于变量数 |
Default:全 1 权重(即总次数序)。
示例:
#![allow(unused)] fn main() { use ocas_poly::sparse::{MonomialOrder, WeightOrder}; use smallvec::smallvec; let ord = WeightOrder::new(smallvec![2, 1]); // [1,0] → 权重 2, [0,1] → 权重 1 → [1,0] 更大 assert_eq!(ord.cmp(&[1, 0], &[0, 1]), std::cmp::Ordering::Less); }
BlockOrder 与 SubOrder
签名:
#![allow(unused)] fn main() { #[derive(Debug, Clone, PartialEq, Eq)] pub struct BlockOrder { boundaries: SmallVec<[usize; 4]>, orders: SmallVec<[SubOrder; 4]>, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum SubOrder { Lex, Grevlex, Grlex, } }
功能:分块消元序。将变量分为连续的块,每个块内使用独立的子序。
构造方法:
#![allow(unused)] fn main() { pub fn new(boundaries: SmallVec<[usize; 4]>, orders: SmallVec<[SubOrder; 4]>) -> Self }
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
boundaries | SmallVec<[usize; 4]> | 排序的分割点列表(排他上界,不含 n_vars) |
orders | SmallVec<[SubOrder; 4]> | 每个块的子序,长度必须等于 boundaries.len() + 1 |
说明:boundaries = [2] 加 orders = [Lex, Grevlex] 在 4 变量多项式上表示:先比较变量 0–1(Lex),相等再比较变量 2–3(Grevlex)。
Default:单块 Grevlex。
示例:
#![allow(unused)] fn main() { use ocas_poly::sparse::{BlockOrder, MonomialOrder, SubOrder}; use smallvec::smallvec; let ord = BlockOrder::new( smallvec![2], smallvec![SubOrder::Lex, SubOrder::Grevlex], ); let a = [1, 0, 0, 0]; // x₀ let b = [0, 1, 0, 0]; // x₁ // 块内 Lex:[1,0] > [0,1] assert_eq!(ord.cmp(&a, &b), std::cmp::Ordering::Greater); }
MatrixOrder
签名:
#![allow(unused)] fn main() { #[derive(Debug, Clone, PartialEq, Eq)] pub struct MatrixOrder { matrix: Vec<Vec<i64>>, n_vars: usize, } }
功能:矩阵序。将指数向量乘以整数矩阵后按字典序比较。给定 $n \times n$ 矩阵 $M$,单项式 $\alpha > \beta$ 当且仅当 $M\alpha >_{\text{lex}} M\beta$。泛化所有标准序,特别适合构造消元序。
构造方法:
#![allow(unused)] fn main() { pub fn new(matrix: Vec<Vec<i64>>) -> Self pub fn elimination_order(elim_vars: usize, n_vars: usize) -> Self }
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
matrix | Vec<Vec<i64>> | $n \times n$ 权重矩阵(行优先) |
elim_vars | usize | 要消元的前几个变量数 |
n_vars | usize | 总变量数 |
elimination_order:构造等价于 BlockOrder([elim_vars in Lex, rest in Grevlex]) 的矩阵序。
Default:$1 \times 1$ 单位矩阵。
示例:
#![allow(unused)] fn main() { use ocas_poly::sparse::{MatrixOrder, MonomialOrder}; // 2×2 单位矩阵 = Lex 序 let ord = MatrixOrder::new(vec![vec![1, 0], vec![0, 1]]); assert_eq!(ord.cmp(&[1, 0], &[0, 1]), std::cmp::Ordering::Greater); }
DenseUnivariatePolynomial
#![allow(unused)] fn main() { #[derive(Debug, Clone, PartialEq, Eq)] pub struct DenseUnivariatePolynomial<D: Domain> { coeffs: Vec<D::Element>, // 从常数项开始,已去除尾零 domain: D, } }
稠密一元多项式。系数存储在连续向量中,从常数项 $a_0$ 到最高次项 $a_n$。零多项式用空向量表示。乘法自动在 Karatsuba(两个多项式系数个数均 $\geq 32$ 时)和学校乘法之间选择。
Dense 构造与属性
DenseUnivariatePolynomial::new
签名:pub fn new(domain: D) -> Self
功能:创建零多项式。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
domain | D | 系数域 |
返回值:零多项式(空系数向量)。
参见:from_coeffs
DenseUnivariatePolynomial::from_coeffs
签名:pub fn from_coeffs(domain: D, coeffs: Vec<D::Element>) -> Self
功能:从系数向量构造多项式。自动去除尾零系数。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
domain | D | 系数域 |
coeffs | Vec<D::Element> | 系数 $[a_0, a_1, \dots, a_n]$,常数项在前 |
返回值:多项式 $a_0 + a_1 x + \cdots + a_n x^n$。
示例:
#![allow(unused)] fn main() { use ocas_domain::{IntegerDomain, Integer}; use ocas_poly::DenseUnivariatePolynomial; let domain = IntegerDomain; let p = DenseUnivariatePolynomial::from_coeffs( domain, vec![Integer::from(1), Integer::from(0), Integer::from(2)], ); assert_eq!(p.degree(), Some(2)); assert_eq!(p.coeff(2), Some(&Integer::from(2))); // p(x) = 1 + 2x^2,中间的零系数被保留(非尾零) }
参见:new
DenseUnivariatePolynomial::domain
签名:pub fn domain(&self) -> &D
功能:返回系数域的引用。
DenseUnivariatePolynomial::coeffs
签名:pub fn coeffs(&self) -> &[D::Element]
功能:返回系数切片,从常数项开始。
DenseUnivariatePolynomial::is_zero
签名:pub fn is_zero(&self) -> bool
功能:判断是否为零多项式。
DenseUnivariatePolynomial::degree
签名:pub fn degree(&self) -> Option<usize>
功能:返回多项式的次数。零多项式返回 None。
DenseUnivariatePolynomial::coeff
签名:pub fn coeff(&self, n: usize) -> Option<&D::Element>
功能:返回 $x^n$ 的系数,不存在则返回 None。
DenseUnivariatePolynomial::leading_coeff
签名:pub fn leading_coeff(&self) -> Option<&D::Element>
功能:返回首项系数。零多项式返回 None。
DenseUnivariatePolynomial::lcoeff
签名:pub fn lcoeff(&self) -> D::Element
功能:返回首项系数的便捷别名。零多项式返回域的零元。
返回值:首项系数,或域的零元。
DenseUnivariatePolynomial::constant
签名:pub fn constant(&self) -> D::Element
功能:返回常数项($x^0$ 的系数)。零多项式返回域的零元。
DenseUnivariatePolynomial::zero
签名:pub fn zero(&self) -> Self
功能:返回同域的零多项式。
DenseUnivariatePolynomial::one
签名:pub fn one(&self) -> Self
功能:返回常数多项式 $1$。
DenseUnivariatePolynomial::is_one
签名:pub fn is_one(&self) -> bool
功能:判断是否为常数多项式 $1$。
Dense 基本算术
以下方法在 D: Domain 上可用:
DenseUnivariatePolynomial::neg
签名:pub fn neg(&self) -> Self
功能:返回 $-p(x)$。
DenseUnivariatePolynomial::add
签名:pub fn add(&self, other: &Self) -> Self
功能:多项式加法。
DenseUnivariatePolynomial::sub
签名:pub fn sub(&self, other: &Self) -> Self
功能:多项式减法。
DenseUnivariatePolynomial::mul_scalar
签名:pub fn mul_scalar(&self, scalar: &D::Element) -> Self
功能:标量乘法。所有系数乘以 scalar。
DenseUnivariatePolynomial::mul
签名:pub fn mul(&self, other: &Self) -> Self
功能:多项式乘法。根据次数自动选择学校乘法或 Karatsuba。
示例:
#![allow(unused)] fn main() { use ocas_domain::{IntegerDomain, Integer}; use ocas_poly::DenseUnivariatePolynomial; let domain = IntegerDomain; let a = DenseUnivariatePolynomial::from_coeffs( domain, vec![Integer::from(1), Integer::from(1)], ); let b = DenseUnivariatePolynomial::from_coeffs( domain, vec![Integer::from(1), Integer::from(-1)], ); let c = a.mul(&b); assert_eq!(c.coeffs(), &[ Integer::from(1), Integer::from(0), Integer::from(-1), ]); // (1 + x)(1 - x) = 1 - x^2 }
参见:mul_into
DenseUnivariatePolynomial::mul_into
签名:pub fn mul_into(&self, other: &Self, buf: &mut Vec<D::Element>)
功能:乘法,将结果写入缓冲区(避免热循环中的重复堆分配)。缓冲区被清空后重用。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
other | &Self | 另一个多项式 |
buf | &mut Vec<D::Element> | 输出缓冲区,调用后包含乘积系数 |
DenseUnivariatePolynomial::eval
签名:pub fn eval(&self, x: &D::Element) -> D::Element
功能:用 Horner 法在 $x$ 处求值。零多项式返回域的零元。
示例:
#![allow(unused)] fn main() { use ocas_domain::{IntegerDomain, Integer}; use ocas_poly::DenseUnivariatePolynomial; let domain = IntegerDomain; let p = DenseUnivariatePolynomial::from_coeffs( domain, vec![Integer::from(1), Integer::from(2), Integer::from(3)], ); let value = p.eval(&Integer::from(2)); assert_eq!(value, Integer::from(17)); // p(2) = 1 + 2·2 + 3·4 = 17 }
DenseUnivariatePolynomial::derivative
签名:pub fn derivative(&self) -> Self
功能:返回形式导数 $p'(x)$。
DenseUnivariatePolynomial::integral
签名:pub fn integral(&self) -> Self
功能:返回形式积分 $\int p(x),dx$,常数项为零。要求域支持除法(如 RationalDomain)。
EuclideanDomain 操作
以下方法要求 D: EuclideanDomain:
DenseUnivariatePolynomial::mul_coeff
签名:pub fn mul_coeff(&self, c: &D::Element) -> Self
功能:所有系数乘以常数 $c$。等价于 mul_scalar,但限于 EuclideanDomain。
参见:mul_scalar
DenseUnivariatePolynomial::div_coeff
签名:pub fn div_coeff(&self, c: &D::Element) -> Self
功能:所有系数除以常数 $c$(必须整除)。
错误:若 $c$ 在域中不可逆(例如 $\mathbb{Z}$ 上 $c$ 不是 $\pm 1$),则无条件 panic(即使所有系数都能被 $c$ 整除)。
DenseUnivariatePolynomial::div_rem
签名:pub fn div_rem(&self, divisor: &Self) -> Option<(Self, Self)>
功能:带余除法。返回 $(q, r)$ 使得 $p = q \cdot \text{divisor} + r$,其中 $\deg(r) < \deg(\text{divisor})$。
返回值:Some((quotient, remainder)),除数为零时返回 None。
示例:
#![allow(unused)] fn main() { use ocas_domain::{IntegerDomain, Integer}; use ocas_poly::DenseUnivariatePolynomial; let domain = IntegerDomain; let p = DenseUnivariatePolynomial::from_coeffs( domain, vec![Integer::from(1), Integer::from(0), Integer::from(-1)], ); let q = DenseUnivariatePolynomial::from_coeffs( domain, vec![Integer::from(1), Integer::from(1)], ); let (quot, rem) = p.div_rem(&q).unwrap(); assert_eq!(quot.coeffs(), &[Integer::from(1), Integer::from(-1)]); assert!(rem.is_zero()); // (x^2 - 1) / (x + 1) = x - 1,余数为 0 }
参见:gcd
DenseUnivariatePolynomial::gcd
签名:pub fn gcd(&self, other: &Self) -> Self
功能:计算两个多项式的最大公因式。使用 Euclid 算法(非域时用伪余式)。结果总是本原的(content-free)。
示例:
#![allow(unused)] fn main() { use ocas_domain::{IntegerDomain, Integer}; use ocas_poly::DenseUnivariatePolynomial; let d = IntegerDomain; let a = DenseUnivariatePolynomial::from_coeffs(d, vec![ Integer::from(-1), Integer::from(0), Integer::from(1), ]); // x^2 - 1 let b = DenseUnivariatePolynomial::from_coeffs(d, vec![ Integer::from(1), Integer::from(2), Integer::from(1), ]); // (x+1)^2 let g = a.gcd(&b); assert_eq!(g.coeffs(), &[Integer::from(1), Integer::from(1)]); // gcd = x + 1 }
DenseUnivariatePolynomial::content
签名:pub fn content(&self) -> D::Element
功能:返回所有系数的最大公因式。零多项式返回域的零元。
返回值:系数的 GCD。
DenseUnivariatePolynomial::primitive_part
签名:pub fn primitive_part(&self) -> Self
功能:返回本原部分 $p / \text{content}(p)$。结果的 content 为 1(或为零多项式)。
参见:content
DenseUnivariatePolynomial::extended_gcd_poly
签名:pub fn extended_gcd_poly(&self, other: &Self) -> (Self, Self, Self)
功能:扩展 Euclid 算法。返回 $(g, s, t)$ 使得 $s \cdot p + t \cdot q = g$,其中 $g = \gcd(p, q)$,$g$ 为首一多项式。
返回值:(gcd, bezout_s, bezout_t)
示例:
#![allow(unused)] fn main() { use ocas_domain::{RationalDomain, Rational}; use ocas_poly::DenseUnivariatePolynomial; let d = RationalDomain; let a = DenseUnivariatePolynomial::from_coeffs(d, vec![ Rational::new(1, 1), Rational::new(0, 1), Rational::new(1, 1), ]); // x^2 + 1 let b = DenseUnivariatePolynomial::from_coeffs(d, vec![ Rational::new(1, 1), Rational::new(1, 1), ]); // x + 1 let (g, s, t) = a.extended_gcd_poly(&b); // s·a + t·b = g }
参见:gcd
DenseUnivariatePolynomial::pow
签名:pub fn pow(&self, n: u32) -> Self
功能:快速幂(反复平方法)。$p^0 = 1$。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
n | u32 | 非负整数指数 |
返回值:$p(x)^n$。
DenseUnivariatePolynomial::p_adic_expansion
签名:pub fn p_adic_expansion(&self, p: &Self) -> Vec<Self>
功能:$p$-adic 展开。返回 $[a_0, a_1, a_2, \dots]$ 使得 $\text{self} = a_0 + a_1 \cdot p + a_2 \cdot p^2 + \cdots$,每个 $a_k$ 的次数小于 $\deg(p)$。
实现:通过反复带余除法。
示例:
#![allow(unused)] fn main() { use ocas_domain::{IntegerDomain, Integer}; use ocas_poly::DenseUnivariatePolynomial; let d = IntegerDomain; // 对 f(x) = x^3 关于 p(x) = x + 1 做 p-adic 展开 let f = DenseUnivariatePolynomial::from_coeffs(d, vec![ Integer::from(0), Integer::from(0), Integer::from(0), Integer::from(1), ]); let p = DenseUnivariatePolynomial::from_coeffs(d, vec![ Integer::from(1), Integer::from(1), ]); let expansion = f.p_adic_expansion(&p); // expansion = [a0, a1, a2, ...] 使得 f = a0 + a1*p + a2*p^2 + ... }
参见:div_rem
DenseUnivariatePolynomial::diophantine
签名:pub fn diophantine(polys: &mut [Self], b: &Self) -> Vec<Self>
功能:多项式 CRT(丢番图求解器)。给定两两互素的多项式列表 polys 和目标 $b$,返回 $[s_0, \dots, s_n]$ 使得:
$$\sum_i s_i \cdot \prod_{j \neq i} p_j \equiv b \pmod{\prod_i p_i}$$
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
polys | &mut [Self] | 两两互素的多项式列表 |
b | &Self | 目标多项式 |
返回值:余式列表 $[s_0, \dots, s_n]$。
错误:若多项式非两两互素则 panic。
因式分解与结式
DenseUnivariatePolynomial::square_free_factorization
签名:pub fn square_free_factorization(&self) -> SquareFreeFactors<D>
功能:无平方分解。使用 Yun 算法:$g = \gcd(f, f')$,$w = f/g$,迭代 $h = \gcd(w, g)$,$z = w/h$ 收集重数 $k$ 因子。
返回值:Vec<(DenseUnivariatePolynomial<D>, usize)> — (因子, 重数) 对的列表。
示例:
#![allow(unused)] fn main() { use ocas_domain::{IntegerDomain, Integer}; use ocas_poly::DenseUnivariatePolynomial; let d = IntegerDomain; // (x+1)^2*(x-1) = x^3 + x^2 - x - 1 let p = DenseUnivariatePolynomial::from_coeffs(d, vec![ Integer::from(-1), Integer::from(-1), Integer::from(1), Integer::from(1), ]); let factors = p.square_free_factorization(); assert_eq!(factors.len(), 2); }
DenseUnivariatePolynomial::is_square_free
签名:pub fn is_square_free(&self) -> bool
功能:判断多项式是否无平方($\gcd(p, p') = 1$)。
DenseUnivariatePolynomial::factor(IntegerDomain)
签名:impl DenseUnivariatePolynomial<IntegerDomain> { pub fn factor(&self) -> Factors<IntegerDomain> }
功能:将本原整数多项式完全分解为不可约因子。使用无平方分解 + Berlekamp–Zassenhaus + Hensel 提升算法。
前置条件:输入必须是本原的(content = 1)。对任意多项式先调用 primitive_part()。
返回值:Vec<(DenseUnivariatePolynomial<IntegerDomain>, usize)> — (不可约因子, 重数) 对。
示例:
#![allow(unused)] fn main() { use ocas_domain::{Integer, IntegerDomain}; use ocas_poly::DenseUnivariatePolynomial; let d = IntegerDomain; // x^2 - 1 = (x-1)(x+1) let p = DenseUnivariatePolynomial::from_coeffs(d, vec![ Integer::from(-1), Integer::from(0), Integer::from(1), ]); let factors = p.factor(); assert_eq!(factors.len(), 2); }
DenseUnivariatePolynomial::factor(FiniteField)
签名:impl DenseUnivariatePolynomial<FiniteField> { pub fn factor(&self) -> Factors<FiniteField> }
功能:将 $\mathbb{F}_p$ 上的多项式完全分解。使用 Berlekamp 算法(或 Cantor–Zassenhaus)。
返回值:Vec<(DenseUnivariatePolynomial<FiniteField>, usize)> — (不可约因子, 重数) 对。
示例:
#![allow(unused)] fn main() { use num_bigint::BigInt; use ocas_domain::{Domain, FiniteField}; use ocas_poly::DenseUnivariatePolynomial; let f = FiniteField::new(BigInt::from(5)); // x^2 - 1 over F_5 let p = DenseUnivariatePolynomial::from_coeffs( f.clone(), vec![f.element(4), f.element(0), f.element(1)]); let factors = p.factor(); assert!(!factors.is_empty()); }
DenseUnivariatePolynomial::resultant
签名:pub fn resultant(&self, other: &Self) -> D::Element
功能:用 Brown PRS 算法计算结式 $\operatorname{Res}(a, b)$。结式为零当且仅当 $a$ 和 $b$ 有非常数公因子。
实现细节:子结式 PRS,每步用 $\beta$ 精确除法(子结式定理保证整除)。
示例:
#![allow(unused)] fn main() { use ocas_domain::{IntegerDomain, Integer}; use ocas_poly::DenseUnivariatePolynomial; let d = IntegerDomain; // Res(x - 1, x - 2) = 1 - 2 = -1 let a = DenseUnivariatePolynomial::from_coeffs(d, vec![ Integer::from(-1), Integer::from(1), ]); let b = DenseUnivariatePolynomial::from_coeffs(d, vec![ Integer::from(-2), Integer::from(1), ]); assert_eq!(a.resultant(&b), Integer::from(-1)); }
性质:$\operatorname{Res}(a, b) = (-1)^{\deg a \cdot \deg b} \operatorname{Res}(b, a)$。
参见:gcd
SparseMultivariatePolynomial
#![allow(unused)] fn main() { #[derive(Debug, Clone, PartialEq, Eq)] pub struct SparseMultivariatePolynomial<D: Domain, O: MonomialOrder = Grevlex> { terms: HashMap<SmallVec<[usize; 4]>, D::Element>, domain: D, n_vars: usize, pub order: O, } }
稀疏多元多项式。只存储非零项,以指数向量 $\vec{e} = [e_1, e_2, \dots]$ 为键、系数为值的 HashMap。指数向量 $\vec{e}$ 表示单项式 $x_1^{e_1} x_2^{e_2} \cdots$。单项式序由类型参数 O 控制,默认为 Grevlex。
Sparse 构造与属性
SparseMultivariatePolynomial::new
签名:pub fn new(domain: D, n_vars: usize) -> Self
功能:创建零多项式,使用默认单项式序。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
domain | D | 系数域 |
n_vars | usize | 变量数 |
SparseMultivariatePolynomial::new_with_order
签名:pub fn new_with_order(domain: D, n_vars: usize, order: O) -> Self
功能:创建零多项式,指定单项式序。
示例:
#![allow(unused)] fn main() { use ocas_domain::IntegerDomain; use ocas_poly::sparse::{SparseMultivariatePolynomial, WeightOrder}; let order = WeightOrder::from_slice(&[2, 1]); let p = SparseMultivariatePolynomial::<_, WeightOrder>::new_with_order( IntegerDomain, 2, order, ); assert_eq!(p.n_vars(), 2); }
SparseMultivariatePolynomial::from_terms
签名:pub fn from_terms(domain: D, n_vars: usize, terms: Vec<(Vec<usize>, D::Element)>) -> Self
功能:从(指数向量, 系数)对的列表构造多项式。零系数项自动丢弃。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
domain | D | 系数域 |
n_vars | usize | 变量数 |
terms | Vec<(Vec<usize>, D::Element)> | (指数向量, 系数) 对 |
示例:
#![allow(unused)] fn main() { use ocas_domain::{IntegerDomain, Integer}; use ocas_poly::sparse::Grevlex; use ocas_poly::SparseMultivariatePolynomial; let domain = IntegerDomain; let p = SparseMultivariatePolynomial::<IntegerDomain, Grevlex>::from_terms( domain, 2, vec![(vec![1, 0], Integer::from(2)), (vec![0, 1], Integer::from(3))], ); assert_eq!(p.n_terms(), 2); assert_eq!(p.coeff(&[1, 0]), Integer::from(2)); // p = 2x + 3y }
参见:new
SparseMultivariatePolynomial::domain
签名:pub fn domain(&self) -> &D
功能:返回系数域的引用。
SparseMultivariatePolynomial::n_vars
签名:pub fn n_vars(&self) -> usize
功能:返回变量数。
SparseMultivariatePolynomial::n_terms
签名:pub fn n_terms(&self) -> usize
功能:返回非零项数。
SparseMultivariatePolynomial::is_zero
签名:pub fn is_zero(&self) -> bool
功能:判断是否为零多项式。
SparseMultivariatePolynomial::terms_ref
签名:pub fn terms_ref(&self) -> &HashMap<SmallVec<[usize; 4]>, D::Element>
功能:返回内部项映射的引用(指数 → 系数)。
SparseMultivariatePolynomial::set_term_external
签名:pub fn set_term_external(&mut self, exp: Vec<usize>, coeff: D::Element)
功能:设置单项式的系数。零系数会移除该项。
SparseMultivariatePolynomial::total_degree
签名:pub fn total_degree(&self) -> Option<usize>
功能:返回总次数(所有单项式次数的最大值)。零多项式返回 None。
SparseMultivariatePolynomial::coeff
签名:pub fn coeff(&self, exp: &[usize]) -> D::Element
功能:返回给定单项式的系数,不存在则返回域的零元。
SparseMultivariatePolynomial::degree_in
签名:pub fn degree_in(&self, var_index: usize) -> usize
功能:返回关于变量 var_index 的次数。零多项式返回 0。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
var_index | usize | 变量索引(0-based) |
返回值:该变量在所有项中的最大指数。
SparseMultivariatePolynomial::zero / one
签名:
#![allow(unused)] fn main() { pub fn zero(&self) -> Self pub fn one(&self) -> Self }
功能:返回同形状的零多项式或常数 $1$。
Sparse 基本算术
SparseMultivariatePolynomial::neg
签名:pub fn neg(&self) -> Self
功能:返回 $-p$。
SparseMultivariatePolynomial::add
签名:pub fn add(&self, other: &Self) -> Self
功能:多项式加法。
错误:若变量数不同则 panic。
SparseMultivariatePolynomial::sub
签名:pub fn sub(&self, other: &Self) -> Self
功能:多项式减法。
错误:若变量数不同则 panic。
SparseMultivariatePolynomial::mul_scalar
签名:pub fn mul_scalar(&self, scalar: &D::Element) -> Self
功能:标量乘法。
SparseMultivariatePolynomial::mul
签名:pub fn mul(&self, other: &Self) -> Self
功能:多项式乘法。对每对项分别相乘后合并同类项。
错误:若变量数不同则 panic。
示例:
#![allow(unused)] fn main() { use ocas_domain::{IntegerDomain, Integer}; use ocas_poly::sparse::Grevlex; use ocas_poly::SparseMultivariatePolynomial; let domain = IntegerDomain; let p = SparseMultivariatePolynomial::<IntegerDomain, Grevlex>::from_terms( domain, 2, vec![(vec![1, 0], Integer::from(2)), (vec![0, 1], Integer::from(3))], ); let q = SparseMultivariatePolynomial::<IntegerDomain, Grevlex>::from_terms( domain, 2, vec![(vec![1, 0], Integer::from(1)), (vec![0, 0], Integer::from(1))], ); let r = p.mul(&q); assert_eq!(r.coeff(&[1, 0]), Integer::from(2)); assert_eq!(r.coeff(&[0, 1]), Integer::from(3)); assert_eq!(r.coeff(&[2, 0]), Integer::from(2)); // (2x + 3y)(x + 1) = 2x^2 + 2x + 3xy + 3y }
SparseMultivariatePolynomial::mul_monomial
签名:pub fn mul_monomial(&self, exp: &[usize]) -> Self
功能:每个项的指数向量逐分量加上 exp。用于 Gröbner 基归约。
SparseMultivariatePolynomial::sorted_terms
签名:pub fn sorted_terms(&self) -> Vec<(&SmallVec<[usize; 4]>, &D::Element)>
功能:按单项式序返回排序后的(指数, 系数)对。
SparseMultivariatePolynomial::eval
签名:pub fn eval(&self, var_index: usize, value: &D::Element) -> Self
功能:将变量 var_index 替换为值 value,返回少一个变量的多项式。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
var_index | usize | 要替换的变量索引 |
value | &D::Element | 替换值 |
返回值:少一个变量的多项式(剩余变量保持相对顺序)。
示例:
#![allow(unused)] fn main() { use ocas_domain::{Integer, IntegerDomain}; use ocas_poly::SparseMultivariatePolynomial; use ocas_poly::Lex; let p = SparseMultivariatePolynomial::<_, Lex>::from_terms( IntegerDomain, 2, vec![ (vec![1, 1], Integer::from(1)), // xy (vec![0, 1], Integer::from(2)), // 2y ], ); // 代入 x=3:结果 = 3y + 2y = 5y let r = p.eval(0, &Integer::from(3)); assert_eq!(r.coeff(&[1]), Integer::from(5)); }
SparseMultivariatePolynomial::eval_keep
签名:pub fn eval_keep(&self, var_index: usize, value: &D::Element) -> Self
功能:将变量 var_index 替换为值,但保持变量总数不变(被替换变量的指数置零)。用于 Hensel 提升中变量位置需保持固定的场景。
SparseMultivariatePolynomial::leading_term
签名:pub fn leading_term(&self) -> Option<(&SmallVec<[usize; 4]>, &D::Element)>
功能:返回首项(指数向量, 系数)。零多项式返回 None。$O(n)$ 扫描 HashMap。
SparseMultivariatePolynomial::leading_monomial
签名:pub fn leading_monomial(&self) -> Option<&SmallVec<[usize; 4]>>
功能:返回首单项式的指数向量。
SparseMultivariatePolynomial::leading_coeff
签名:pub fn leading_coeff(&self) -> Option<&D::Element>
功能:返回首项系数。
SparseMultivariatePolynomial::content
签名:
#![allow(unused)] fn main() { pub fn content(&self) -> D::Element where D: EuclideanDomain }
功能:返回所有系数的 GCD。
示例:
#![allow(unused)] fn main() { use ocas_domain::{Integer, IntegerDomain}; use ocas_poly::SparseMultivariatePolynomial; use ocas_poly::Lex; let p = SparseMultivariatePolynomial::<_, Lex>::from_terms( IntegerDomain, 1, vec![(vec![2], Integer::from(6)), (vec![1], Integer::from(9)), (vec![0], Integer::from(3))], ); assert_eq!(p.content(), Integer::from(3)); }
SparseMultivariatePolynomial::primitive_part
签名:
#![allow(unused)] fn main() { pub fn primitive_part(&self) -> Self where D: EuclideanDomain }
功能:返回本原部分(多项式 / content)。
参见:content
SparseMultivariatePolynomial::div_exact
签名:pub fn div_exact(&self, divisor: &Self) -> Self
功能:精确除法(假设无余式)。用于有理函数规范化中已知 GCD 整除的场景。
错误:debug 模式下若除法不精确则 panic。
SparseMultivariatePolynomial::checked_div_exact
签名:pub fn checked_div_exact(&self, divisor: &Self) -> Option<Self>
功能:精确除法,不精确时返回 None。
返回值:Some(quotient) 或 None。
SparseMultivariatePolynomial::derivative
签名:pub fn derivative(&self, var_index: usize) -> Self
功能:对变量 var_index 求形式偏导。
SparseMultivariatePolynomial::taylor_coefficients
签名:pub fn taylor_coefficients(&self, var_index: usize, a: &D::Element) -> Vec<Self>
功能:计算关于变量 var_index 在点 $a$ 处的 Taylor 系数。返回 $[t_0, t_1, \dots, t_d]$ 使得 $f = \sum_j t_j (x_{\text{var}} - a)^j$。
Gröbner 基支持
SparseMultivariatePolynomial::reduce
签名:pub fn reduce(&self, basis: &[Self]) -> Self
功能:多元多项式除法。反复寻找能除尽当前首项的基元素,减去适当倍数;否则将首项移入余式。要求域为域(div 总是成功)。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
basis | &[Self] | 除数列表 |
返回值:归约后的余式。
SparseMultivariatePolynomial::spoly
签名:pub fn spoly(&self, other: &Self) -> Self
功能:计算 S-多项式。$S(f, g) = \frac{\text{lcm}}{\text{lt}(f)} \cdot f - \frac{\text{lcm}}{\text{lt}(g)} \cdot g$。
SparseMultivariatePolynomial::make_monic_inplace
签名:pub fn make_monic_inplace(&mut self) -> bool
功能:原地将多项式化为首一(除以首项系数)。首项系数不可逆时返回 false。
SparseMultivariatePolynomial::exponents_iter
签名:pub fn exponents_iter(&self) -> impl Iterator<Item = &SmallVec<[usize; 4]>>
功能:按单项式序(降序)迭代所有指数向量。用于 F4 算法的符号预处理。
多元因式分解
SparseMultivariatePolynomial::factor(IntegerDomain, Lex)
签名:
#![allow(unused)] fn main() { impl SparseMultivariatePolynomial<IntegerDomain, Lex> { pub fn factor(&self) -> Vec<(Self, usize)> } }
功能:将多元整数多项式分解为不可约因子。二元且主变量首项系数为常数时使用双变量 Hensel 提升(Wang);三元及以上、或主变量首项系数非常数的二元情形使用 EEZ 算法。
返回值:(不可约因子, 重数) 对的列表。
示例:
#![allow(unused)] fn main() { use ocas_domain::{Integer, IntegerDomain}; use ocas_poly::SparseMultivariatePolynomial; use ocas_poly::Lex; // (x^2 + y + 1)(x + y + 2) let f = SparseMultivariatePolynomial::<_, Lex>::from_terms( IntegerDomain, 2, vec![ (vec![3, 0], Integer::from(1)), (vec![2, 1], Integer::from(1)), (vec![2, 0], Integer::from(2)), (vec![1, 1], Integer::from(1)), (vec![1, 0], Integer::from(1)), (vec![0, 2], Integer::from(1)), (vec![0, 1], Integer::from(3)), (vec![0, 0], Integer::from(2)), ], ); let factors = f.factor(); assert!(factors.len() >= 2); }
SparseMultivariatePolynomial::factor(FiniteField, Lex)
签名:
#![allow(unused)] fn main() { impl SparseMultivariatePolynomial<FiniteField, Lex> { pub fn factor(&self) -> Vec<(Self, usize)> } }
功能:将多元 $\mathbb{F}_p$ 多项式分解。二元用求值–Hensel 路径,三元及以上用 EEZ。
RationalPolynomial
#![allow(unused)] fn main() { #[derive(Debug, Clone, PartialEq, Eq)] pub struct RationalPolynomial<D: Domain, O: MonomialOrder = Grevlex> { pub numerator: SparseMultivariatePolynomial<D, O>, pub denominator: SparseMultivariatePolynomial<D, O>, } }
多项式分式域元素 $\frac{\text{num}}{\text{den}}$。通过 from_num_den 构造时自动规范化为最简形式(分子分母互素,分母首项系数归一化)。
RationalPolynomial::new
签名:
#![allow(unused)] fn main() { pub fn new( numerator: SparseMultivariatePolynomial<D, O>, denominator: SparseMultivariatePolynomial<D, O>, ) -> Self }
功能:不化简地构造有理多项式。调用者须确保分母非零。
参见:from_num_den
RationalPolynomial::from_num_den
签名:
#![allow(unused)] fn main() { impl<D: EuclideanDomain, O: MonomialOrder> RationalPolynomial<D, O> { pub fn from_num_den( numerator: SparseMultivariatePolynomial<D, O>, denominator: SparseMultivariatePolynomial<D, O>, ) -> Self } }
功能:从分子分母构造并规范化。结果为最简形式:分子分母互素,分母首项系数归一化(有限域下为 1,整数域下为正)。
前置条件:分母非零。
规范化步骤:
- 计算 $\gcd(\text{num}, \text{den})$ 并约去
- 一元时用稠密 Euclid 算法精确 GCD 约简
- 归一化分母首项系数
错误:分母为零时 panic。
参见:new
RationalPolynomial::from_polynomial
签名:pub fn from_polynomial(poly: SparseMultivariatePolynomial<D, O>) -> Self
功能:从多项式构造(分母 = 1)。
RationalPolynomial::zero / one
签名:
#![allow(unused)] fn main() { pub fn zero(domain: &D, n_vars: usize) -> Self pub fn one(domain: &D, n_vars: usize) -> Self }
功能:返回零或单位有理多项式。
RationalPolynomial::is_zero / is_one
签名:
#![allow(unused)] fn main() { pub fn is_zero(&self) -> bool pub fn is_one(&self) -> bool }
功能:判断是否为零或 $1/1$。
RationalPolynomial::n_vars
签名:pub fn n_vars(&self) -> usize
功能:返回变量数。
RationalPolynomial::domain
签名:pub fn domain(&self) -> &D
功能:返回系数域引用。
RationalPolynomial::neg
签名:pub fn neg(&self) -> Self
功能:返回 $-\frac{n}{d}$。
RationalPolynomial::inv
签名:pub fn inv(&self) -> Option<Self>
功能:返回乘法逆 $\frac{d}{n}$。分子为零时返回 None。
返回值:Some(逆) 或 None(分子为零)。
RationalPolynomial::pow
签名:pub fn pow(&self, k: u32) -> Self
功能:快速幂。$\left(\frac{n}{d}\right)^k$,分子分母分别用反复平方法。
RationalPolynomial::add
签名:
#![allow(unused)] fn main() { impl<D: EuclideanDomain, O: MonomialOrder> RationalPolynomial<D, O> { pub fn add(&self, other: &Self) -> Self } }
功能:有理多项式加法。同分母时直接加分子;异分母时交叉相乘后规范化。
RationalPolynomial::sub
签名:pub fn sub(&self, other: &Self) -> Self
功能:有理多项式减法。等价于 self.add(&other.neg())。
RationalPolynomial::mul
签名:pub fn mul(&self, other: &Self) -> Self
功能:有理多项式乘法。交叉相乘后规范化。
RationalPolynomial::div
签名:pub fn div(&self, other: &Self) -> Option<Self>
功能:有理多项式除法。$\frac{a/b}{c/d} = \frac{ad}{bc}$。
返回值:Some(商) 或 None(除数分子为零)。
辅助函数
以下函数位于 ocas_poly::sparse 模块中:
monomial_divides
签名:pub fn monomial_divides(a: &[usize], b: &[usize]) -> bool
功能:判断单项式 $b$ 是否整除 $a$(即 $a$ 是 $b$ 的倍式)。返回 true 当且仅当 $a_i \geq b_i$ 对所有 $i$。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
a | &[usize] | 被除数的指数向量(倍式) |
b | &[usize] | 除数的指数向量 |
返回值:true 若 $b$ 整除 $a$。
monomial_lcm
签名:pub fn monomial_lcm(a: &[usize], b: &[usize]) -> SmallVec<[usize; 4]>
功能:计算两个单项式的最小公倍式:逐分量取最大值。
$$\text{lcm}(x^a y^b, x^c y^d) = x^{\max(a,c)} y^{\max(b,d)}$$
monomial_are_coprime
签名:pub fn monomial_are_coprime(a: &[usize], b: &[usize]) -> bool
功能:判断两个单项式是否互素(没有变量同时出现在两者中)。
类型别名
#![allow(unused)] fn main() { /// 无平方分解结果:(因子, 重数) 对的列表。 pub type SquareFreeFactors<D> = Vec<(DenseUnivariatePolynomial<D>, usize)>; /// 完全分解结果:(不可约因子, 重数) 对的列表。 pub type Factors<D> = Vec<(DenseUnivariatePolynomial<D>, usize)>; }
矩阵
来源:
ocas-poly/src/matrix.rs
Matrix<D> 是 oCAS 中通用的稠密矩阵类型,元素来自任意 EuclideanDomain。采用行优先存储,所有消元操作使用 无分数(fraction-free) 算法,避免域元素的分数膨胀。
MatrixError
#![allow(unused)] fn main() { #[derive(Debug, Clone, PartialEq, Eq)] pub enum MatrixError { ShapeMismatch, RightHandSideIsNotVector, Inconsistent, Underdetermined { rank: usize }, ResultNotInDomain, } }
矩阵运算中可能产生的错误。实现了 Display 和 std::error::Error。
ShapeMismatch
功能:矩阵形状不兼容。
触发场景:trace 用于非方阵;matmul 的 self.ncols != other.nrows;solve 中 self.nrows != b.len();augment 中行数不一致。
RightHandSideIsNotVector
功能:右端不是列向量。
触发场景:当前版本中未直接由 solve 使用(solve 接受 &[D::Element] 切片),但保留供扩展。
Inconsistent
功能:线性方程组无解(不相容)。
触发场景:solve 中消元后增广矩阵的自由行在最后一列(常数项)非零。
Underdetermined { rank }
功能:线性方程组有无穷多解(欠定)。
触发场景:solve 中系数矩阵的秩 rank 小于变量数 nvars。
ResultNotInDomain
功能:解不在目标域中。
触发场景:solve 的最终除法步骤中,div 返回 None(例如整数域中不能整除)。
Display 输出:
| 变体 | 输出 |
|---|---|
ShapeMismatch | "matrix shape mismatch" |
RightHandSideIsNotVector | "right-hand side must be a column vector" |
Inconsistent | "inconsistent linear system" |
Underdetermined { rank } | "underdetermined system (rank N)" |
ResultNotInDomain | "solution does not lie in the expected domain" |
示例:
#![allow(unused)] fn main() { use ocas_domain::{Integer, IntegerDomain}; use ocas_poly::matrix::{Matrix, MatrixError}; let d = IntegerDomain; // 不相容系统:两行相同但常数项不同 let a = Matrix::from_rows( vec![vec![Integer::from(1), Integer::from(1)], vec![Integer::from(1), Integer::from(1)]], d, ); let b = vec![Integer::from(1), Integer::from(2)]; assert_eq!(a.solve(&b), Err(MatrixError::Inconsistent)); }
参见:solve
Matrix<D>
#![allow(unused)] fn main() { #[derive(Debug, Clone, PartialEq, Eq)] pub struct Matrix<D: EuclideanDomain> { data: Vec<D::Element>, // 行优先存储 nrows: usize, ncols: usize, domain: D, } }
功能:域 D 上的稠密矩阵,元素以行优先顺序存储于 Vec<D::Element> 中。
设计不变量:
data.len() == nrows * ncols(由构造函数保证)- 元素
(i, j)在data中的位置为i * ncols + j D约束为EuclideanDomain,支持div、gcd等操作,使无分数消元成为可能
Trait 实现:
Index<(usize, usize)>/IndexMut<(usize, usize)>— 通过matrix[(i, j)]访问元素Display— 每行一行,元素间空格分隔Debug、Clone、PartialEq、Eq
构造方法
new
签名:pub fn new(nrows: usize, ncols: usize, data: Vec<D::Element>, domain: D) -> Self
功能:从行优先的一维数据创建矩阵。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
nrows | usize | 行数 |
ncols | usize | 列数 |
data | Vec<D::Element> | 行优先排列的元素,长度必须等于 nrows * ncols |
domain | D | 系数域 |
返回值:Matrix<D>
错误:若 data.len() != nrows * ncols,运行时 panic。
示例:
#![allow(unused)] fn main() { use ocas_domain::{Integer, IntegerDomain}; use ocas_poly::matrix::Matrix; let d = IntegerDomain; // 创建 2×3 矩阵: // [ 1 2 3 ] // [ 4 5 6 ] let m = Matrix::new(2, 3, vec![ Integer::from(1), Integer::from(2), Integer::from(3), Integer::from(4), Integer::from(5), Integer::from(6), ], d); assert_eq!(m.nrows(), 2); assert_eq!(m.ncols(), 3); assert_eq!(m[(0, 0)], Integer::from(1)); assert_eq!(m[(1, 2)], Integer::from(6)); }
zeros
签名:pub fn zeros(nrows: usize, ncols: usize, domain: D) -> Self
功能:创建指定形状的零矩阵。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
nrows | usize | 行数 |
ncols | usize | 列数 |
domain | D | 系数域 |
返回值:所有元素为 domain.zero() 的 Matrix<D>。
示例:
#![allow(unused)] fn main() { use ocas_domain::{Integer, IntegerDomain}; use ocas_poly::matrix::Matrix; let d = IntegerDomain; let z = Matrix::zeros(2, 3, d); assert_eq!(z[(0, 0)], Integer::from(0)); assert_eq!(z[(1, 2)], Integer::from(0)); }
identity
签名:pub fn identity(n: usize, domain: D) -> Self
功能:创建 n × n 单位矩阵。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
n | usize | 矩阵维度 |
domain | D | 系数域 |
返回值:对角线为 domain.one()、其余为 domain.zero() 的方阵。
示例:
#![allow(unused)] fn main() { use ocas_domain::{Integer, IntegerDomain}; use ocas_poly::matrix::Matrix; let d = IntegerDomain; let id = Matrix::identity(3, d); assert_eq!(id[(0, 0)], Integer::from(1)); assert_eq!(id[(0, 1)], Integer::from(0)); assert_eq!(id[(2, 2)], Integer::from(1)); }
参见:zeros
from_rows
签名:pub fn from_rows(rows: Vec<Vec<D::Element>>, domain: D) -> Self
功能:从嵌套行向量创建矩阵。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
rows | Vec<Vec<D::Element>> | 每个内层 Vec 是一行,所有行长度必须相同 |
domain | D | 系数域 |
返回值:Matrix<D>
错误:若行长度不一致,运行时 panic。
示例:
#![allow(unused)] fn main() { use ocas_domain::{Integer, IntegerDomain}; use ocas_poly::matrix::Matrix; let d = IntegerDomain; let m = Matrix::from_rows( vec![ vec![Integer::from(1), Integer::from(2), Integer::from(3)], vec![Integer::from(4), Integer::from(5), Integer::from(6)], ], d, ); assert_eq!(m.nrows(), 2); assert_eq!(m.ncols(), 3); assert_eq!(m[(0, 2)], Integer::from(3)); }
访问器方法
nrows
签名:pub fn nrows(&self) -> usize
功能:返回矩阵行数。
示例:
#![allow(unused)] fn main() { use ocas_domain::{Integer, IntegerDomain}; use ocas_poly::matrix::Matrix; let d = IntegerDomain; let m = Matrix::new(3, 2, vec![Integer::from(0); 6], d); assert_eq!(m.nrows(), 3); }
ncols
签名:pub fn ncols(&self) -> usize
功能:返回矩阵列数。
示例:
#![allow(unused)] fn main() { use ocas_domain::{Integer, IntegerDomain}; use ocas_poly::matrix::Matrix; let d = IntegerDomain; let m = Matrix::new(3, 2, vec![Integer::from(0); 6], d); assert_eq!(m.ncols(), 2); }
shape
矩阵没有单独的 shape() 方法。形状通过组合 nrows() 和 ncols() 获取:
#![allow(unused)] fn main() { use ocas_domain::{Integer, IntegerDomain}; use ocas_poly::matrix::Matrix; let d = IntegerDomain; let m = Matrix::new(3, 4, vec![Integer::from(0); 12], d); let (rows, cols) = (m.nrows(), m.ncols()); assert_eq!((rows, cols), (3, 4)); }
domain
签名:pub fn domain(&self) -> &D
功能:返回矩阵系数域的引用。
示例:
#![allow(unused)] fn main() { use ocas_domain::{IntegerDomain, EuclideanDomain}; use ocas_poly::matrix::Matrix; let d = IntegerDomain; let m = Matrix::zeros(1, 1, d); let dom = m.domain(); assert!(dom.is_zero(&dom.zero())); }
data
签名:pub fn data(&self) -> &[D::Element]
功能:返回底层行优先存储的只读切片。
示例:
#![allow(unused)] fn main() { use ocas_domain::{Integer, IntegerDomain}; use ocas_poly::matrix::Matrix; let d = IntegerDomain; let m = Matrix::from_rows( vec![vec![Integer::from(1), Integer::from(2)], vec![Integer::from(3), Integer::from(4)]], d, ); let raw = m.data(); assert_eq!(raw.len(), 4); assert_eq!(raw[0], Integer::from(1)); // (0,0) assert_eq!(raw[3], Integer::from(4)); // (1,1) }
row
签名:pub fn row(&self, i: usize) -> Vec<D::Element>
功能:返回第 i 行的拷贝。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
i | usize | 行索引,从 0 开始 |
返回值:Vec<D::Element>,长度等于 ncols。
错误:索引越界时 panic。
示例:
#![allow(unused)] fn main() { use ocas_domain::{Integer, IntegerDomain}; use ocas_poly::matrix::Matrix; let d = IntegerDomain; let m = Matrix::from_rows( vec![vec![Integer::from(1), Integer::from(2)], vec![Integer::from(3), Integer::from(4)]], d, ); let r = m.row(1); assert_eq!(r, vec![Integer::from(3), Integer::from(4)]); }
column
签名:pub fn column(&self, j: usize) -> Vec<D::Element>
功能:返回第 j 列的拷贝。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
j | usize | 列索引,从 0 开始 |
返回值:Vec<D::Element>,长度等于 nrows。
错误:索引越界时 panic。
示例:
#![allow(unused)] fn main() { use ocas_domain::{Integer, IntegerDomain}; use ocas_poly::matrix::Matrix; let d = IntegerDomain; let m = Matrix::from_rows( vec![vec![Integer::from(1), Integer::from(2)], vec![Integer::from(3), Integer::from(4)]], d, ); let c = m.column(0); assert_eq!(c, vec![Integer::from(1), Integer::from(3)]); }
参见:row
into_rows
签名:pub fn into_rows(self) -> Vec<Vec<D::Element>>
功能:将矩阵消费并转换为嵌套行向量。
返回值:外层 Vec 长度为 nrows,每个内层 Vec 长度为 ncols。
示例:
#![allow(unused)] fn main() { use ocas_domain::{Integer, IntegerDomain}; use ocas_poly::matrix::Matrix; let d = IntegerDomain; let m = Matrix::from_rows( vec![vec![Integer::from(1), Integer::from(2)], vec![Integer::from(3), Integer::from(4)]], d, ); let rows = m.into_rows(); assert_eq!(rows.len(), 2); assert_eq!(rows[0], vec![Integer::from(1), Integer::from(2)]); assert_eq!(rows[1], vec![Integer::from(3), Integer::from(4)]); }
索引与显示
Index<(usize, usize)>
签名:impl Index<(usize, usize)> for Matrix<D>
功能:通过 matrix[(i, j)] 只读访问元素 (i, j),等价于 data[i * ncols + j]。
示例:
#![allow(unused)] fn main() { use ocas_domain::{Integer, IntegerDomain}; use ocas_poly::matrix::Matrix; let d = IntegerDomain; let m = Matrix::from_rows( vec![vec![Integer::from(5), Integer::from(6)]], d, ); assert_eq!(m[(0, 0)], Integer::from(5)); assert_eq!(m[(0, 1)], Integer::from(6)); }
IndexMut<(usize, usize)>
签名:impl IndexMut<(usize, usize)> for Matrix<D>
功能:通过 matrix[(i, j)] = val 可写访问元素。
示例:
#![allow(unused)] fn main() { use ocas_domain::{Integer, IntegerDomain}; use ocas_poly::matrix::Matrix; let d = IntegerDomain; let mut m = Matrix::zeros(2, 2, d); m[(0, 1)] = Integer::from(42); m[(1, 0)] = Integer::from(-7); assert_eq!(m[(0, 1)], Integer::from(42)); assert_eq!(m[(1, 0)], Integer::from(-7)); }
Display
签名:impl Display for Matrix<D> where D::Element: Display
功能:格式化输出矩阵,每行占一行,元素间空格分隔。
示例:
#![allow(unused)] fn main() { use ocas_domain::{Integer, IntegerDomain}; use ocas_poly::matrix::Matrix; let d = IntegerDomain; let m = Matrix::from_rows( vec![vec![Integer::from(1), Integer::from(2)], vec![Integer::from(3), Integer::from(4)]], d, ); let s = format!("{:?}", m); // 输出:Debug 表示(IntegerDomain 未实现 Display,Matrix 的 Display 需要元素实现 Display) assert!(s.contains("1")); }
矩阵运算
swap_rows
签名:pub fn swap_rows(&mut self, i: usize, j: usize, start_col: usize)
功能:交换第 i 行和第 j 行,从第 start_col 列开始。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
i | usize | 第一行索引 |
j | usize | 第二行索引 |
start_col | usize | 起始列(含),此列之前的元素不变 |
返回值:无(就地修改)。
示例:
#![allow(unused)] fn main() { use ocas_domain::{Integer, IntegerDomain}; use ocas_poly::matrix::Matrix; let d = IntegerDomain; let mut m = Matrix::from_rows( vec![vec![Integer::from(1), Integer::from(2)], vec![Integer::from(3), Integer::from(4)]], d, ); m.swap_rows(0, 1, 0); assert_eq!(m[(0, 0)], Integer::from(3)); assert_eq!(m[(0, 1)], Integer::from(4)); assert_eq!(m[(1, 0)], Integer::from(1)); assert_eq!(m[(1, 1)], Integer::from(2)); }
transpose
签名:pub fn transpose(&self) -> Matrix<D>
功能:返回矩阵的转置。$(A^T){ij} = A{ji}$。
返回值:新的 Matrix<D>,形状为 (ncols, nrows)。
示例:
#![allow(unused)] fn main() { use ocas_domain::{Integer, IntegerDomain}; use ocas_poly::matrix::Matrix; let d = IntegerDomain; let a = Matrix::from_rows( vec![vec![Integer::from(1), Integer::from(2), Integer::from(3)], vec![Integer::from(4), Integer::from(5), Integer::from(6)]], d, ); let t = a.transpose(); assert_eq!(t.nrows(), 3); assert_eq!(t.ncols(), 2); assert_eq!(t[(0, 0)], Integer::from(1)); assert_eq!(t[(0, 1)], Integer::from(4)); assert_eq!(t[(2, 1)], Integer::from(6)); }
参见:trace
trace
签名:pub fn trace(&self) -> Result<D::Element, MatrixError>
功能:计算方阵的迹,即对角线元素之和:$\text{tr}(A) = \sum_{i=0}^{n-1} a_{ii}$。
返回值:
Ok(sum)— 迹的值Err(MatrixError::ShapeMismatch)— 矩阵非方阵
示例:
#![allow(unused)] fn main() { use ocas_domain::{Integer, IntegerDomain}; use ocas_poly::matrix::Matrix; let d = IntegerDomain; let a = Matrix::from_rows( vec![vec![Integer::from(1), Integer::from(2)], vec![Integer::from(3), Integer::from(4)]], d, ); // tr = 1 + 4 = 5 assert_eq!(a.trace().unwrap(), Integer::from(5)); }
非方阵错误:
#![allow(unused)] fn main() { use ocas_domain::{Integer, IntegerDomain}; use ocas_poly::matrix::{Matrix, MatrixError}; let d = IntegerDomain; let m = Matrix::from_rows(vec![vec![Integer::from(1), Integer::from(2)]], d); assert_eq!(m.trace(), Err(MatrixError::ShapeMismatch)); }
参见:determinant
matmul
签名:pub fn matmul(&self, other: &Matrix<D>) -> Result<Matrix<D>, MatrixError>
功能:计算矩阵乘积 $C = AB$,其中 $C_{ij} = \sum_k A_{ik} \cdot B_{kj}$。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
other | &Matrix<D> | 右乘矩阵 |
返回值:
Ok(Matrix)— 乘积矩阵,形状为(self.nrows, other.ncols)Err(MatrixError::ShapeMismatch)—self.ncols != other.nrows
示例:
#![allow(unused)] fn main() { use ocas_domain::{Integer, IntegerDomain}; use ocas_poly::matrix::Matrix; let d = IntegerDomain; let a = Matrix::from_rows( vec![vec![Integer::from(1), Integer::from(2)], vec![Integer::from(3), Integer::from(4)]], d, ); let b = Matrix::from_rows( vec![vec![Integer::from(5), Integer::from(6)], vec![Integer::from(7), Integer::from(8)]], d, ); let c = a.matmul(&b).unwrap(); // C = [[1*5+2*7, 1*6+2*8], [3*5+4*7, 3*6+4*8]] // = [[19, 22], [43, 50]] assert_eq!(c[(0, 0)], Integer::from(19)); assert_eq!(c[(0, 1)], Integer::from(22)); assert_eq!(c[(1, 0)], Integer::from(43)); assert_eq!(c[(1, 1)], Integer::from(50)); }
形状不兼容:
#![allow(unused)] fn main() { use ocas_domain::{Integer, IntegerDomain}; use ocas_poly::matrix::{Matrix, MatrixError}; let d = IntegerDomain; let a = Matrix::from_rows(vec![vec![Integer::from(1), Integer::from(2)]], d); let b = Matrix::from_rows(vec![vec![Integer::from(3), Integer::from(4)]], d); // a: 1×2, b: 1×2 → ncols(a)=2 ≠ nrows(b)=1 assert_eq!(a.matmul(&b), Err(MatrixError::ShapeMismatch)); }
参见:solve
determinant
签名:pub fn determinant(&self) -> Result<D::Element, MatrixError>
功能:使用 Bareiss 无分数算法(带部分选主元)计算方阵行列式。
算法要点:
- 通过消元将矩阵化为上三角,每步除以前一步的主元(Bareiss 保证整除)
- 部分选主元:遇到零主元时向下搜索非零行并交换
- 若消元过程中整列全零,行列式为零
- 符号由行交换次数的奇偶性决定
返回值:
Ok(det)— 行列式值Err(MatrixError::ShapeMismatch)— 矩阵非方阵
示例:
#![allow(unused)] fn main() { use ocas_domain::{Integer, IntegerDomain}; use ocas_poly::matrix::Matrix; let d = IntegerDomain; // 2×2: det = 1*4 - 2*3 = -2 let a = Matrix::from_rows( vec![vec![Integer::from(1), Integer::from(2)], vec![Integer::from(3), Integer::from(4)]], d, ); assert_eq!(a.determinant().unwrap(), Integer::from(-2)); }
奇异矩阵:
#![allow(unused)] fn main() { use ocas_domain::{Integer, IntegerDomain}; use ocas_poly::matrix::Matrix; let d = IntegerDomain; // 第二行是第一行的 2 倍 let a = Matrix::from_rows( vec![vec![Integer::from(1), Integer::from(2)], vec![Integer::from(2), Integer::from(4)]], d, ); assert_eq!(a.determinant().unwrap(), Integer::from(0)); }
rank
签名:pub fn rank(&self) -> usize
功能:通过无分数高斯消元计算矩阵的秩。克隆矩阵后执行 row_echelon,返回非零行数。
返回值:usize,矩阵的秩。
示例:
#![allow(unused)] fn main() { use ocas_domain::{Integer, IntegerDomain}; use ocas_poly::matrix::Matrix; let d = IntegerDomain; // 满秩 2×2 let id = Matrix::identity(2, d); assert_eq!(id.rank(), 2); // 秩亏缺:第二行 = 2 × 第一行 let a = Matrix::from_rows( vec![vec![Integer::from(1), Integer::from(2), Integer::from(3)], vec![Integer::from(2), Integer::from(4), Integer::from(6)], vec![Integer::from(1), Integer::from(1), Integer::from(1)]], d, ); assert_eq!(a.rank(), 2); }
inverse
签名:pub fn inverse(&self) -> Result<Matrix<D>, MatrixError>
功能:计算方阵的逆矩阵 $A^{-1}$。通过逐列求解 $A x_j = e_j$($e_j$ 为单位矩阵第 $j$ 列)实现。
返回值:
Ok(Matrix)— 逆矩阵,满足 $A \cdot A^{-1} = I$Err(MatrixError::ShapeMismatch)— 矩阵非方阵Err(MatrixError::Inconsistent)— 矩阵奇异(无解)Err(MatrixError::Underdetermined { .. })— 奇异导致欠定Err(MatrixError::ResultNotInDomain)— 逆矩阵元素不在域中
⚠️ 注意:在整数域 IntegerDomain 上,仅单模矩阵($\det = \pm 1$)有整数逆。一般矩阵在有理数域 RationalDomain 上求逆。
示例:
#![allow(unused)] fn main() { use ocas_domain::{Integer, IntegerDomain}; use ocas_poly::matrix::Matrix; let d = IntegerDomain; // 单模矩阵:det = 1*1 - 0*2 = 1 let a = Matrix::from_rows( vec![vec![Integer::from(1), Integer::from(2)], vec![Integer::from(0), Integer::from(1)]], d, ); let inv = a.inverse().unwrap(); assert_eq!(inv[(0, 0)], Integer::from(1)); assert_eq!(inv[(0, 1)], Integer::from(-2)); assert_eq!(inv[(1, 0)], Integer::from(0)); assert_eq!(inv[(1, 1)], Integer::from(1)); // 验证 A * A^{-1} = I let prod = a.matmul(&inv).unwrap(); assert_eq!(prod, Matrix::identity(2, IntegerDomain)); }
奇异矩阵错误:
#![allow(unused)] fn main() { use ocas_domain::{Integer, IntegerDomain}; use ocas_poly::matrix::Matrix; let d = IntegerDomain; let a = Matrix::from_rows( vec![vec![Integer::from(1), Integer::from(2)], vec![Integer::from(2), Integer::from(4)]], d, ); assert!(a.inverse().is_err()); }
参见:solve、determinant
augment
签名:pub fn augment(&self, other: &Matrix<D>) -> Result<Matrix<D>, MatrixError>
功能:水平拼接两个矩阵,得到增广矩阵 $[A \mid B]$。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
other | &Matrix<D> | 要拼接在右侧的矩阵 |
返回值:
Ok(Matrix)— 增广矩阵,形状为(nrows, ncols_a + ncols_b)Err(MatrixError::ShapeMismatch)— 两矩阵行数不一致
示例:
#![allow(unused)] fn main() { use ocas_domain::{Integer, IntegerDomain}; use ocas_poly::matrix::Matrix; let d = IntegerDomain; let a = Matrix::from_rows( vec![vec![Integer::from(3), Integer::from(2)], vec![Integer::from(1), Integer::from(1)]], d, ); let b = Matrix::new(2, 1, vec![Integer::from(5), Integer::from(2)], d); let aug = a.augment(&b).unwrap(); assert_eq!(aug.nrows(), 2); assert_eq!(aug.ncols(), 3); assert_eq!(aug[(0, 0)], Integer::from(3)); assert_eq!(aug[(0, 2)], Integer::from(5)); }
参见:solve
solve
签名:pub fn solve(&self, b: &[D::Element]) -> Result<Vec<D::Element>, MatrixError>
功能:求解线性方程组 $Ax = b$。使用无分数高斯消元 + 回代。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
b | &[D::Element] | 右端向量,长度必须等于 self.nrows |
返回值:
Ok(Vec<D::Element>)— 解向量,长度等于self.ncolsErr(MatrixError::ShapeMismatch)—b.len() != self.nrowsErr(MatrixError::Inconsistent)— 方程组无解Err(MatrixError::Underdetermined { rank })— 无穷多解,rank < ncolsErr(MatrixError::ResultNotInDomain)— 解不在域中(如整数除法失败)
算法步骤:
- 构造增广矩阵 $[A \mid b]$
- 对前
ncols列执行无分数行阶梯化(row_echelon) - 检查秩以下行的常数项是否为零(一致性检查)
- 执行无分数回代(
back_substitution) - 若秩 < 变量数,返回
Underdetermined - 最终除以主元得到解
示例:
#![allow(unused)] fn main() { use ocas_domain::{Integer, IntegerDomain}; use ocas_poly::matrix::Matrix; let d = IntegerDomain; // 2x + y = 4 // x + y = 3 → x = 1, y = 2 let a = Matrix::from_rows( vec![vec![Integer::from(2), Integer::from(1)], vec![Integer::from(1), Integer::from(1)]], d, ); let b = vec![Integer::from(4), Integer::from(3)]; let x = a.solve(&b).unwrap(); assert_eq!(x, vec![Integer::from(1), Integer::from(2)]); }
3×3 系统:
#![allow(unused)] fn main() { use ocas_domain::{Integer, IntegerDomain}; use ocas_poly::matrix::Matrix; let d = IntegerDomain; // x + y + z = 6 // 2x - y + z = 3 // x + 2y - z = 2 → x=1, y=2, z=3 let a = Matrix::from_rows( vec![ vec![Integer::from(1), Integer::from(1), Integer::from(1)], vec![Integer::from(2), Integer::from(-1), Integer::from(1)], vec![Integer::from(1), Integer::from(2), Integer::from(-1)], ], d, ); let b = vec![Integer::from(6), Integer::from(3), Integer::from(2)]; let x = a.solve(&b).unwrap(); assert_eq!(x, vec![Integer::from(1), Integer::from(2), Integer::from(3)]); }
不相容系统:
#![allow(unused)] fn main() { use ocas_domain::{Integer, IntegerDomain}; use ocas_poly::matrix::{Matrix, MatrixError}; let d = IntegerDomain; let a = Matrix::from_rows( vec![vec![Integer::from(1), Integer::from(1)], vec![Integer::from(1), Integer::from(1)]], d, ); let b = vec![Integer::from(1), Integer::from(2)]; assert_eq!(a.solve(&b), Err(MatrixError::Inconsistent)); }
欠定系统:
#![allow(unused)] fn main() { use ocas_domain::{Integer, IntegerDomain}; use ocas_poly::matrix::{Matrix, MatrixError}; let d = IntegerDomain; let a = Matrix::from_rows( vec![vec![Integer::from(1), Integer::from(1), Integer::from(1)]], d, ); let b = vec![Integer::from(3)]; assert!(matches!(a.solve(&b), Err(MatrixError::Underdetermined { .. }))); }
内部消元方法
以下两个方法为 pub,通常不需要直接调用,但在某些高级场景下(如自定义消元管线)可能有用。
row_echelon
签名:pub fn row_echelon(&mut self, max_col: usize) -> usize
功能:对前 max_col 列执行无分数高斯消元,将矩阵化为行阶梯形。就地修改矩阵。返回秩(非零行数)。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
max_col | usize | 消元涉及的最大列数(不含),自动钳制到 ncols |
返回值:usize — 矩阵的秩。
算法细节:
- 每列寻找非零主元,必要时交换行
- 从主元行提取 GCD 内容以防止系数增长
- 消元采用 gcd 约化的无分数缩放:设 $g = \gcd(\text{pivot}, a_{kj})$,被消行乘以 $\text{pivot} / g$,主元行乘以 $a_{kj} / g$,相减后不含分数
示例:
#![allow(unused)] fn main() { use ocas_domain::{Integer, IntegerDomain}; use ocas_poly::matrix::Matrix; let d = IntegerDomain; let mut a = Matrix::from_rows( vec![ vec![Integer::from(1), Integer::from(2), Integer::from(3)], vec![Integer::from(2), Integer::from(4), Integer::from(6)], vec![Integer::from(0), Integer::from(1), Integer::from(1)], ], d, ); let rank = a.row_echelon(3); assert_eq!(rank, 2); }
back_substitution
签名:pub fn back_substitution(&mut self, max_col: usize)
功能:对已处于行阶梯形的矩阵执行无分数回代。就地修改矩阵的前 max_col 列。
算法细节:
- 从最后一行开始向上处理
- 对每个主元行,先提取 GCD 内容
- 对主元上方的行,用同样的 gcd 约化缩放(被消行乘以 $\text{pivot} / g$、主元行乘以 $a_{kj} / g$,其中 $g = \gcd(\text{pivot}, a_{kj})$)后消去主元列
参见:row_echelon、solve
完整示例
以下综合示例演示矩阵的基本工作流:构造、运算、求解。
use ocas_domain::{Integer, IntegerDomain}; use ocas_poly::matrix::Matrix; fn main() { let d = IntegerDomain; // 构造矩阵 A let a = Matrix::from_rows( vec![ vec![Integer::from(2), Integer::from(1), Integer::from(-1)], vec![Integer::from(-3), Integer::from(-1), Integer::from(2)], vec![Integer::from(-2), Integer::from(1), Integer::from(2)], ], d, ); // 行列式 let det = a.determinant().unwrap(); assert_eq!(det, Integer::from(-1)); // det = -1(单模) // 秩 assert_eq!(a.rank(), 3); // 迹 let tr = a.trace().unwrap(); assert_eq!(tr, Integer::from(3)); // 2 + (-1) + 2 // 转置 let at = a.transpose(); assert_eq!(at[(0, 1)], Integer::from(-3)); // 求解 Ax = b let b = vec![Integer::from(8), Integer::from(-11), Integer::from(-3)]; let x = a.solve(&b).unwrap(); assert_eq!(x, vec![Integer::from(2), Integer::from(3), Integer::from(-1)]); // 逆矩阵(det = -1,整数逆存在) let inv = a.inverse().unwrap(); // 验证 A * A^{-1} = I let prod = a.matmul(&inv).unwrap(); assert_eq!(prod, Matrix::identity(3, IntegerDomain)); // 矩阵乘法 let c = a.matmul(&a).unwrap(); assert_eq!(c.nrows(), 3); assert_eq!(c.ncols(), 3); }
参见
- 表达式系统 — 表达式树构造与操作
- 系数域 —
EuclideanDomaintrait 与域实现 - 多项式 — 多项式类型,使用相同的域 trait
- 线性代数基础 — Bareiss 算法与高斯消元的数学原理
微积分
来源:
ocas-calc/src/
oCAS 的微积分模块提供符号求导、积分、级数展开和部分分式分解。所有运算在 AtomArena 上执行,结果为新的 Atom 句柄。无法化简的导数或积分以保留函数形式 Derivative(expr, var) 或 Integral(expr, var) 返回。
模块结构:
| 子模块 | 功能 |
|---|---|
derivative | 符号微分 diff |
integral | 分层积分管线 integrate、integrate_heuristic、integrate_with_fuel |
series | Taylor 展开 taylor、替换 substitute |
partial_fraction | 部分分式分解 apart、合并 together |
tower | Risch 算法的微分域塔构造 |
ode | 常微分方程求解(见 求解器) |
diff
签名:
#![allow(unused)] fn main() { pub fn diff<'a>(ctx: &'a AtomArena<'a>, expr: Atom<'a>, var: Symbol) -> Atom<'a> }
功能:对 expr 关于 var 求符号导数。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
ctx | &AtomArena<'a> | 表达式 arena,用于构造结果 |
expr | Atom<'a> | 被求导的表达式 |
var | Symbol | 求导变量(interned 字符串) |
返回值:Atom<'a> — 求导并化简后的表达式。
算法说明:
diff 递归遍历表达式树,对每种节点类型应用标准求导规则:
| 节点类型 | 求导规则 |
|---|---|
Num(_) | $\frac{d}{dx} c = 0$ |
Var(s) | 若 $s = \text{var}$ 则为 $1$,否则为 $0$ |
Add([a₁, …, aₙ]) | 和的导数 = 导数的和:$\sum \frac{d a_i}{dx}$ |
Mul([a₁, …, aₙ]) | 乘积法则:$\frac{d}{dx}\prod a_i = \sum_i \left(\frac{d a_i}{dx} \prod_{j \neq i} a_j\right)$ |
Pow([base, exp]) | 幂函数法则(含链式法则),支持常数和非常数指数 |
Fun(name, args) | 内置函数查表 + 链式法则 |
内置函数求导表:
| 函数 $f(u)$ | 导数 $f'(u) \cdot u'$ |
|---|---|
| $\sin(u)$ | $\cos(u) \cdot u'$ |
| $\cos(u)$ | $-\sin(u) \cdot u'$ |
| $\tan(u)$ | $\sec^2(u) \cdot u'$ |
| $\sec(u)$ | $\sec(u)\tan(u) \cdot u'$ |
| $\exp(u)$ | $\exp(u) \cdot u'$ |
| $\log(u)$ | $u^{-1} \cdot u'$ |
| $\sqrt{u}$ | $(2\sqrt{u})^{-1} \cdot u'$ |
| $\text{atan}(u)$ | $(1 + u^2)^{-1} \cdot u'$ |
表在 derivative.rs 的 diff_function 中硬编码。不在表中的函数(如 $\text{asin}$、$\sinh$、$\cosh$、$\tanh$ 等)也返回未求值形式 Derivative(f, var)。
未求值形式:当求导函数不在内置表中时,结果为 Derivative(expr, var),可通过后续模式匹配检测(匹配函数名 "Derivative" 的 Fun 节点;注意 is_fallback 只检测 "Integral",不检测 Derivative)。
示例:
#![allow(unused)] fn main() { use ocas_atom::{AtomArena, Symbol}; use ocas_calc::diff; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let x = ctx.var("x"); // d/dx [sin(x)] = cos(x) let sin_x = ctx.fun("sin", &[x]); let result = diff(&ctx, sin_x, Symbol::new("x")); assert_eq!(result.to_string(), "cos(x)"); // d/dx [x^3] = 3*x^2 let x_cubed = ctx.pow(x, ctx.num(3)); let result = diff(&ctx, x_cubed, Symbol::new("x")); assert_eq!(result.to_string(), "3*(x^2)"); // d/dx [exp(x^2)] = 2*x*exp(x^2) (链式法则) let inner = ctx.pow(x, ctx.num(2)); let expr = ctx.fun("exp", &[inner]); let result = diff(&ctx, expr, Symbol::new("x")); // 输出包含 2*x*exp(x^2) 的化简形式 // 未知函数返回 Derivative 形式 let f = ctx.fun("my_func", &[x]); let result = diff(&ctx, f, Symbol::new("x")); // result 包含 Derivative(my_func(x), x) }
integrate_outcome
签名(0.28.0 新增):
#![allow(unused)] fn main() { pub fn integrate_outcome<'a>( ctx: &'a AtomArena<'a>, expr: Atom<'a>, var: Symbol, ) -> Outcome<'a> pub fn integrate_outcome_with_options<'a>( ctx: &'a AtomArena<'a>, expr: Atom<'a>, var: Symbol, options: IntegrateOptions, ) -> Outcome<'a> }
功能:认证积分。返回三值 [Outcome]:
| 取值 | 说明 |
|---|---|
Found { value, certificate } | 原函数 + 可机检的符号证书(精确检查器中 D(F) − f ≡ 0) |
ProvedNonElementary { witness } | 已证明非初等;0.28.0 无生产者(变体为后续非初等层预留) |
Unknown { residue, uncertified } | 诚实未知:residue 是 Integral(f, var);uncertified 是管线候选,不是答案,仅供诊断 |
被精确检查器证伪的结果永远不会以 Found 返回(Risch 结果还会在塔自身的域内二次校验)。
示例:
#![allow(unused)] fn main() { use ocas::prelude::*; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let x = ctx.var("x"); let two_x = ctx.mul(&[ctx.num(2), x]); let outcome = integrate_outcome(&ctx, two_x, Symbol::new("x")); assert!(outcome.is_found()); println!("{}", outcome.value()); // (2^-1)*(x^2) println!("{:?}", outcome.certificate()); // Some(Certificate { .. }) }
参见:integrate、[Certificate]、[CertDecline]
integrate
签名:
#![allow(unused)] fn main() { pub fn integrate<'a>(ctx: &'a AtomArena<'a>, expr: Atom<'a>, var: Symbol) -> Atom<'a> }
功能:对 expr 关于 var 求符号积分。使用分层积分管线,逐层尝试不同算法。
0.28.0:返回的结果未经认证(可能是管线候选)。需要「原函数 + 可机检符号证书」时用
integrate_outcome。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
ctx | &AtomArena<'a> | 表达式 arena |
expr | Atom<'a> | 被积表达式 |
var | Symbol | 积分变量 |
返回值:Atom<'a> — 积分结果。无法求解时返回 Integral(expr, var)。
积分管线:
integrate 内部调用 integrate_raw(递归,深度上限 MAX_DEPTH = 8)。管线分两个阶段:
┌───────────────────────────────────────────┐
│ 阶段一:integrate_raw 按节点类型分派 │
├───────────────────────────────────────────┤
│ Num / Var → 常数积分:c·x; │
│ ∫x dx = (1/2)·x² │
│ Add → 逐项递归积分 │
│ Mul (integrate_product) → 提取常数因子 │
│ Pow (integrate_power) → 幂函数积分 │
│ ∫xⁿ, ∫(a·x+b)ⁿ, │
│ 分数指数 (a·x+b)^(p/q) │
│ Fun (integrate_function) → 查表 + 线性替换 │
│ ∫sin(u), ∫cos(u), │
│ ∫exp(u), ∫log(u) │
│ 若 u = a·x+b 自动应用 │
│ 链式法则 │
├───────────────────────────────────────────┤
│ 阶段二:以上分派失败(返回 fallback 形式) │
│ → try_risch_or_fallback,按顺序尝试: │
├───────────────────────────────────────────┤
│ 1. 有理函数积分 (integrate_rational) │
│ 2. 二次/线性分母幂递推 (integrate_quad_power) │
│ 3. 有界展开预通道 (expand_prepass, 0.27.2) │
│ 3b. 精确线性平方折叠 (fold_linear_squares, │
│ p²+2pq+q² → (p+q)²,仿射基,0.27.3) │
│ 4. 有理导数核代换 (integrate_kernel_subst, │
│ tan/cot/tanh/coth, 0.27.2) │
│ 5. 双曲闭式族 (integrate_hyperbolic_reduction,│
│ 0.27.2) │
│ 6. 符号常数有理后端 (integrate_rational_symbolic, │
│ ℚ(symbols)) │
│ 7. Risch 算法 (risch_integrate) │
│ 8. 三角→指数 + Risch + realify │
│ 9. 特殊函数 (special_integrate) │
│ 10. 规则表引擎 (integrate_rules, A–H 族) │
│ 11. 二次根式引擎 (integrate_sqrt_quadratic) │
│ 12. Chebyshev 二项微分 (integrate_binomial) │
│ 12b. 多根式基归约 (integrate_multi_radical, │
│ 0.30.0;binomial 之后,exp_log 之前) │
│ 13. exp/log 核代换 (integrate_exp_log) │
│ 14. 反三角/反双曲 (integrate_inverse_trig) │
│ 14b. 反函数代换 M4 (inverse_trig 分派序末, 0.30.0) │
│ u = F(w):11 头、仿射变元、整数核幂、多项式权 │
│ 15. 三角积化和差 (trig_reduce_products) │
│ 16. 三角分母幂递推 (integrate_trig_reduction) │
│ 17. 单三角核有理式 + tan/sec │
│ (integrate_trig_kernel) │
│ 18. 有界展开重试 (expand_bounded) │
│ 19. 半幂前端 (integrate_half_power, 0.27.2)│
│ 20. 椭圆约化 (integrate_elliptic, │
│ EllipticF/E/Pi, 0.27.2) │
│ 21. 启发式方法 (heuristic_integrate) │
│ 分部积分 (LIATE)、三角替换、Weierstrass、Euler 替换│
│ 22. 未求值形式 (fallback) │
│ Integral(expr, var) │
└───────────────────────────────────────────┘
OCAS_INTEGRATE_TRACE=1 会为每个阶段打印
[trace] enter <阶段> :: <表达式> / [trace] decline <阶段>,
用于把悬挂或回退的题目无猜测地归因到具体阶段。
OCAS_INTEGRATE_RULES=0 仅关闭第 10 阶段(规则表)。
第 3 与 3b 是前置改写而非可打点阶段:它们在管线入口重写被积函数,早于任何机制。
线性平方折叠的动机是题库把「线性式平方」写成展开的三项式(a² + 2abx + b²x²),
符号有理后端会在其上磨算——rubi-00854 在折叠前是 10 s 单题超时,折叠后 0.01 s 求解。
折叠有双重闸门:重构出的底必须对积分变量仿射(因此不会把积化和差阶段需要的三角平方
重新合成),且只有当重新展开候选平方能精确复现原和(或经 normalize 后相同)时才接受。
它绝不在非整数幂之下生效——那里 ((p+q)²)^{1/2} = |p+q| ≠ p+q。
阶段一 — 节点类型分派:
Num(_):常数的积分为线性函数c * xVar(var):$\int x,dx = \frac{1}{2} x^2$;其他变量视为常数Add:逐项递归积分再求和Mul(integrate_product):识别c · f(x)形式(常数乘以非常数因子),提取常数因子后递归积分Pow(integrate_power):幂函数积分,支持 $\int x^n,dx$、线性形式 $(a \cdot x + b)^n$(含 $n = -1$ 时 $\int \frac{dx}{ax+b} = \frac{\log(ax+b)}{a}$)以及分数指数 $(a \cdot x + b)^{p/q}$Fun(integrate_function):查表 + 线性替换
查表与线性替换:
内置积分表覆盖幂函数(integrate_power)以及 sin、cos、exp、log。当被积函数(或幂的底)的参数为线性形式 $a \cdot x + b$ 时,自动应用链式法则 $\int f(ax+b),dx = \frac{1}{a} F(ax+b)$。
阶段二 — 回退链(try_risch_or_fallback):
-
有理函数积分(
integrate_rational):当表达式为 $\var$ 的有理函数(无其他变量或函数应用)时,执行:- 多项式部分逐项积分
- Hermite 约化分离有理部分
- 对数部分:完全平方(二次分母 → $\log$ 或 $\text{atan}$)+ Rothstein–Trager 结式
详见 有理函数积分。
-
Risch 算法(
risch_integrate):构造微分域塔 $\mathbb{Q}(x, t_1, \dots, t_n)$,逐层递归积分。详见 Risch 算法。 -
三角→指数重写:若 Risch 对原表达式失败且表达式含三角函数,通过
trig_to_exp重写为复指数形式,再次尝试 Risch,最后用realify转回实数形式。 -
特殊函数(
special_integrate):当 Risch 与三角重写均未成功时,尝试匹配已知的特殊函数反导数。详见 特殊函数积分。 -
启发式方法(
heuristic_integrate):分部积分 (LIATE)、三角替换、Weierstrass 替换、Euler 替换。详见 integrate_heuristic。 -
未求值形式(
fallback):以上均失败时,返回Integral(expr, var)。可通过is_fallback检测:
#![allow(unused)] fn main() { pub(crate) fn is_fallback<'a>(atom: &Atom<'a>) -> bool { matches!(atom.node(), AtomNode::Fun(name, _) if name.as_str() == "Integral") } }
示例:
#![allow(unused)] fn main() { use ocas_atom::{AtomArena, Symbol}; use ocas_calc::integrate; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let x = ctx.var("x"); // ∫ x² dx = (1/3) x³ let expr = ctx.pow(x, ctx.num(2)); let result = integrate(&ctx, expr, Symbol::new("x")); assert_eq!(result.to_string(), "(3^-1)*(x^3)"); // ∫ sin(x) dx = -cos(x) let expr = ctx.fun("sin", &[x]); let result = integrate(&ctx, expr, Symbol::new("x")); // 输出:-cos(x) 的化简形式 // ∫ 1/x dx = log(x) (通过有理函数积分器或 Risch) let expr = ctx.pow(x, ctx.num(-1)); let result = integrate(&ctx, expr, Symbol::new("x")); // 输出:log(x) // 不可积表达式返回 Integral 形式 let expr = ctx.fun("my_func", &[x]); let result = integrate(&ctx, expr, Symbol::new("x")); // result 为 Integral(my_func(x), x) }
参见:integrate_heuristic、integrate_with_fuel、有理函数积分、Risch 算法
integrate_heuristic
签名:
#![allow(unused)] fn main() { pub fn integrate_heuristic<'a>(ctx: &'a AtomArena<'a>, expr: Atom<'a>, var: Symbol) -> Atom<'a> }
功能:尝试启发式积分技巧。仅使用分部积分、三角替换、Weierstrass 替换和 Euler 替换,不调用 Risch 算法或有理函数积分器。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
ctx | &AtomArena<'a> | 表达式 arena |
expr | Atom<'a> | 被积表达式 |
var | Symbol | 积分变量 |
返回值:Atom<'a> — 若启发式成功则返回积分结果,否则返回 Integral(expr, var)。
启发式方法:
1. 分部积分(LIATE 启发式)
对 $\int u \cdot v' , dx$ 形式的乘积,按 LIATE 优先级选择 $u$(优先级从高到低):
| 优先级 | 类型 | LIATE 评分 | 示例 |
|---|---|---|---|
| 0(最高) | 对数 (L) | 0 | $\log(x)$ |
| 1 | 反三角 (I) | 1 | $\text{asin}(x)$ |
| 2 | 代数 (A) | 2 | $x^2$ |
| 3 | 三角 (T) | 3 | $\sin(x)$ |
| 4 | 指数 (E) | 4 | $\exp(x)$ |
| 5(最低) | 其他 | 5 | — |
选择评分最低的因子作为 $u$,其余作为 $v'$。递归深度上限为 PARTS_MAX_DEPTH = 2。
2. 三角替换
匹配以下模式并返回已知反导数:
| 被积函数 | 替换 | 结果 |
|---|---|---|
| $\frac{1}{\sqrt{a^2 - x^2}}$ | $x = a\sin\theta$ | $\text{asin}(x/a)$ |
| $\frac{1}{\sqrt{a^2 + x^2}}$ | $x = a\sinh t$ | $\text{asinh}(x/a)$ |
| $\frac{1}{\sqrt{x^2 - a^2}}$ | $x = a\cosh t$ | $\text{acosh}(x/a)$ |
| $\sqrt{a^2 - x^2}$ | $x = a\sin\theta$ | $\frac{x\sqrt{a^2-x^2} + a^2,\text{asin}(x/a)}{2}$ |
| $\sqrt{a^2 + x^2}$ | $x = a\sinh t$ | $\frac{x\sqrt{a^2+x^2} + a^2,\text{asinh}(x/a)}{2}$ |
| $\sqrt{x^2 - a^2}$ | $x = a\cosh t$ | $\frac{x\sqrt{x^2-a^2} - a^2,\text{acosh}(x/a)}{2}$ |
3. Weierstrass 替换
当被积函数是 $\sin(u)$ 和 $\cos(u)$ 的有理函数($u$ 关于 $\text{var}$ 线性)时,应用 $t = \tan(u/2)$ 替换:
$$\sin(u) = \frac{2t}{1+t^2}, \quad \cos(u) = \frac{1-t^2}{1+t^2}$$
将三角有理函数化为 $t$ 的有理函数后递归积分。
4. Euler 替换
当被积函数包含 $\sqrt{ax^2 + bx + c}$ 时,尝试 Euler 替换消去根号。
示例:
#![allow(unused)] fn main() { use ocas_atom::{AtomArena, Symbol}; use ocas_calc::integrate_heuristic; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let x = ctx.var("x"); // ∫ x·exp(x) dx — 分部积分 (LIATE: x=代数, exp=指数) let expr = ctx.mul(&[x, ctx.fun("exp", &[x])]); let result = integrate_heuristic(&ctx, expr, Symbol::new("x")); // 结果包含 (x-1)*exp(x) 的化简形式 // ∫ 1/√(1 - x²) dx = asin(x) // 构造 (1 - x²)^(-1/2):指数为 -1·(2^-1) let one = ctx.num(1); let sqrt_arg = ctx.add(&[one, ctx.mul(&[ctx.num(-1), ctx.pow(x, ctx.num(2))])]); let neg_half = ctx.mul(&[ctx.num(-1), ctx.pow(ctx.num(2), ctx.num(-1))]); let expr = ctx.pow(sqrt_arg, neg_half); let result = integrate_heuristic(&ctx, expr, Symbol::new("x")); // 输出:asin(x) 的化简形式 }
参见:integrate
integrate_with_fuel
签名:
#![allow(unused)] fn main() { pub fn integrate_with_fuel<'a>( ctx: &'a AtomArena<'a>, expr: Atom<'a>, var: Symbol, fuel: &Fuel, ) -> Result<Atom<'a>, FuelError> }
功能:带燃料预算的积分。积分遍历本身使用内部深度限制(MAX_DEPTH = 8),此入口将 fuel 线程化到积分后的两个化简阶段,防止病态结果使重写引擎无限循环。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
ctx | &AtomArena<'a> | 表达式 arena |
expr | Atom<'a> | 被积表达式 |
var | Symbol | 积分变量 |
fuel | &Fuel | 燃料预算(重写步数上限) |
返回值:
Ok(Atom<'a>)— 积分化简成功Err(FuelError)— 燃料在化简过程中耗尽
与 integrate 的区别:
integrate 在化简阶段不设上限;integrate_with_fuel 对化简阶段施加燃料约束。积分遍历本身的深度限制(MAX_DEPTH)两者相同。
示例:
#![allow(unused)] fn main() { use ocas_atom::{AtomArena, Symbol}; use ocas_core::arena::Arena; use ocas_core::fuel::Fuel; use ocas_calc::integrate_with_fuel; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let x = ctx.var("x"); let fuel = Fuel::new(500); let result = integrate_with_fuel(&ctx, x, Symbol::new("x"), &fuel); match result { Ok(expr) => assert_eq!(expr.to_string(), "(2^-1)*(x^2)"), Err(_) => panic!("fuel exhausted"), } }
参见:integrate
taylor
签名:
#![allow(unused)] fn main() { pub fn taylor<'a>( ctx: &'a AtomArena<'a>, expr: Atom<'a>, var: Symbol, point: Atom<'a>, order: usize, ) -> Atom<'a> }
功能:计算 expr 在 point 处关于 var 的 Taylor 展开,展开到 order 阶(含)。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
ctx | &AtomArena<'a> | 表达式 arena |
expr | Atom<'a> | 被展开的表达式 |
var | Symbol | 展开变量 |
point | Atom<'a> | 展开点(可为任意表达式,通常为 0 或某常数) |
order | usize | 展开阶数(含) |
返回值:Atom<'a> — 截断多项式:
$$\sum_{n=0}^{\text{order}} \frac{f^{(n)}(\text{point})}{n!} \cdot (\text{var} - \text{point})^n$$
算法说明:
通过重复符号微分 + 在展开点处求值计算各阶系数:
- 对 $n = 0, 1, \dots, \text{order}$:
- 计算 $f^{(n)}(x)$(通过
diff) - 用
substitute在 $x = \text{point}$ 处求值得 $f^{(n)}(\text{point})$ - 乘以 $\frac{1}{n!}$(通过
mul_by_factorial_inverse) - 乘以 $(x - \text{point})^n$
- 计算 $f^{(n)}(x)$(通过
- 将所有项求和,应用微积分化简规则
示例:
#![allow(unused)] fn main() { use ocas_atom::{AtomArena, Symbol}; use ocas_calc::taylor; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let x = ctx.var("x"); // exp(x) 在 x=0 处展开到 3 阶 let expr = ctx.fun("exp", &[x]); let result = taylor(&ctx, expr, Symbol::new("x"), ctx.num(0), 3); assert_eq!( result.to_string(), "1 + x + ((2^-1)*(x^2)) + ((6^-1)*(x^3))" ); // sin(x) 在 x=0 处展开到 5 阶 let expr = ctx.fun("sin", &[x]); let result = taylor(&ctx, expr, Symbol::new("x"), ctx.num(0), 5); // 输出:x + (-1*(6^-1)*(x^3)) + ((120^-1)*(x^5)) }
参见:diff、substitute
substitute
签名:
#![allow(unused)] fn main() { pub fn substitute<'a>( ctx: &'a AtomArena<'a>, expr: Atom<'a>, var: Symbol, replacement: Atom<'a>, ) -> Atom<'a> }
功能:将 expr 中所有出现的变量 var 替换为 replacement。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
ctx | &AtomArena<'a> | 表达式 arena |
expr | Atom<'a> | 被替换的表达式 |
var | Symbol | 要替换的变量名 |
replacement | Atom<'a> | 替换表达式 |
返回值:Atom<'a> — 替换后的新表达式。原表达式不受影响(Arena 上的不可变数据结构)。
语义:
- 深度遍历表达式树
- 仅替换精确匹配
var的叶节点Var(Symbol) - 替换后不自动化简(如需化简,需额外调用
simplify)
示例:
#![allow(unused)] fn main() { use ocas_atom::{AtomArena, Symbol}; use ocas_calc::substitute; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let x = ctx.var("x"); let y = ctx.var("y"); // (x² + x)[x → y] = y² + y let expr = ctx.add(&[ctx.pow(x, ctx.num(2)), x]); let result = substitute(&ctx, expr, Symbol::new("x"), y); assert_eq!(result.to_string(), "(y^2) + y"); // sin(x)[x → 2*y] = sin(2*y) let expr = ctx.fun("sin", &[x]); let two_y = ctx.mul(&[ctx.num(2), y]); let result = substitute(&ctx, expr, Symbol::new("x"), two_y); assert_eq!(result.to_string(), "sin(2*y)"); }
apart
签名:
#![allow(unused)] fn main() { pub fn apart<D: EuclideanDomain>( num: &DenseUnivariatePolynomial<D>, den: &DenseUnivariatePolynomial<D>, ) -> ( Option<DenseUnivariatePolynomial<D>>, Vec<PartialFractionTerm<D>>, ) }
功能:对有理函数 $\frac{\text{num}(x)}{\text{den}(x)}$ 执行部分分式分解。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
num | &DenseUnivariatePolynomial<D> | 分子多项式 |
den | &DenseUnivariatePolynomial<D> | 分母多项式 |
返回值:(Option<poly>, Vec<PartialFractionTerm<D>>) — 多项式部分(若分子次数 ≥ 分母次数)和部分分式项列表。
数学原理:
给定适当分式 $\frac{p(x)}{q(x)}$(即 $\deg(p) < \deg(q)$),将分母作无平方分解 $q = \prod_i f_i^{e_i}$,然后分解为:
$$\frac{p(x)}{q(x)} = \text{poly_part} + \sum_i \sum_{k=1}^{e_i} \frac{a_{i,k}(x)}{f_i(x)^k}$$
若 $\deg(p) \geq \deg(q)$,先执行多项式除法,商作为 poly_part,余式继续分解。
PartialFractionTerm<D> 结构:
#![allow(unused)] fn main() { pub struct PartialFractionTerm<D: EuclideanDomain> { pub numer: DenseUnivariatePolynomial<D>, // 分子 a_{i,k}(x) pub denom: DenseUnivariatePolynomial<D>, // 不可约因子 f_i(x) pub exp: usize, // 指数 k } }
表示分数 $\frac{\text{numer}(x)}{\text{denom}(x)^{\text{exp}}}$。
示例:
#![allow(unused)] fn main() { use ocas_domain::{RationalDomain, Rational}; use ocas_poly::DenseUnivariatePolynomial; use ocas_calc::partial_fraction::apart; let d = RationalDomain; // 1 / (x² - 1) — 分母无平方(单一平方自由因子 x²-1) let num = DenseUnivariatePolynomial::from_coeffs(d, vec![Rational::new(1, 1)]); let den = DenseUnivariatePolynomial::from_coeffs(d, vec![ Rational::new(-1, 1), // 常数项 Rational::new(0, 1), // x 系数 Rational::new(1, 1), // x² 系数 ]); let (poly_part, terms) = apart(&num, &den); assert!(poly_part.is_none()); // deg(num) < deg(den),无多项式部分 // x²-1 无平方 → 返回单项:分母为 x²-1、指数为 1 // (apart 基于无平方分解而非不可约分解,不会拆成 (x-1)(x+1)) // (x³ + 1) / (x² - 1) — 需要多项式除法 let num = DenseUnivariatePolynomial::from_coeffs(d, vec![ Rational::new(1, 1), Rational::new(0, 1), Rational::new(0, 1), Rational::new(1, 1), ]); let (poly_part, terms) = apart(&num, &den); assert!(poly_part.is_some()); // 商多项式 x }
together
签名:
#![allow(unused)] fn main() { pub fn together<D: EuclideanDomain>( poly_part: Option<&DenseUnivariatePolynomial<D>>, terms: &[PartialFractionTerm<D>], ) -> (DenseUnivariatePolynomial<D>, DenseUnivariatePolynomial<D>) }
功能:将部分分式项合并回单一有理函数。是 apart 的逆操作。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
poly_part | Option<&DenseUnivariatePolynomial<D>> | 多项式部分(apart 返回的第一项) |
terms | &[PartialFractionTerm<D>] | 部分分式项列表 |
返回值:(numerator, denominator) — 合并后的分子和分母多项式。
算法:
将多项式部分和所有分式项通分:计算公分母 $\text{lcm}$,将每项扩展到公分母后求和,返回结果的分子和分母。
示例:
#![allow(unused)] fn main() { use ocas_domain::{RationalDomain, Rational}; use ocas_poly::DenseUnivariatePolynomial; use ocas_calc::partial_fraction::{apart, together}; let d = RationalDomain; let num = DenseUnivariatePolynomial::from_coeffs(d, vec![Rational::new(1, 1)]); let den = DenseUnivariatePolynomial::from_coeffs(d, vec![ Rational::new(-1, 1), Rational::new(0, 1), Rational::new(1, 1), ]); // apart → round-trip → together 应还原原始分数 let (poly_part, terms) = apart(&num, &den); let (result_num, result_den) = together(poly_part.as_ref(), &terms); // result_num/result_den 等价于 num/den(可能差一个常数因子) }
参见:apart
有理函数积分
来源:ocas-calc/src/integral/rational.rs
有理函数积分器 integrate_rational 处理 $\var$ 的有理函数(不含其他变量或函数应用)。采用 Bronstein《Symbolic Integration I》第 2 章的标准三步法:
1. 多项式部分
对 $\int \sum c_k x^k , dx = \sum \frac{c_k}{k+1} x^{k+1}$ 逐项积分。
2. Hermite 约化
对真分式 $\frac{a(x)}{d(x)}$($d$ 为首一多项式),分解为:
$$\frac{a}{d} = \frac{d}{dx}\left(\frac{g_{\text{num}}}{g_{\text{den}}}\right) + \frac{a_1}{d_1}$$
其中 $d_1$ 是无平方的。有理部分 $\frac{g_{\text{num}}}{g_{\text{den}}}$ 的导数可直接计算。
3. 对数部分
对无平方分母的剩余部分 $\frac{a_1}{d_1}$,根据分母次数选择策略:
| 分母次数 | 策略 |
|---|---|
| 0 | 常数积分 |
| 1 | $c \cdot \log(ax + b)$ |
| 2 | 完全平方 → $\log$ 或 $\text{atan}$ |
| ≥ 3 | Rothstein–Trager 结式法 |
Rothstein–Trager 方法:
计算 $R(t) = \text{Res}_x(d, a - t \cdot d')$,$R(t)$ 的有理根 $c_i$ 给出:
$$\int \frac{a}{d} = \sum_i c_i \cdot \log(\gcd(d, a - c_i \cdot d'))$$
当 $R(t)$ 在 $\mathbb{Q}$ 上不能完全分解时,对应项以未求值形式 Integral(term, var) 返回。
返回值:
Some(Atom)— 表达式是 $\var$ 的有理函数,返回积分结果None— 表达式不是 $\var$ 的纯有理函数(含其他变量或函数)
示例:
#![allow(unused)] fn main() { use ocas_atom::{AtomArena, Symbol}; use ocas_calc::integral::rational::integrate_rational; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let x = ctx.var("x"); // ∫ 1/x dx = log(x) let result = integrate_rational(&ctx, ctx.pow(x, ctx.num(-1)), Symbol::new("x")); assert_eq!(result.unwrap().to_string(), "log(x)"); // ∫ 1/(x²+1) dx = atan(x) (通过完全平方) // 表达式需构建为 x 的有理函数 }
Risch 算法
来源:ocas-calc/src/integral/risch.rs、ocas-calc/src/tower/
Risch 算法处理初等超越函数的积分,基于 Bronstein《Symbolic Integration I》第 5–6 章。
微分域塔
算法首先构造微分域塔 $\mathbb{Q}(x, t_1, \dots, t_n)$,其中每个生成元 $t_i$ 是对数或指数:
#![allow(unused)] fn main() { pub(crate) enum GenKind { Constant, // 常数符号(如虚数单位 I),D t = 0 Log, // t_i = log(u) Exp, // t_i = exp(u) } }
塔的构造(build_tower)遍历表达式中的函数应用,为每个 log 或 exp 创建新层。限制条件:
- 仅接受
log和exp函数(三角函数需先通过trig_to_exp重写) - 拒绝代数函数(非整数指数,如 $\sqrt{x}$)
- 拒绝代数相关的生成元(如 $\log(x)$ 和 $\log(2x)$)
逐层积分
在塔的每一层 $k(t_i)$,算法执行:
- Hermite 约化(
hermite_tower):将 $\frac{a}{d}$ 分解为 $D(g) + \frac{a_1}{d_1}$,其中 $d_1$ 无平方 - 多项式部分:
- 原语层($t = \log(u)$):待定系数法,顶层常数由对数约束确定
- 超指数层($t = \exp(u)$):每层解 Risch 微分方程 $Dq + fq = g$
- 对数部分:匹配对数导数恒等式 $c \cdot \frac{D d}{d} \to c \cdot \log(d)$
三角函数处理
integrate 先对原表达式直接尝试 Risch;失败且表达式含三角函数时,通过 trig_to_exp 将三角函数重写为复指数形式后再尝试:
$$\sin(u) \to \frac{e^{iu} - e^{-iu}}{2i}, \quad \cos(u) \to \frac{e^{iu} + e^{-iu}}{2}$$
积分结果通过 realify 尝试转回实数形式:
| 复数模式 | 实数形式 |
|---|---|
| $c \cdot \log(u + iv) + c \cdot \log(u - iv)$ | $c \cdot \log(u^2 + v^2)$ |
| $c \cdot \log(u + iv) - c \cdot \log(u - iv)$ | $2c \cdot \text{atan}(v/u)$ |
| $\exp(Iu) \cdot \exp(Iv)$ | $\exp(I(u+v))$(合并指数使抵消可见,如 $e^{Ix}e^{-Ix} \to e^0$) |
若 realify 无法匹配模式,返回复数形式(数学上仍正确,可通过微分验证)。
深度限制
MAX_RISCH_DEPTH = 16,通过线程本地计数器 RISCH_DEPTH 和守卫 RischDepthGuard(RAII 自动减计数)实现。
返回值:
Some(Atom)— 积分成功None— 表达式超出已实现片段(调用者回退到其他积分器)
示例:
#![allow(unused)] fn main() { // Risch 算法通过 integrate 自动调用,通常不直接使用 // ∫ exp(x²) · 2x dx = exp(x²) — Risch 可处理的典型例子 // ∫ sin(x)/x dx — 无初等原函数,Risch 返回 None,特殊函数层处理 }
特殊函数积分
来源:ocas-calc/src/integral/special.rs
当 Risch 算法证明积分无初等原函数时,特殊函数积分器尝试匹配已知的非初等反导数。
支持的特殊函数
| 被积函数 | 反导数 | 函数名 |
|---|---|---|
| $e^{-x^2}$ | $\frac{\sqrt{\pi}}{2},\text{erf}(x)$ | 误差函数 |
| $e^{x^2}$ | $\frac{\sqrt{\pi}}{2},\text{erfi}(x)$ | 虚误差函数 |
| $\frac{e^x}{x}$ | $\text{Ei}(x)$ | 指数积分 |
| $\frac{\sin x}{x}$ | $\text{Si}(x)$ | 正弦积分 |
| $\frac{\cos x}{x}$ | $\text{Ci}(x)$ | 余弦积分 |
| $\frac{\sinh x}{x}$ | $\text{Shi}(x)$ | 双曲正弦积分 |
| $\frac{\cosh x}{x}$ | $\text{Chi}(x)$ | 双曲余弦积分 |
| $\sin(x^2)$ | $\sqrt{\pi/2},\text{fresnels}(\sqrt{2/\pi},x)$ | Fresnel S 积分(函数名 fresnels) |
| $\cos(x^2)$ | $\sqrt{\pi/2},\text{fresnelc}(\sqrt{2/\pi},x)$ | Fresnel C 积分(函数名 fresnelc) |
匹配策略:
special_integrate 检查被积函数的因子结构:
erf_family:匹配 $e^{c \cdot x^2}$ 形式ei_family:匹配 $e^{cx} / x$ 形式trig_integral_family:匹配 $\sin(x)/x$、$\cos(x)/x$、$\sinh(x)/x$、$\cosh(x)/x$(自变量必须恰为 $x$)fresnel_family:匹配 $\sin(cx^2)$、$\cos(cx^2)$reduction_families(0.27.3):把「多项式(或单项分母幂)× 特殊函数头」化为闭式的归约族, 只使用本模块测试中逐条验证过的恒等式:- 多项式 $\times F(a + b x)$,$F$ 取
erf、erfc、erfi、Si、Ci、Shi、Chi、Ei(代换 $u = a + bx$、展开、再分部,残项为初等函数); - 多项式 $\times \text{Ei}(n, a + bx)$(整数阶 $n$),用反向递推 $E_n = (e^{-u} - n E_{n+1})/u$;
- $F(bx)/x^m$($m \ge 2$,$F = \text{Ei}$ 或 $\text{Ei}(n, \cdot)$),递降终止于 $\text{Ei}(\pm u)$ 加初等项;
- 多项式 $\times F(a + bx)^2$(六个可平方的头),分部 + 高斯矩递推(
erf/erfi)或 $F \cdot \cos/\sin$ 耦合递推(Si/Ci/Shi/Chi)。
- 多项式 $\times F(a + b x)$,$F$ 取
每条归约链都带硬步数预算(MAX_SPECIAL_STEPS = 16、MAX_SPECIAL_DEG = 8),无法匹配时诚实拒绝,
任何族都不会留下 Integral(...) 残项。
返回值:
Some(Atom)— 匹配成功,返回含特殊函数的反导数None— 不匹配任何已知模式,调用者以Integral(expr, var)形式返回
设计说明:
特殊函数定义与 SymPy 一致,结果可通过 sympy.integrate 交叉验证。数值验证 oracle
(ocas-tests/src/integral_eval.rs)已实现上述全部函数头,因此本族的「已解」结果是用
「对发射形式求导」判定的,而不是靠「结果里没有 Integral(」这一字符串判据。
辅助函数
is_fallback
#![allow(unused)] fn main() { pub(crate) fn is_fallback<'a>(atom: &Atom<'a>) -> bool }
功能:检查 atom 是否为未求值积分形式(函数名为 "Integral")。注意:未求值的导数(Derivative 形式)由 diff 直接构造,不经由此函数检测。
is_constant
#![allow(unused)] fn main() { pub(crate) fn is_constant<'a>(expr: Atom<'a>, var: Symbol) -> bool }
功能:检查 expr 是否不包含变量 var(即关于 var 为常数)。
fraction_exponent
#![allow(unused)] fn main() { fn fraction_exponent<'a>(exp: Atom<'a>) -> Option<(i64, i64)> }
功能:将指数原子解析为分数 $p/q$(小整数)。接受 $p \cdot q^{-1}$ 形式,用于幂函数积分。
linear_form
#![allow(unused)] fn main() { fn linear_form<'a>(ctx: &'a AtomArena<'a>, expr: Atom<'a>, var: Symbol) -> Option<(Atom<'a>, Atom<'a>)> }
功能:若 expr 形如 $a \cdot \text{var} + b$($a, b$ 关于 var 为常数),返回 (a, b);否则返回 None。
内部架构
化简管线
integrate的结果经过两级化简:先用默认重写规则default_rules($0 + x = x$、$1 \cdot x = x$、$x^1 = x$ 等代数恒等式)化简 20 步,再用calculus_rules化简 10 步,最后normalize到规范形式diff与taylor直接使用calculus_rules化简后normalize
calculus_rules(rules.rs)以 default_rules 为基础,追加微积分专用恒等式:exp(0) → 1、log(1) → 0、sin(0) → 0、cos(0) → 1、tan(0) → 0、$(-1) \cdot (-1) \to 1$、$1^x \to 1$ 等。
深度限制
| 限制 | 值 | 用途 |
|---|---|---|
MAX_DEPTH | 8 | integrate_raw 递归深度 |
MAX_RISCH_DEPTH | 16 | Risch 算法递归深度 |
PARTS_MAX_DEPTH | 2 | 分部积分递归深度 |
模块依赖关系
ocas-calc
├── derivative::diff
├── integral
│ ├── mod.rs — integrate, integrate_with_fuel, integrate_heuristic
│ ├── heuristic.rs — 分部积分, 三角/Weierstrass/Euler 替换
│ ├── rational.rs — Hermite 约化, Rothstein–Trager
│ ├── risch.rs — Risch 算法核心
│ ├── rde.rs — Risch 微分方程求解器
│ ├── trig.rs — 三角→指数重写, realify
│ ├── special.rs — erf/Ei/Si/Ci/Fresnel
│ └── tower/
│ ├── build.rs — 微分域塔构造
│ ├── elem.rs — 塔元素类型 (KElem, KPoly, KRat)
│ └── convert.rs — Atom ↔ 塔元素转换
├── series.rs — taylor, substitute
├── partial_fraction.rs — apart, together
└── rules.rs — 微积分化简规则
参见
- 数学:符号微积分 — 微分与 Taylor 展开的数学原理
- 数学:Risch 积分算法 — 微分域塔与 Risch 算法的完整数学推导
- 数学:多项式 GCD 与因式分解 — Hermite 约化与无平方分解的数学基础
- 求解器 API — ODE 求解(
dsolve、classify_ode) - 表达式系统 —
Atom、AtomArena、Symbol的基本用法
Rust API 参考:求解器
本章涵盖 oCAS 中的方程求解功能,分为三大模块:线性方程组与丢番图方程(ocas-calc::solve)、多项式系统求解(ocas-poly::ideal)、以及常微分方程求解(ocas-calc::ode)。
线性方程组
solve_linear_rational
签名:pub fn solve_linear_rational(a: &[Vec<i64>], b: &[i64]) -> Result<Vec<(i64, i64)>, SolveError>
功能:在有理数域 $\mathbb{Q}$ 上求解线性方程组 $A\mathbf{x} = \mathbf{b}$。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
a | &[Vec<i64>] | $n \times n$ 系数矩阵,行优先存储,每个 Vec<i64> 为一行 |
b | &[i64] | 长度为 $n$ 的右端向量 |
返回值:Result<Vec<(i64, i64)>, SolveError>
Ok(vec):解向量,每个元素(num, den)表示有理数 $\frac{\text{num}}{\text{den}}$,保证 $\text{den} > 0$ 且 $\gcd(|\text{num}|, \text{den}) = 1$Err(SolveError):见下方错误列表
错误:
SolveError::EmptySystem— $A$ 或 $\mathbf{b}$ 为空SolveError::NonSquare— $A$ 不是方阵(或 $\mathbf{b}$ 长度 ≠ 行数)SolveError::Inconsistent— 方程组无解(增广矩阵秩 > 系数矩阵秩)SolveError::Underdetermined { rank }— 方程组有无穷多解(秩不足)SolveError::ResultNotInDomain— 解不在目标数域中SolveError::Matrix(_)— 内部矩阵运算失败(经From<MatrixError>转换)SolveError::Other(_)— 其他错误
示例:
#![allow(unused)] fn main() { use ocas_calc::solve::solve_linear_rational; // 求解: // 2x + y = 5 // x - y = 1 // → x = 2, y = 1 let a = vec![vec![2, 1], vec![1, -1]]; let b = vec![5, 1]; let x = solve_linear_rational(&a, &b).unwrap(); assert_eq!(x, vec![(2, 1), (1, 1)]); }
参见:solve_linear_integer、Matrix::solve
solve_linear_integer
签名:pub fn solve_linear_integer(a: &[Vec<i64>], b: &[i64]) -> Result<Vec<i64>, SolveError>
功能:在整数环 $\mathbb{Z}$ 上求解线性方程组 $A\mathbf{x} = \mathbf{b}$。要求解恰好为整数,否则返回错误。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
a | &[Vec<i64>] | $n \times n$ 系数矩阵,行优先存储 |
b | &[i64] | 长度为 $n$ 的右端向量 |
返回值:Result<Vec<i64>, SolveError>
Ok(vec):整数解向量Err(SolveError):错误类型同solve_linear_rational,额外可能返回ResultNotInDomain(解含分数)
错误:同 solve_linear_rational。
示例:
#![allow(unused)] fn main() { use ocas_calc::solve::solve_linear_integer; // 求解: // x + y = 3 // x - y = 1 // → x = 2, y = 1 let a = vec![vec![1, 1], vec![1, -1]]; let b = vec![3, 1]; let x = solve_linear_integer(&a, &b).unwrap(); assert_eq!(x, vec![2, 1]); }
丢番图方程
DiophantineSolution
签名:
#![allow(unused)] fn main() { #[derive(Debug, Clone, PartialEq, Eq)] pub struct DiophantineSolution { /// 特解 (x0, y0) pub particular: (i64, i64), /// 齐次解步长 (x_step, y_step) pub general: (i64, i64), } }
功能:线性丢番图方程 $ax + by = c$ 的解的表示。
字段:
| 字段 | 类型 | 说明 |
|---|---|---|
particular | (i64, i64) | 一组特解 $(x_0, y_0)$ |
general | (i64, i64) | 齐次方程 $ax + by = 0$ 的基解 $(x_{\text{step}}, y_{\text{step}})$,通解为 $(x_0 + k \cdot x_{\text{step}},; y_0 + k \cdot y_{\text{step}})$,$k \in \mathbb{Z}$ |
solve_diophantine
签名:pub fn solve_diophantine(a: i64, b: i64, c: i64) -> Option<DiophantineSolution>
功能:求解线性丢番图方程 $ax + by = c$。使用扩展 Euclid 算法,当且仅当 $\gcd(a, b) \mid c$ 时有解。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
a | i64 | $x$ 的系数 |
b | i64 | $y$ 的系数 |
c | i64 | 右端常数 |
返回值:Option<DiophantineSolution>
Some(sol):解存在时返回特解和通解结构None:$\gcd(a, b) \nmid c$,方程无整数解
错误:无返回 Result;不可解时返回 None。
示例:
#![allow(unused)] fn main() { use ocas_calc::solve::solve_diophantine; // 求解 3x + 5y = 1 // 特解:x = 2, y = -1(因为 3×2 + 5×(-1) = 1) // 通解:x = 2 + 5k, y = -1 - 3k let sol = solve_diophantine(3, 5, 1).unwrap(); assert_eq!(sol.particular, (2, -1)); assert_eq!(sol.general, (5, -3)); // 无解的情况:2x + 4y = 3(gcd(2,4)=2 不整除 3) assert!(solve_diophantine(2, 4, 3).is_none()); }
多项式系统求解
RealSolution
签名:
#![allow(unused)] fn main() { #[derive(Debug, Clone)] pub struct RealSolution { /// 每个变量的值(按变量顺序) pub values: Vec<f64>, /// 代数重数 pub multiplicity: usize, } }
功能:多项式方程组的一个实数解(数值近似)。
字段:
| 字段 | 类型 | 说明 |
|---|---|---|
values | Vec<f64> | 变量值 $[x_1, x_2, \dots, x_n]$,按原方程组中的变量顺序排列 |
multiplicity | usize | 解的代数重数(当前实现中固定为 1,尚未计算实际重数) |
ZeroDimSolutions
签名:
#![allow(unused)] fn main() { #[derive(Debug, Clone)] pub struct ZeroDimSolutions { /// 所有实数解 pub solutions: Vec<RealSolution>, /// 商环 k[x₁,...,xₙ]/I 的向量空间维数 pub vector_space_dimension: usize, } }
功能:零维多项式方程组的求解结果。
字段:
| 字段 | 类型 | 说明 |
|---|---|---|
solutions | Vec<RealSolution> | 所有实数解(可能包含近似值) |
vector_space_dimension | usize | 商环 $k[x_1, \dots, x_n]/I$ 的向量空间维数,等于 Lex GB 中各变量一元多项式次数之积 |
PolynomialSystemSolution
签名:
#![allow(unused)] fn main() { #[derive(Debug, Clone)] pub enum PolynomialSystemSolution { /// 零维系统:有限个解 ZeroDimensional(ZeroDimSolutions), /// 正维系统:解集有正维数分量,返回 Gröbner 基 PositiveDimensional(GroebnerBasis<RationalDomain, Lex>), /// 矛盾系统(理想为 ⟨1⟩),无解 Empty, } }
功能:多项式方程组求解结果的枚举类型。系统自动判定维数并选择合适的求解策略。
变体:
| 变体 | 说明 |
|---|---|
ZeroDimensional(z) | 系统有有限个解,z 包含所有实数解和向量空间维数 |
PositiveDimensional(gb) | 系统的解集有正维分量(无穷多解),返回 Lex 序下的 Gröbner 基 |
Empty | 方程组矛盾(Gröbner 基为 {1}),无解。注意:传入空方程组时返回的是 PositiveDimensional(空基),而非 Empty |
solve_polynomial_system
签名:
#![allow(unused)] fn main() { pub fn solve_polynomial_system( equations: &[SparseMultivariatePolynomial<RationalDomain, Lex>], algo: Algorithm, ) -> PolynomialSystemSolution }
功能:求解多项式方程组 $f_1 = f_2 = \cdots = f_m = 0$。自动计算 Lex 序 Gröbner 基,判定系统维数,并对零维系统通过三角分解回代求出所有实数解。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
equations | &[SparseMultivariatePolynomial<RationalDomain, Lex>] | 多项式方程组,每个多项式表示一个方程 $f_i = 0$ |
algo | Algorithm | Gröbner 基算法选择:Algorithm::Auto(自动)、Algorithm::Buchberger、Algorithm::F4、Algorithm::F5 |
错误:不返回 Result;通过枚举变体表达所有可能结果(零维解、正维基、空系统)。
算法说明:
- 计算方程组生成理想的 Gröbner 基(使用指定算法)。输入多项式本身已按 Lex 序类型化(
SparseMultivariatePolynomial<RationalDomain, Lex>),因此 GB 直接在 Lex 序下计算,无需换序 - 若 GB = {1},返回
Empty(方程组矛盾、无解) - 判定是否零维:每个变量 $x_i$ 在 GB 中是否有纯幂首单项式 $x_i^{N_i}$
- 零维:提取 Lex GB 的三角结构,从最后一个变量开始逐个回代(Sturm 实根隔离 + 二分精化),对每个变量求解一元多项式的实根;商环维数按各变量一元多项式次数之积估计
- 正维:返回 Lex GB 供进一步分析
示例:
#![allow(unused)] fn main() { use ocas_domain::{RationalDomain, Rational}; use ocas_poly::sparse::Lex; use ocas_poly::{Algorithm, SparseMultivariatePolynomial}; use ocas_poly::ideal::{solve_polynomial_system, PolynomialSystemSolution}; let d = RationalDomain; // 方程组: // x² + y² - 1 = 0 (单位圆) // x - y = 0 (直线 y = x) // 解:(±1/√2, ±1/√2) let f1 = SparseMultivariatePolynomial::<_, Lex>::from_terms(d, 2, vec![ (vec![2, 0], Rational::new(1, 1)), // x² (vec![0, 2], Rational::new(1, 1)), // y² (vec![0, 0], Rational::new(-1, 1)), // -1 ]); let f2 = SparseMultivariatePolynomial::<_, Lex>::from_terms(d, 2, vec![ (vec![1, 0], Rational::new(1, 1)), // x (vec![0, 1], Rational::new(-1, 1)), // -y ]); let sol = solve_polynomial_system(&[f1, f2], Algorithm::Auto); match sol { PolynomialSystemSolution::ZeroDimensional(z) => { assert_eq!(z.solutions.len(), 2); // 两个解:约 (0.707, 0.707) 和 (-0.707, -0.707) } _ => panic!("期望零维系统"), } }
参见:GroebnerBasis、Algorithm、is_zero_dimensional
常微分方程(ODE)求解
ODE
签名:
#![allow(unused)] fn main() { #[derive(Debug, Clone, Copy)] pub struct ODE<'a> { /// 方程,标准形式 lhs - rhs = 0 pub equation: Atom<'a>, /// 未知函数,例如 y(x) pub func: Atom<'a>, /// 自变量,例如 x pub var: Symbol, } }
功能:描述一个常微分方程。方程以 equation = 0 的标准形式存储,其中 func 是未知函数(如 $y(x)$),var 是自变量(如 $x$)。
字段:
| 字段 | 类型 | 说明 |
|---|---|---|
equation | Atom<'a> | 方程的规范化形式(lhs - rhs),已归一化 |
func | Atom<'a> | 未知函数,例如 y(x) |
var | Symbol | 自变量符号,例如 "x" |
构造示例:
#![allow(unused)] fn main() { use ocas_atom::{AtomArena, Symbol}; use ocas_calc::ode::ODE; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let x = ctx.var("x"); let y = ctx.fun("y", &[x]); let dy = ctx.fun("Derivative", &[y, x]); // 表示 y' - y = 0 let ode = ODE { equation: ctx.add(&[dy, ctx.mul(&[ctx.num(-1), y])]), func: y, var: Symbol::new("x"), }; }
ODESolution
签名:
#![allow(unused)] fn main() { #[derive(Debug, Clone, Copy)] pub enum ODESolution<'a> { /// 显式解 y = expr Explicit(Atom<'a>), /// 隐式解 F(x, y) = 0 Implicit(Atom<'a>), /// 参数解 (x(t), y(t)) Parametric(Atom<'a>, Atom<'a>), /// 级数解(截断表达式 + 项数) Series(Atom<'a>, usize), /// 系统解(各分量) System(&'a [Atom<'a>]), /// 未能求解,返回原 ODE Unsolved(ODE<'a>), } }
功能:ODE 求解结果的枚举类型。
变体:
| 变体 | 说明 |
|---|---|
Explicit(expr) | 显式解 $y = \text{expr}$,含自由常数 C1, C2, ... |
Implicit(expr) | 隐式解 $F(x, y) = 0$(存储表达式 $F$) |
Parametric(x(t), y(t)) | 参数解:两个表达式分别给出 $x(t)$ 和 $y(t)$ |
Series(expr, n) | 级数解,expr 为截断后的级数表达式,n 为项数 |
System(components) | 系统解,components[i] 为第 $i$ 个未知函数的解 |
Unsolved(ode) | 未能找到解析解,返回原 ODE |
ODEType
签名:
#![allow(unused)] fn main() { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ODEType { Separable, LinearFirst, Bernoulli, Exact, Homogeneous, LinearConstantCoeff, CauchyEuler, ReductionOfOrder, PowerSeries, } }
功能:ODE 类型枚举,决定使用哪种求解方法。
变体:
| 变体 | 方程形式 | 说明 |
|---|---|---|
Separable | $g(y),y' = f(x)$ | 可分离变量:$\int g(y),dy = \int f(x),dx + C$ |
LinearFirst | $y' + p(x),y = q(x)$ | 一阶线性:积分因子 $\mu = e^{\int p,dx}$ |
Bernoulli | $y' + p(x),y = q(x),y^n$ | Bernoulli 方程:替换 $v = y^{1-n}$ 化为线性 |
Exact | $M,dx + N,dy = 0$,$\frac{\partial M}{\partial y} = \frac{\partial N}{\partial x}$ | 恰当方程:势函数 $F(x,y) = C$ |
Homogeneous | $y' = f(y/x)$ | 齐次方程:替换 $v = y/x$ 化为可分离 |
LinearConstantCoeff | $a,y'' + b,y' + c,y = f(x)$ | 常系数线性:特征方程 + 待定系数法(多项式/指数/三角强迫项) |
CauchyEuler | $a,x^2,y'' + b,x,y' + c,y = f(x)$ | Cauchy-Euler:替换 $x = e^t$ 化为常系数 |
ReductionOfOrder | $a(x),y'' + b(x),y' + c(x),y = f(x)$ | 降阶法:尝试简单候选解 $y_1$,$y_2 = y_1 \int \frac{e^{-\int p,dx}}{y_1^2},dx$,非零强迫项用参数变易求特解 |
PowerSeries | 线性 ODE,常点 $x_0 = 0$ | 幂级数法:$y = \sum_{n=0}^{N-1} a_n x^n$;失败后自动回退到 Frobenius 方法 |
注:变易常数(variation of parameters)与 Frobenius 方法在内部作为求解例程使用(前者用于降阶法的特解,后者作为幂级数失败后的回退),但它们不是
ODEType的独立变体。
classify_ode
签名:pub fn classify_ode<'a>(ctx: &'a AtomArena<'a>, ode: ODE<'a>) -> Vec<ODEType>
功能:分析 ODE 并返回所有适用的求解方法,按优先级排列。实际返回顺序(实现于 classify.rs):一阶方程依次检查 LinearFirst → Bernoulli → Separable → Exact → Homogeneous;二阶(及以上)线性方程依次检查 LinearConstantCoeff → CauchyEuler → ReductionOfOrder(仅二阶);最后,任何线性方程(阶 ≥ 1)都会追加 PowerSeries 作为兜底。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
ctx | &'a AtomArena<'a> | 表达式 arena |
ode | ODE<'a> | 待分类的 ODE |
返回值:Vec<ODEType> — 所有适用的求解方法列表(可能为空)。
示例:
#![allow(unused)] fn main() { use ocas_atom::{AtomArena, Symbol}; use ocas_core::arena::Arena; use ocas_calc::ode::{classify_ode, ODE, ODEType}; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let x = ctx.var("x"); let y = ctx.fun("y", &[x]); // y' - y = 0 是一阶线性 let dy = ctx.fun("Derivative", &[y, x]); let eq = ctx.add(&[dy, ctx.mul(&[ctx.num(-1), y])]); let ode = ODE { equation: eq, func: y, var: Symbol::new("x") }; let methods = classify_ode(&ctx, ode); assert!(methods.contains(&ODEType::LinearFirst)); }
dsolve
签名:pub fn dsolve<'a>(ctx: &'a AtomArena<'a>, ode: ODE<'a>, hint: Option<ODEType>) -> ODESolution<'a>
功能:求解常微分方程。自动分类 ODE 类型并按优先级尝试各种方法;也可通过 hint 指定特定方法。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
ctx | &'a AtomArena<'a> | 表达式 arena |
ode | ODE<'a> | 待求解的 ODE |
hint | Option<ODEType> | 可选:指定求解方法。None 时自动分类并按优先级尝试 |
返回值:ODESolution<'a>
求解策略(hint = None 时):
- 调用
classify_ode获取候选方法列表 - 按优先级依次尝试每种方法
- 第一个成功的方法的解被返回
- 所有方法均失败时返回
ODESolution::Unsolved
一阶求解器:
- 可分离:$\int g(y),dy = \int f(x),dx + C$
- 一阶线性:积分因子 $\mu(x) = e^{\int p(x),dx}$,通解 $y = \frac{1}{\mu}\left(\int \mu,q,dx + C\right)$
- Bernoulli:替换 $v = y^{1-n}$ 化为一阶线性
- 恰当方程:求势函数 $F$ 使 $\frac{\partial F}{\partial x} = M$,$\frac{\partial F}{\partial y} = N$;非恰当时尝试积分因子 $\mu(x)$ 或 $\mu(y)$
- 齐次:替换 $v = y/x$ 化为可分离
二阶求解器:
- 常系数:特征方程 $ar^2 + br + c = 0$,根据判别式 $\Delta = b^2 - 4ac$ 构造基本解集,待定系数法求特解
- Cauchy-Euler:替换 $x = e^t$ 转化为常系数方程,指标方程 $ar^2 + (b-a)r + c = 0$
- 降阶法:尝试简单候选解($1, x, x^2, e^x, e^{-x}, e^{2x}$),找到后用 $y_2 = y_1 \int \frac{e^{-\int p,dx}}{y_1^2},dx$ 构造第二解
- 参数变易:已知齐次基本解 $y_1, y_2$ 时,用 Wronskian $W = y_1 y_2' - y_1' y_2$ 求特解
级数求解器(PowerSeries 分派,固定展开点 $x_0 = 0$,8 项):
- 幂级数:在常点 $x_0 = 0$ 展开 $y = \sum_{n=0}^{N-1} a_n x^n$,代入 ODE 递推求系数
- Frobenius(内部回退):若幂级数在常点不适用,自动回退到正则奇点方法,展开 $y = x^r \sum_{n=0}^{N-1} a_n x^n$,指标方程确定 $r$
示例:
#![allow(unused)] fn main() { use ocas_atom::{AtomArena, Symbol}; use ocas_calc::ode::{dsolve, ODE, ODESolution}; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let x = ctx.var("x"); let y = ctx.fun("y", &[x]); let dy = ctx.fun("Derivative", &[y, x]); // 求解 y' - y = 0 → C1*exp(x) let ode = ODE { equation: ctx.add(&[dy, ctx.mul(&[ctx.num(-1), y])]), func: y, var: Symbol::new("x"), }; let sol = dsolve(&ctx, ode, None); assert!(matches!(sol, ODESolution::Explicit(_))); // 指定方法 let sol_hint = dsolve(&ctx, ode, Some(ODEType::LinearFirst)); assert!(matches!(sol_hint, ODESolution::Explicit(_))); }
参见:classify_ode、dsolve_ivp、dsolve_system
dsolve_ivp
签名:
#![allow(unused)] fn main() { pub fn dsolve_ivp<'a>( ctx: &'a AtomArena<'a>, ode: ODE<'a>, y0: Atom<'a>, y1: Option<Atom<'a>>, ) -> ODESolution<'a> }
功能:用 Laplace 变换求解一阶或二阶线性常系数 ODE 的初值问题(IVP)。结果为不含自由常数的显式解。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
ctx | &'a AtomArena<'a> | 表达式 arena |
ode | ODE<'a> | ODE(须为线性常系数) |
y0 | Atom<'a> | 初始条件 $y(0)$ |
y1 | Option<Atom<'a>> | 二阶问题时为 $y'(0)$;一阶时忽略 |
返回值:ODESolution<'a>
Explicit(expr):不含自由常数的显式解Unsolved:Laplace 变换法不适用或无法求逆
方法说明:
- 对 ODE 两端取 Laplace 变换,利用 $\mathcal{L}{y'} = sY - y(0)$ 和 $\mathcal{L}{y''} = s^2 Y - sy(0) - y'(0)$
- 代入初始条件,得到 $Y(s)$ 的代数方程
- 解出 $Y(s)$
- 通过部分分式分解 + 标准对查表进行逆 Laplace 变换
支持的强迫项:多项式、指数 $e^{kx}$、正弦/余弦 $\sin(\omega x)$/$\cos(\omega x)$,以及 $e^{kx}\sin(\omega x)$/$e^{kx}\cos(\omega x)$ 的线性组合。
示例:
#![allow(unused)] fn main() { use ocas_atom::{AtomArena, Symbol}; use ocas_calc::ode::{dsolve_ivp, ODE, ODESolution}; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let x = ctx.var("x"); let y = ctx.fun("y", &[x]); let dy = ctx.fun("Derivative", &[y, x]); // y' - y = 0, y(0) = 1 → y = exp(x) let ode = ODE { equation: ctx.add(&[dy, ctx.mul(&[ctx.num(-1), y])]), func: y, var: Symbol::new("x"), }; let sol = dsolve_ivp(&ctx, ode, ctx.num(1), None); assert!(matches!(sol, ODESolution::Explicit(_))); }
参见:dsolve
dsolve_system
签名:
#![allow(unused)] fn main() { pub fn dsolve_system<'a>( ctx: &'a AtomArena<'a>, equations: &[Atom<'a>], funcs: &[Atom<'a>], var: Symbol, ) -> ODESolution<'a> }
功能:求解 $2 \times 2$ 常系数线性 ODE 系统 $\mathbf{Y}' = A\mathbf{Y}$。通过特征值分解得到通解。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
ctx | &'a AtomArena<'a> | 表达式 arena |
equations | &[Atom<'a>] | 方程列表,每个方程形式为 Derivative(y_i, x) - (a_i1*y1 + a_i2*y2) = 0 |
funcs | &[Atom<'a>] | 未知函数列表,如 [y1(x), y2(x)] |
var | Symbol | 自变量符号 |
返回值:ODESolution<'a>
System(&[Atom]):各分量的通解(含自由常数 C1, C2)Unsolved:系统不支持(非 2×2 或特征值无闭式解)
支持情况:
- 不同实特征值 $\lambda_1 \neq \lambda_2$:$\mathbf{Y} = C_1 \mathbf{v}_1 e^{\lambda_1 x} + C_2 \mathbf{v}_2 e^{\lambda_2 x}$
- 重复实特征值(含广义特征向量):$\mathbf{Y} = (C_1 \mathbf{v} + C_2(\mathbf{w} + x\mathbf{v}))e^{\lambda x}$
- 共轭复特征值 $\alpha \pm \beta i$:实值基本解 $e^{\alpha x}(\mathbf{p}\cos\beta x - \mathbf{q}\sin\beta x)$ 等
示例:
#![allow(unused)] fn main() { use ocas_atom::{AtomArena, Symbol}; use ocas_calc::ode::{dsolve_system, ODESolution}; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let x = ctx.var("x"); let y1 = ctx.fun("y1", &[x]); let y2 = ctx.fun("y2", &[x]); let dy1 = ctx.fun("Derivative", &[y1, x]); let dy2 = ctx.fun("Derivative", &[y2, x]); // 谐振子:y1' = y2, y2' = -y1 let eq1 = ctx.add(&[dy1, ctx.mul(&[ctx.num(-1), y2])]); let eq2 = ctx.add(&[dy2, y1]); let sol = dsolve_system(&ctx, &[eq1, eq2], &[y1, y2], Symbol::new("x")); assert!(matches!(sol, ODESolution::System(_))); }
参见:dsolve
SolveError
签名:
#![allow(unused)] fn main() { #[derive(Debug, Clone, PartialEq, Eq)] pub enum SolveError { /// 方程组为空 EmptySystem, /// 方程组非线性 NonLinear, /// 系数矩阵不是方阵(或方程数 ≠ 未知数个数) NonSquare, /// 方程组矛盾(无解) Inconsistent, /// 方程组欠定(无穷多解) Underdetermined { rank: usize }, /// 结果不在目标域中 ResultNotInDomain, /// 内部矩阵运算失败 Matrix(MatrixError), /// 带描述的其他错误 Other(String), } }
功能:方程求解过程中可能出现的错误。实现 Display 和 Error trait。实现 From<MatrixError>:ShapeMismatch → NonSquare、Inconsistent → Inconsistent、Underdetermined { rank } → Underdetermined { rank }、ResultNotInDomain → ResultNotInDomain、RightHandSideIsNotVector → Other("right-hand side is not a vector")。
变体:
| 变体 | 说明 |
|---|---|
EmptySystem | 输入方程组为空 |
NonLinear | 方程组在目标变量中非线性 |
NonSquare | 系数矩阵不是方阵(方程数 ≠ 未知数个数) |
Inconsistent | 增广矩阵的秩大于系数矩阵的秩 |
Underdetermined { rank } | 系数矩阵的秩小于变量数,rank 为实际秩 |
ResultNotInDomain | 解不在目标数域中(如要求整数解但解为分数) |
Matrix(MatrixError) | 内部矩阵运算失败(由 From<MatrixError> 自动转换) |
Other(String) | 其他带描述的错误 |
示例:
#![allow(unused)] fn main() { use ocas_calc::solve::{solve_linear_rational, SolveError}; // 矛盾系统:x + y = 1, x + y = 2 let a = vec![vec![1, 1], vec![1, 1]]; let b = vec![1, 2]; let result = solve_linear_rational(&a, &b); assert_eq!(result, Err(SolveError::Inconsistent)); }
辅助函数
substitute_solution_collected
签名:
#![allow(unused)] fn main() { pub fn substitute_solution_collected<'a>( ctx: &'a AtomArena<'a>, equation: Atom<'a>, func: Atom<'a>, sol: Atom<'a>, var: Symbol, ) -> Atom<'a> }
功能:将候选解代入 ODE 方程,合并同类项后返回残差。残差为零说明候选解满足 ODE。主要用于验证。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
ctx | &'a AtomArena<'a> | 表达式 arena |
equation | Atom<'a> | ODE 方程(lhs - rhs 形式) |
func | Atom<'a> | 未知函数(如 y(x)) |
sol | Atom<'a> | 候选解表达式 |
var | Symbol | 自变量符号 |
返回值:Atom<'a> — 代入并化简后的残差表达式。为零则候选解正确。
来源模块
| 模块 | 路径 | 内容 |
|---|---|---|
| 线性求解 | ocas-calc/src/solve.rs | solve_linear_rational、solve_linear_integer、solve_diophantine、SolveError |
| 多项式系统 | ocas-poly/src/ideal.rs | solve_polynomial_system、PolynomialSystemSolution、RealSolution、ZeroDimSolutions |
| ODE 分类 | ocas-calc/src/ode/classify.rs | classify_ode、ODEType |
| ODE 一阶 | ocas-calc/src/ode/first_order.rs | 可分离、线性、Bernoulli、恰当、齐次求解器 |
| ODE 二阶 | ocas-calc/src/ode/second_order.rs | 常系数、Cauchy-Euler、降阶、参数变易求解器 |
| ODE 级数 | ocas-calc/src/ode/series.rs | 幂级数、Frobenius 求解器 |
| ODE Laplace | ocas-calc/src/ode/laplace.rs | dsolve_ivp 的 Laplace 变换实现 |
| ODE 系统 | ocas-calc/src/ode/systems.rs | dsolve_system 的特征值分解实现 |
Rust API 参考:重写与化简
本章记录 ocas-rewrite crate 的全部公共 API。该 crate 构建于 ocas-atom 之上,提供:
- 模式匹配:支持通配符(
_、__、___)与 AC(交换-结合)回溯匹配 - 重写规则:
Rule抽象将模式与替换闭包配对 - 化简引擎:
simplify/simplify_with_fuel迭代应用规则直到不动点 - 替换:
replace_once/replace_all/replace_all_multiple受控替换 - 变换:
transform自底向上遍历,partition_expr组合分区 - E-图:基于
eggcrate 的等式饱和化简(需eggfeature)
模块层级:
ocas_rewrite
├── pattern — Pattern, WildcardLevel, PatternAlloc
├── matcher — match_pattern, Bindings, MatchValue, MatchError
├── rules — Rule, default_rules, 9 条内置规则
├── simplify — simplify, simplify_with_fuel
├── replace — replace_once, replace_all, replace_all_multiple, Replacement, Condition
├── transformer — transform, partition_expr
├── combinatorics — partitions, PartitionSolution
└── egraph — AtomLanguage, simplify_with_egraph (feature = "egg")
模式系统
WildcardLevel
#![allow(unused)] fn main() { pub enum WildcardLevel { Single, // x_ — 匹配单个原子 Sequence, // __x — 匹配 1 个或多个原子(在 Add/Mul/Fun 参数列表中) NullSequence, // ___x — 匹配 0 个或多个原子 } }
功能:定义通配符的匹配范围。
| 变体 | 说明 | 命名约定 |
|---|---|---|
Single | 恰好匹配一个原子 | 变量名以 _ 结尾,如 x_ |
Sequence | 匹配一个或多个原子(非空序列) | 变量名以 __ 开头,如 __x |
NullSequence | 匹配零个或多个原子(可空序列) | 变量名以 ___ 结尾,如 x___ |
参见:Pattern、match_pattern
Pattern
#![allow(unused)] fn main() { pub enum Pattern<'a> { Literal(Atom<'a>), Wildcard(Symbol, WildcardLevel), Add(Vec<Pattern<'a>>), Mul(Vec<Pattern<'a>>), Pow(Box<(Pattern<'a>, Pattern<'a>)>), Fun(Symbol, Vec<Pattern<'a>>), } }
功能:模式 AST,镜像 Atom 结构但增加通配符节点。
| 变体 | 说明 |
|---|---|
Literal(Atom) | 精确匹配给定原子 |
Wildcard(name, level) | 通配符,按 WildcardLevel 匹配并绑定到 name |
Add(pats) | 匹配加法节点,参数由 pats 匹配(AC 匹配) |
Mul(pats) | 匹配乘法节点,参数由 pats 匹配(AC 匹配) |
Pow(Box<(base, exp)>) | 匹配幂节点 |
Fun(head, pats) | 匹配函数应用,head 为函数名,参数按序匹配 |
Pattern::from_atom
#![allow(unused)] fn main() { pub fn from_atom(_ctx: &'a impl PatternAlloc<'a>, atom: Atom<'a>) -> Pattern<'a> }
功能:将 Atom 转换为 Pattern。变量名以 _、__、___ 结尾或开头的自动识别为对应级别的通配符(如 x_、__x、x___)。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
ctx | &impl PatternAlloc<'a> | 模式分配器(传 &() 即可) |
atom | Atom<'a> | 待转换的表达式 |
返回值:Pattern<'a> — 对应的模式。
示例:
#![allow(unused)] fn main() { use ocas_atom::AtomArena; use ocas_core::arena::Arena; use ocas_rewrite::pattern::{Pattern, WildcardLevel}; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let atom = ctx.var("x_"); let pat = Pattern::from_atom(&(), atom); // pat == Pattern::Wildcard(Symbol::new("x"), WildcardLevel::Single) }
参见:WildcardLevel、match_pattern
PatternAlloc
#![allow(unused)] fn main() { pub trait PatternAlloc<'a> { fn alloc_slice(&self, items: &[Pattern<'a>]) -> &'a [Pattern<'a>]; } }
功能:模式切片分配辅助 trait,用于在调用方持有的暂存 arena 中分配模式切片,避免泄漏到全局 arena。() 实现直接通过 Box::leak 返回切片(适用于一次性示例)。
参见:Rule、default_rules
模式匹配
MatchValue
#![allow(unused)] fn main() { pub enum MatchValue<'a> { Single(Atom<'a>), Sequence(&'a [Atom<'a>]), } }
功能:通配符绑定值。
| 变体 | 说明 |
|---|---|
Single(Atom) | 单个通配符(_)绑定到一个原子 |
Sequence(&[Atom]) | 序列通配符(__ 或 ___)绑定到原子切片 |
参见:Bindings
Bindings
#![allow(unused)] fn main() { pub struct Bindings<'a> { /* 私有字段 */ } }
功能:模式匹配成功后产生的通配符绑定集合。
Bindings::new
#![allow(unused)] fn main() { pub fn new() -> Self }
功能:创建空绑定集合。
Bindings::get
#![allow(unused)] fn main() { pub fn get(&self, name: Symbol) -> Option<&MatchValue<'a>> }
功能:按名称查询绑定值。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
name | Symbol | 通配符名称(不含尾部下划线) |
返回值:Option<&MatchValue<'a>> — 存在则返回绑定值引用。
示例:
#![allow(unused)] fn main() { use ocas_rewrite::matcher::Bindings; let bindings = Bindings::new(); // 通常由 match_pattern 产生 }
参见:match_pattern、MatchValue
MatchError
#![allow(unused)] fn main() { pub enum MatchError { NoMatch, InconsistentBinding, BudgetExhausted, } }
功能:模式匹配失败原因。
| 变体 | 说明 |
|---|---|
NoMatch | 模式结构不匹配 |
InconsistentBinding | 同名通配符绑定到不同值 |
BudgetExhausted | AC 回溯次数超出预算(默认 10,000 次) |
实现 Display 和 Error trait。
参见:match_pattern_with_budget
match_pattern
#![allow(unused)] fn main() { pub fn match_pattern<'a>( pattern: Pattern<'a>, atom: Atom<'a>, ) -> Result<Bindings<'a>, MatchError> }
功能:将模式与原子进行匹配,返回绑定集合。使用默认回溯预算(10,000 次)。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
pattern | Pattern<'a> | 待匹配的模式 |
atom | Atom<'a> | 目标表达式 |
返回值:
Ok(Bindings)— 匹配成功,包含所有通配符绑定Err(MatchError)— 匹配失败
匹配语义:
Add/Mul节点使用 AC(交换-结合)匹配:参数顺序无关,自动处理结合性Fun节点按序匹配参数,支持序列通配符- 同名通配符必须绑定到相同值
示例:
#![allow(unused)] fn main() { use ocas_atom::AtomArena; use ocas_atom::Symbol; use ocas_core::arena::Arena; use ocas_rewrite::pattern::{Pattern, WildcardLevel}; use ocas_rewrite::matcher::{match_pattern, MatchValue}; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let pat = Pattern::Add(vec![ Pattern::Wildcard(Symbol::new("x"), WildcardLevel::Single), Pattern::Literal(ctx.num(0)), ]); let expr = ctx.add(&[ctx.var("y"), ctx.num(0)]); let bindings = match_pattern(pat, expr).unwrap(); let x = bindings.get(Symbol::new("x")).unwrap(); // MatchValue::Single(Atom("y")) }
参见:match_pattern_with_budget、Pattern、Bindings
match_pattern_with_budget
#![allow(unused)] fn main() { pub fn match_pattern_with_budget<'a>( pattern: Pattern<'a>, atom: Atom<'a>, max_backtracks: usize, ) -> Result<Bindings<'a>, MatchError> }
功能:带自定义回溯预算的模式匹配。适用于模式复杂度较高、需要更大搜索空间的场景。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
pattern | Pattern<'a> | 待匹配的模式 |
atom | Atom<'a> | 目标表达式 |
max_backtracks | usize | 最大回溯次数 |
返回值:同 match_pattern。
错误:预算耗尽时返回 MatchError::BudgetExhausted。
示例:
#![allow(unused)] fn main() { use ocas_atom::AtomArena; use ocas_atom::Symbol; use ocas_core::arena::Arena; use ocas_rewrite::matcher::match_pattern_with_budget; use ocas_rewrite::pattern::{Pattern, WildcardLevel}; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let pat = Pattern::Wildcard(Symbol::new("x"), WildcardLevel::Single); let atom = ctx.var("y"); // 使用更大预算处理复杂模式 let bindings = match_pattern_with_budget(pat, atom, 100_000).unwrap(); assert!(bindings.get(Symbol::new("x")).is_some()); }
参见:match_pattern、DEFAULT_MAX_BACKTRACKS
DEFAULT_MAX_BACKTRACKS
#![allow(unused)] fn main() { pub const DEFAULT_MAX_BACKTRACKS: usize = 10_000; }
功能:match_pattern 的默认回溯预算常量。
重写规则
Rule
#![allow(unused)] fn main() { pub struct Rule<'a> { /* 私有字段 */ } }
功能:重写规则,将模式与替换闭包配对。当模式匹配子表达式时,替换闭包接收绑定并产生新原子。
Rule::new
#![allow(unused)] fn main() { pub fn new<F>(pattern: Pattern<'a>, replacement: F) -> Self where F: Fn(&Bindings<'a>, &AtomArena<'a>) -> Atom<'a> + 'a, }
功能:从模式和替换闭包创建规则。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
pattern | Pattern<'a> | 匹配模式 |
replacement | Fn(&Bindings, &AtomArena) -> Atom | 替换闭包,接收绑定和 arena 上下文 |
返回值:Rule<'a> — 无条件规则。
示例:
#![allow(unused)] fn main() { use ocas_atom::AtomArena; use ocas_atom::Symbol; use ocas_core::arena::Arena; use ocas_rewrite::matcher::{Bindings, MatchValue}; use ocas_rewrite::pattern::{Pattern, WildcardLevel}; use ocas_rewrite::rules::Rule; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let pat = Pattern::Wildcard(Symbol::new("x"), WildcardLevel::Single); let rule = Rule::new(pat, |bindings: &Bindings, _ctx: &AtomArena| { let x = bindings.get(Symbol::new("x")).unwrap(); let MatchValue::Single(v) = x else { panic!("expected single"); }; _ctx.mul(&[_ctx.num(2), *v]) }); let y = ctx.var("y"); let result = rule.apply(&ctx, y).unwrap(); assert_eq!(result.to_string(), "2*y"); }
Rule::with_condition
#![allow(unused)] fn main() { pub fn with_condition<F>(self, condition: F) -> Self where F: Fn(&Bindings<'a>) -> bool + 'a, }
功能:为规则添加前置条件。条件在匹配成功后、替换之前求值。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
condition | Fn(&Bindings) -> bool | 条件闭包,返回 true 时才执行替换 |
返回值:Rule<'a> — 带条件的规则。
示例:
#![allow(unused)] fn main() { use ocas_atom::AtomArena; use ocas_atom::Symbol; use ocas_core::arena::Arena; use ocas_rewrite::matcher::{Bindings, MatchValue}; use ocas_rewrite::pattern::{Pattern, WildcardLevel}; use ocas_rewrite::rules::Rule; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let pat = Pattern::Wildcard(Symbol::new("x"), WildcardLevel::Single); let rule = Rule::new(pat, |_bindings: &Bindings, ctx: &AtomArena| { ctx.num(99) }).with_condition(|bindings: &Bindings| { let x = bindings.get(Symbol::new("x")).unwrap(); let MatchValue::Single(v) = x else { return false; }; v.to_string() == "y" }); let y = ctx.var("y"); let z = ctx.var("z"); assert_eq!(rule.apply(&ctx, y).unwrap().to_string(), "99"); assert!(rule.apply(&ctx, z).is_none()); }
Rule::apply
#![allow(unused)] fn main() { pub fn apply(&self, ctx: &AtomArena<'a>, atom: Atom<'a>) -> Option<Atom<'a>> }
功能:尝试将规则应用于原子。匹配成功且条件满足时返回 Some(替换结果),否则返回 None。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
ctx | &AtomArena<'a> | 表达式 arena 上下文 |
atom | Atom<'a> | 目标表达式 |
返回值:Option<Atom<'a>> — Some 表示规则已应用,None 表示未匹配或条件不满足。
参见:match_pattern、simplify
default_rules
#![allow(unused)] fn main() { pub fn default_rules<'a>( ctx: &'a AtomArena<'a>, alloc: &'a impl PatternAlloc<'a>, ) -> Vec<Rule<'a>> }
功能:返回内置代数化简规则集(共 9 条)。
内置规则列表:
| 规则 | 化简 | 说明 |
|---|---|---|
add_zero | x + 0 → x | 加法零元 |
add_zero_left | 0 + x → x | 加法零元(左侧) |
mul_zero | x × 0 → 0 | 乘法零元 |
mul_zero_left | 0 × x → 0 | 乘法零元(左侧) |
mul_one | x × 1 → x | 乘法单位元 |
mul_one_left | 1 × x → x | 乘法单位元(左侧) |
add_same | x + x → 2x | 合并同类项 |
pow_zero | x⁰ → 1 | 零次幂 |
pow_one | x¹ → x | 一次幂 |
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
ctx | &AtomArena<'a> | 表达式 arena 上下文 |
alloc | &impl PatternAlloc<'a> | 模式分配器(传 &() 即可) |
返回值:Vec<Rule<'a>> — 规则列表。
示例:
#![allow(unused)] fn main() { use ocas_atom::AtomArena; use ocas_core::arena::Arena; use ocas_rewrite::rules::default_rules; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let rules = default_rules(&ctx, &()); // rules.len() == 9 }
参见:simplify、Rule
内置规则函数
以下函数各自返回一条内置规则,可单独使用或组合自定义规则集:
#![allow(unused)] fn main() { pub fn add_zero<'a>(ctx: &'a AtomArena<'a>, alloc: &'a impl PatternAlloc<'a>) -> Rule<'a> pub fn add_zero_left<'a>(ctx: &'a AtomArena<'a>, alloc: &'a impl PatternAlloc<'a>) -> Rule<'a> pub fn mul_zero<'a>(ctx: &'a AtomArena<'a>, alloc: &'a impl PatternAlloc<'a>) -> Rule<'a> pub fn mul_zero_left<'a>(ctx: &'a AtomArena<'a>, alloc: &'a impl PatternAlloc<'a>) -> Rule<'a> pub fn mul_one<'a>(ctx: &'a AtomArena<'a>, alloc: &'a impl PatternAlloc<'a>) -> Rule<'a> pub fn mul_one_left<'a>(ctx: &'a AtomArena<'a>, alloc: &'a impl PatternAlloc<'a>) -> Rule<'a> pub fn add_same<'a>(ctx: &'a AtomArena<'a>, alloc: &'a impl PatternAlloc<'a>) -> Rule<'a> pub fn pow_zero<'a>(ctx: &'a AtomArena<'a>, alloc: &'a impl PatternAlloc<'a>) -> Rule<'a> pub fn pow_one<'a>(ctx: &'a AtomArena<'a>, alloc: &'a impl PatternAlloc<'a>) -> Rule<'a> }
每条规则的签名和用法相同:传入 ctx 和 alloc,返回 Rule<'a>。
示例(单独使用 add_same):
#![allow(unused)] fn main() { use ocas_atom::AtomArena; use ocas_core::arena::Arena; use ocas_rewrite::rules::add_same; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let x = ctx.var("x"); let expr = ctx.add(&[x, x]); let rule = add_same(&ctx, &()); let result = rule.apply(&ctx, expr).unwrap(); assert_eq!(result.to_string(), "2*x"); }
化简引擎
simplify
#![allow(unused)] fn main() { pub fn simplify<'a>( ctx: &'a AtomArena<'a>, atom: Atom<'a>, rules: &[Rule<'a>], iter_limit: usize, ) -> Atom<'a> }
功能:对表达式反复应用重写规则,自底向上遍历,直到达到不动点或迭代上限。这是 oCAS 化简引擎的核心入口。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
ctx | &AtomArena<'a> | 表达式 arena 上下文 |
atom | Atom<'a> | 待化简的表达式 |
rules | &[Rule<'a>] | 重写规则列表 |
iter_limit | usize | 最大迭代次数(推荐 10–20) |
返回值:Atom<'a> — 化简后的表达式(已注册到 arena)。
行为:
- 每轮迭代自底向上遍历表达式树
- 对每个节点依次尝试所有规则
- 任何规则触发则该轮有变化,继续下一轮
- 无规则触发或达到
iter_limit则停止
示例:
#![allow(unused)] fn main() { use ocas_atom::AtomArena; use ocas_core::arena::Arena; use ocas_rewrite::rules::default_rules; use ocas_rewrite::simplify::simplify; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let rules = default_rules(&ctx, &()); let x = ctx.var("x"); let expr = ctx.mul(&[x, ctx.num(0)]); let result = simplify(&ctx, expr, &rules, 10); assert_eq!(result.to_string(), "0"); }
参见:simplify_with_fuel、default_rules、Rule
simplify_with_fuel
#![allow(unused)] fn main() { pub fn simplify_with_fuel<'a>( ctx: &'a AtomArena<'a>, atom: Atom<'a>, rules: &[Rule<'a>], iter_limit: usize, fuel: &Fuel, ) -> Result<Atom<'a>, OcasError> }
功能:带燃料预算的化简。每轮自底向上遍历消耗一个燃料单位,燃料耗尽时提前终止。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
ctx | &AtomArena<'a> | 表达式 arena 上下文 |
atom | Atom<'a> | 待化简的表达式 |
rules | &[Rule<'a>] | 重写规则列表 |
iter_limit | usize | 最大迭代次数 |
fuel | &Fuel | 燃料预算(Fuel::default() 为有效无限) |
返回值:
Ok(Atom)— 化简完成(不动点达成)Err(OcasError::OutOfFuel)— 燃料耗尽(此时不返回表达式,调用方需自行保存中间结果)
用途:防止病态输入导致化简引擎无限循环。在嵌套调用链中参与燃料记账。
示例:
#![allow(unused)] fn main() { use ocas_atom::AtomArena; use ocas_core::arena::Arena; use ocas_core::fuel::Fuel; use ocas_rewrite::rules::default_rules; use ocas_rewrite::simplify::simplify_with_fuel; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let expr = ctx.var("x"); let rules = default_rules(&ctx, &()); let fuel = Fuel::new(100); let result = simplify_with_fuel(&ctx, expr, &rules, 20, &fuel); match result { Ok(e) => println!("simplified: {}", e), Err(_) => println!("fuel exhausted"), } }
参见:simplify、Fuel(ocas-core crate)
替换
replace_once
#![allow(unused)] fn main() { pub fn replace_once<'a, F>( ctx: &'a AtomArena<'a>, atom: Atom<'a>, pattern: Pattern<'a>, replacement: F, ) -> Atom<'a> where F: Fn(&Bindings<'a>, &AtomArena<'a>) -> Atom<'a>, }
功能:自顶向下遍历,替换第一个匹配的子表达式后停止。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
ctx | &AtomArena<'a> | 表达式 arena 上下文 |
atom | Atom<'a> | 目标表达式 |
pattern | Pattern<'a> | 匹配模式 |
replacement | Fn(&Bindings, &AtomArena) -> Atom | 替换闭包 |
返回值:Atom<'a> — 替换后的表达式(若无匹配则原样返回)。
示例:
#![allow(unused)] fn main() { use ocas_atom::AtomArena; use ocas_atom::Symbol; use ocas_core::arena::Arena; use ocas_rewrite::pattern::{Pattern, WildcardLevel}; use ocas_rewrite::replace::replace_once; use ocas_rewrite::matcher::MatchValue; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let pat = Pattern::Wildcard(Symbol::new("x"), WildcardLevel::Single); let expr = ctx.add(&[ctx.var("a"), ctx.var("b")]); let result = replace_once(&ctx, expr, pat, |bindings, ctx| { let x = bindings.get(Symbol::new("x")).unwrap(); let MatchValue::Single(v) = x else { panic!() }; ctx.mul(&[ctx.num(2), *v]) }); // 第一个匹配的子表达式被替换为 2*a }
参见:replace_all、replace_all_multiple
replace_all
#![allow(unused)] fn main() { pub fn replace_all<'a, F>( ctx: &'a AtomArena<'a>, atom: Atom<'a>, pattern: Pattern<'a>, replacement: F, ) -> Atom<'a> where F: Fn(&Bindings<'a>, &AtomArena<'a>) -> Atom<'a>, }
功能:自顶向下遍历,替换所有匹配的子表达式。不进行嵌套替换(即替换结果不再参与后续匹配)。
参数:同 replace_once。
返回值:Atom<'a> — 所有匹配替换后的表达式。
参见:replace_once、replace_all_multiple
replace_all_multiple
#![allow(unused)] fn main() { pub fn replace_all_multiple<'a, F>( ctx: &'a AtomArena<'a>, atom: Atom<'a>, replacements: &[Replacement<'a, F>], ) -> Atom<'a> where F: Fn(&Bindings<'a>, &AtomArena<'a>) -> Atom<'a>, }
功能:使用多条替换规则,按顺序尝试,第一个匹配的规则生效。自顶向下遍历所有节点。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
ctx | &AtomArena<'a> | 表达式 arena 上下文 |
atom | Atom<'a> | 目标表达式 |
replacements | &[Replacement<'a, F>] | 替换规则列表(按优先级排序) |
返回值:Atom<'a> — 替换后的表达式。
参见:Replacement、replace_all
Replacement
#![allow(unused)] fn main() { pub struct Replacement<'a, F> where F: Fn(&Bindings<'a>, &AtomArena<'a>) -> Atom<'a>, { pub pattern: Pattern<'a>, pub replacement: F, pub condition: Option<Condition<'a>>, } }
功能:单条替换规则,包含模式、替换闭包和可选条件。
字段:
| 字段 | 类型 | 说明 |
|---|---|---|
pattern | Pattern<'a> | 匹配模式 |
replacement | F | 替换闭包 |
condition | Option<Condition<'a>> | 可选前置条件 |
参见:replace_all_multiple、Condition
Condition
#![allow(unused)] fn main() { pub enum Condition<'a> { Predicate(Arc<dyn Fn(&Bindings<'a>) -> bool + 'a>), } }
功能:替换条件,当前仅支持谓词形式。
Condition::new
#![allow(unused)] fn main() { pub fn new<F: Fn(&Bindings<'a>) -> bool + 'a>(f: F) -> Self }
功能:从闭包创建条件。
示例:
#![allow(unused)] fn main() { use ocas_rewrite::replace::Condition; let cond = Condition::new(|bindings| { // 仅当绑定变量 x 为正数时替换 true }); }
参见:Replacement、Rule::with_condition
ReplaceSettings
#![allow(unused)] fn main() { pub struct ReplaceSettings { pub once: bool, pub bottom_up: bool, pub nested: bool, } }
功能:替换遍历设置。
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
once | bool | false | 是否只替换第一个匹配 |
bottom_up | bool | false | 是否自底向上遍历(默认自顶向下) |
nested | bool | false | 是否在已替换的子表达式中继续替换 |
参见:replace_all
变换
transform
#![allow(unused)] fn main() { pub fn transform<'a, F>( ctx: &'a AtomArena<'a>, atom: Atom<'a>, f: F, ) -> Atom<'a> where F: FnMut(Atom<'a>) -> Option<Atom<'a>>, }
功能:自底向上遍历表达式树,对每个节点调用闭包 f。闭包返回 Some(atom) 时替换该节点,返回 None 时保留原节点(子表达式已变换)。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
ctx | &AtomArena<'a> | 表达式 arena 上下文 |
atom | Atom<'a> | 目标表达式 |
f | FnMut(Atom) -> Option<Atom> | 变换闭包 |
返回值:Atom<'a> — 变换后的表达式。
行为:
- 递归遍历子表达式(叶子节点直接返回)
- 用变换后的子表达式重建当前节点
- 对重建后的节点调用
f f返回Some则使用新节点,None则使用重建节点
示例:
#![allow(unused)] fn main() { use ocas_atom::{Atom, AtomArena, AtomNode}; use ocas_core::arena::Arena; use ocas_rewrite::transformer::transform; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let x = ctx.var("x"); let y = ctx.var("y"); let sum = ctx.add(&[x, y]); let result = transform(&ctx, sum, |a| { if let AtomNode::Add(args) = a.node() { if args.len() == 2 && args[0] == x && args[1] == y { return Some(ctx.add(&[y, x])); } } None }); assert_eq!(result.to_string(), "y + x"); }
参见:simplify、partition_expr
partition_expr
#![allow(unused)] fn main() { pub fn partition_expr<'a>( ctx: &'a AtomArena<'a>, expr: Atom<'a>, bins: &[(Symbol, usize)], fill_last: bool, repeat: bool, ) -> Atom<'a> }
功能:将 arg(a₁, a₂, …, aₙ) 表达式分入命名桶,返回乘积之和 $\sum \text{coeff} \cdot f_1(\cdots) \cdot f_2(\cdots) \cdots$。参数设计镜像 Symbolica 的 Transformer::Partition。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
ctx | &AtomArena<'a> | 表达式 arena 上下文 |
expr | Atom<'a> | 输入表达式(必须是 arg(...) 形式) |
bins | &[(Symbol, usize)] | 桶列表,每个元素为 (函数名, 容量) |
fill_last | bool | 多余元素是否吸收到最后一个桶 |
repeat | bool | 桶模式是否重复直到所有元素消耗完毕 |
返回值:Atom<'a> — 分区结果之和;无有效分区时返回 ctx.num(0);输入不是 arg(...) 函数应用或参数含非数值时原样返回 expr。
行为:
- 输入必须是
arg(...)函数应用,参数为数值 - 枚举所有合法分配方式
- 每种分配的系数为多项式系数 $\binom{n}{k_1, k_2, \dots}$
示例:
#![allow(unused)] fn main() { use ocas_atom::{AtomArena, Symbol}; use ocas_core::arena::Arena; use ocas_rewrite::transformer::partition_expr; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let args = ctx.fun("arg", &[ctx.num(1), ctx.num(2), ctx.num(3)]); let bins = &[(Symbol::new("f"), 2), (Symbol::new("g"), 1)]; let result = partition_expr(&ctx, args, bins, false, false); // 返回 Σ coeff · f(…, …) · g(…) }
参见:transform、combinatorics::partitions
组合数学辅助
partitions
#![allow(unused)] fn main() { pub fn partitions<T, B>( elements: &[T], bins: &[(B, usize)], fill_last: bool, repeat: bool, ) -> Vec<PartitionSolution<T, B>> where T: Clone + Ord + Hash, B: Clone + Ord + Hash, }
功能:枚举将 elements 分入 bins 的所有方式。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
elements | &[T] | 待分配元素 |
bins | &[(B, usize)] | 桶列表 (名称, 容量) |
fill_last | bool | 多余元素吸收到最后一个桶 |
repeat | bool | 桶模式重复直到元素耗尽 |
返回值:Vec<PartitionSolution<T, B>> — 所有合法分区。
参见:partition_expr、PartitionSolution
PartitionSolution
#![allow(unused)] fn main() { pub struct PartitionSolution<T, B> { pub coefficient: usize, pub bins: Vec<(B, Vec<T>)>, } }
功能:单个分区解。
| 字段 | 类型 | 说明 |
|---|---|---|
coefficient | usize | 多项式系数 |
bins | Vec<(B, Vec<T>)> | 每个桶的名称和内容 |
E-图化简
Feature Gate:需要启用
eggfeature。在Cargo.toml中添加:[dependencies] ocas-rewrite = { version = "0.23", features = ["egg"] }
simplify_with_egraph
#![allow(unused)] fn main() { pub fn simplify_with_egraph<'a>( atom: Atom<'a>, ocas_arena: &'a AtomArena<'a>, iter_limit: usize, ) -> Atom<'a> }
功能:使用 egg 等式饱和引擎化简表达式。将 oCAS 表达式转换为 egg E-图,运行内置规则集,使用 AST 大小作为代价函数提取最优表达式。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
atom | Atom<'a> | 待化简的表达式 |
ocas_arena | &AtomArena<'a> | oCAS 表达式 arena |
iter_limit | usize | 等式饱和迭代上限 |
返回值:Atom<'a> — 化简后的表达式。
内置 E-图规则:
| 规则名 | 化简 |
|---|---|
add-zero | (add 0 ?a) → ?a |
mul-zero | (mul ?a 0) → 0 |
mul-one | (mul 1 ?a) → ?a |
pow-zero | (pow ?a 0) → 1 |
pow-one | (pow ?a 1) → ?a |
pythagorean | $\sin^2 x + \cos^2 x \to 1$ |
示例:
#![allow(unused)] fn main() { use ocas_atom::AtomArena; use ocas_core::arena::Arena; use ocas_rewrite::egraph::simplify_with_egraph; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let x = ctx.var("x"); let two = ctx.num(2); let sin_x = ctx.fun("sin", &[x]); let cos_x = ctx.fun("cos", &[x]); let sum = ctx.add(&[ctx.pow(sin_x, two), ctx.pow(cos_x, two)]); let result = simplify_with_egraph(sum, &ctx, 5); assert_eq!(result.to_string(), "1"); }
参见:AtomLanguage(ocas-rewrite::egraph)
AtomLanguage
#![allow(unused)] fn main() { pub enum AtomLanguage { Num(i64), Var(Symbol), Fun(Vec<Id>), Add(Vec<Id>), Mul(Vec<Id>), Pow([Id; 2]), } }
功能:egg::Language trait 的 oCAS 实现,用于 E-图节点表示。
说明:手动实现 Language trait,因为 egg 的 define_language! 宏不支持 i64 和 Symbol 作为叶子类型。
AtomLanguage::to_recexpr
#![allow(unused)] fn main() { pub fn to_recexpr<'b>( atom: Atom<'b>, egraph: &mut egg::EGraph<Self, ()>, cache: &mut Vec<(Atom<'b>, Id)>, ) -> Id }
功能:将 oCAS Atom 转换为 egg RecExpr。共享子表达式通过 cache 去重。
AtomLanguage::from_recexpr
#![allow(unused)] fn main() { pub fn from_recexpr<'a>( expr: &RecExpr<Self>, id: Id, ocas_arena: &'a AtomArena<'a>, ) -> Atom<'a> }
功能:将 egg RecExpr 转换回 oCAS Atom。
参见:simplify_with_egraph、egg::Language
模块依赖关系
graph TD
A[ocas-atom] --> B[ocas-rewrite]
C[ocas-core] --> B
B --> D[pattern]
B --> E[matcher]
B --> F[rules]
B --> G[simplify]
B --> H[replace]
B --> I[transformer]
B --> J[combinatorics]
B --> K[egraph]
D --> E
D --> F
E --> F
E --> G
E --> H
F --> G
I --> G
J --> I
K -.->|feature = "egg"| B
Rust API 参考:求值与 JIT
oCAS 的求值系统将符号表达式编译为栈式虚拟机指令序列,然后在数值类型上高效执行。支持三种执行后端:解释器(通用)、JIT 编译(Cranelift 生成原生机器码)、SIMD 批量求值(向量化处理多组参数)。
模块路径:ocas_eval
编译管线:
Atom (arena-backed)
→ EvalTree (owned intermediate, 可选常量折叠优化)
→ Instr sequence (编译 + 优化 pass)
→ ExpressionEvaluator (栈式 VM 解释器)
→ 或 JitCompiledFunction (Cranelift, feature = "jit")
→ 或 VectorEvaluator (SIMD 批量, feature = "simd")
Feature flags:
| Flag | 功能 |
|---|---|
jit | 启用 Cranelift JIT 编译后端 |
simd | 启用 SIMD 批量求值(pulp,运行时检测 SSE2/AVX2/AVX-512) |
fast-poly | 启用快速多项式求值优化 |
导入方式:
#![allow(unused)] fn main() { use ocas_eval::{ ExpressionEvaluator, EvaluationDomain, PowfExtension, FunctionMap, EvalTree, Instr, Instruction, Slot, EvaluationError, StreamingEvaluator, compile_atom, compile_atom_with, compile_atoms_multi, compile_atoms_multi_with, }; use ocas_eval::instruction::BuiltinOp; // JIT (feature = "jit") use ocas_eval::jit::{JitCompiledFunction, JitCompiledF32, JitEngine, FloatWidth}; // SIMD (feature = "simd") use ocas_eval::simd::{VectorEvaluator, VectorEvaluatorF32}; }
EvalTree
签名:
#![allow(unused)] fn main() { pub enum EvalTree { Num(f64), Var(String), Fun(String, Vec<EvalTree>), Add(Vec<EvalTree>), Mul(Vec<EvalTree>), Pow(Box<EvalTree>, Box<EvalTree>), } }
功能:arena 无关的、拥有所有权的中间表示。从 Atom 转换而来,断开与 arena 生命周期的绑定,使编译管线可自由进行多遍优化。
变体:
| 变体 | 说明 |
|---|---|
Num(f64) | 数值常量 |
Var(String) | 变量引用 |
Fun(String, Vec<EvalTree>) | 命名函数应用 |
Add(Vec<EvalTree>) | 求和 |
Mul(Vec<EvalTree>) | 乘积 |
Pow(Box<EvalTree>, Box<EvalTree>) | 幂运算 base^exp |
EvalTree::from_atom
签名:pub fn from_atom(atom: Atom<'_>) -> Self
功能:从 Atom 转换为拥有的 EvalTree。递归遍历表达式树,将 arena 句柄转为独立数据。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
atom | Atom<'_> | 源表达式 |
返回值:拥有的 EvalTree。
示例:
#![allow(unused)] fn main() { use ocas_atom::AtomArena; use ocas_eval::EvalTree; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let atom = ctx.add(&[ctx.var("x"), ctx.num(2)]); let tree = EvalTree::from_atom(atom); // tree = Add([Var("x"), Num(2.0)]) }
EvalTree::fold_constants
签名:pub fn fold_constants(&self) -> EvalTree
功能:折叠常量子树并应用代数恒等式。在编译前运行,减少指令数。
优化规则:
| 模式 | 规则 |
|---|---|
Add | 删除零项;全常量项求和;单项折叠 |
Mul | 吸收零因子;删除单位因子;全常量因子求积;单因子折叠 |
Pow | x^1 → x,x^0 → 1,Num^Num 直接求值 |
Fun | 内建函数的全常量参数直接求值(外部函数不折叠,可能有副作用) |
返回值:优化后的新 EvalTree。
示例:
#![allow(unused)] fn main() { use ocas_eval::EvalTree; let tree = EvalTree::Add(vec![ EvalTree::Num(3.0), EvalTree::Mul(vec![ EvalTree::Num(0.0), EvalTree::Var("x".into()), ]), ]); let folded = tree.fold_constants(); assert_eq!(folded, EvalTree::Num(3.0)); // 输出:3 + 0*x → 3 }
ExpressionEvaluator<T>
签名:
#![allow(unused)] fn main() { pub struct ExpressionEvaluator<T: EvaluationDomain> { /* private fields */ } }
功能:编译好的表达式,可反复执行数值求值。持有指令序列、预分配栈、常量表和可选的用户自定义函数注册表。类型参数 T 实现 EvaluationDomain,通常为 f64。
编译方法
ExpressionEvaluator::compile
签名:
#![allow(unused)] fn main() { impl<T: EvaluationDomain + PowfExtension> ExpressionEvaluator<T> { pub fn compile(atom: Atom<'_>) -> Result<Self> } }
功能:将 Atom 编译为可执行求值器。内部先转为 EvalTree,再折叠常量、生成指令序列。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
atom | Atom<'_> | 源表达式 |
返回值:Ok(ExpressionEvaluator<T>) 或编译错误。
错误:见 EvaluationError。
示例:
#![allow(unused)] fn main() { use ocas_eval::ExpressionEvaluator; use ocas_atom::AtomArena; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let expr = ctx.mul(&[ctx.var("x"), ctx.var("x")]); let eval: ExpressionEvaluator<f64> = ExpressionEvaluator::compile(expr).unwrap(); assert_eq!(eval.param_count(), 1); assert_eq!(eval.result_count(), 1); }
ExpressionEvaluator::compile_with
签名:
#![allow(unused)] fn main() { impl<T: EvaluationDomain + PowfExtension> ExpressionEvaluator<T> { pub fn compile_with(atom: Atom<'_>, map: FunctionMap<T>) -> Result<Self> } }
功能:编译表达式,并传入用户自定义函数注册表。表达式中出现的 ExternalFun 节点通过 map 解析。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
atom | Atom<'_> | 源表达式 |
map | FunctionMap<T> | 自定义函数注册表(无外部函数时传入空映射) |
返回值:Ok(ExpressionEvaluator<T>) 或编译/解析错误。
示例:
#![allow(unused)] fn main() { use ocas_eval::{ExpressionEvaluator, FunctionMap}; use ocas_atom::AtomArena; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let expr = ctx.fun("myfunc", &[ctx.var("x")]); let mut fmap = FunctionMap::<f64>::new(); fmap.register("myfunc", 1, Box::new(|args| args[0] * args[0])); let eval = ExpressionEvaluator::compile_with(expr, fmap).unwrap(); let result = eval.evaluate(&[3.0]).unwrap(); assert_eq!(result, vec![9.0]); // 输出:myfunc(3) = 3² = 9 }
ExpressionEvaluator::compile_multi
签名:
#![allow(unused)] fn main() { impl<T: EvaluationDomain + PowfExtension> ExpressionEvaluator<T> { pub fn compile_multi(atoms: &[Atom<'_>]) -> Result<Self> } }
功能:将多个 Atom 编译为单个多输出求值器。跨所有输出共享常量、公共子表达式(CSE)和栈槽位,避免重复计算。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
atoms | &[Atom<'_>] | 多个表达式 |
返回值:Ok(ExpressionEvaluator<T>),result_count() == atoms.len()。
示例:
#![allow(unused)] fn main() { use ocas_eval::ExpressionEvaluator; use ocas_atom::AtomArena; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let x = ctx.var("x"); let expr1 = ctx.mul(&[x, x]); // x² let expr2 = ctx.add(&[x, ctx.num(1)]); // x+1 let eval: ExpressionEvaluator<f64> = ExpressionEvaluator::compile_multi(&[expr1, expr2]).unwrap(); let results = eval.evaluate(&[3.0]).unwrap(); assert_eq!(results, vec![9.0, 4.0]); // 输出:x²=9, x+1=4 }
ExpressionEvaluator::compile_multi_with
签名:
#![allow(unused)] fn main() { impl<T: EvaluationDomain + PowfExtension> ExpressionEvaluator<T> { pub fn compile_multi_with(atoms: &[Atom<'_>], map: FunctionMap<T>) -> Result<Self> } }
功能:compile_multi 的带函数映射版本(map 直接传入,而非 Option)。
执行方法
ExpressionEvaluator::evaluate
签名:
#![allow(unused)] fn main() { impl<T: EvaluationDomain + PowfExtension> ExpressionEvaluator<T> { pub fn evaluate(&self, params: &[T]) -> Result<Vec<T>> } }
功能:用给定参数值执行编译好的表达式。每次调用分配新的栈和结果缓冲区。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
params | &[T] | 参数值,长度必须等于 param_count() |
返回值:Ok(Vec<T>),长度等于 result_count()。
错误:
| 错误 | 条件 |
|---|---|
WrongArity | params.len() != param_count() |
DivisionByZero | 求值过程中遇到除零 |
FunctionNotFound | 外部函数未注册 |
示例:
#![allow(unused)] fn main() { use ocas_eval::ExpressionEvaluator; use ocas_atom::AtomArena; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let expr = ctx.add(&[ctx.mul(&[ctx.var("x"), ctx.var("x")]), ctx.num(1)]); let eval: ExpressionEvaluator<f64> = ExpressionEvaluator::compile(expr).unwrap(); let result = eval.evaluate(&[3.0]).unwrap(); assert_eq!(result[0], 10.0); // 输出:3² + 1 = 10 }
ExpressionEvaluator::evaluate_with_stack
签名:
#![allow(unused)] fn main() { impl<T: EvaluationDomain + PowfExtension> ExpressionEvaluator<T> { pub fn evaluate_with_stack( &self, params: &[T], stack: &mut Vec<T>, results: &mut Vec<T>, ) -> Result<()> } }
功能:使用调用者提供的缓冲区执行求值,避免每次调用的堆分配。流式和批处理场景应优先使用此方法。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
params | &[T] | 参数值 |
stack | &mut Vec<T> | 栈缓冲区(自动 resize 到 stack_size()) |
results | &mut Vec<T> | 结果缓冲区(自动填充) |
返回值:Ok(()) 或求值错误。
访问器
ExpressionEvaluator::param_count
签名:pub fn param_count(&self) -> usize
功能:返回期望的参数个数。
ExpressionEvaluator::result_count
签名:pub fn result_count(&self) -> usize
功能:返回输出结果个数(单输出表达式为 1,compile_multi 创建的可能大于 1)。
ExpressionEvaluator::stack_size
签名:pub fn stack_size(&self) -> usize
功能:返回求值所需栈大小(参数 + 常量 + 临时值 + 输出)。evaluate_with_stack 的 stack 参数应预分配此容量。
JIT 编译(feature = "jit")
JIT 后端使用 Cranelift 将指令序列编译为原生机器码,常量嵌入为立即数,调用 libm 进行超越函数计算。性能通常比解释器快 10 倍以上。
ExpressionEvaluator::compile_jit
签名:
#![allow(unused)] fn main() { #[cfg(feature = "jit")] impl ExpressionEvaluator<f64> { pub fn compile_jit(&self) -> Result<JitCompiledFunction> } }
功能:将当前求值器的指令序列编译为双精度(f64)原生代码。
返回值:Ok(JitCompiledFunction) 或编译错误。
错误:
| 错误 | 条件 |
|---|---|
UnsupportedOperation | 包含 JIT 无法降低的指令(如外部函数、sec/csc/cot) |
JitCompilationError | Cranelift 后端编译失败 |
示例:
#![allow(unused)] fn main() { use ocas_eval::ExpressionEvaluator; use ocas_atom::AtomArena; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let expr = ctx.add(&[ctx.mul(&[ctx.var("x"), ctx.var("x")]), ctx.num(1)]); let eval: ExpressionEvaluator<f64> = ExpressionEvaluator::compile(expr).unwrap(); let jit = eval.compile_jit().unwrap(); let result = jit.call(&[3.0]); assert_eq!(result, vec![10.0]); // 输出:3² + 1 = 10(原生机器码执行) }
ExpressionEvaluator::compile_jit_f32
签名:
#![allow(unused)] fn main() { #[cfg(feature = "jit")] impl ExpressionEvaluator<f64> { pub fn compile_jit_f32(&self) -> Result<JitCompiledF32> } }
功能:将指令序列编译为单精度(f32)原生代码。常量从 f64 窄化为 f32;结果精度为 f32。
错误:与 compile_jit 相同。
示例:
#![allow(unused)] fn main() { use ocas_eval::ExpressionEvaluator; use ocas_atom::AtomArena; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let expr = ctx.mul(&[ctx.var("x"), ctx.num(2)]); let eval: ExpressionEvaluator<f64> = ExpressionEvaluator::compile(expr).unwrap(); let jit_f32 = eval.compile_jit_f32().unwrap(); let result = jit_f32.call(&[3.0_f32]); assert_eq!(result, vec![6.0_f32]); // 输出:3 × 2 = 6(f32 精度) }
JitCompiledFunction
签名:
#![allow(unused)] fn main() { pub struct JitCompiledFunction { /* private fields */ } }
功能:JIT 编译好的双精度函数,可反复调用。
JitCompiledFunction::call
签名:pub fn call(&self, params: &[f64]) -> Vec<f64>
功能:用给定参数调用 JIT 编译的函数。每次分配新的结果向量。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
params | &[f64] | 参数值 |
返回值:结果向量,长度等于 result_count()。
示例:
#![allow(unused)] fn main() { use ocas_eval::ExpressionEvaluator; use ocas_atom::AtomArena; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let expr = ctx.add(&[ctx.mul(&[ctx.var("x"), ctx.var("x")]), ctx.num(1)]); let eval: ExpressionEvaluator<f64> = ExpressionEvaluator::compile(expr).unwrap(); let jit = eval.compile_jit().unwrap(); let result = jit.call(&[3.0]); assert_eq!(result, vec![10.0]); // 输出:x²+1 在 x=3 时的值 10 }
JitCompiledFunction::call_into
签名:pub fn call_into(&self, params: &[f64], results: &mut [f64])
功能:调用 JIT 函数,结果写入调用者提供的缓冲区,避免每次调用的堆分配。results.len() 必须至少为 result_count()。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
params | &[f64] | 参数值 |
results | &mut [f64] | 结果缓冲区 |
示例:
#![allow(unused)] fn main() { use ocas_eval::ExpressionEvaluator; use ocas_atom::AtomArena; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let expr = ctx.add(&[ctx.mul(&[ctx.var("x"), ctx.var("x")]), ctx.num(1)]); let eval: ExpressionEvaluator<f64> = ExpressionEvaluator::compile(expr).unwrap(); let jit = eval.compile_jit().unwrap(); let mut buf = vec![0.0_f64; jit.result_count()]; jit.call_into(&[42.0], &mut buf); assert_eq!(buf, vec![1765.0]); // 结果在 buf 中(42² + 1 = 1765),无堆分配 }
JitCompiledFunction::param_count
签名:pub fn param_count(&self) -> usize
功能:返回期望的参数个数。
JitCompiledFunction::result_count
签名:pub fn result_count(&self) -> usize
功能:返回结果个数。
JitCompiledF32
签名:
#![allow(unused)] fn main() { pub struct JitCompiledF32 { /* private fields */ } }
功能:JIT 编译好的单精度函数。API 与 JitCompiledFunction 完全对称,参数和返回值为 f32。
JitCompiledF32::call
签名:pub fn call(&self, params: &[f32]) -> Vec<f32>
JitCompiledF32::call_into
签名:pub fn call_into(&self, params: &[f32], results: &mut [f32])
JitCompiledF32::param_count
签名:pub fn param_count(&self) -> usize
JitCompiledF32::result_count
签名:pub fn result_count(&self) -> usize
JitEngine
签名:
#![allow(unused)] fn main() { pub struct JitEngine; }
功能:JIT 编译引擎的入口点。提供底层 compile 方法,可直接从指令序列编译。
JitEngine::compile
签名:
#![allow(unused)] fn main() { impl JitEngine { pub fn compile( instructions: &[Instr], param_count: usize, constants: &[f64], result_indices: &[usize], ) -> Result<JitCompiledFunction> pub fn compile_f32( instructions: &[Instr], param_count: usize, constants: &[f32], result_indices: &[usize], ) -> Result<JitCompiledF32> } }
功能:将指令序列编译为可调用的 JIT 函数。compile 生成双精度 JitCompiledFunction,compile_f32 生成单精度 JitCompiledF32(libm 调用使用 *f 符号变体)。这是 ExpressionEvaluator::compile_jit / compile_jit_f32 的底层实现。内部 compile_module 返回 JIT 模块(由调用者保持存活)与入口函数指针,但对外只暴露编译好的函数对象。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
instructions | &[Instr] | 指令序列 |
param_count | usize | 参数个数 |
constants | &[f64](compile)/ &[f32](compile_f32) | 常量值(嵌入为立即数) |
result_indices | &[usize] | 结果槽索引 |
FloatWidth
签名:
#![allow(unused)] fn main() { pub enum FloatWidth { F32, F64, } }
功能:JIT 代码生成的浮点宽度选择。F32 使用 *f 变体的 libm 函数(如 sinf),F64 使用标准 libm。
SIMD 批量求值(feature = "simd")
VectorEvaluator 使用 pulp 库实现运行时检测的 SIMD 向量化求值。在 x86_64 上自动选择 SSE2(2 路 f64)、AVX2(4 路 f64)或 AVX-512(8 路 f64)。f32 版本的通道数翻倍。
VectorEvaluator
签名:
#![allow(unused)] fn main() { pub struct VectorEvaluator { /* private fields */ } }
功能:双精度 SIMD 批量求值器。将多组输入参数分为 SIMD 宽度的 chunk 并行处理,余量回退到标量。
ExpressionEvaluator::compile_vector_evaluator
签名:
#![allow(unused)] fn main() { #[cfg(feature = "simd")] impl ExpressionEvaluator<f64> { pub fn compile_vector_evaluator(&self) -> Result<VectorEvaluator> } }
功能:从编译好的 ExpressionEvaluator 创建 SIMD 批量求值器。构造时检测 SIMD 宽度,预分配栈和常量缓冲区。若表达式包含外部函数(SIMD 模式不支持),返回 UnsupportedOperation。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
self | &ExpressionEvaluator<f64> | 源求值器 |
示例:
#![allow(unused)] fn main() { use ocas_eval::{ExpressionEvaluator, simd::VectorEvaluator}; use ocas_atom::AtomArena; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let expr = ctx.add(&[ctx.mul(&[ctx.var("x"), ctx.var("x")]), ctx.num(1)]); let eval: ExpressionEvaluator<f64> = ExpressionEvaluator::compile(expr).unwrap(); let simd_eval = eval.compile_vector_evaluator().unwrap(); }
VectorEvaluator::evaluate
签名:
#![allow(unused)] fn main() { impl VectorEvaluator { pub fn evaluate(&self, params: &[Vec<f64>]) -> Result<Vec<Vec<f64>>> } }
功能:批量求值。params[i] 是第 i 个参数的所有采样点。所有 params[i] 长度必须相同。返回 Vec<Vec<f64>>,其中 result[j][i] 是第 i 个采样点的第 j 个输出。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
params | &[Vec<f64>] | 参数向量,每个 Vec 长度相同 |
返回值:Ok(Vec<Vec<f64>>) 或求值错误。
示例:
#![allow(unused)] fn main() { use ocas_eval::{ExpressionEvaluator, simd::VectorEvaluator}; use ocas_atom::AtomArena; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let expr = ctx.add(&[ctx.mul(&[ctx.var("x"), ctx.var("x")]), ctx.num(1)]); let eval: ExpressionEvaluator<f64> = ExpressionEvaluator::compile(expr).unwrap(); let simd_eval = eval.compile_vector_evaluator().unwrap(); let x_values: Vec<f64> = (0..1000).map(|i| i as f64 * 0.01).collect(); let results = simd_eval.evaluate(&[x_values]).unwrap(); assert_eq!(results.len(), 1); assert_eq!(results[0].len(), 1000); assert_eq!(results[0][0], 1.0); // results[0] 包含 1000 个 x²+1 的值,SIMD 并行计算 }
VectorEvaluatorF32
签名:
#![allow(unused)] fn main() { pub struct VectorEvaluatorF32 { /* private fields */ } }
功能:单精度 SIMD 批量求值器。通道数是 VectorEvaluator 的两倍(如 AVX2 上为 8 路 f32 vs 4 路 f64)。
ExpressionEvaluator::compile_vector_evaluator_f32
签名:
#![allow(unused)] fn main() { #[cfg(feature = "simd")] impl ExpressionEvaluator<f64> { pub fn compile_vector_evaluator_f32(&self) -> Result<VectorEvaluatorF32> } }
功能:从 f64 求值器创建 f32 SIMD 求值器,常量窄化为 f32。外部函数同样不支持(返回 UnsupportedOperation)。
VectorEvaluatorF32::evaluate
签名:pub fn evaluate(&self, params: &[Vec<f32>]) -> Result<Vec<Vec<f32>>>
功能:单精度批量求值,接口与 VectorEvaluator::evaluate 对称。
StreamingEvaluator<'a, T>
签名:
#![allow(unused)] fn main() { pub struct StreamingEvaluator<'a, T: EvaluationDomain> { evaluator: &'a ExpressionEvaluator<T>, params: Vec<T>, stack: Vec<T>, results: Vec<T>, } }
功能:流式求值器,复用内部缓冲区,处理任意长的输入流时内存使用恒定。所有 scratch 内存(参数暂存、求值栈、结果缓冲区)在创建时一次性分配,每行复用。
StreamingEvaluator::new
签名:
#![allow(unused)] fn main() { impl<T: EvaluationDomain + PowfExtension> StreamingEvaluator<'_, T> { pub fn new(evaluator: &ExpressionEvaluator<T>) -> StreamingEvaluator<'_, T> } }
功能:创建流式求值器,预分配所有缓冲区。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
evaluator | &ExpressionEvaluator<T> | 源求值器(借用) |
示例:
#![allow(unused)] fn main() { use ocas_eval::{ExpressionEvaluator, StreamingEvaluator}; use ocas_atom::AtomArena; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let expr = ctx.add(&[ctx.var("x"), ctx.num(1)]); let eval: ExpressionEvaluator<f64> = ExpressionEvaluator::compile(expr).unwrap(); let mut stream = StreamingEvaluator::new(&eval); }
StreamingEvaluator::for_each
签名:
#![allow(unused)] fn main() { impl<T: EvaluationDomain + PowfExtension> StreamingEvaluator<'_, T> { pub fn for_each<I, S, F>(&mut self, rows: I, mut sink: F) -> Result<usize> where I: IntoIterator<Item = S>, S: AsRef<[T]>, F: FnMut(&[T]), } }
功能:处理输入流的每一行,对每行结果调用 sink 回调。内存使用恒定,不随行数增长。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
rows | I: IntoIterator<Item = S> | 输入行迭代器,每行 AsRef<[T]> 长度等于 param_count() |
sink | FnMut(&[T]) | 结果回调,&[T] 长度等于 result_count(),仅在回调内有效 |
返回值:Ok(处理的行数) 或求值错误。
示例:
#![allow(unused)] fn main() { use ocas_eval::{ExpressionEvaluator, StreamingEvaluator}; use ocas_atom::AtomArena; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let expr = ctx.add(&[ctx.var("x"), ctx.num(1)]); let eval: ExpressionEvaluator<f64> = ExpressionEvaluator::compile(expr).unwrap(); let mut stream = StreamingEvaluator::new(&eval); let rows = (0..1_000_000).map(|i| [i as f64]); let n = stream.for_each(rows, |results| { // results[0] 为当前行的 x+1,仅在回调内有效 }).unwrap(); assert_eq!(n, 1_000_000); }
参见:StreamingEvaluator::evaluate_chunk
StreamingEvaluator::evaluate_chunk
签名:
#![allow(unused)] fn main() { impl<T: EvaluationDomain + PowfExtension> StreamingEvaluator<'_, T> { pub fn evaluate_chunk<S: AsRef<[T]>>(&mut self, rows: &[S]) -> Result<Vec<Vec<T>>> } }
功能:处理一批行并收集所有结果。适用于有界批次;无界流应使用 for_each。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
rows | &[S] | 输入行切片 |
返回值:Ok(Vec<Vec<T>>),每个 Vec<T> 对应一行的结果。
示例:
#![allow(unused)] fn main() { use ocas_eval::{ExpressionEvaluator, StreamingEvaluator}; use ocas_atom::AtomArena; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let expr = ctx.add(&[ctx.var("x"), ctx.num(1)]); let eval: ExpressionEvaluator<f64> = ExpressionEvaluator::compile(expr).unwrap(); let mut stream = StreamingEvaluator::new(&eval); let rows: Vec<[f64; 1]> = (0..100).map(|i| [i as f64]).collect(); let all_results = stream.evaluate_chunk(&rows).unwrap(); assert_eq!(all_results.len(), 100); assert_eq!(all_results[0][0], 1.0); // x=0 → 0+1=1 assert_eq!(all_results[99][0], 100.0); // x=99 → 99+1=100 }
FunctionMap<T>
签名:
#![allow(unused)] fn main() { pub struct FunctionMap<T: EvaluationDomain> { entries: Vec<(String, FunctionEntry<T>)>, name_to_idx: HashMap<String, usize>, aliases: HashMap<String, String>, } }
功能:用户自定义函数注册表。允许在表达式求值期间调用注册的外部函数。名称查找不区分大小写,支持别名。
FunctionMap::new
签名:
#![allow(unused)] fn main() { impl<T: EvaluationDomain> FunctionMap<T> { pub fn new() -> Self } }
功能:创建空的函数映射。
FunctionMap::register
签名:
#![allow(unused)] fn main() { impl<T: EvaluationDomain> FunctionMap<T> { pub fn register(&mut self, name: &str, arity: usize, func: Box<dyn Fn(&[T]) -> T + Send + Sync>) } }
功能:注册一个外部函数。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
name | &str | 函数名(不区分大小写) |
arity | usize | 期望的参数个数 |
func | Box<dyn Fn(&[T]) -> T + Send + Sync> | 函数实现 |
示例:
#![allow(unused)] fn main() { use ocas_eval::FunctionMap; let mut fmap = FunctionMap::<f64>::new(); fmap.register("square", 1, Box::new(|args| args[0] * args[0])); fmap.register("add3", 3, Box::new(|args| args[0] + args[1] + args[2])); }
FunctionMap::register_alias
签名:
#![allow(unused)] fn main() { impl<T: EvaluationDomain> FunctionMap<T> { pub fn register_alias(&mut self, alias: &str, canonical: &str) } }
功能:为已注册的函数名添加别名。查找 alias 时会重定向到 canonical。
FunctionMap::resolve
签名:
#![allow(unused)] fn main() { impl<T: EvaluationDomain> FunctionMap<T> { pub fn resolve(&self, name: &str) -> Option<&FunctionEntry<T>> } }
功能:按名称查找函数(解析别名和大小写)。
FunctionMap::index_of
签名:pub fn index_of(&self, name: &str) -> Option<usize>
功能:返回函数在映射中的索引(用于指令中的 fn_idx)。
FunctionMap::call_by_index
签名:pub fn call_by_index(&self, idx: usize, args: &[T]) -> Option<T>
功能:按索引调用函数。
FunctionMap::len / is_empty
签名:pub fn len(&self) -> usize / pub fn is_empty(&self) -> bool
功能:返回已注册函数数量 / 判断是否为空。
FunctionEntry<T>
签名:
#![allow(unused)] fn main() { pub struct FunctionEntry<T: EvaluationDomain> { pub arity: usize, // private func field } }
功能:已注册的外部函数条目。arity 为期望的参数个数。
指令集
Slot
签名:
#![allow(unused)] fn main() { pub enum Slot { Param(usize), Const(usize), Temp(usize), } }
功能:求值器栈中的命名槽位。用于公开的 Instruction 类型,以语义而非原始索引引用栈位置。
| 变体 | 说明 |
|---|---|
Param(usize) | 参数槽,0 基索引 |
Const(usize) | 常量槽,0 基索引 |
Temp(usize) | 临时值槽,0 基索引 |
Instruction
签名:
#![allow(unused)] fn main() { pub enum Instruction { Add(Slot, Vec<Slot>), Mul(Slot, Vec<Slot>), Pow(Slot, Slot, i64), Powf(Slot, Slot, Slot), Fun(Slot, Symbol, Slot), ExternalFun(Slot, usize, Vec<Slot>), Assign(Slot, Slot), } }
功能:自文档化的公开指令。与内部 Instr 不同,使用 Slot 而非原始索引,便于检查和序列化。
| 变体 | 格式 | 说明 |
|---|---|---|
Add | dst = sum(sources) | 求和 |
Mul | dst = product(sources) | 求积 |
Pow | dst = base^exp | 整数次幂 |
Powf | dst = base^exp | 浮点次幂 |
Fun | dst = builtin(src) | 内建函数 |
ExternalFun | dst = fns[idx](srcs...) | 外部函数 |
Assign | dst = src | 复制 |
Instr
签名:
#![allow(unused)] fn main() { pub enum Instr { Add { dst: usize, srcs: Vec<usize> }, Mul { dst: usize, srcs: Vec<usize> }, Pow { dst: usize, base: usize, exp: i64 }, Powf { dst: usize, base: usize, exp: usize }, BuiltinOp { dst: usize, op: BuiltinOp, src: usize }, ExternalFun { dst: usize, fn_idx: usize, srcs: Vec<usize> }, Copy { dst: usize, src: usize }, } }
功能:内部索引式指令,由 ExpressionEvaluator 执行。所有索引是栈中的绝对位置。
栈布局:
[params (param_count)] [constants (const_count)] [temporaries (temp_count)] [outputs (result_count)]
| 变体 | 说明 |
|---|---|
Add { dst, srcs } | stack[dst] = stack[srcs[0]] + stack[srcs[1]] + ... |
Mul { dst, srcs } | stack[dst] = stack[srcs[0]] * stack[srcs[1]] * ... |
Pow { dst, base, exp } | stack[dst] = stack[base]^exp,exp 为 i64 |
Powf { dst, base, exp } | stack[dst] = stack[base]^stack[exp],浮点指数 |
BuiltinOp { dst, op, src } | stack[dst] = op(stack[src]) |
ExternalFun { dst, fn_idx, srcs } | stack[dst] = fns[fn_idx](&stack[srcs...]) |
Copy { dst, src } | stack[dst] = stack[src] |
BuiltinOp
签名:
#![allow(unused)] fn main() { pub enum BuiltinOp { Sin, Cos, Tan, Sec, Csc, Cot, Exp, Log, Sqrt, Abs, } }
功能:预解析的内建函数操作码。编译器在编译时将 Symbol 名称转换为 BuiltinOp 变体,避免在热路径上进行字符串匹配。
| 变体 | 数学 | 说明 |
|---|---|---|
Sin | $\sin(x)$ | 正弦 |
Cos | $\cos(x)$ | 余弦 |
Tan | $\tan(x)$ | 正切 |
Sec | $\sec(x) = 1/\cos(x)$ | 正割 |
Csc | $\csc(x) = 1/\sin(x)$ | 余割 |
Cot | $\cot(x) = 1/\tan(x)$ | 余切 |
Exp | $e^x$ | 指数函数 |
Log | $\ln(x)$ | 自然对数 |
Sqrt | $\sqrt{x}$ | 平方根 |
Abs | $ | x |
BuiltinOp::from_name
签名:pub fn from_name(name: &str) -> Option<Self>
功能:尝试从函数名解析为内建操作码。不区分大小写。"log" 和 "ln" 均映射到 Log。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
name | &str | 函数名 |
返回值:Some(BuiltinOp) 或 None(非内建函数)。
示例:
#![allow(unused)] fn main() { use ocas_eval::instruction::BuiltinOp; assert_eq!(BuiltinOp::from_name("sin"), Some(BuiltinOp::Sin)); assert_eq!(BuiltinOp::from_name("Log"), Some(BuiltinOp::Log)); assert_eq!(BuiltinOp::from_name("ln"), Some(BuiltinOp::Log)); assert_eq!(BuiltinOp::from_name("myfunc"), None); }
EvaluationDomain trait
签名:
#![allow(unused)] fn main() { pub trait EvaluationDomain: Sized + Clone + 'static { fn from_f64(value: f64) -> Self; fn zero() -> Self; fn one() -> Self; fn add_ref(&self, other: &Self) -> Self; fn sub_ref(&self, other: &Self) -> Self; fn mul_ref(&self, other: &Self) -> Self; fn div_ref(&self, other: &Self) -> Result<Self>; fn neg_ref(&self) -> Self; fn powi_ref(&self, exp: i64) -> Self; fn resolve_builtin(name: &str, arg: &Self) -> Result<Self>; } }
功能:可作为求值域的类型的 trait。与代数 Domain trait 不同(Domain 是对象安全的、使用 &self 接收器),EvaluationDomain 使用静态方法和值操作,兼容 f64 等 Copy 类型。
方法:
| 方法 | 签名 | 说明 |
|---|---|---|
from_f64 | fn from_f64(f64) -> Self | 从 f64 创建值 |
zero | fn zero() -> Self | 加法单位元 |
one | fn one() -> Self | 乘法单位元 |
add_ref | fn add_ref(&self, &Self) -> Self | 加法 |
sub_ref | fn sub_ref(&self, &Self) -> Self | 减法 |
mul_ref | fn mul_ref(&self, &Self) -> Self | 乘法 |
div_ref | fn div_ref(&self, &Self) -> Result<Self> | 除法,除零返回 DivisionByZero |
neg_ref | fn neg_ref(&self) -> Self | 取反 |
powi_ref | fn powi_ref(&self, i64) -> Self | 整数次幂,exp == 0 时返回 1 |
resolve_builtin | fn resolve_builtin(name: &str, &Self) -> Result<Self> | 执行内建函数 |
实现:
| 类型 | 说明 |
|---|---|
f64 | 标准双精度浮点(始终可用) |
DoubleF64 | oCAS 双精度浮点(~31 位有效数字) |
示例:
#![allow(unused)] fn main() { use ocas_eval::EvaluationDomain; let x = f64::from_f64(3.0); let y = f64::from_f64(2.0); assert_eq!(x.add_ref(&y), 5.0); assert_eq!(f64::resolve_builtin("sin", &std::f64::consts::FRAC_PI_2).unwrap(), 1.0); }
PowfExtension trait
签名:
#![allow(unused)] fn main() { pub trait PowfExtension: EvaluationDomain { fn powf_ref(&self, exp: &Self) -> Result<Self>; } }
功能:浮点指数幂的扩展 trait。从 EvaluationDomain 分离出来,因为整数域无法有意义地计算 $a^b$($b$ 非整数)。
实现:f64、DoubleF64。
示例:
#![allow(unused)] fn main() { use ocas_eval::PowfExtension; let base = 2.0_f64; let exp = 0.5_f64; let result = base.powf_ref(&exp).unwrap(); assert!((result - std::f64::consts::SQRT_2).abs() < 1e-15); // 输出:2^0.5 ≈ 1.4142135623730951 }
编译函数
以下自由函数是 ExpressionEvaluator 编译方法的底层入口。
compile_atom
签名:
#![allow(unused)] fn main() { pub fn compile_atom<T: EvaluationDomain + PowfExtension>( atom: Atom<'_>, ) -> Result<ExpressionEvaluator<T>> }
功能:将 Atom 编译为求值器。等价于 ExpressionEvaluator::compile。
compile_atom_with
签名:
#![allow(unused)] fn main() { pub fn compile_atom_with<T: EvaluationDomain + PowfExtension>( atom: Atom<'_>, function_map: Option<FunctionMap<T>>, ) -> Result<ExpressionEvaluator<T>> }
功能:带可选函数映射的编译。
compile_tree / compile_tree_with
签名:
#![allow(unused)] fn main() { pub fn compile_tree<T: EvaluationDomain + PowfExtension>( tree: &EvalTree, ) -> Result<ExpressionEvaluator<T>> pub fn compile_tree_with<T: EvaluationDomain + PowfExtension>( tree: &EvalTree, function_map: Option<FunctionMap<T>>, ) -> Result<ExpressionEvaluator<T>> }
功能:从 EvalTree 编译。适用于需要先对 EvalTree 进行自定义优化再编译的场景。
compile_atoms_multi / compile_atoms_multi_with
签名:
#![allow(unused)] fn main() { pub fn compile_atoms_multi<T: EvaluationDomain + PowfExtension>( atoms: &[Atom<'_>], ) -> Result<ExpressionEvaluator<T>> pub fn compile_atoms_multi_with<T: EvaluationDomain + PowfExtension>( atoms: &[Atom<'_>], function_map: Option<FunctionMap<T>>, ) -> Result<ExpressionEvaluator<T>> }
功能:将多个 Atom 编译为单个多输出求值器。跨输出共享 CSE 和常量。
compile_trees_multi
签名:
#![allow(unused)] fn main() { pub fn compile_trees_multi<T: EvaluationDomain + PowfExtension>( trees: &[&EvalTree], ) -> Result<ExpressionEvaluator<T>> }
功能:将多个 EvalTree 编译为单个多输出求值器。
EvaluationError
签名:
#![allow(unused)] fn main() { #[non_exhaustive] pub enum EvaluationError { UndefinedVariable { name: String }, TypeMismatch { expected: String, found: String }, DivisionByZero, FunctionNotFound { name: String }, WrongArity { name: String, expected: usize, got: usize }, JitCompilationError { message: String }, UnsupportedOperation { message: String }, } }
功能:求值过程中可能产生的错误。
| 变体 | 说明 |
|---|---|
UndefinedVariable { name } | 表达式引用了未提供的变量 |
TypeMismatch { expected, found } | 类型不匹配(如期望浮点却得到整数) |
DivisionByZero | 除零 |
FunctionNotFound { name } | 用户自定义函数未在注册表中找到 |
WrongArity { name, expected, got } | 函数调用参数个数错误 |
JitCompilationError { message } | JIT 编译失败(Cranelift 后端错误) |
UnsupportedOperation { message } | 请求的操作在当前域不支持 |
Display 格式:
undefined variable 'x'
type mismatch: expected float, found integer
division by zero
function 'f' not found
wrong arity for 'f': expected 2, got 1
JIT compilation error: <message>
unsupported operation: <message>
示例:
#![allow(unused)] fn main() { use ocas_eval::EvaluationError; let err = EvaluationError::UndefinedVariable { name: "x".into() }; assert_eq!(err.to_string(), "undefined variable 'x'"); let err = EvaluationError::WrongArity { name: "f".into(), expected: 2, got: 1, }; assert_eq!(err.to_string(), "wrong arity for 'f': expected 2, got 1"); }
使用模式
基本求值
#![allow(unused)] fn main() { use ocas_eval::ExpressionEvaluator; use ocas_atom::AtomArena; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); // 构建 x² + 2x + 1 let x = ctx.var("x"); let expr = ctx.add(&[ ctx.mul(&[x, x]), ctx.mul(&[ctx.num(2), x]), ctx.num(1), ]); let eval: ExpressionEvaluator<f64> = ExpressionEvaluator::compile(expr).unwrap(); // 求值 let result = eval.evaluate(&[3.0]).unwrap(); assert_eq!(result[0], 16.0); // 9 + 6 + 1 = 16 }
JIT 热循环
#![allow(unused)] fn main() { use ocas_eval::ExpressionEvaluator; use ocas_atom::AtomArena; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let expr = ctx.fun("sin", &[ctx.var("x")]); let eval: ExpressionEvaluator<f64> = ExpressionEvaluator::compile(expr).unwrap(); let jit = eval.compile_jit().unwrap(); let mut results = vec![0.0; jit.result_count()]; for i in 0..1_000_000 { jit.call_into(&[i as f64 * 0.001], &mut results); // 使用 results[0]... } // 零分配热循环,原生机器码执行 }
SIMD 批量处理
#![allow(unused)] fn main() { use ocas_eval::{ExpressionEvaluator, simd::VectorEvaluator}; use ocas_atom::AtomArena; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let expr = ctx.fun("exp", &[ctx.var("x")]); let eval: ExpressionEvaluator<f64> = ExpressionEvaluator::compile(expr).unwrap(); let simd = eval.compile_vector_evaluator().unwrap(); let x: Vec<f64> = (0..10000).map(|i| i as f64 * 0.001).collect(); let results = simd.evaluate(&[x]).unwrap(); // results[0] 包含 10000 个 e^x 的值 }
流式处理大数据集
#![allow(unused)] fn main() { use ocas_eval::{ExpressionEvaluator, StreamingEvaluator}; use ocas_atom::AtomArena; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let expr = ctx.add(&[ctx.var("x"), ctx.num(1)]); let eval: ExpressionEvaluator<f64> = ExpressionEvaluator::compile(expr).unwrap(); let mut stream = StreamingEvaluator::new(&eval); // 处理 100 万行,内存使用恒定 let rows = (0..1_000_000).map(|i| [i as f64]); let n = stream.for_each(rows, |_results| { // 处理 results... }).unwrap(); assert_eq!(n, 1_000_000); }
自定义函数
#![allow(unused)] fn main() { use ocas_eval::{ExpressionEvaluator, FunctionMap}; use ocas_atom::AtomArena; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let expr = ctx.fun("sigmoid", &[ctx.var("x")]); let mut fmap = FunctionMap::<f64>::new(); fmap.register("sigmoid", 1, Box::new(|args| { 1.0 / (1.0 + (-args[0]).exp()) })); let eval = ExpressionEvaluator::compile_with(expr, fmap).unwrap(); let result = eval.evaluate(&[0.0]).unwrap(); assert!((result[0] - 0.5).abs() < 1e-15); // 输出:sigmoid(0) = 0.5 }
参见:表达式系统(Atom、AtomArena)、数值积分(Vegas 使用求值器)、自动微分(HyperDual 域上的求值)
自动微分
oCAS 通过超对偶数(hyper-dual numbers)实现前向模式自动微分。一个 HyperDual<T> 携带一个标量值和一组导数分量,分量布局由共享的 DualShape(通过 Arc 共享)预先计算,并在算术运算中使用预构建的乘法表。
源文件:
ocas-domain/src/dual.rs
DualCoeff trait
#![allow(unused)] fn main() { pub trait DualCoeff: Clone + PartialEq + std::ops::Add<Output = Self> + std::ops::Sub<Output = Self> + std::ops::Mul<Output = Self> + std::ops::Div<Output = Self> + std::ops::Neg<Output = Self> + std::ops::AddAssign + std::ops::MulAssign { fn zero() -> Self; fn one() -> Self; } }
功能:定义超对偶数所需的系数类型约束。要求完整的四则运算(含乘法逆元)以及加法/乘法单位元。
参数:无(trait bound)。
内置实现:
| 类型 | 说明 |
|---|---|
Rational | 任意精度有理数,zero() = 0/1,one() = 1/1 |
限制:当前仅 Rational 实现了 DualCoeff。不支持 f64 或其他浮点类型作为系数。
DualShape
#![allow(unused)] fn main() { #[derive(Debug, Clone)] pub struct DualShape { /* private fields */ } }
功能:描述超对偶数的分量布局。每个分量由一个多指标(multi-index)标识——长度为变量数的非负整数向量,记录各变量的微分阶数。布局必须满足祖先封闭性:若多指标 $\mathbf{m}$ 存在,则所有分量逐项不大于 $\mathbf{m}$ 的多指标也必须存在。
分量 0 始终是全零多指标(标量值分量)。
DualShape::new
#![allow(unused)] fn main() { pub fn new(components: Vec<Vec<usize>>) -> Option<Self> }
功能:从祖先封闭的多指标列表构建布局。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
components | Vec<Vec<usize>> | 多指标列表,每个 Vec<usize> 长度相同(不足部分自动补零) |
返回值:Some(DualShape) 若布局合法,否则 None。
错误条件:
- 列表为空 →
None - 不含全零多指标 →
None - 不满足祖先封闭性 →
None
示例:
#![allow(unused)] fn main() { use ocas_domain::dual::DualShape; // 一阶二变量:值 + ∂/∂x₀ + ∂/∂x₁ let shape = DualShape::new(vec![ vec![0, 0], vec![1, 0], vec![0, 1], ]).unwrap(); assert_eq!(shape.n_components(), 3); assert_eq!(shape.n_vars(), 2); // 非法:缺少 [1](不祖先封闭) assert!(DualShape::new(vec![vec![0], vec![2]]).is_none()); }
DualShape::n_vars
#![allow(unused)] fn main() { pub fn n_vars(&self) -> usize }
功能:返回微分变量数(多指标的长度)。
返回值:usize
DualShape::n_components
#![allow(unused)] fn main() { pub fn n_components(&self) -> usize }
功能:返回分量总数(含标量值分量)。
返回值:usize
DualShape::components
#![allow(unused)] fn main() { pub fn components(&self) -> &[Vec<usize>] }
功能:返回分量多指标切片。索引 0 为标量值分量。
返回值:&[Vec<usize>]
DualShape::index_of
#![allow(unused)] fn main() { pub fn index_of(&self, multi_index: &[usize]) -> Option<usize> }
功能:查找给定多指标的分量索引。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
multi_index | &[usize] | 待查找的多指标 |
返回值:Some(index) 若存在,否则 None。
DualShape::mult_table
#![allow(unused)] fn main() { pub fn mult_table(&self) -> &[(usize, usize, usize)] }
功能:返回乘法表(用于调试/测试)。每个三元组 (a, b, c) 表示分量 a 与分量 b 的乘积贡献到分量 c。不包含标量分量(索引 0)参与的对。
返回值:&[(usize, usize, usize)]
new_first_order
#![allow(unused)] fn main() { pub fn new_first_order<T: DualCoeff>(nvars: usize) -> Arc<DualShape> }
功能:构建一阶形状,追踪每个变量的偏导数 $\frac{\partial}{\partial x_i}$。不追踪高阶或混合偏导。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
nvars | usize | 微分变量数 |
返回值:Arc<DualShape> — 包含 $n+1$ 个分量:值分量 $[0,\dots,0]$ 加每个变量的 $[0,\dots,\underset{i}{1},\dots,0]$。
示例:
#![allow(unused)] fn main() { use ocas_domain::dual::new_first_order; use ocas_domain::Rational; let shape = new_first_order::<Rational>(3); assert_eq!(shape.n_components(), 4); // 值 + 3 个偏导 assert_eq!(shape.n_vars(), 3); }
HyperDual<T>
#![allow(unused)] fn main() { #[derive(Debug, Clone)] pub struct HyperDual<T: DualCoeff> { /* private fields */ } }
功能:超对偶数类型,携带标量值和按 DualShape 布局的导数分量。所有算术运算自动传播导数。
类型约束:T: DualCoeff
HyperDual::variable
#![allow(unused)] fn main() { pub fn variable(shape: &Arc<DualShape>, i: usize, c: T) -> Self }
功能:构造第 $i$ 个独立变量,取值为 $c$。导数分量 $[0,\dots,\underset{i}{1},\dots,0]$ 设为 1,其余为 0。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
shape | &Arc<DualShape> | 分量布局 |
i | usize | 变量编号(0-indexed),须 < n_vars()(越界时静默忽略,不 panic) |
c | T | 该变量在求值点的值 |
返回值:HyperDual<T>
示例:
#![allow(unused)] fn main() { use ocas_domain::dual::{HyperDual, new_first_order}; use ocas_domain::Rational; let shape = new_first_order::<Rational>(2); let x = HyperDual::variable(&shape, 0, Rational::new(3, 1)); assert_eq!(x.value(), &Rational::new(3, 1)); assert_eq!(x.deriv(0), Some(&Rational::new(1, 1))); assert_eq!(x.deriv(1), Some(&Rational::new(0, 1))); }
HyperDual::constant
#![allow(unused)] fn main() { pub fn constant(shape: &Arc<DualShape>, c: T) -> Self }
功能:构造常量——值为 $c$,所有导数分量为零。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
shape | &Arc<DualShape> | 分量布局 |
c | T | 常量值 |
返回值:HyperDual<T>
示例:
#![allow(unused)] fn main() { use ocas_domain::dual::{HyperDual, new_first_order}; use ocas_domain::Rational; let shape = new_first_order::<Rational>(1); let c = HyperDual::constant(&shape, Rational::new(7, 1)); assert_eq!(c.value(), &Rational::new(7, 1)); assert_eq!(c.deriv(0), Some(&Rational::new(0, 1))); }
HyperDual::value
#![allow(unused)] fn main() { pub fn value(&self) -> &T }
功能:返回标量值分量(分量 0)的引用。
返回值:&T
HyperDual::deriv
#![allow(unused)] fn main() { pub fn deriv(&self, i: usize) -> Option<&T> }
功能:返回对变量 $i$ 的一阶偏导数 $\frac{\partial f}{\partial x_i}$。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
i | usize | 变量编号(0-indexed) |
返回值:Some(&T) 若一阶分量 $[0,\dots,\underset{i}{1},\dots,0]$ 存在于形状中(对 new_first_order 形状等价于 $i < n_vars$),否则 None。
HyperDual::values
#![allow(unused)] fn main() { pub fn values(&self) -> &[T] }
功能:返回所有分量的切片,按形状顺序排列。
返回值:&[T]
HyperDual::shape
#![allow(unused)] fn main() { pub fn shape(&self) -> &Arc<DualShape> }
功能:返回共享的形状引用。
返回值:&Arc<DualShape>
HyperDual::from_values
#![allow(unused)] fn main() { pub fn from_values(shape: Arc<DualShape>, values: Vec<T>) -> Option<Self> }
功能:从完整的分量向量构造。长度必须匹配形状的分量数。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
shape | Arc<DualShape> | 分量布局 |
values | Vec<T> | 分量值,长度须为 shape.n_components() |
返回值:Some(HyperDual<T>) 若长度匹配,否则 None。
HyperDual::zero / HyperDual::one
#![allow(unused)] fn main() { pub fn zero(shape: &Arc<DualShape>) -> Self pub fn one(shape: &Arc<DualShape>) -> Self }
功能:构造加法单位元(所有分量为零)和乘法单位元(值为 1,导数分量为零)。
HyperDual::inv
#![allow(unused)] fn main() { pub fn inv(&self) -> Option<Self> }
功能:乘法逆元 $\frac{1}{f}$,通过几何级数 $\frac{1}{v+\varepsilon} = \frac{1}{v}\sum_{k \geq 0}\left(-\frac{\varepsilon}{v}\right)^k$ 截断计算。
返回值:Some(1/self) 若值分量非零,否则 None(除以零)。
算术运算
HyperDual<T> 实现了以下标准 trait,所有运算自动传播导数:
| Trait | 运算 | 导数规则 |
|---|---|---|
Add | a + b | $\frac{\partial}{\partial x_i}(a+b) = a'_i + b'_i$ |
Sub | a - b | $\frac{\partial}{\partial x_i}(a-b) = a'_i - b'_i$ |
Neg | -a | $\frac{\partial}{\partial x_i}(-a) = -a'_i$ |
Mul | a * b | $\frac{\partial}{\partial x_i}(ab) = a'b + ab'$(通过乘法表处理高阶项) |
Div | a / b | 等价于 a * b.inv(),要求 b 的值分量非零 |
约束:Add、Sub、Mul、Div 要求两个操作数共享同一 DualShape。debug 模式下校验分量数是否一致,不一致时 panic。
除零:Div 调用 inv(),若除数的值分量为零则 panic。
完整示例
示例 1:二元函数偏导数
计算 $f(x, y) = x \cdot y$ 在 $(3, 5)$ 处的值和偏导:
#![allow(unused)] fn main() { use ocas_domain::dual::{HyperDual, new_first_order}; use ocas_domain::Rational; let shape = new_first_order::<Rational>(2); let x = HyperDual::variable(&shape, 0, Rational::new(3, 1)); let y = HyperDual::variable(&shape, 1, Rational::new(5, 1)); let f = x * y; assert_eq!(f.value(), &Rational::new(15, 1)); // f(3,5) = 15 assert_eq!(f.deriv(0), Some(&Rational::new(5, 1))); // ∂f/∂x = y = 5 assert_eq!(f.deriv(1), Some(&Rational::new(3, 1))); // ∂f/∂y = x = 3 }
示例 2:幂函数导数
计算 $f(x) = x^3$ 在 $x=3$ 处的导数 $\frac{d}{dx}x^3 = 3x^2 = 27$:
#![allow(unused)] fn main() { use ocas_domain::dual::{HyperDual, new_first_order}; use ocas_domain::Rational; let shape = new_first_order::<Rational>(1); let x = HyperDual::variable(&shape, 0, Rational::new(3, 1)); // x^3 = x * x * x let x2 = x.clone() * x.clone(); let x3 = x2 * x; assert_eq!(x3.value(), &Rational::new(27, 1)); assert_eq!(x3.deriv(0), Some(&Rational::new(27, 1))); }
示例 3:商的导数
计算 $f(x,y) = \frac{x}{y}$ 在 $(6, 3)$ 处:$\frac{\partial f}{\partial x} = \frac{1}{y} = \frac{1}{3}$,$\frac{\partial f}{\partial y} = -\frac{x}{y^2} = -\frac{2}{3}$。
#![allow(unused)] fn main() { use ocas_domain::dual::{HyperDual, new_first_order}; use ocas_domain::Rational; let shape = new_first_order::<Rational>(2); let x = HyperDual::variable(&shape, 0, Rational::new(6, 1)); let y = HyperDual::variable(&shape, 1, Rational::new(3, 1)); let f = x / y; assert_eq!(f.value(), &Rational::new(2, 1)); assert_eq!(f.deriv(0), Some(&Rational::new(1, 3))); assert_eq!(f.deriv(1), Some(&Rational::new(-2, 3))); }
示例 4:三变量乘积
计算 $f(x,y,z) = xyz$ 在 $(2, 3, 5)$ 处:$\frac{\partial f}{\partial x} = yz = 15$,$\frac{\partial f}{\partial y} = xz = 10$,$\frac{\partial f}{\partial z} = xy = 6$。
#![allow(unused)] fn main() { use ocas_domain::dual::{HyperDual, new_first_order}; use ocas_domain::Rational; let shape = new_first_order::<Rational>(3); let x = HyperDual::variable(&shape, 0, Rational::new(2, 1)); let y = HyperDual::variable(&shape, 1, Rational::new(3, 1)); let z = HyperDual::variable(&shape, 2, Rational::new(5, 1)); let f = x * y * z; assert_eq!(f.value(), &Rational::new(30, 1)); assert_eq!(f.deriv(0), Some(&Rational::new(15, 1))); assert_eq!(f.deriv(1), Some(&Rational::new(10, 1))); assert_eq!(f.deriv(2), Some(&Rational::new(6, 1))); }
示例 5:二阶导数
使用自定义形状追踪二阶导数。对于 $f(x) = x^2$,有 $f''(x) = 2$。注意:多指标为 $[2]$ 的分量存储的是 $\varepsilon^2$ 的系数,即 $f''(x)/2!$,而非 $f''(x)$ 本身。
#![allow(unused)] fn main() { use ocas_domain::dual::{DualShape, HyperDual}; use ocas_domain::Rational; // 形状:[0](值)、[1](一阶)、[2](二阶) let shape = std::sync::Arc::new( DualShape::new(vec![vec![0], vec![1], vec![2]]).unwrap() ); let x = HyperDual::variable(&shape, 0, Rational::new(3, 1)); // f = x * x let f = x.clone() * x; assert_eq!(f.value(), &Rational::new(9, 1)); // 3^2 = 9 assert_eq!(f.deriv(0), Some(&Rational::new(6, 1))); // 2x = 6 // 分量 [2] 存储 ε² 的系数 = f''(3)/2! = 2/2 = 1 assert_eq!(f.values()[2], Rational::new(1, 1)); // f''(3)/2! = 1 }
实现细节
乘法表
DualShape 预计算一个乘法表:对于每对非标量分量 $(a, b)$,若其多指标之和也存在于布局中,记录三元组 $(a, b, c)$。乘法运算时,分量 $k$ 的结果为:
$$\text{result}[k] = a[k] \cdot b[0] + a[0] \cdot b[k] + \sum_{(i,j,k) \in \text{table}} a[i] \cdot b[j]$$
超出布局的高阶项被截断(truncation)。
逆元的几何级数
inv() 通过几何级数 $\frac{1}{v+\varepsilon} = \frac{1}{v}\sum_{p=0}^{\infty}\left(-\frac{\varepsilon}{v}\right)^p$ 计算,迭代直到高阶项为零(自动截断至形状精度)。
共享形状
所有参与同一表达式计算的 HyperDual 必须共享同一 Arc<DualShape>。形状通过 Arc 克隆成本极低。
限制
| 限制 | 说明 |
|---|---|
| 仅支持有理系数 | 当前 DualCoeff 仅对 Rational 实现,不支持 f64 或其他浮点类型 |
| 无超越函数 | sin、cos、exp、ln 等超越函数未实现——它们需要实系数 trait |
| 除零 panic | Div 调用 inv(),值分量为零时 panic |
| 形状不匹配 panic | Add/Sub/Mul/Div 要求操作数共享同一形状(debug 模式下校验分量数),不一致时 panic |
| 高阶导数需手动布局 | 默认的 new_first_order 仅追踪一阶偏导;二阶及以上需通过 DualShape::new 手动构建布局 |
参见
Rust API 参考:张量
oCAS 提供带显式指标管理的基础张量代数。Tensor 携带命名指标槽(上标/下标)、可选的对称性元数据,并可转换为 Atom 表达式进行符号处理。自 0.22.0 起,额外提供基于图同构的规范化引擎和 Young 投影子。
模块路径:ocas_atom::tensor(mod.rs)、ocas_atom::tensor::spec、ocas_atom::tensor::canon、ocas_atom::tensor::dummy、ocas_atom::tensor::young
导入方式:
#![allow(unused)] fn main() { use ocas_atom::tensor::{ IndexPosition, IndexSlot, Symmetry, Tensor, contract, Contracted, TensorProduct, symmetrise_sign, }; use ocas_atom::tensor::spec::{SymmetrySpec, TensorRegistry}; use ocas_atom::tensor::canon::{canonicalize_tensors, CanonicalTensor, TensorCanonError}; use ocas_atom::tensor::dummy::{refresh_dummies, DummyError}; use ocas_atom::tensor::young::{YoungTableau, young_project}; }
IndexPosition
签名:
#![allow(unused)] fn main() { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum IndexPosition { Upper, Lower, } }
功能:指标在张量中的位置(方差)。Upper 表示逆变指标(上标),Lower 表示协变指标(下标)。缩并要求两个相同标签的指标具有相反的位置。
变体:
| 变体 | 含义 |
|---|---|
Upper | 逆变指标(上标),物理中对应反变向量 |
Lower | 协变指标(下标),物理中对应协变向量 |
示例:
#![allow(unused)] fn main() { use ocas_atom::tensor::IndexPosition; assert_eq!(IndexPosition::Upper, IndexPosition::Upper); assert_ne!(IndexPosition::Upper, IndexPosition::Lower); // 输出:所有断言通过 }
IndexSlot
签名:
#![allow(unused)] fn main() { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct IndexSlot<'a> { /* private fields */ } }
功能:张量的单个指标槽,由指标标签(Atom)和位置(IndexPosition)组成。Copy 类型,可廉价复制。
IndexSlot::new
签名:pub fn new(label: Atom<'a>, position: IndexPosition) -> Self
功能:创建一个新的指标槽。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
label | Atom<'a> | 指标标签表达式(通常为变量名如 mu、i) |
position | IndexPosition | 指标位置:Upper 或 Lower |
返回值:IndexSlot<'a>
示例:
#![allow(unused)] fn main() { use ocas_atom::tensor::{IndexSlot, IndexPosition}; use ocas_atom::AtomArena; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let mu = IndexSlot::new(ctx.var("mu"), IndexPosition::Upper); let nu = IndexSlot::new(ctx.var("nu"), IndexPosition::Lower); assert_eq!(mu.position(), IndexPosition::Upper); assert_eq!(nu.position(), IndexPosition::Lower); // 输出:所有断言通过 }
IndexSlot::label
签名:pub fn label(&self) -> Atom<'a>
功能:返回指标标签表达式。
返回值:Atom<'a> — 指标标签的 Atom 句柄。
IndexSlot::position
签名:pub fn position(&self) -> IndexPosition
功能:返回指标的位置(Upper 或 Lower)。
返回值:IndexPosition
Symmetry
签名:
#![allow(unused)] fn main() { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Symmetry { None, Symmetric, Antisymmetric, } }
功能:张量指标槽的对称性。这是建议性元数据——contract 等运算不会自动对称化;对称性由下游消费者(如规范化引擎或 Young 投影子)使用。
变体:
| 变体 | 含义 |
|---|---|
None | 无对称性(一般张量) |
Symmetric | 对称:任意交换指标槽不改变张量 |
Antisymmetric | 反对称:交换任意两个指标槽改变符号 |
示例:
#![allow(unused)] fn main() { use ocas_atom::tensor::Symmetry; assert_eq!(Symmetry::None, Symmetry::None); assert_ne!(Symmetry::Symmetric, Symmetry::Antisymmetric); // 输出:所有断言通过 }
Tensor
签名:
#![allow(unused)] fn main() { #[derive(Debug, Clone, PartialEq, Eq)] pub struct Tensor<'a> { /* private fields */ } }
功能:命名张量对象,由名称(Symbol)、指标槽列表(Vec<IndexSlot>)和对称性(Symmetry)组成。可通过 to_atom 降级为标准 Atom 表达式节点。
Tensor::new
签名:pub fn new(name: Symbol, slots: Vec<IndexSlot<'a>>) -> Self
功能:创建一个新张量,默认对称性为 Symmetry::None。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
name | Symbol | 张量名称(interned 字符串,如 "g"、"Riemann") |
slots | Vec<IndexSlot<'a>> | 指标槽列表,顺序有意义 |
返回值:Tensor<'a>
示例:
#![allow(unused)] fn main() { use ocas_atom::tensor::{Tensor, IndexSlot, IndexPosition}; use ocas_atom::{AtomArena, Symbol}; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let slots = vec![ IndexSlot::new(ctx.var("mu"), IndexPosition::Upper), IndexSlot::new(ctx.var("nu"), IndexPosition::Lower), ]; let t = Tensor::new(Symbol::new("g"), slots); assert_eq!(t.rank(), 2); assert_eq!(t.name().as_str(), "g"); // 输出:所有断言通过 }
Tensor::with_symmetry
签名:pub fn with_symmetry(mut self, symmetry: Symmetry) -> Self
功能:Builder 方法,设置张量的对称性。消费并返回 self。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
symmetry | Symmetry | 目标对称性 |
返回值:Self — 设置了对称性的张量。
示例:
#![allow(unused)] fn main() { use ocas_atom::tensor::{Tensor, IndexSlot, IndexPosition, Symmetry}; use ocas_atom::{AtomArena, Symbol}; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let slots = vec![ IndexSlot::new(ctx.var("mu"), IndexPosition::Upper), IndexSlot::new(ctx.var("nu"), IndexPosition::Upper), ]; let sym = Tensor::new(Symbol::new("g"), slots.clone()) .with_symmetry(Symmetry::Symmetric); let anti = Tensor::new(Symbol::new("epsilon"), slots) .with_symmetry(Symmetry::Antisymmetric); assert_eq!(sym.symmetry(), Symmetry::Symmetric); assert_eq!(anti.symmetry(), Symmetry::Antisymmetric); // 输出:所有断言通过 }
Tensor::name
签名:pub fn name(&self) -> Symbol
功能:返回张量名称。
返回值:Symbol — interned 字符串句柄。
Tensor::slots
签名:pub fn slots(&self) -> &[IndexSlot<'a>]
功能:返回指标槽的切片引用。
返回值:&[IndexSlot<'a>]
Tensor::symmetry
签名:pub fn symmetry(&self) -> Symmetry
功能:返回张量的对称性。
返回值:Symmetry
Tensor::rank
签名:pub fn rank(&self) -> usize
功能:返回张量的阶(指标槽数量)。
返回值:usize
Tensor::dummy_labels
签名:pub fn dummy_labels(&self) -> Vec<Atom<'a>>
功能:返回哑指标标签列表。哑指标定义为在所有槽中恰好出现两次的标签(一次 Upper、一次 Lower),即缩并候选。
返回值:Vec<Atom<'a>> — 哑指标标签列表。
示例:
#![allow(unused)] fn main() { use ocas_atom::tensor::{Tensor, IndexSlot, IndexPosition}; use ocas_atom::{AtomArena, Symbol}; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); // A^μ_μ:mu 出现两次(Upper + Lower)→ 哑指标 let t = Tensor::new(Symbol::new("A"), vec![ IndexSlot::new(ctx.var("mu"), IndexPosition::Upper), IndexSlot::new(ctx.var("mu"), IndexPosition::Lower), ]); assert_eq!(t.dummy_labels().len(), 1); // 输出:1 个哑指标 }
Tensor::to_atom
签名:pub fn to_atom(&self, ctx: &'a AtomArena<'a>) -> Atom<'a>
功能:将张量降级为标准 Atom 函数节点 name(slot₁, slot₂, …)。不应用对称化——原子保留 self 的槽顺序。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
ctx | &'a AtomArena<'a> | 表达式 arena |
返回值:Atom<'a> — 函数节点表达式。
示例:
#![allow(unused)] fn main() { use ocas_atom::tensor::{Tensor, IndexSlot, IndexPosition}; use ocas_atom::{AtomArena, Symbol}; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let t = Tensor::new(Symbol::new("g"), vec![ IndexSlot::new(ctx.var("mu"), IndexPosition::Upper), IndexSlot::new(ctx.var("nu"), IndexPosition::Lower), ]); let atom = t.to_atom(&ctx); println!("{}", atom); // g(mu, nu) // 输出:g(mu, nu) }
参见:Atom
contract
签名:
#![allow(unused)] fn main() { pub fn contract<'a>( ctx: &'a AtomArena<'a>, a: &Tensor<'a>, b: &Tensor<'a>, ) -> Contracted<'a> }
功能:通过求和共享哑指标来缩并两个张量。两个具有相同标签但相反位置的槽会缩并。结果保留存活的自由指标,顺序为 (a 的自由槽, b 的自由槽)。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
ctx | &'a AtomArena<'a> | 表达式 arena |
a | &Tensor<'a> | 第一个张量 |
b | &Tensor<'a> | 第二个张量 |
返回值:Contracted<'a>
- 无缩并对时返回
Contracted::Product,包含原始两个张量 - 部分缩并时返回
Contracted::Product,包含一个携带自由槽的新张量 - 完全缩并(无自由槽)时返回
Contracted::Scalar
示例:
#![allow(unused)] fn main() { use ocas_atom::tensor::{Tensor, IndexSlot, IndexPosition, contract, Contracted}; use ocas_atom::{AtomArena, Symbol}; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); // A^μ B_μ → 标量 let a = Tensor::new(Symbol::new("A"), vec![ IndexSlot::new(ctx.var("mu"), IndexPosition::Upper), ]); let b = Tensor::new(Symbol::new("B"), vec![ IndexSlot::new(ctx.var("mu"), IndexPosition::Lower), ]); match contract(&ctx, &a, &b) { Contracted::Scalar(expr) => println!("scalar: {}", expr), Contracted::Product(tp) => { for f in &tp.factors { println!("free: {} (rank {})", f.name().as_str(), f.rank()); } } } // 输出:scalar: A(mu)*B(mu) }
Contracted
签名:
#![allow(unused)] fn main() { #[derive(Debug, Clone, PartialEq, Eq)] pub enum Contracted<'a> { Product(TensorProduct<'a>), Scalar(Atom<'a>), } }
功能:缩并两个张量的结果。当无自由指标存活时为标量表达式;否则为含自由槽的张量积。
变体:
| 变体 | 类型 | 说明 |
|---|---|---|
Product | TensorProduct<'a> | 部分缩并或无缩并,含自由指标的张量积 |
Scalar | Atom<'a> | 完全缩并为标量表达式 |
示例:
#![allow(unused)] fn main() { use ocas_atom::tensor::{Tensor, IndexSlot, IndexPosition, contract, Contracted}; use ocas_atom::{AtomArena, Symbol}; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); // A^μ_ν B^ν_ρ → 部分缩并,剩余 A^μ_ρ let a = Tensor::new(Symbol::new("A"), vec![ IndexSlot::new(ctx.var("mu"), IndexPosition::Upper), IndexSlot::new(ctx.var("nu"), IndexPosition::Lower), ]); let b = Tensor::new(Symbol::new("B"), vec![ IndexSlot::new(ctx.var("nu"), IndexPosition::Upper), IndexSlot::new(ctx.var("rho"), IndexPosition::Lower), ]); match contract(&ctx, &a, &b) { Contracted::Product(tp) => { assert_eq!(tp.factors.len(), 1); assert_eq!(tp.factors[0].rank(), 2); // mu, rho } Contracted::Scalar(_) => panic!("expected partial contraction"), } // 输出:部分缩并,剩余 2 个自由指标 }
TensorProduct
签名:
#![allow(unused)] fn main() { #[derive(Debug, Clone, PartialEq, Eq)] pub struct TensorProduct<'a> { pub factors: Vec<Tensor<'a>>, } }
功能:缩并后保留自由槽的张量积。factors 包含缩并后的存活张量,其槽按 (a 的自由槽, b 的自由槽) 顺序拼接。
字段:
| 字段 | 类型 | 说明 |
|---|---|---|
factors | Vec<Tensor<'a>> | 缩并后的张量因子列表 |
symmetrise_sign
签名:pub fn symmetrise_sign(tensor: &Tensor<'_>) -> i64
功能:对张量的指标槽按标签升序排列,并返回反对称化的符号。
Symmetry::None或Symmetry::Symmetric:返回+1Symmetry::Antisymmetric:返回排列奇偶性(偶排列+1,奇排列-1)
注意:这不是置换群下的完全规范化(那需要图同构);仅为对称张量提供稳定的等价比较顺序。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
tensor | &Tensor<'_> | 输入张量 |
返回值:i64 — +1 或 -1。
示例:
#![allow(unused)] fn main() { use ocas_atom::tensor::{Tensor, IndexSlot, IndexPosition, Symmetry, symmetrise_sign}; use ocas_atom::{AtomArena, Symbol}; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let slots = vec![ IndexSlot::new(ctx.var("a"), IndexPosition::Upper), IndexSlot::new(ctx.var("b"), IndexPosition::Upper), ]; let anti = Tensor::new(Symbol::new("epsilon"), slots) .with_symmetry(Symmetry::Antisymmetric); let sign = symmetrise_sign(&anti); // 若 a < b 则排列不变,sign = +1 // 输出:+1 }
SymmetrySpec
签名:
#![allow(unused)] fn main() { #[derive(Debug, Clone, Default)] pub struct SymmetrySpec { pub symmetric_subsets: Vec<Vec<usize>>, pub antisymmetric_subsets: Vec<Vec<usize>>, pub cyclic: Option<Vec<usize>>, } }
功能:张量的细粒度对称性规格,用于规范化引擎。比 Symmetry 枚举更灵活——可以为不同的槽位子集指定不同的对称行为。
字段:
| 字段 | 类型 | 说明 |
|---|---|---|
symmetric_subsets | Vec<Vec<usize>> | 对称的槽位子集列表。同一子集内的槽位可互换 |
antisymmetric_subsets | Vec<Vec<usize>> | 反对称的槽位子集列表。交换子集内两个槽位翻转符号 |
cyclic | Option<Vec<usize>> | 循环置换的槽位子集 |
SymmetrySpec::none
签名:pub fn none() -> Self
功能:无对称性——所有槽位独立。
返回值:SymmetrySpec
示例:
#![allow(unused)] fn main() { use ocas_atom::tensor::spec::SymmetrySpec; let spec = SymmetrySpec::none(); assert!(spec.symmetric_subsets.is_empty()); assert!(spec.antisymmetric_subsets.is_empty()); assert!(spec.cyclic.is_none()); // 输出:所有断言通过 }
SymmetrySpec::fully_symmetric
签名:pub fn fully_symmetric(rank: usize) -> Self
功能:所有槽位完全对称。创建一个包含全部 0..rank 槽位的对称子集。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
rank | usize | 张量阶数(槽位总数) |
返回值:SymmetrySpec
示例:
#![allow(unused)] fn main() { use ocas_atom::tensor::spec::SymmetrySpec; let spec = SymmetrySpec::fully_symmetric(3); assert_eq!(spec.symmetric_subsets, vec![vec![0, 1, 2]]); // 输出:对称子集 = [[0, 1, 2]] }
SymmetrySpec::fully_antisymmetric
签名:pub fn fully_antisymmetric(rank: usize) -> Self
功能:所有槽位完全反对称。创建一个包含全部 0..rank 槽位的反对称子集。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
rank | usize | 张量阶数(槽位总数) |
返回值:SymmetrySpec
示例:
#![allow(unused)] fn main() { use ocas_atom::tensor::spec::SymmetrySpec; let spec = SymmetrySpec::fully_antisymmetric(4); assert_eq!(spec.antisymmetric_subsets, vec![vec![0, 1, 2, 3]]); // 输出:反对称子集 = [[0, 1, 2, 3]] }
SymmetrySpec::is_slot_hidden
签名:pub fn is_slot_hidden(&self, pos: usize) -> bool
功能:检查给定槽位位置是否应在图编码中"隐藏"(即不参与规范化比较)。对称槽位和循环槽位被隐藏。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
pos | usize | 槽位位置索引 |
返回值:bool — true 表示该槽位隐藏。
示例:
#![allow(unused)] fn main() { use ocas_atom::tensor::spec::SymmetrySpec; let spec = SymmetrySpec::fully_symmetric(2); assert!(spec.is_slot_hidden(0)); // 在对称子集中 assert!(spec.is_slot_hidden(1)); // 在对称子集中 let none = SymmetrySpec::none(); assert!(!none.is_slot_hidden(0)); // 不在任何子集中 // 输出:所有断言通过 }
TensorRegistry
签名:
#![allow(unused)] fn main() { #[derive(Debug, Clone, Default)] pub struct TensorRegistry { /* private fields */ } }
功能:张量规范化的完整注册表。记录哪些函数头是张量以及它们的对称性规格,同时管理指标维度组分配(防止跨维度的哑指标重命名冲突)。
TensorRegistry::new
签名:pub fn new() -> Self
功能:创建空注册表。
返回值:TensorRegistry
TensorRegistry::register
签名:pub fn register(&mut self, name: Symbol, spec: SymmetrySpec)
功能:注册一个张量名称及其对称性规格。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
name | Symbol | 张量函数头名称 |
spec | SymmetrySpec | 对称性规格 |
示例:
#![allow(unused)] fn main() { use ocas_atom::tensor::spec::{SymmetrySpec, TensorRegistry}; use ocas_atom::Symbol; let mut reg = TensorRegistry::new(); reg.register(Symbol::new("g"), SymmetrySpec::fully_symmetric(2)); reg.register(Symbol::new("Riemann"), SymmetrySpec::none()); reg.register(Symbol::new("epsilon"), SymmetrySpec::fully_antisymmetric(4)); // 输出:注册了 3 个张量 }
TensorRegistry::set_index_group
签名:pub fn set_index_group(&mut self, label: Symbol, group: u64)
功能:设置指标标签的维度组标识符。不同组的哑指标不会被重命名为相同的规范名,从而避免跨维度冲突。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
label | Symbol | 指标标签(如 "mu"、"i") |
group | u64 | 组标识符。0 表示默认/未分组 |
示例:
#![allow(unused)] fn main() { use ocas_atom::tensor::spec::TensorRegistry; use ocas_atom::Symbol; let mut reg = TensorRegistry::new(); reg.set_index_group(Symbol::new("mu"), 1); // 时空指标 reg.set_index_group(Symbol::new("i"), 2); // 内部指标 assert_eq!(reg.index_group(Symbol::new("mu")), 1); assert_eq!(reg.index_group(Symbol::new("i")), 2); assert_eq!(reg.index_group(Symbol::new("other")), 0); // 默认组 // 输出:所有断言通过 }
TensorRegistry::spec
签名:pub fn spec(&self, name: Symbol) -> Option<&SymmetrySpec>
功能:查找已注册张量的对称性规格。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
name | Symbol | 张量名称 |
返回值:Option<&SymmetrySpec> — 未注册时返回 None。
TensorRegistry::index_group
签名:pub fn index_group(&self, label: Symbol) -> u64
功能:查找指标标签的维度组。未设置的标签返回 0。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
label | Symbol | 指标标签 |
返回值:u64 — 组标识符。
参见:SymmetrySpec
canonicalize_tensors
签名:
#![allow(unused)] fn main() { pub fn canonicalize_tensors<'a>( ctx: &'a AtomArena<'a>, expr: Atom<'a>, registry: &TensorRegistry, ) -> Result<CanonicalTensor<'a>, TensorCanonError> }
功能:将张量表达式规范化为与指标命名无关的唯一规范形。内部将表达式编码为图(张量头→头顶点、指标槽→槽顶点、缩并→边),运行 McKay 细化-个体化算法计算规范标号,然后重建表达式并重命名哑指标。
对于和表达式(Add),逐项规范化并验证所有项具有相同的自由指标集。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
ctx | &'a AtomArena<'a> | 表达式 arena |
expr | Atom<'a> | 待规范化的张量表达式(Mul of Fun 节点) |
registry | &TensorRegistry | 张量注册表(对称性规格 + 指标组) |
返回值:Result<CanonicalTensor<'a>, TensorCanonError>
错误:
| 错误变体 | 说明 |
|---|---|
ContractedMoreThanOnce(Symbol) | 某个指标标签被缩并超过两次 |
BadContraction(Symbol) | 两个相同标签的槽具有相同位置(不是上/下对) |
NotATensor(Symbol) | 表达式中出现未在注册表中注册的函数头 |
InconsistentOpenIndices | 和表达式中各项的自由指标不一致 |
UnsupportedPower | 表达式中出现不支持的幂次节点 |
示例:
#![allow(unused)] fn main() { use ocas_atom::tensor::spec::{SymmetrySpec, TensorRegistry}; use ocas_atom::tensor::canon::canonicalize_tensors; use ocas_atom::{AtomArena, Symbol}; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let mut reg = TensorRegistry::new(); reg.register(Symbol::new("T"), SymmetrySpec::none()); // T(i, j) 和 T(j, i) 是不同的(无对称性) let t_ij = ctx.fun("T", &[ctx.var("i"), ctx.var("j")]); let t_ji = ctx.fun("T", &[ctx.var("j"), ctx.var("i")]); let ct1 = canonicalize_tensors(&ctx, t_ij, ®).unwrap(); let ct2 = canonicalize_tensors(&ctx, t_ji, ®).unwrap(); // 无对称性时,T(i,j) ≠ T(j,i) // 注意:无对称性时 T(i,j) 与 T(j,i) 的规范形不同 }
参见:CanonicalTensor、TensorCanonError、TensorRegistry
CanonicalTensor
签名:
#![allow(unused)] fn main() { #[derive(Debug, Clone)] pub struct CanonicalTensor<'a> { pub canonical_form: Atom<'a>, pub external_indices: Vec<Atom<'a>>, pub dummy_indices: Vec<Atom<'a>>, } }
功能:张量规范化的结果。
字段:
| 字段 | 类型 | 说明 |
|---|---|---|
canonical_form | Atom<'a> | 规范化后的表达式(哑指标已重命名) |
external_indices | Vec<Atom<'a>> | 自由(外部)指标列表 |
dummy_indices | Vec<Atom<'a>> | 哑指标列表(已重命名为 d0、d1、…) |
示例:
#![allow(unused)] fn main() { use ocas_atom::tensor::spec::{SymmetrySpec, TensorRegistry}; use ocas_atom::tensor::canon::canonicalize_tensors; use ocas_atom::{AtomArena, Symbol}; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let mut reg = TensorRegistry::new(); reg.register(Symbol::new("T"), SymmetrySpec::none()); reg.register(Symbol::new("U"), SymmetrySpec::none()); let prod = ctx.mul(&[ctx.fun("T", &[ctx.var("i"), ctx.var("j")]), ctx.fun("U", &[ctx.var("j"), ctx.var("k")])]); let ct = canonicalize_tensors(&ctx, prod, ®).unwrap(); println!("规范形: {}", ct.canonical_form); println!("外部指标: {:?}", ct.external_indices); println!("哑指标: {:?}", ct.dummy_indices); // 输出:规范形含重命名后的哑指标,外部指标为 i, k }
TensorCanonError
签名:
#![allow(unused)] fn main() { #[derive(Debug, Clone, PartialEq, Eq)] pub enum TensorCanonError { ContractedMoreThanOnce(Symbol), BadContraction(Symbol), NotATensor(Symbol), InconsistentOpenIndices, UnsupportedPower, } }
功能:张量规范化过程中可能产生的错误。
变体:
| 变体 | 说明 |
|---|---|
ContractedMoreThanOnce(Symbol) | 指标标签被缩并超过两次(出现 3 次以上) |
BadContraction(Symbol) | 两个相同标签的槽具有相同方差(不是有效的上/下对) |
NotATensor(Symbol) | 出现未在 TensorRegistry 中注册的函数头 |
InconsistentOpenIndices | 和表达式中各项的自由指标集不一致 |
UnsupportedPower | 表达式中出现不支持的幂次节点 |
refresh_dummies
签名:
#![allow(unused)] fn main() { pub fn refresh_dummies<'a>( ctx: &'a AtomArena<'a>, expr: Atom<'a>, registry: &TensorRegistry, ) -> Result<Atom<'a>, DummyError> }
功能:重命名张量表达式中的哑指标以避免与外部(自由)指标冲突。恰好出现两次的标签(一次上标、一次下标)被替换为按维度组分配的新名称(d0、d1、…)。外部指标保持不变。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
ctx | &'a AtomArena<'a> | 表达式 arena |
expr | Atom<'a> | 待处理的张量表达式 |
registry | &TensorRegistry | 张量注册表(用于维度组分配) |
返回值:Result<Atom<'a>, DummyError>
错误:
| 错误变体 | 说明 |
|---|---|
OverContracted(Symbol) | 某个指标标签出现超过两次 |
BadContraction(Symbol) | 两个相同标签的槽具有相同方差 |
示例:
#![allow(unused)] fn main() { use ocas_atom::tensor::spec::{SymmetrySpec, TensorRegistry}; use ocas_atom::tensor::dummy::refresh_dummies; use ocas_atom::{AtomArena, Symbol}; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let mut reg = TensorRegistry::new(); reg.register(Symbol::new("T"), SymmetrySpec::none()); reg.register(Symbol::new("U"), SymmetrySpec::none()); // T(i,j) * U(j,i):i 和 j 都恰好出现两次 → 哑指标 let expr = ctx.mul(&[ctx.fun("T", &[ctx.var("i"), ctx.var("j")]), ctx.fun("U", &[ctx.var("j"), ctx.var("i")])]); let refreshed = refresh_dummies(&ctx, expr, ®).unwrap(); println!("{}", refreshed); // 输出:哑指标被重命名为 d0, d1(按维度组分配) }
DummyError
签名:
#![allow(unused)] fn main() { #[derive(Debug, Clone, PartialEq, Eq)] pub enum DummyError { OverContracted(Symbol), BadContraction(Symbol), } }
功能:哑指标操作的错误类型。
变体:
| 变体 | 说明 |
|---|---|
OverContracted(Symbol) | 指标标签出现超过两次(不是合法的缩并候选) |
BadContraction(Symbol) | 两个相同标签的槽具有相同方差(不是上/下对) |
YoungTableau
签名:
#![allow(unused)] fn main() { #[derive(Debug, Clone)] pub struct YoungTableau { pub row_lengths: Vec<usize>, } }
功能:Young 盘,由行长度列表定义形状(如 [2, 1] 表示 □□/□)。Young 投影子实现经典对称子 $c_\lambda = a_\lambda \cdot b_\lambda$:对行内置换 $\sigma \in R$ 与列内置换 $\tau \in C$ 的所有组合求和,每项带列置换奇偶符号 $\operatorname{sgn}(\tau)$。
这是显式展开(非 BSGS 群论实现):结果是 $\prod r_i! \cdot \prod c_j!$ 个项的和(行、列阶乘之积),每项符号 ±1,不做 Hook 长度归一化。
字段:
| 字段 | 类型 | 说明 |
|---|---|---|
row_lengths | Vec<usize> | 每行的格子数 |
YoungTableau::new
签名:pub fn new(row_lengths: Vec<usize>) -> Self
功能:从行长度创建 Young 盘。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
row_lengths | Vec<usize> | 每行的格子数列表。总格子数应等于张量阶数 |
返回值:YoungTableau
示例:
#![allow(unused)] fn main() { use ocas_atom::tensor::young::YoungTableau; // [2, 1] = □□/□(3 格) let tab = YoungTableau::new(vec![2, 1]); assert_eq!(tab.total_boxes(), 3); // [1, 1, 1] = 全反对称 let anti = YoungTableau::new(vec![1, 1, 1]); assert_eq!(anti.total_boxes(), 3); // [3] = 全对称 let sym = YoungTableau::new(vec![3]); assert_eq!(sym.total_boxes(), 3); // 输出:所有断言通过 }
YoungTableau::total_boxes
签名:pub fn total_boxes(&self) -> usize
功能:返回盘中总格子数(等于张量阶数)。
返回值:usize
young_project
签名:
#![allow(unused)] fn main() { pub fn young_project<'a>( ctx: &'a AtomArena<'a>, tensor_expr: Atom<'a>, tableau: &YoungTableau, ) -> Atom<'a> }
功能:对张量表达式应用 Young 投影子。将 $T(i_1, i_2, \dots, i_n)$ 展开为置换和:
$$\sum_\sigma \text{sign}(\sigma) \cdot T(i_{\sigma(1)}, i_{\sigma(2)}, \dots, i_{\sigma(n)})$$
当前实现不做 Hook 长度积归一化——每项系数仅为 $\pm 1$。对于全反对称盘 [1, 1, …, 1],得到标准交替和。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
ctx | &'a AtomArena<'a> | 表达式 arena |
tensor_expr | Atom<'a> | 张量表达式(Fun 节点) |
tableau | &YoungTableau | Young 盘形状 |
返回值:Atom<'a> — 投影后的表达式(Add 节点,各项带符号)。
示例:
#![allow(unused)] fn main() { use ocas_atom::tensor::young::{YoungTableau, young_project}; use ocas_atom::AtomArena; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); // 全反对称 [1, 1]:f(a, b) → f(a, b) - f(b, a) let f_ab = ctx.fun("f", &[ctx.var("a"), ctx.var("b")]); let result = young_project(&ctx, f_ab, &YoungTableau::new(vec![1, 1])); println!("{}", result); // 输出:f(a,b) - f(b,a) // 全对称 [2]:g(a, b) → g(a, b) + g(b, a) let g_ab = ctx.fun("g", &[ctx.var("a"), ctx.var("b")]); let result = young_project(&ctx, g_ab, &YoungTableau::new(vec![2])); println!("{}", result); // 输出:g(a,b) + g(b,a) // 全反对称 [1, 1, 1]:h(a, b, c) → 6 项交错和(系数 ±1,未归一化) let h = ctx.fun("h", &[ctx.var("a"), ctx.var("b"), ctx.var("c")]); let result = young_project(&ctx, h, &YoungTableau::new(vec![1, 1, 1])); println!("{}", result); // 输出:h(a,b,c) - h(b,a,c) - ...(6 项,各项系数 ±1) }
设计不变量
Atom 是 Copy 的 arena 句柄
Atom<'a> 是指向 arena 中节点的 Copy 句柄。IndexSlot<'a> 也是 Copy。这意味着张量可以廉价复制和比较——结构相等等价于指针相等(hash-consing)。
显式指标匹配
oCAS 的张量代数使用显式指标匹配而非爱因斯坦求和约定。缩并必须手动调用 contract,指标按标签和位置(上/下)配对。
对称性是建议性的
Symmetry 枚举和 SymmetrySpec 是元数据,不会被 contract 等基础运算自动使用。完全的对称性处理需要:
- 规范化(
canonicalize_tensors)—— 图同构级别的对称性强制 - Young 投影(
young_project)—— 显式置换展开
图同构引擎
0.22.0 的规范化基于 McKay 细化-个体化图同构算法(实现在 tensor::graph 模块)。该引擎是一个独立的 nauty 实现,提供:
- 1-WL 颜色细化(邻居签名 + 边数据 + 方向)
- 公平划分 + 个体化-细化 DFS
- 路径不变量剪枝 + 自同构轨道
- 规范形 = 字典序最大证书
完整示例
use ocas_atom::tensor::{ IndexPosition, IndexSlot, Symmetry, Tensor, contract, Contracted, symmetrise_sign, }; use ocas_atom::tensor::spec::{SymmetrySpec, TensorRegistry}; use ocas_atom::tensor::canon::canonicalize_tensors; use ocas_atom::tensor::dummy::refresh_dummies; use ocas_atom::tensor::young::{YoungTableau, young_project}; use ocas_atom::{AtomArena, Symbol}; use ocas_core::arena::Arena; fn main() { let arena = Arena::new(); let ctx = AtomArena::new(&arena); // 1. 创建带对称性的度规张量 g_μν let g = Tensor::new(Symbol::new("g"), vec![ IndexSlot::new(ctx.var("mu"), IndexPosition::Lower), IndexSlot::new(ctx.var("nu"), IndexPosition::Lower), ]).with_symmetry(Symmetry::Symmetric); println!("g rank = {}, symmetry = {:?}", g.rank(), g.symmetry()); // 输出:g rank = 2, symmetry = Symmetric // 2. 缩并 g^μν A_ν → 部分缩并 let g_inv = Tensor::new(Symbol::new("g"), vec![ IndexSlot::new(ctx.var("mu"), IndexPosition::Upper), IndexSlot::new(ctx.var("nu"), IndexPosition::Upper), ]); let a = Tensor::new(Symbol::new("A"), vec![ IndexSlot::new(ctx.var("nu"), IndexPosition::Lower), ]); match contract(&ctx, &g_inv, &a) { Contracted::Product(tp) => { println!("缩并后自由指标数: {}", tp.factors[0].rank()); // 输出:缩并后自由指标数: 1 (mu) } _ => {} } // 3. 规范化:T(i,j)*U(j,k) 中 j 被重命名 let mut reg = TensorRegistry::new(); reg.register(Symbol::new("T"), SymmetrySpec::none()); reg.register(Symbol::new("U"), SymmetrySpec::none()); let prod = ctx.mul(&[ ctx.fun("T", &[ctx.var("i"), ctx.var("j")]), ctx.fun("U", &[ctx.var("j"), ctx.var("k")]), ]); let ct = canonicalize_tensors(&ctx, prod, ®).unwrap(); println!("规范形: {}", ct.canonical_form); // 输出:规范形含 d0, d1 等哑指标 // 4. Young 投影:全反对称化 f(a, b) let f = ctx.fun("f", &[ctx.var("a"), ctx.var("b")]); let anti = young_project(&ctx, f, &YoungTableau::new(vec![1, 1])); println!("反对称化: {}", anti); // 输出:f(a,b) - f(b,a) }
参见
Rust API 参考:数论
本章记录 oCAS 数论模块 ocas_domain::number_theory 的完整 API。该模块提供素性检测、整数分解、中国剩余定理、离散对数、二次剩余符号和积性数论函数,是多项式因式分解(Berlekamp、Cantor–Zassenhaus、Hensel 提升)、有理重构和模 GCD 算法的基础设施。
模块路径:ocas_domain::number_theory
子模块:
| 子模块 | 功能 |
|---|---|
primes | 素性检测、素数生成、模逆、扩展 Euclid、二次剩余 |
factor | 整数完全分解(试除、Pollard rho/p−1/p+1、ECM) |
crt | 多模中国剩余定理 |
dlog | 离散对数(BSGS、Pohlig–Hellman) |
functions | 积性数论函数(φ、μ、τ、σ_k、λ) |
导入方式:
#![allow(unused)] fn main() { use ocas_domain::number_theory::{ is_prime, next_prime, primes_from, mod_inv, extended_gcd, symmetric_mod, crt, legendre, jacobi, mod_sqrt, factor::factor_integer, functions::{euler_phi, moebius_mu, divisor_tau, divisor_sigma, liouville_lambda}, }; // 或从子模块精确导入 use ocas_domain::number_theory::primes::is_prime_bpsw; use ocas_domain::number_theory::primes::is_prime_u64; use ocas_domain::number_theory::crt::crt_many; use ocas_domain::number_theory::dlog::{dlog_bsgs, dlog_pohlig_hellman}; }
素性检测
is_prime
签名:pub fn is_prime(n: &Integer) -> bool
功能:判断 n 是否为素数。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
n | &Integer | 待测整数 |
返回值:bool——true 表示 n 是(强)拟素数或确定素数。
实现细节:
- 对 $n < 3.317 \times 10^{24}$,使用固定 12 个 Miller–Rabin 证人 ${2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37}$ 的确定性判定。
- 对更大的
n,退化为强拟素数测试(复合数通过概率极低)。 - 显式处理 $n \leq 3$ 和偶数情况。
示例:
#![allow(unused)] fn main() { use ocas_domain::Integer; use ocas_domain::number_theory::is_prime; assert!(is_prime(&Integer::from(97))); assert!(!is_prime(&Integer::from(561))); // Carmichael 数,不是素数 assert!(is_prime(&Integer::from(2_147_483_647_i64))); // Mersenne 素数 M31 }
参见:is_prime_bpsw、is_prime_u64、next_prime
is_prime_bpsw
签名:pub fn is_prime_bpsw(n: &Integer) -> bool
功能:BPSW 拟素数判定——base-2 强 Miller–Rabin + 强 Lucas 拟素数测试(Selfridge 参数选择)。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
n | &Integer | 待测整数 |
返回值:bool——true 表示 n 通过 BPSW 测试。
实现细节:
- 先做 base-2 强 Miller–Rabin(与
is_prime共享mr_witness内核)。 - 再做强 Lucas 测试:Selfridge 参数选择——从序列 $5, -7, 9, -11, \ldots$ 中取首个满足 $\text{jacobi}(D, n) = -1$ 的 $D$,令 $P = 1$,$Q = (1 - D)/4$。
- 写 $n + 1 = d \cdot 2^r$($d$ 奇),二进制 ladder 计算 Lucas 序列 $(U_k, V_k)$。$n$ 通过当 $U_d \equiv 0$ 或 $V_{d \cdot 2^i} \equiv 0 \pmod{n}$ 对某个 $0 \leq i < r$。
已知性质:目前没有任何复合数通过 BPSW 测试(截至 2026 年)。对 $n < 3.317 \times 10^{24}$ 结果是确定性的。
示例:
#![allow(unused)] fn main() { use ocas_domain::Integer; use ocas_domain::number_theory::primes::is_prime_bpsw; assert!(is_prime_bpsw(&Integer::from(97))); assert!(!is_prime_bpsw(&Integer::from(561))); // Carmichael 数 assert!(!is_prime_bpsw(&Integer::from(2047))); // base-2 强伪素数,但非素数 }
参见:is_prime、factor_integer(内部使用 BPSW 作为最终素性判定)
is_prime_u64
签名:pub fn is_prime_u64(n: u64) -> bool
功能:对 u64 范围内的整数做确定性素性检测。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
n | u64 | 待测整数 |
返回值:bool。
实现细节:内部将 n 转换为 Integer 后调用 is_prime。12 个 Miller–Rabin 证人覆盖整个 u64 范围($< 3.317 \times 10^{24}$),因此结果是确定性的。
示例:
#![allow(unused)] fn main() { use ocas_domain::number_theory::primes::is_prime_u64; assert!(is_prime_u64(97)); assert!(!is_prime_u64(561)); assert!(is_prime_u64(u64::MAX - 58)); // 2^64 − 59,最大的 u64 素数 }
参见:is_prime
素数生成
next_prime
签名:pub fn next_prime(n: &Integer) -> Integer
功能:返回严格大于 n 的最小素数。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
n | &Integer | 起始整数 |
返回值:Integer——大于 n 的最小素数。
实现细节:从 $n + 1$ 开始(若 $n < 2$ 则从 2 开始),依次测试奇数候选。
示例:
#![allow(unused)] fn main() { use ocas_domain::Integer; use ocas_domain::number_theory::next_prime; assert_eq!(next_prime(&Integer::from(10)), Integer::from(11)); assert_eq!(next_prime(&Integer::from(13)), Integer::from(17)); assert_eq!(next_prime(&Integer::from(0)), Integer::from(2)); }
primes_from
签名:pub fn primes_from(n: &Integer) -> PrimesFrom
功能:创建一个从 n 之后开始的连续素数迭代器。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
n | &Integer | 起始整数 |
返回值:PrimesFrom——实现 Iterator<Item = Integer> 的迭代器,依次产出严格大于 n 的素数。
使用场景:在 Hensel 提升中扫描素数(寻找不整除首项系数且保持 $f \bmod p$ 无平方的素数)。
示例:
#![allow(unused)] fn main() { use ocas_domain::Integer; use ocas_domain::number_theory::primes_from; let mut it = primes_from(&Integer::from(100)); assert_eq!(it.next().unwrap().to_string(), "101"); assert_eq!(it.next().unwrap().to_string(), "103"); }
参见:next_prime
模运算
mod_inv
签名:pub fn mod_inv(a: &Integer, m: &Integer) -> Option<Integer>
功能:计算 a 模 m 的乘法逆元,即满足 $a \cdot x \equiv 1 \pmod{m}$ 的 x。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
a | &Integer | 被求逆元素 |
m | &Integer | 模数 |
返回值:Option<Integer>——Some(x) 且 $0 \leq x < m$,或 None。
错误条件:当 $\gcd(a, m) \neq 1$ 或 $m \leq 1$ 时返回 None。
示例:
#![allow(unused)] fn main() { use ocas_domain::Integer; use ocas_domain::number_theory::mod_inv; assert_eq!(mod_inv(&Integer::from(3), &Integer::from(11)), Some(Integer::from(4))); // 3 × 4 = 12 ≡ 1 (mod 11) assert_eq!(mod_inv(&Integer::from(2), &Integer::from(4)), None); // gcd(2, 4) = 2 ≠ 1,逆元不存在 }
参见:extended_gcd
extended_gcd
签名:pub fn extended_gcd(a: &Integer, b: &Integer) -> (Integer, Integer, Integer)
功能:扩展 Euclid 算法——计算 $g = \gcd(a, b)$ 以及 Bézout 系数 $x, y$ 使得 $g = a \cdot x + b \cdot y$。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
a | &Integer | 第一个整数 |
b | &Integer | 第二个整数 |
返回值:(g, x, y)——g 非负,满足 $g = a \cdot x + b \cdot y$。
示例:
#![allow(unused)] fn main() { use ocas_domain::Integer; use ocas_domain::number_theory::extended_gcd; let (g, x, y) = extended_gcd(&Integer::from(240), &Integer::from(46)); assert_eq!(g, Integer::from(2)); // 验证 Bézout 等式:240·x + 46·y = 2 assert_eq!(&x * &Integer::from(240) + &y * &Integer::from(46), g); }
参见:mod_inv
symmetric_mod
签名:pub fn symmetric_mod(a: &Integer, m: &Integer) -> Integer
功能:将 a 约化到对称区间 $(-m/2, m/2]$。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
a | &Integer | 被约化整数 |
m | &Integer | 模数(正数) |
返回值:Integer——在 $(-m/2, m/2]$ 内的代表元。
使用场景:Hensel 提升中从模表示恢复整数系数时使用。
示例:
#![allow(unused)] fn main() { use ocas_domain::Integer; use ocas_domain::number_theory::symmetric_mod; // mod 7,区间 (-3.5, 3.5] assert_eq!(symmetric_mod(&Integer::from(3), &Integer::from(7)), Integer::from(3)); assert_eq!(symmetric_mod(&Integer::from(5), &Integer::from(7)), Integer::from(-2)); assert_eq!(symmetric_mod(&Integer::from(6), &Integer::from(7)), Integer::from(-1)); }
参见:crt
中国剩余定理
crt
签名:pub fn crt(r1: &Integer, m1: &Integer, r2: &Integer, m2: &Integer) -> Option<(Integer, Integer)>
功能:合并两个同余方程 $x \equiv r_1 \pmod{m_1}$,$x \equiv r_2 \pmod{m_2}$。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
r1 | &Integer | 第一个余数 |
m1 | &Integer | 第一个模数 |
r2 | &Integer | 第二个余数 |
m2 | &Integer | 第二个模数 |
返回值:Option<(Integer, Integer)>——Some((r, m)) 其中 $m = \operatorname{lcm}(m_1, m_2)$,$0 \leq r < m$,$r \equiv r_1 \pmod{m_1}$,$r \equiv r_2 \pmod{m_2}$。
错误条件:当系统不相容($r_1 - r_2$ 不被 $\gcd(m_1, m_2)$ 整除)时返回 None。模数无需互素。
示例:
#![allow(unused)] fn main() { use ocas_domain::Integer; use ocas_domain::number_theory::crt; // x ≡ 2 (mod 3), x ≡ 3 (mod 5) => x ≡ 8 (mod 15) let (r, m) = crt(&Integer::from(2), &Integer::from(3), &Integer::from(3), &Integer::from(5)).unwrap(); assert_eq!(r, Integer::from(8)); assert_eq!(m, Integer::from(15)); }
参见:crt_many
crt_many
签名:pub fn crt_many(congruences: &[(Integer, Integer)]) -> Option<(Integer, Integer)>
功能:合并多个同余方程 $x \equiv r_i \pmod{m_i}$。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
congruences | &[(Integer, Integer)] | 同余方程列表,每个元素为 (r_i, m_i) |
返回值:Option<(Integer, Integer)>——Some((R, M)) 其中 $M = \operatorname{lcm}(m_1, \ldots, m_k)$,$0 \leq R < M$。
错误条件:
- 空列表返回
None。 - 任意一对不相容时返回
None。
实现细节:逐对折叠调用 crt,模数无需两两互素。
示例:
#![allow(unused)] fn main() { use ocas_domain::Integer; use ocas_domain::number_theory::crt::crt_many; // 孙子算经:x ≡ 2 (mod 3), x ≡ 3 (mod 5), x ≡ 2 (mod 7) let cs = [ (Integer::from(2), Integer::from(3)), (Integer::from(3), Integer::from(5)), (Integer::from(2), Integer::from(7)), ]; let (r, m) = crt_many(&cs).unwrap(); assert_eq!(r, Integer::from(23)); assert_eq!(m, Integer::from(105)); // 3 × 5 × 7 = 105 }
参见:crt、dlog_pohlig_hellman(内部使用 CRT 合并部分结果)
二次剩余
legendre
签名:pub fn legendre(a: &Integer, p: &Integer) -> i8
功能:计算 Legendre 符号 $\left(\frac{a}{p}\right)$。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
a | &Integer | 被判别整数 |
p | &Integer | 奇素数(调用者保证素性) |
返回值:
| 值 | 含义 |
|---|---|
1 | $a$ 是模 $p$ 的二次剩余(QR) |
-1 | $a$ 是模 $p$ 的二次非剩余(QNR) |
0 | $p \mid a$ |
注意:p 的素性由调用者保证;函数内部等价于 jacobi(a, p)。
示例:
#![allow(unused)] fn main() { use ocas_domain::Integer; use ocas_domain::number_theory::legendre; assert_eq!(legendre(&Integer::from(2), &Integer::from(7)), 1); // 2 是 mod 7 的 QR assert_eq!(legendre(&Integer::from(3), &Integer::from(7)), -1); // 3 是 mod 7 的 QNR }
jacobi
签名:pub fn jacobi(a: &Integer, n: &Integer) -> i8
功能:计算 Jacobi 符号 $\left(\frac{a}{n}\right)$——Legendre 符号的推广,对任意正奇数 n 定义。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
a | &Integer | 被判别整数 |
n | &Integer | 正奇数 |
返回值:i8——0、1 或 -1。
实现细节:使用二次互反律计算,包括 2-adic 剥离和 mod-8/mod-4 符号规则。
注意:若 n 为偶数或非正,结果未定义(函数返回 0)。
示例:
#![allow(unused)] fn main() { use ocas_domain::Integer; use ocas_domain::number_theory::jacobi; assert_eq!(jacobi(&Integer::from(2), &Integer::from(15)), 1); assert_eq!(jacobi(&Integer::from(7), &Integer::from(15)), -1); }
参见:legendre
mod_sqrt
签名:pub fn mod_sqrt(a: &Integer, p: &Integer) -> Option<Integer>
功能:计算 $a$ 模奇素数 $p$ 的平方根,即满足 $x^2 \equiv a \pmod{p}$ 的 $x$。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
a | &Integer | 被开方数 |
p | &Integer | 奇素数 |
返回值:Option<Integer>——Some(x) 且 $0 \leq x < p$(另一个根为 $p - x$)。
错误条件:当 $p \leq 2$ 或 $a$ 是二次非剩余(legendre(a, p) = -1)时返回 None。p 的素性由调用者保证——函数内部不检查素性,对奇合数 p 行为未定义。
实现细节:
- 当 $p \equiv 3 \pmod{4}$ 时使用快速路径 $x = a^{(p+1)/4} \bmod p$。
- 其他情况使用完整 Tonelli–Shanks 算法:找非剩余 $z$,维护不变量 $c, t, r, m$。
示例:
#![allow(unused)] fn main() { use ocas_domain::Integer; use ocas_domain::number_theory::mod_sqrt; // 2 是 mod 7 的 QR:根为 3 和 4(3² = 9 ≡ 2, 4² = 16 ≡ 2 mod 7) let r = mod_sqrt(&Integer::from(2), &Integer::from(7)).unwrap(); assert!(r == Integer::from(3) || r == Integer::from(4)); }
参见:legendre
整数分解
factor_integer
签名:pub fn factor_integer(n: &Integer) -> Vec<(Integer, u32)>
功能:将 $|n|$ 完全分解为素因子,返回按升序排列的 (素数, 指数) 对。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
n | &Integer | 待分解整数 |
返回值:Vec<(Integer, u32)>——素因子列表,按素数升序排列。$n \in {0, \pm 1}$ 返回空列表。
分解策略:
- 试除(
factor_trial):移除 $\leq 1000$ 的小因子。 - 对剩余的复合余因子,使用递增策略逐级尝试:
- Pollard rho–Brent 变体(批量 gcd + 回溯)
- Pollard $p-1$ stage 1(光滑性界倍增 $\times 4$)
- Williams $p+1$ stage 1(Lucas $V$ 序列,随机 $P$ 使 $\text{jacobi}(P^2-4, n) = -1$)
- ECM Lenstra(Suyama 参数化,Montgomery 曲线射影坐标,曲线预算 $\approx B_1/550$)
- 每个叶子通过 BPSW 素性判定。
示例:
#![allow(unused)] fn main() { use ocas_domain::Integer; use ocas_domain::number_theory::factor::factor_integer; let f = factor_integer(&Integer::from(2 * 2 * 3 * 5 * 101 * 1000003)); assert_eq!(f, vec![ (Integer::from(2), 2), (Integer::from(3), 1), (Integer::from(5), 1), (Integer::from(101), 1), (Integer::from(1000003), 1), ]); // 空列表 assert!(factor_integer(&Integer::from(0)).is_empty()); assert!(factor_integer(&Integer::from(1)).is_empty()); }
参见:is_prime_bpsw、euler_phi、moebius_mu
factor_integer_with_rng
签名:pub fn factor_integer_with_rng(n: &Integer, rng: &mut Xoshiro256PlusPlus) -> Vec<(Integer, u32)>
功能:与 factor_integer 相同,但接受显式 RNG 以实现可重复的分解结果。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
n | &Integer | 待分解整数 |
rng | &mut Xoshiro256PlusPlus | 随机数生成器(rand_xoshiro crate) |
返回值:Vec<(Integer, u32)>。
使用场景:测试中需要确定性结果时使用。
factor_trial
签名:pub fn factor_trial(n: &Integer, limit: u64) -> (Vec<(Integer, u32)>, Integer)
功能:试除法移除 $\leq \text{limit}$ 的素因子。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
n | &Integer | 待分解整数 |
limit | u64 | 试除上界 |
返回值:(factors, cofactor)——factors 为已找到的因子列表,cofactor 无 $\leq \text{limit}$ 的素因子(但未必是素数)。
示例:
#![allow(unused)] fn main() { use ocas_domain::Integer; use ocas_domain::number_theory::factor::factor_trial; let (factors, rest) = factor_trial(&Integer::from(2 * 2 * 3 * 7 * 1000003), 100); assert_eq!(factors, vec![ (Integer::from(2), 2), (Integer::from(3), 1), (Integer::from(7), 1), ]); assert_eq!(rest, Integer::from(1000003)); }
pollard_rho_brent
签名:pub fn pollard_rho_brent(n: &Integer, rng: &mut Xoshiro256PlusPlus) -> Option<Integer>
功能:Brent 变体的 Pollard rho 算法——搜索奇复合数 n 的非平凡因子。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
n | &Integer | 奇复合数 |
rng | &mut Xoshiro256PlusPlus | 随机数生成器 |
返回值:Option<Integer>——Some(d) 为 n 的非平凡因子,None 表示多次尝试均未成功(极少出现)。
实现细节:批量 gcd + 回溯策略提高成功率,有界重试次数。
示例:
#![allow(unused)] fn main() { use ocas_domain::Integer; use ocas_domain::number_theory::factor::pollard_rho_brent; use rand::SeedableRng; use rand_xoshiro::Xoshiro256PlusPlus; let n = Integer::from(1000003) * Integer::from(1000033); let mut rng = Xoshiro256PlusPlus::seed_from_u64(42); let d = pollard_rho_brent(&n, &mut rng).unwrap(); assert!(d == Integer::from(1000003) || d == Integer::from(1000033)); }
pollard_pm1
签名:pub fn pollard_pm1(n: &Integer, b1: u64, rng: &mut Xoshiro256PlusPlus) -> Option<Integer>
功能:Pollard $p-1$ 方法(stage 1)——当 $p-1$ 是 $B_1$-光滑时找到 n 的因子 p。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
n | &Integer | 奇复合数 |
b1 | u64 | 光滑性上界 |
rng | &mut Xoshiro256PlusPlus | 随机数生成器 |
返回值:Option<Integer>。
实现细节:计算 $a^M \bmod n$,其中 $M = \prod q^e \leq B_1$($q$ 取遍素数,$q^e \leq B_1$),定期测试 $\gcd(a^M - 1, n)$。
示例:
#![allow(unused)] fn main() { use ocas_domain::Integer; use ocas_domain::number_theory::factor::pollard_pm1; use rand::SeedableRng; use rand_xoshiro::Xoshiro256PlusPlus; // 65537 是素数,65536 = 2^16 是 2^17-光滑的 let n = Integer::from(65537) * Integer::from(1000003); let mut rng = Xoshiro256PlusPlus::seed_from_u64(7); let d = pollard_pm1(&n, 1 << 17, &mut rng).unwrap(); assert_eq!(n.mod_floor(&d), Integer::from(0)); }
参见:williams_pp1、ecm
williams_pp1
签名:pub fn williams_pp1(n: &Integer, b1: u64, rng: &mut Xoshiro256PlusPlus) -> Option<Integer>
功能:Williams $p+1$ 方法(stage 1)——当 $p+1$ 是 $B_1$-光滑时找到 n 的因子 p。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
n | &Integer | 奇复合数 |
b1 | u64 | 光滑性上界 |
rng | &mut Xoshiro256PlusPlus | 随机数生成器 |
返回值:Option<Integer>。
实现细节:使用 Lucas $V$ 序列($Q = 1$),随机选择 $P$ 使 $\text{jacobi}(P^2 - 4, n) = -1$。
示例:
#![allow(unused)] fn main() { use ocas_domain::Integer; use ocas_domain::number_theory::factor::williams_pp1; use rand::SeedableRng; use rand_xoshiro::Xoshiro256PlusPlus; // 31 是素数,31 + 1 = 2^5 是 64-光滑的 let n = Integer::from(31) * Integer::from(1000003); let mut rng = Xoshiro256PlusPlus::seed_from_u64(13); let d = williams_pp1(&n, 64, &mut rng).unwrap(); assert_eq!(n.mod_floor(&d), Integer::from(0)); }
参见:pollard_pm1、ecm
ecm
签名:pub fn ecm(n: &Integer, b1: u64, max_curves: u32, rng: &mut Xoshiro256PlusPlus) -> Option<Integer>
功能:Lenstra 椭圆曲线方法(ECM,stage 1)——当某条曲线在 $\mathbb{F}_p$ 上的群阶是 $B_1$-光滑时找到 n 的因子 p。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
n | &Integer | 奇复合数 |
b1 | u64 | 光滑性上界 |
max_curves | u32 | 最大尝试曲线数 |
rng | &mut Xoshiro256PlusPlus | 随机数生成器 |
返回值:Option<Integer>。
实现细节:
- Suyama 参数化:从 $\sigma \notin {0, 1, 5}$ 构建 Montgomery 曲线,$a24 = (A+2)/4$,$A = \frac{(v-u)^3(3u+v)}{4u^3v} - 2$,其中 $u = \sigma^2 - 5$,$v = 4\sigma$。
- 射影坐标 $(X:Z)$ 下的 Montgomery ladder 标量乘。
- 曲线预算 $\approx B_1 / 550$。
示例:
#![allow(unused)] fn main() { use ocas_domain::Integer; use ocas_domain::number_theory::factor::ecm; use rand::SeedableRng; use rand_xoshiro::Xoshiro256PlusPlus; let n = Integer::from(1000003) * Integer::from(1000033); let mut rng = Xoshiro256PlusPlus::seed_from_u64(99); let d = ecm(&n, 2_000, 50, &mut rng).unwrap(); assert!(d == Integer::from(1000003) || d == Integer::from(1000033)); }
离散对数
dlog_bsgs
签名:pub fn dlog_bsgs(base: &Integer, target: &Integer, modulus: &Integer) -> Option<Integer>
功能:用 baby-step giant-step(BSGS)算法求解 $base^x \equiv \text{target} \pmod{\text{modulus}}$,搜索 $x < \text{modulus}$。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
base | &Integer | 底数 |
target | &Integer | 目标值 |
modulus | &Integer | 模数 |
返回值:Option<Integer>——Some(x) 且 $0 \leq x < \text{modulus}$,None 表示无解。
错误条件:
- $\gcd(\text{base}, \text{modulus}) \neq 1$(底数不是单位)。
- 不存在满足条件的 $x$。
复杂度:时间和空间均为 $O(\sqrt{\text{modulus}})$。仅适用于较小的 modulus。
实现细节:HashMap 存储 baby steps,$O(\sqrt{m})$ 查找 giant steps。
示例:
#![allow(unused)] fn main() { use ocas_domain::Integer; use ocas_domain::number_theory::dlog::dlog_bsgs; // 2 是 mod 11 的原根;2^7 = 128 ≡ 7 (mod 11) let x = dlog_bsgs(&Integer::from(2), &Integer::from(7), &Integer::from(11)).unwrap(); assert_eq!(Integer::from(2).modpow(&x, &Integer::from(11)), Integer::from(7)); }
dlog_pohlig_hellman
签名:pub fn dlog_pohlig_hellman(base: &Integer, target: &Integer, p: &Integer) -> Option<Integer>
功能:用 Pohlig–Hellman 算法求解 $base^x \equiv \text{target} \pmod{p}$($p$ 为素数)。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
base | &Integer | 底数 |
target | &Integer | 目标值 |
p | &Integer | 素数模数 |
返回值:Option<Integer>——Some(x),None 表示无解或输入不满足条件。
错误条件:
p是合数。base不是模p的单位。target不在base生成的子群中。
实现细节:
- 用
factor_integer分解base的阶。 - 对每个素幂 $q^e$,逐位 BSGS 恢复离散对数(数字恢复)。
- 用
crt_many合并部分结果。 - 最终验证 $\text{base}^x \equiv \text{target} \pmod{p}$。
复杂度:由最大素因子 $q$ 支配,为 $O(\sqrt{q})$。当阶为光滑数时效率极高。
示例:
#![allow(unused)] fn main() { use ocas_domain::Integer; use ocas_domain::number_theory::dlog::dlog_pohlig_hellman; // p = 101, p − 1 = 2²·5²(光滑)。2 是 mod 101 的原根。 let p = Integer::from(101); let base = Integer::from(2); let target = base.modpow(&Integer::from(83), &p); let x = dlog_pohlig_hellman(&base, &target, &p).unwrap(); assert_eq!(x, Integer::from(83)); }
参见:dlog_bsgs、factor_integer、crt_many
积性数论函数
所有积性函数基于 factor_integer 分解后组合计算。
euler_phi
签名:pub fn euler_phi(n: &Integer) -> Integer
功能:Euler totient 函数 $\varphi(n)$——$[1, |n|]$ 中与 $n$ 互素的整数个数。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
n | &Integer | 输入整数 |
返回值:Integer——$\varphi(|n|)$。
计算公式:$|n| \cdot \prod_{p \mid n} \left(1 - \frac{1}{p}\right)$,遍历 $n$ 的不同素因子。
约定:$\varphi(0) = 0$,$\varphi(\pm 1) = 1$。
示例:
#![allow(unused)] fn main() { use ocas_domain::Integer; use ocas_domain::number_theory::functions::euler_phi; assert_eq!(euler_phi(&Integer::from(9)), Integer::from(6)); // φ(9) = 9 × (1 − 1/3) = 6,与 9 互素的有 {1,2,4,5,7,8} assert_eq!(euler_phi(&Integer::from(36)), Integer::from(12)); assert_eq!(euler_phi(&Integer::from(97)), Integer::from(96)); // 素数 p: φ(p) = p−1 }
moebius_mu
签名:pub fn moebius_mu(n: &Integer) -> i8
功能:Möbius 函数 $\mu(n)$。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
n | &Integer | 输入整数 |
返回值:i8。
定义:
$$ \mu(n) = \begin{cases} 1 & n = \pm 1 \ (-1)^k & n \text{ 有 } k \text{ 个不同的素因子(无平方因子)} \ 0 & n \text{ 被某个素数的平方整除} \end{cases} $$
约定:$\mu(0) = 0$。
示例:
#![allow(unused)] fn main() { use ocas_domain::Integer; use ocas_domain::number_theory::functions::moebius_mu; assert_eq!(moebius_mu(&Integer::from(1)), 1); assert_eq!(moebius_mu(&Integer::from(6)), 1); // 6 = 2·3,2 个素因子 → (−1)² = 1 assert_eq!(moebius_mu(&Integer::from(30)), -1); // 30 = 2·3·5,3 个素因子 → (−1)³ = −1 assert_eq!(moebius_mu(&Integer::from(12)), 0); // 4 = 2² 整除 12 }
divisor_tau
签名:pub fn divisor_tau(n: &Integer) -> Integer
功能:除数函数 $\tau(n)$(也记作 $d(n)$)——$|n|$ 的正因子个数。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
n | &Integer | 输入整数 |
返回值:Integer——$\tau(|n|)$。
计算公式:若 $n = \prod p_i^{e_i}$,则 $\tau(n) = \prod (e_i + 1)$。
约定:$\tau(0) = 0$。
示例:
#![allow(unused)] fn main() { use ocas_domain::Integer; use ocas_domain::number_theory::functions::divisor_tau; assert_eq!(divisor_tau(&Integer::from(12)), Integer::from(6)); // 12 = 2²·3¹ → (2+1)(1+1) = 6,因子为 {1,2,3,4,6,12} assert_eq!(divisor_tau(&Integer::from(97)), Integer::from(2)); // 素数只有 1 和自身 }
参见:divisor_sigma、factor_integer
divisor_sigma
签名:pub fn divisor_sigma(n: &Integer, k: u32) -> Integer
功能:除数函数 $\sigma_k(n)$——$|n|$ 的正因子的 $k$ 次幂之和。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
n | &Integer | 输入整数 |
k | u32 | 幂次(非负整数) |
返回值:Integer——$\sigma_k(|n|)$。
计算公式:$\sigma_k(n) = \prod \frac{p_i^{k(e_i+1)} - 1}{p_i^k - 1}$。
特殊值:
- $\sigma_0(n) = \tau(n)$(除数个数)
- $\sigma_1(n) = \sigma(n)$(除数之和——完美数判定:$n$ 为完全数当且仅当 $\sigma_1(n) = 2n$)
约定:$\sigma_k(0) = 0$。
示例:
#![allow(unused)] fn main() { use ocas_domain::Integer; use ocas_domain::number_theory::functions::divisor_sigma; assert_eq!(divisor_sigma(&Integer::from(12), 1), Integer::from(28)); // σ₁(12) = 1+2+3+4+6+12 = 28 assert_eq!(divisor_sigma(&Integer::from(12), 2), Integer::from(210)); // σ₂(12) = 1+4+9+16+36+144 = 210 assert_eq!(divisor_sigma(&Integer::from(12), 0), Integer::from(6)); // σ₀(12) = τ(12) = 6 }
liouville_lambda
签名:pub fn liouville_lambda(n: &Integer) -> i8
功能:Liouville 函数 $\lambda(n) = (-1)^{\Omega(n)}$,其中 $\Omega(n)$ 为含重数的素因子总个数。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
n | &Integer | 输入整数 |
返回值:i8——1 或 -1。
约定:$\lambda(0) = 0$,$\lambda(\pm 1) = 1$。
示例:
#![allow(unused)] fn main() { use ocas_domain::Integer; use ocas_domain::number_theory::functions::liouville_lambda; assert_eq!(liouville_lambda(&Integer::from(12)), -1); // 12 = 2²·3,Ω = 3 → (−1)³ = −1 assert_eq!(liouville_lambda(&Integer::from(6)), 1); // 6 = 2·3,Ω = 2 → (−1)² = 1 }
模块依赖关系
graph TD
subgraph primes["primes 模块"]
is_prime
is_prime_bpsw
is_prime_u64
next_prime
primes_from
mod_inv
extended_gcd
symmetric_mod
crt
legendre
jacobi
mod_sqrt
end
subgraph factor["factor 模块"]
factor_integer
factor_trial
pollard_rho_brent
pollard_pm1
williams_pp1
ecm
end
subgraph crt_mod["crt 模块"]
crt_many
end
subgraph dlog["dlog 模块"]
dlog_bsgs
dlog_pohlig_hellman
end
subgraph functions["functions 模块"]
euler_phi
moebius_mu
divisor_tau
divisor_sigma
liouville_lambda
end
factor_integer --> is_prime_bpsw
factor_integer --> factor_trial
factor_integer --> pollard_rho_brent
factor_integer --> pollard_pm1
factor_integer --> williams_pp1
factor_integer --> ecm
is_prime --> mr_witness
is_prime_bpsw --> is_prime
is_prime_u64 --> is_prime
next_prime --> is_prime
primes_from --> next_prime
crt_many --> crt
dlog_pohlig_hellman --> factor_integer
dlog_pohlig_hellman --> dlog_bsgs
dlog_pohlig_hellman --> crt_many
euler_phi --> factor_integer
moebius_mu --> factor_integer
divisor_tau --> factor_integer
divisor_sigma --> factor_integer
liouville_lambda --> factor_integer
williams_pp1 --> jacobi
legendre --> jacobi
mod_sqrt --> legendre
Gröbner 基与理想
本章记录 oCAS 中 Gröbner 基计算和理想运算的完整 Rust API。所有算法通过统一入口 [groebner_basis] 访问,理想运算在 ocas_poly::ideal 模块中。
目录
Algorithm— 算法选择枚举GroebnerBasis— Gröbner 基结构体groebner_basis— 统一入口函数buchberger— Buchberger 算法f4::f4— F4 矩阵算法f5::f5— F5 签名算法fglm— FGLM 换序算法HilbertSeries— Hilbert 级数hilbert_series— 计算 Hilbert 级数eliminate— 消元- 理想运算 —
ideal_contains、ideal_sum、ideal_product等 PrimaryComponent— 准素分量PolynomialSystemSolution— 多项式系统求解结果
Algorithm
#![allow(unused)] fn main() { #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum Algorithm { Auto, F4, F5, Buchberger, } }
功能:Gröbner 基计算的算法选择器,传递给 [groebner_basis] 统一入口。
变体:
| 变体 | 说明 |
|---|---|
Auto(默认) | 根据理想大小和结构自动选择算法。当前路由到 F4,后续将根据 cyclic-n 基准测试调整 F5 的切换阈值。 |
F4 | 强制使用 F4 矩阵算法(Faugère 1999)。批量处理 S-多项式归约为稀疏矩阵行操作,对较大理想显著快于 Buchberger。 |
F5 | 强制使用 F5 签名算法(Faugère 2002)。通过 syzygy 判据在矩阵构造前拒绝零归约器,对困难理想(如 cyclic-n)可实现数量级加速。 |
Buchberger | 强制使用经典 Buchberger S-多项式迭代算法,适合小型理想。 |
示例:
#![allow(unused)] fn main() { use ocas_domain::{RationalDomain, Rational}; use ocas_poly::sparse::Lex; use ocas_poly::{Algorithm, groebner_basis, SparseMultivariatePolynomial}; let d = RationalDomain; let f1 = SparseMultivariatePolynomial::<_, Lex>::from_terms(d, 2, vec![ (vec![1, 0], Rational::new(1, 1)), (vec![0, 1], Rational::new(1, 1)), ]); let f2 = SparseMultivariatePolynomial::<_, Lex>::from_terms(d, 2, vec![ (vec![1, 0], Rational::new(1, 1)), (vec![0, 1], Rational::new(-1, 1)), ]); let gb = groebner_basis(&[f1, f2], Algorithm::F4); assert!(gb.is_groebner_basis()); }
GroebnerBasis
#![allow(unused)] fn main() { #[derive(Debug, Clone, PartialEq, Eq)] pub struct GroebnerBasis<D: Domain, O: MonomialOrder> { pub basis: Vec<SparseMultivariatePolynomial<D, O>>, } }
功能:多项式理想的 Gröbner 基。basis 字段存储基的多项式列表。通过 buchberger、f4::f4、f5::f5 或 groebner_basis 构造,所有入口函数均返回既约 Gröbner 基(minimized + auto-reduced)。
类型参数:
| 参数 | 约束 | 说明 |
|---|---|---|
D | Domain | 系数域(如 RationalDomain、FiniteField) |
O | MonomialOrder | 单项式序(如 Lex、Grevlex) |
字段:
| 字段 | 类型 | 说明 |
|---|---|---|
basis | Vec<SparseMultivariatePolynomial<D, O>> | 基的多项式列表 |
GroebnerBasis::buchberger
#![allow(unused)] fn main() { pub fn buchberger(ideal: &[SparseMultivariatePolynomial<D, O>]) -> Self }
功能:使用 Buchberger 算法从生成元计算 Gröbner 基。内部过滤零多项式,应用 Buchberger 第一判据(首项互素时跳过 S-多项式),最大迭代 10000 次。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
ideal | &[SparseMultivariatePolynomial<D, O>] | 理想的生成元列表 |
返回值:GroebnerBasis<D, O> — 未最小化/未自归约的原始基。
注意:要求系数域支持精确除法(即为域)。除法失败时会 panic。便捷函数 buchberger(自由函数)会额外调用 minimize().auto_reduce()。
示例:
#![allow(unused)] fn main() { use ocas_domain::{RationalDomain, Rational}; use ocas_poly::sparse::Lex; use ocas_poly::{GroebnerBasis, SparseMultivariatePolynomial}; let d = RationalDomain; let f1 = SparseMultivariatePolynomial::<_, Lex>::from_terms(d, 2, vec![ (vec![1, 0], Rational::new(1, 1)), (vec![0, 1], Rational::new(1, 1)), ]); let f2 = SparseMultivariatePolynomial::<_, Lex>::from_terms(d, 2, vec![ (vec![1, 0], Rational::new(1, 1)), (vec![0, 1], Rational::new(-1, 1)), ]); let gb = GroebnerBasis::buchberger(&[f1, f2]); assert!(gb.basis.len() >= 2); }
参见:buchberger(便捷自由函数)、f4::f4、f5::f5
minimize
#![allow(unused)] fn main() { pub fn minimize(mut self) -> Self }
功能:最小化基——移除首项单项式可被其他元素首项整除的多项式。
返回值:Self — 最小化后的基(消费 self)。
参见:auto_reduce
auto_reduce
#![allow(unused)] fn main() { pub fn auto_reduce(mut self) -> Self }
功能:自归约基——将每个元素对其他元素归约并使其为首一多项式。按首项单项式升序处理,确保标准既约 Gröbner 基性质:任何基元素的单项式不被其他基元素的首项整除。
返回值:Self — 既约基(消费 self)。
参见:minimize
is_groebner_basis
#![allow(unused)] fn main() { pub fn is_groebner_basis(&self) -> bool }
功能:验证此基确实是 Gröbner 基——检查所有 S-多项式是否归约为零。
返回值:bool — true 表示是合法的 Gröbner 基。
示例:
#![allow(unused)] fn main() { use ocas_domain::{RationalDomain, Rational}; use ocas_poly::sparse::Lex; use ocas_poly::{Algorithm, SparseMultivariatePolynomial, groebner_basis}; let d = RationalDomain; let f1 = SparseMultivariatePolynomial::<_, Lex>::from_terms(d, 2, vec![ (vec![1, 0], Rational::new(1, 1)), (vec![0, 1], Rational::new(1, 1)), ]); let f2 = SparseMultivariatePolynomial::<_, Lex>::from_terms(d, 2, vec![ (vec![1, 0], Rational::new(1, 1)), (vec![0, 1], Rational::new(-1, 1)), ]); let gb = groebner_basis(&[f1, f2], Algorithm::Auto); assert!(gb.is_groebner_basis()); }
reorder
#![allow(unused)] fn main() { pub fn reorder<O2: MonomialOrder>(&self) -> GroebnerBasis<D, O2> where D: 'static, }
功能:改变 Gröbner 基的单项式序。将多项式重新解释为目标序并重新运行 F4。这是简单换序路径;对零维理想,使用 fglm 获得更快的 $O(n \cdot D^3)$ 转换。
类型参数:
| 参数 | 说明 |
|---|---|
O2 | 目标单项式序 |
返回值:GroebnerBasis<D, O2> — 目标序下的 Gröbner 基。
示例:
#![allow(unused)] fn main() { use ocas_domain::{RationalDomain, Rational}; use ocas_poly::sparse::{Grevlex, Lex}; use ocas_poly::{GroebnerBasis, SparseMultivariatePolynomial, f4}; let d = RationalDomain; let f1 = SparseMultivariatePolynomial::<_, Lex>::from_terms(d, 2, vec![ (vec![1, 0], Rational::new(1, 1)), (vec![0, 1], Rational::new(1, 1)), ]); let f2 = SparseMultivariatePolynomial::<_, Lex>::from_terms(d, 2, vec![ (vec![1, 0], Rational::new(1, 1)), (vec![0, 1], Rational::new(-1, 1)), ]); let gb_lex = f4::f4(&[f1, f2]); let gb_grevlex = gb_lex.reorder::<Grevlex>(); assert!(gb_grevlex.is_groebner_basis()); }
参见:fglm(零维理想更快的换序方法)
groebner_basis
#![allow(unused)] fn main() { pub fn groebner_basis<D: Domain + 'static, O: MonomialOrder>( ideal: &[SparseMultivariatePolynomial<D, O>], algo: Algorithm, ) -> GroebnerBasis<D, O> }
功能:Gröbner 基计算的统一入口。根据 algo 参数选择算法(Auto/F4/F5/Buchberger)。零多项式在内部由各后端过滤。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
ideal | &[SparseMultivariatePolynomial<D, O>] | 理想的生成元列表 |
algo | Algorithm | 算法选择 |
返回值:GroebnerBasis<D, O> — 既约 Gröbner 基。
示例:
#![allow(unused)] fn main() { use ocas_domain::{RationalDomain, Rational}; use ocas_poly::sparse::Lex; use ocas_poly::{Algorithm, groebner_basis, SparseMultivariatePolynomial}; let d = RationalDomain; let f1 = SparseMultivariatePolynomial::<_, Lex>::from_terms(d, 2, vec![ (vec![1, 0], Rational::new(1, 1)), (vec![0, 1], Rational::new(1, 1)), ]); let f2 = SparseMultivariatePolynomial::<_, Lex>::from_terms(d, 2, vec![ (vec![1, 0], Rational::new(1, 1)), (vec![0, 1], Rational::new(-1, 1)), ]); let gb = groebner_basis(&[f1, f2], Algorithm::Auto); assert!(gb.is_groebner_basis()); }
buchberger
#![allow(unused)] fn main() { pub fn buchberger<D: Domain, O: MonomialOrder>( ideal: &[SparseMultivariatePolynomial<D, O>], ) -> GroebnerBasis<D, O> }
功能:便捷函数——计算 Gröbner 基并执行最小化和自归约(等价于 GroebnerBasis::buchberger(ideal).minimize().auto_reduce())。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
ideal | &[SparseMultivariatePolynomial<D, O>] | 理想的生成元列表 |
返回值:GroebnerBasis<D, O> — 既约 Gröbner 基。
参见:GroebnerBasis::buchberger、groebner_basis
f4::f4
#![allow(unused)] fn main() { pub fn f4<D: Domain + 'static, O: MonomialOrder>( ideal: &[SparseMultivariatePolynomial<D, O>], ) -> GroebnerBasis<D, O> }
功能:使用 F4 矩阵算法计算 Gröbner 基(Faugère 1999)。将顺序 S-多项式归替换为批量稀疏矩阵行操作(Gaussian 消元)。对 FiniteField 系数域自动使用原生 ℤ_p 快速路径(FpPoly i64 模算术),避免 BigInt 开销。
内部优化:
- Gebauer–Moeller 对筛选:chain criterion + update criterion,严格整除守卫
SimpCache:缓存多项式乘单项式的结果,避免重复计算DivisorIndex:基于支持位掩码的 O(1)-ish 归约器查找(变量索引 ≤ 63 时位掩码精确,>63 时退化为正确性保守的过滤)- 双指针稀疏行减法
sub_scaled_fp:O(nnz) 复杂度 - 原生 ℤ_p 快速路径:
FpPoly使用 i64 模算术,仅在读取输入和输出结果时进行 BigInt 转换
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
ideal | &[SparseMultivariatePolynomial<D, O>] | 理想的生成元列表 |
返回值:GroebnerBasis<D, O> — 既约 Gröbner 基。
示例:
#![allow(unused)] fn main() { use ocas_domain::{RationalDomain, Rational}; use ocas_poly::sparse::Lex; use ocas_poly::groebner::f4::f4; use ocas_poly::SparseMultivariatePolynomial; let d = RationalDomain; let f1 = SparseMultivariatePolynomial::<_, Lex>::from_terms(d, 2, vec![ (vec![1, 0], Rational::new(1, 1)), (vec![0, 1], Rational::new(1, 1)), ]); let f2 = SparseMultivariatePolynomial::<_, Lex>::from_terms(d, 2, vec![ (vec![1, 0], Rational::new(1, 1)), (vec![0, 1], Rational::new(-1, 1)), ]); let gb = f4(&[f1, f2]); assert!(!gb.basis.is_empty()); }
f5::f5
#![allow(unused)] fn main() { pub fn f5<D: Domain + 'static, O: MonomialOrder>( ideal: &[SparseMultivariatePolynomial<D, O>], ) -> GroebnerBasis<D, O> }
功能:使用 F5 签名算法计算 Gröbner 基(Faugère 2002)。为每个多项式附加签名(module_pos, monomial),通过 syzygy 判据在矩阵构造前拒绝零归约器,对困难理想(如 cyclic-n)可实现数量级加速。
算法要点:
- 签名:
(module_pos, monomial)记录多项式的历史——module_pos是输入生成元的索引,monomial是应用的单项式倍数。签名按 pot(position-over-term)序比较:先比较 module position(小者优先),再按单项式序O比较。 - Syzygy 跟踪:矩阵行归约为零时,其签名为 syzygy。未来签名是已知 syzygy 的单项式倍数的行将被立即跳过(F5 syzygy 判据)。
- 增量处理:逐个处理生成元,每个新生成元触发一轮矩阵构造和归约。
当前实现提供通用域 F5 核心。对 FiniteField 系数域自动使用原生 ℤ_p 快速路径。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
ideal | &[SparseMultivariatePolynomial<D, O>] | 理想的生成元列表 |
返回值:GroebnerBasis<D, O> — 既约 Gröbner 基(与 F4 输出完全相同)。
示例:
#![allow(unused)] fn main() { use ocas_domain::{RationalDomain, Rational}; use ocas_poly::sparse::Lex; use ocas_poly::SparseMultivariatePolynomial; use ocas_poly::groebner::f5::f5; let d = RationalDomain; let f1 = SparseMultivariatePolynomial::<_, Lex>::from_terms(d, 2, vec![ (vec![1, 0], Rational::new(1, 1)), (vec![0, 1], Rational::new(1, 1)), ]); let f2 = SparseMultivariatePolynomial::<_, Lex>::from_terms(d, 2, vec![ (vec![1, 0], Rational::new(1, 1)), (vec![0, 1], Rational::new(-1, 1)), ]); let gb = f5(&[f1, f2]); assert!(gb.is_groebner_basis()); }
fglm
#![allow(unused)] fn main() { pub fn fglm<D: Domain, O2: MonomialOrder>( gb: &GroebnerBasis<D, impl MonomialOrder>, ) -> Option<GroebnerBasis<D, O2>> }
功能:FGLM 换序算法(Faugère–Gianni–Lazard–Mora 1993)。将零维理想的 Gröbner 基从一种单项式序转换为另一种,复杂度 $O(n \cdot D^3)$($D$ 为 $R/I$ 的向量空间维数),对大型零维理想远快于重新运行 F4。
算法步骤:
- 计算阶梯(staircase)——不被任何首项单项式整除的单项式集合
- 按目标序遍历阶梯单项式
- 对每个单项式计算当前基下的正规形
- 若正规形与已见向量线性相关,从系数关系构造新基元素
- 否则将其添加到已见向量集合
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
gb | &GroebnerBasis<D, impl MonomialOrder> | 输入 Gröbner 基(必须是既约的) |
返回值:Option<GroebnerBasis<D, O2>> — Some(gb) 为转换后的基,None 表示理想非零维(阶梯无限)。
示例:
#![allow(unused)] fn main() { use ocas_domain::{RationalDomain, Rational}; use ocas_poly::sparse::{Grevlex, Lex}; use ocas_poly::{GroebnerBasis, SparseMultivariatePolynomial, f4}; use ocas_poly::groebner::fglm::fglm; let d = RationalDomain; let f1 = SparseMultivariatePolynomial::<_, Lex>::from_terms(d, 2, vec![ (vec![1, 0], Rational::new(1, 1)), (vec![0, 1], Rational::new(1, 1)), ]); let f2 = SparseMultivariatePolynomial::<_, Lex>::from_terms(d, 2, vec![ (vec![1, 0], Rational::new(1, 1)), (vec![0, 1], Rational::new(-1, 1)), ]); let gb_lex = f4::f4(&[f1, f2]); let gb_grevlex = fglm::<_, Grevlex>(&gb_lex).expect("zero-dimensional"); assert!(gb_grevlex.is_groebner_basis()); }
参见:GroebnerBasis::reorder(简单换序,适用于任意维数)、is_zero_dimensional
HilbertSeries
#![allow(unused)] fn main() { #[derive(Debug, Clone)] pub struct HilbertSeries { pub numerator: Vec<i64>, pub denominator_power: usize, } }
功能:商环 $R/I$ 的 Hilbert 级数,表示为有理函数 $H(t) = N(t) / (1-t)^n$。分子 $N(t)$ 存储为系数向量(numerator[i] 为 $t^i$ 的系数),分母为 $(1-t)^n$($n$ 为变量数)。
字段:
| 字段 | 类型 | 说明 |
|---|---|---|
numerator | Vec<i64> | 分子 $N(t)$ 的系数(从常数项起) |
denominator_power | usize | $(1-t)$ 的幂次(= 变量数 $n$) |
hilbert_function
#![allow(unused)] fn main() { pub fn hilbert_function(&self, degree: usize) -> i64 }
功能:计算度数 $d$ 处的 Hilbert 函数值 $\dim_k (R/I)_d$。使用公式 $H(d) = [t^d] N(t) / (1-t)^n$,其中 $(1-t)^{-n}$ 中 $t^k$ 的系数为 $\binom{n+k-1}{k}$。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
degree | usize | 度数 $d$ |
返回值:i64 — $\dim_k (R/I)_d$ 的值。
dimension
#![allow(unused)] fn main() { pub fn dimension(&self) -> usize }
功能:计算 $R/I$ 的 Krull 维数。通过检查分子在 $t=1$ 处的零点阶数(逐次求导直到在 $t=1$ 处非零)得到。
返回值:usize — Krull 维数。零维理想返回 0。
degree
#![allow(unused)] fn main() { pub fn degree(&self) -> i64 }
功能:计算射影簇的次数。对良构 Hilbert 级数,等于分子在除去维数因子后在 $t=1$ 处的值。
返回值:i64 — 射影簇的次数。
hilbert_polynomial
#![allow(unused)] fn main() { pub fn hilbert_polynomial(&self) -> Vec<f64> }
功能:计算 Hilbert 多项式 $P(d)$ 的系数($H(d) = P(d)$ 当 $d \gg 0$ 时),使用 Lagrange 插值。返回升幂排列的系数(result[i] 为 $d^i$ 的系数),多项式次数为 self.dimension()。
返回值:Vec<f64> — Hilbert 多项式系数(升幂排列)。
示例:
#![allow(unused)] fn main() { use ocas_domain::{RationalDomain, Rational}; use ocas_poly::sparse::Lex; use ocas_poly::{Algorithm, SparseMultivariatePolynomial, groebner_basis}; use ocas_poly::groebner::hilbert::hilbert_series; let d = RationalDomain; // 理想 (x² - 1, y² - 1):LM 理想 (x², y²),分子 N(t) = 1 - 2t² + t⁴ let f1 = SparseMultivariatePolynomial::<_, Lex>::from_terms(d, 2, vec![ (vec![2, 0], Rational::new(1, 1)), (vec![0, 0], Rational::new(-1, 1)), ]); let f2 = SparseMultivariatePolynomial::<_, Lex>::from_terms(d, 2, vec![ (vec![0, 2], Rational::new(1, 1)), (vec![0, 0], Rational::new(-1, 1)), ]); let gb = groebner_basis(&[f1, f2], Algorithm::F4); let hs = hilbert_series(&gb); assert_eq!(hs.dimension(), 0); // 零维:4 个解 assert_eq!(hs.degree(), 4); // 次数 = 解的个数 println!("维数: {}, 次数: {}", hs.dimension(), hs.degree()); for d in 0..10 { println!("H({}) = {}", d, hs.hilbert_function(d)); } let hp = hs.hilbert_polynomial(); println!("Hilbert 多项式系数: {:?}", hp); }
hilbert_series
#![allow(unused)] fn main() { pub fn hilbert_series( gb: &GroebnerBasis<RationalDomain, Lex>, ) -> HilbertSeries }
功能:从 Gröbner 基计算 $R/I$ 的 Hilbert 级数。使用 Macaulay 定理:$R/I$ 的 Hilbert 级数等于 $R/\text{LM}(I)$(首项理想)的 Hilbert 级数。通过容斥原理计算单项式理想 $\langle m_1, \dots, m_s \rangle$ 的 Hilbert 分子:$N(t) = \sum_k (-1)^k \sum_{|S|=k} t^{\deg \text{lcm}(S)}$。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
gb | &GroebnerBasis<RationalDomain, Lex> | 有理数域上 Lex 序的 Gröbner 基 |
返回值:HilbertSeries — 商环的 Hilbert 级数。
示例:
#![allow(unused)] fn main() { use ocas_domain::{RationalDomain, Rational}; use ocas_poly::sparse::Lex; use ocas_poly::{Algorithm, SparseMultivariatePolynomial, groebner_basis}; use ocas_poly::groebner::hilbert::hilbert_series; let d = RationalDomain; let f1 = SparseMultivariatePolynomial::<_, Lex>::from_terms(d, 2, vec![ (vec![2, 0], Rational::new(1, 1)), ]); let f2 = SparseMultivariatePolynomial::<_, Lex>::from_terms(d, 2, vec![ (vec![1, 1], Rational::new(1, 1)), ]); let gb = groebner_basis(&[f1, f2], Algorithm::F4); let hs = hilbert_series(&gb); assert!(hs.dimension() <= 2); }
参见:HilbertSeries、is_zero_dimensional
eliminate
#![allow(unused)] fn main() { pub fn eliminate<D: Domain + 'static>( ideal: &[SparseMultivariatePolynomial<D, Lex>], elim_vars: usize, algo: Algorithm, ) -> GroebnerBasis<D, Lex> }
功能:从理想中消元。返回 $I \cap k[x_{\text{elim_vars}}, \dots, x_{n-1}]$ 的 Gröbner 基,即不包含前 elim_vars 个变量的多项式。使用 Lex 序——在 Lex 序下,理想的既约 Gröbner 基自动包含消元理想的生成元。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
ideal | &[SparseMultivariatePolynomial<D, Lex>] | 理想的生成元(必须为 Lex 序) |
elim_vars | usize | 要消去的变量数(消去 $x_0, \dots, x_{\text{elim_vars}-1}$) |
algo | Algorithm | Gröbner 基算法选择 |
返回值:GroebnerBasis<D, Lex> — 消元理想的 Gröbner 基。
示例:
#![allow(unused)] fn main() { use ocas_domain::{RationalDomain, Rational}; use ocas_poly::sparse::Lex; use ocas_poly::{SparseMultivariatePolynomial, eliminate, Algorithm}; let d = RationalDomain; // 理想: x + y + z, x*y + x*z 于 k[x,y,z]; 消去 x let f1 = SparseMultivariatePolynomial::<_, Lex>::from_terms(d, 3, vec![ (vec![1, 0, 0], Rational::new(1, 1)), (vec![0, 1, 0], Rational::new(1, 1)), (vec![0, 0, 1], Rational::new(1, 1)), ]); let f2 = SparseMultivariatePolynomial::<_, Lex>::from_terms(d, 3, vec![ (vec![1, 1, 0], Rational::new(1, 1)), (vec![1, 0, 1], Rational::new(1, 1)), ]); let elim = eliminate(&[f1, f2], 1, Algorithm::Auto); // 结果应在 k[y,z] 中 for p in &elim.basis { assert!(p.degree_in(0) == 0, "消去的变量 x 不应出现"); } }
参见:groebner_basis、ideal_quotient
理想运算
所有理想运算位于 ocas_poly::ideal 模块,使用 Lex 序以保持与消元计算的一致性。
ideal_contains
#![allow(unused)] fn main() { pub fn ideal_contains<D: Domain + 'static>( generators: &[SparseMultivariatePolynomial<D, Lex>], f: &SparseMultivariatePolynomial<D, Lex>, algo: Algorithm, ) -> bool }
功能:测试 $f$ 是否属于生成元张成的理想。计算理想的 Gröbner 基并对 $f$ 归约,$f \in I$ 当且仅当余式为零。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
generators | &[SparseMultivariatePolynomial<D, Lex>] | 理想的生成元 |
f | &SparseMultivariatePolynomial<D, Lex> | 待测试的多项式 |
algo | Algorithm | Gröbner 基算法选择 |
返回值:bool — true 表示 $f \in I$。
示例:
#![allow(unused)] fn main() { use ocas_domain::{RationalDomain, Rational}; use ocas_poly::sparse::Lex; use ocas_poly::ideal::ideal_contains; use ocas_poly::{Algorithm, SparseMultivariatePolynomial}; let d = RationalDomain; let x = SparseMultivariatePolynomial::<_, Lex>::from_terms(d, 2, vec![ (vec![1, 0], Rational::new(1, 1)), ]); let y = SparseMultivariatePolynomial::<_, Lex>::from_terms(d, 2, vec![ (vec![0, 1], Rational::new(1, 1)), ]); assert!(ideal_contains(&[x.clone(), y.clone()], &x, Algorithm::Auto)); assert!(!ideal_contains(&[y], &x, Algorithm::Auto)); // x ∉ ⟨y⟩ }
ideal_sum
#![allow(unused)] fn main() { pub fn ideal_sum<D: Domain + 'static>( generators_a: &[SparseMultivariatePolynomial<D, Lex>], generators_b: &[SparseMultivariatePolynomial<D, Lex>], ) -> GroebnerBasis<D, Lex> }
功能:两个理想的和 $I + J = \langle f_1, \dots, f_m, g_1, \dots, g_n \rangle$。合并生成元并计算 Gröbner 基。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
generators_a | &[SparseMultivariatePolynomial<D, Lex>] | 理想 $I$ 的生成元 |
generators_b | &[SparseMultivariatePolynomial<D, Lex>] | 理想 $J$ 的生成元 |
返回值:GroebnerBasis<D, Lex> — $I + J$ 的既约 Gröbner 基。
示例:
#![allow(unused)] fn main() { use ocas_domain::{RationalDomain, Rational}; use ocas_poly::sparse::Lex; use ocas_poly::ideal::ideal_sum; use ocas_poly::{Algorithm, SparseMultivariatePolynomial}; let d = RationalDomain; let x = SparseMultivariatePolynomial::<_, Lex>::from_terms(d, 2, vec![ (vec![1, 0], Rational::new(1, 1)), ]); let y = SparseMultivariatePolynomial::<_, Lex>::from_terms(d, 2, vec![ (vec![0, 1], Rational::new(1, 1)), ]); let gb = ideal_sum(&[x], &[y]); // ⟨x⟩ + ⟨y⟩ = ⟨x, y⟩ assert!(gb.basis.len() >= 2); }
ideal_product
#![allow(unused)] fn main() { pub fn ideal_product<D: Domain + 'static>( generators_a: &[SparseMultivariatePolynomial<D, Lex>], generators_b: &[SparseMultivariatePolynomial<D, Lex>], ) -> GroebnerBasis<D, Lex> }
功能:两个理想的积 $I \cdot J = \langle f_i \cdot g_j \rangle$。计算所有 $f_i g_j$ 的乘积并求 Gröbner 基。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
generators_a | &[SparseMultivariatePolynomial<D, Lex>] | 理想 $I$ 的生成元 |
generators_b | &[SparseMultivariatePolynomial<D, Lex>] | 理想 $J$ 的生成元 |
返回值:GroebnerBasis<D, Lex> — $I \cdot J$ 的既约 Gröbner 基。
示例:
#![allow(unused)] fn main() { use ocas_domain::{RationalDomain, Rational}; use ocas_poly::sparse::Lex; use ocas_poly::ideal::ideal_product; use ocas_poly::SparseMultivariatePolynomial; let d = RationalDomain; let x = SparseMultivariatePolynomial::<_, Lex>::from_terms(d, 2, vec![ (vec![1, 0], Rational::new(1, 1)), ]); let y = SparseMultivariatePolynomial::<_, Lex>::from_terms(d, 2, vec![ (vec![0, 1], Rational::new(1, 1)), ]); let gb = ideal_product(&[x], &[y]); // ⟨x⟩ · ⟨y⟩ = ⟨xy⟩ assert_eq!(gb.basis.len(), 1); }
ideal_quotient
#![allow(unused)] fn main() { pub fn ideal_quotient<D: Domain + 'static>( generators_i: &[SparseMultivariatePolynomial<D, Lex>], generators_j: &[SparseMultivariatePolynomial<D, Lex>], ) -> GroebnerBasis<D, Lex> }
功能:理想的商 $I : J = {f : f \cdot g \in I, \forall g \in J}$。对 $J$ 的每个生成元 $g$ 使用 Rabinowitsch 技巧:在扩展环 $k[x_1, \dots, x_n, w]$ 中计算 $\text{GB}(I \cup {1 - w \cdot g})$ 并消去 $w$,然后对结果求交集。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
generators_i | &[SparseMultivariatePolynomial<D, Lex>] | 理想 $I$ 的生成元 |
generators_j | &[SparseMultivariatePolynomial<D, Lex>] | 理想 $J$ 的生成元 |
返回值:GroebnerBasis<D, Lex> — $I : J$ 的既约 Gröbner 基。
示例:
#![allow(unused)] fn main() { use ocas_domain::{RationalDomain, Rational}; use ocas_poly::sparse::Lex; use ocas_poly::ideal::ideal_quotient; use ocas_poly::SparseMultivariatePolynomial; let d = RationalDomain; // ⟨x², xy⟩ : ⟨x⟩ = ⟨x⟩ let f1 = SparseMultivariatePolynomial::<_, Lex>::from_terms(d, 2, vec![ (vec![2, 0], Rational::new(1, 1)), ]); let f2 = SparseMultivariatePolynomial::<_, Lex>::from_terms(d, 2, vec![ (vec![1, 1], Rational::new(1, 1)), ]); let g = SparseMultivariatePolynomial::<_, Lex>::from_terms(d, 2, vec![ (vec![1, 0], Rational::new(1, 1)), ]); let gb = ideal_quotient(&[f1, f2], &[g]); assert!(!gb.basis.is_empty()); }
参见:ideal_saturate、ideal_contains
ideal_saturate
#![allow(unused)] fn main() { pub fn ideal_saturate<D: Domain + 'static>( generators_i: &[SparseMultivariatePolynomial<D, Lex>], generators_j: &[SparseMultivariatePolynomial<D, Lex>], ) -> GroebnerBasis<D, Lex> }
功能:理想的饱和 $I : J^\infty = \bigcup_k (I : J^k)$。迭代计算 $I : J$、$(I : J) : J$ 等,直到稳定。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
generators_i | &[SparseMultivariatePolynomial<D, Lex>] | 理想 $I$ 的生成元 |
generators_j | &[SparseMultivariatePolynomial<D, Lex>] | 理想 $J$ 的生成元 |
返回值:GroebnerBasis<D, Lex> — $I : J^\infty$ 的既约 Gröbner 基。
示例:
#![allow(unused)] fn main() { use ocas_domain::{RationalDomain, Rational}; use ocas_poly::sparse::Lex; use ocas_poly::ideal::ideal_saturate; use ocas_poly::SparseMultivariatePolynomial; let d = RationalDomain; // ⟨x²y, xy²⟩ : ⟨x⟩^∞ = ⟨y⟩ let f1 = SparseMultivariatePolynomial::<_, Lex>::from_terms(d, 2, vec![ (vec![2, 1], Rational::new(1, 1)), ]); let f2 = SparseMultivariatePolynomial::<_, Lex>::from_terms(d, 2, vec![ (vec![1, 2], Rational::new(1, 1)), ]); let g = SparseMultivariatePolynomial::<_, Lex>::from_terms(d, 2, vec![ (vec![1, 0], Rational::new(1, 1)), ]); let gb = ideal_saturate(&[f1, f2], &[g]); assert!(!gb.basis.is_empty()); }
ideal_intersection
#![allow(unused)] fn main() { pub fn ideal_intersection<D: Domain + 'static>( generators_a: &[SparseMultivariatePolynomial<D, Lex>], generators_b: &[SparseMultivariatePolynomial<D, Lex>], ) -> GroebnerBasis<D, Lex> }
功能:两个理想的交 $I \cap J$。使用辅助变量 $t$:$I \cap J = \langle t \cdot f_i, (1-t) \cdot g_j \rangle \cap k[x_1, \dots, x_n]$。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
generators_a | &[SparseMultivariatePolynomial<D, Lex>] | 理想 $I$ 的生成元 |
generators_b | &[SparseMultivariatePolynomial<D, Lex>] | 理想 $J$ 的生成元 |
返回值:GroebnerBasis<D, Lex> — $I \cap J$ 的既约 Gröbner 基。
示例:
#![allow(unused)] fn main() { use ocas_domain::{RationalDomain, Rational}; use ocas_poly::sparse::Lex; use ocas_poly::ideal::ideal_intersection; use ocas_poly::SparseMultivariatePolynomial; let d = RationalDomain; let x = SparseMultivariatePolynomial::<_, Lex>::from_terms(d, 2, vec![ (vec![1, 0], Rational::new(1, 1)), ]); let y = SparseMultivariatePolynomial::<_, Lex>::from_terms(d, 2, vec![ (vec![0, 1], Rational::new(1, 1)), ]); let gb = ideal_intersection(&[x], &[y]); // ⟨x⟩ ∩ ⟨y⟩ = ⟨xy⟩ assert_eq!(gb.basis.len(), 1); }
ideal_radical
#![allow(unused)] fn main() { pub fn ideal_radical( generators: &[SparseMultivariatePolynomial<RationalDomain, Lex>], ) -> GroebnerBasis<RationalDomain, Lex> }
功能:计算理想的根式 $\sqrt{I}$。
- 零维理想:通过 Lex GB 中一元多项式的无平方分解计算($\sqrt{I}$ 的生成元由各变量的无平方一元多项式和非单变量基元素组成)。
- 正维理想:使用 Jacobian 饱和方法(简化 Kemper 算法):$\sqrt{I} = I : h^\infty$。当前实现以启发式选取 $h$——取总次数最小的非平凡偏导数(并非精确 GCD)。若所有偏导数均为常数或零(Jacobian 平凡),保守返回原 GB。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
generators | &[SparseMultivariatePolynomial<RationalDomain, Lex>] | 理想的生成元 |
返回值:GroebnerBasis<RationalDomain, Lex> — $\sqrt{I}$ 的既约 Gröbner 基。
示例:
#![allow(unused)] fn main() { use ocas_domain::{RationalDomain, Rational}; use ocas_poly::sparse::Lex; use ocas_poly::ideal::ideal_radical; use ocas_poly::SparseMultivariatePolynomial; let d = RationalDomain; // √(x², xy) = (x) let f1 = SparseMultivariatePolynomial::<_, Lex>::from_terms(d, 2, vec![ (vec![2, 0], Rational::new(1, 1)), ]); let f2 = SparseMultivariatePolynomial::<_, Lex>::from_terms(d, 2, vec![ (vec![1, 1], Rational::new(1, 1)), ]); let rad = ideal_radical(&[f1, f2]); assert!(!rad.basis.is_empty()); }
is_zero_dimensional
#![allow(unused)] fn main() { pub fn is_zero_dimensional(gb: &GroebnerBasis<RationalDomain, Lex>) -> bool }
功能:检查理想是否为零维。理想零维当且仅当对每个变量 $x_i$,GB 中某个首项单项式是纯幂 $x_i^N$(等价于阶梯/标准单项式集合有限)。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
gb | &GroebnerBasis<RationalDomain, Lex> | 有理数域上 Lex 序的 Gröbner 基 |
返回值:bool — true 表示零维。
示例:
#![allow(unused)] fn main() { use ocas_domain::{RationalDomain, Rational}; use ocas_poly::sparse::Lex; use ocas_poly::{Algorithm, GroebnerBasis, SparseMultivariatePolynomial, groebner_basis}; use ocas_poly::ideal::is_zero_dimensional; let d = RationalDomain; // x² - 1, y - x → 零维 let f1 = SparseMultivariatePolynomial::<_, Lex>::from_terms(d, 2, vec![ (vec![2, 0], Rational::new(1, 1)), (vec![0, 0], Rational::new(-1, 1)), ]); let f2 = SparseMultivariatePolynomial::<_, Lex>::from_terms(d, 2, vec![ (vec![0, 1], Rational::new(1, 1)), (vec![1, 0], Rational::new(-1, 1)), ]); let gb = groebner_basis(&[f1, f2], Algorithm::F4); assert!(is_zero_dimensional(&gb)); }
参见:solve_polynomial_system、fglm
solve_polynomial_system
#![allow(unused)] fn main() { pub fn solve_polynomial_system( equations: &[SparseMultivariatePolynomial<RationalDomain, Lex>], algo: Algorithm, ) -> PolynomialSystemSolution }
功能:求解零维多项式系统。将 GB 转换为 Lex 序,提取各变量的一元多项式,通过回代求解。返回 PolynomialSystemSolution 枚举,区分零维(有限解)、正维(无穷解集)和空集($\langle 1 \rangle$)。
求解过程:
- 计算 Gröbner 基
- 检查是否为 $\langle 1 \rangle$(空集)
- 检查是否零维
- 转换为 Lex 序的三角形分解
- 从最后一个变量开始回代:对每个变量求一元多项式的实根(Sturm 定理隔离 + 精化至 $10^{-14}$),递归代入已知值
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
equations | &[SparseMultivariatePolynomial<RationalDomain, Lex>] | 方程组(多项式 = 0) |
algo | Algorithm | Gröbner 基算法选择 |
返回值:PolynomialSystemSolution — 求解结果。
示例:
#![allow(unused)] fn main() { use ocas_domain::{RationalDomain, Rational}; use ocas_poly::sparse::Lex; use ocas_poly::{Algorithm, SparseMultivariatePolynomial, groebner_basis}; use ocas_poly::ideal::{solve_polynomial_system, PolynomialSystemSolution}; let d = RationalDomain; // x² + y² - 1, x - y → 解在 (±1/√2, ±1/√2) let f1 = SparseMultivariatePolynomial::<_, Lex>::from_terms(d, 2, vec![ (vec![2, 0], Rational::new(1, 1)), (vec![0, 2], Rational::new(1, 1)), (vec![0, 0], Rational::new(-1, 1)), ]); let f2 = SparseMultivariatePolynomial::<_, Lex>::from_terms(d, 2, vec![ (vec![1, 0], Rational::new(1, 1)), (vec![0, 1], Rational::new(-1, 1)), ]); let sol = solve_polynomial_system(&[f1, f2], Algorithm::Auto); match sol { PolynomialSystemSolution::ZeroDimensional(z) => { assert_eq!(z.solutions.len(), 2); } _ => panic!("expected zero-dimensional"), } }
参见:PolynomialSystemSolution、is_zero_dimensional
primary_decomposition
#![allow(unused)] fn main() { pub fn primary_decomposition( generators: &[SparseMultivariatePolynomial<RationalDomain, Lex>], ) -> Vec<PrimaryComponent> }
功能:计算理想的准素分解。
- 零维理想:对 Lex GB 中第一个变量的一元多项式进行因式分解,通过饱和分离准素分量(对每个因子 $f_i$,计算 $I : (\prod_{j \neq i} f_j)^\infty$)。
- 正维理想:保守返回单一准素分量(原 GB 自身作为 primary 和 prime)。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
generators | &[SparseMultivariatePolynomial<RationalDomain, Lex>] | 理想的生成元 |
返回值:Vec<PrimaryComponent> — 准素分量列表。
示例:
#![allow(unused)] fn main() { use ocas_domain::{RationalDomain, Rational}; use ocas_poly::sparse::Lex; use ocas_poly::ideal::primary_decomposition; use ocas_poly::SparseMultivariatePolynomial; let d = RationalDomain; // (x², xy) 为正维理想 → 保守返回单一分量 // (理想论分解为 (x², xy) = (x) ∩ (x², y)) let f1 = SparseMultivariatePolynomial::<_, Lex>::from_terms(d, 2, vec![ (vec![2, 0], Rational::new(1, 1)), ]); let f2 = SparseMultivariatePolynomial::<_, Lex>::from_terms(d, 2, vec![ (vec![1, 1], Rational::new(1, 1)), ]); let decomp = primary_decomposition(&[f1, f2]); assert!(decomp.len() >= 1); }
参见:PrimaryComponent、ideal_radical
is_prime_ideal
#![allow(unused)] fn main() { pub fn is_prime_ideal( generators: &[SparseMultivariatePolynomial<RationalDomain, Lex>], ) -> bool }
功能:测试理想是否为素理想。
- 零维理想:检查 Lex GB 中的一元多项式是否不可约(对次数 ≤ 3 的多项式,使用有理根定理检测)。
- 正维理想:保守返回
false(完整实现需要检查簇的不可约性,尚未实现)。
注意:这是一个保守近似——永远不会返回假阳性(非素理想报告为素),只可能返回假阴性(素理想报告为非素)。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
generators | &[SparseMultivariatePolynomial<RationalDomain, Lex>] | 理想的生成元 |
返回值:bool — true 表示(已确认的)素理想。
参见:is_primary_ideal、primary_decomposition
is_primary_ideal
#![allow(unused)] fn main() { pub fn is_primary_ideal( generators: &[SparseMultivariatePolynomial<RationalDomain, Lex>], ) -> bool }
功能:测试理想是否为准素理想。准素理想恰有一个伴随素理想,即 primary_decomposition 返回的分量数 ≤ 1。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
generators | &[SparseMultivariatePolynomial<RationalDomain, Lex>] | 理想的生成元 |
返回值:bool — true 表示准素理想。
参见:primary_decomposition、is_prime_ideal
PrimaryComponent
#![allow(unused)] fn main() { #[derive(Debug, Clone)] pub struct PrimaryComponent { pub primary: Vec<SparseMultivariatePolynomial<RationalDomain, Lex>>, pub prime: Vec<SparseMultivariatePolynomial<RationalDomain, Lex>>, } }
功能:理想的准素分量——一个准素理想及其伴随素理想(即准素理想的根式)。
字段:
| 字段 | 类型 | 说明 |
|---|---|---|
primary | Vec<SparseMultivariatePolynomial<RationalDomain, Lex>> | 准素理想的生成元 |
prime | Vec<SparseMultivariatePolynomial<RationalDomain, Lex>> | 伴随素理想(根式)的生成元 |
PolynomialSystemSolution
#![allow(unused)] fn main() { #[derive(Debug, Clone)] pub enum PolynomialSystemSolution { ZeroDimensional(ZeroDimSolutions), PositiveDimensional(GroebnerBasis<RationalDomain, Lex>), Empty, } }
功能:多项式系统求解的结果,按解集类型区分。
变体:
| 变体 | 说明 |
|---|---|
ZeroDimensional(ZeroDimSolutions) | 有限个实解(零维理想) |
PositiveDimensional(GroebnerBasis<RationalDomain, Lex>) | 无穷解集(正维理想),返回 Lex 序的 Gröbner 基 |
Empty | 无解(理想为 $\langle 1 \rangle$) |
ZeroDimSolutions
#![allow(unused)] fn main() { #[derive(Debug, Clone)] pub struct ZeroDimSolutions { pub solutions: Vec<RealSolution>, pub vector_space_dimension: usize, } }
字段:
| 字段 | 类型 | 说明 |
|---|---|---|
solutions | Vec<RealSolution> | 找到的实解列表 |
vector_space_dimension | usize | 商环 $k[x_1,\dots,x_n]/I$ 的向量空间维数($\mathbb{C}$ 上解的总数,计重数) |
RealSolution
#![allow(unused)] fn main() { #[derive(Debug, Clone)] pub struct RealSolution { pub values: Vec<f64>, pub multiplicity: usize, } }
字段:
| 字段 | 类型 | 说明 |
|---|---|---|
values | Vec<f64> | 各变量的值(每个变量一个) |
multiplicity | usize | 解的代数重数 |
模块路径速查
| 函数/类型 | 完整路径 |
|---|---|
Algorithm | ocas_poly::Algorithm |
GroebnerBasis | ocas_poly::GroebnerBasis |
groebner_basis | ocas_poly::groebner_basis |
buchberger | ocas_poly::buchberger |
eliminate | ocas_poly::eliminate |
f4::f4 | ocas_poly::groebner::f4::f4 |
f5::f5 | ocas_poly::groebner::f5::f5 |
fglm | ocas_poly::groebner::fglm::fglm |
HilbertSeries | ocas_poly::groebner::hilbert::HilbertSeries |
hilbert_series | ocas_poly::groebner::hilbert::hilbert_series |
ideal_contains | ocas_poly::ideal::ideal_contains |
ideal_sum | ocas_poly::ideal::ideal_sum |
ideal_product | ocas_poly::ideal::ideal_product |
ideal_quotient | ocas_poly::ideal::ideal_quotient |
ideal_saturate | ocas_poly::ideal::ideal_saturate |
ideal_intersection | ocas_poly::ideal::ideal_intersection |
ideal_radical | ocas_poly::ideal::ideal_radical |
primary_decomposition | ocas_poly::ideal::primary_decomposition |
is_zero_dimensional | ocas_poly::ideal::is_zero_dimensional |
solve_polynomial_system | ocas_poly::ideal::solve_polynomial_system |
is_prime_ideal | ocas_poly::ideal::is_prime_ideal |
is_primary_ideal | ocas_poly::ideal::is_primary_ideal |
PrimaryComponent | ocas_poly::ideal::PrimaryComponent |
PolynomialSystemSolution | ocas_poly::ideal::PolynomialSystemSolution |
ZeroDimSolutions | ocas_poly::ideal::ZeroDimSolutions |
RealSolution | ocas_poly::ideal::RealSolution |
因式分解
本章记录 oCAS 中多项式因式分解的完整 API,覆盖一元 $\mathbb{Z}[x]$、一元 $\mathbb{F}_p[x]$、多元 $\mathbb{Z}[x_1,\dots,x_n]$、多元 $\mathbb{F}_p[x_1,\dots,x_n]$、代数数域 $\mathbb{Q}(\alpha)[x]$ 上的因式分解,以及有理函数算术与结式。
相关模块:
| 模块路径 | 功能 |
|---|---|
ocas_poly::factor | 因式分解顶层入口 |
ocas_poly::factor::hensel | Hensel 提升与 Zassenhaus 重组($\mathbb{Z}[x]$) |
ocas_poly::factor::finite_field | $\mathbb{F}_p[x]$ 因式分解(Cantor–Zassenhaus + Berlekamp) |
ocas_poly::factor::multivariate | 双变量 Hensel 提升(Wang 算法) |
ocas_poly::factor::eez | 多元 EEZ Hensel 提升(Wang + 首项系数重建) |
ocas_poly::factor::algebraic | 代数数域因式分解(Trager 算法) |
ocas_poly::gcd | GCD(伪余式 + Euclid 算法) |
ocas_poly::gcd::modular | 模 GCD(Brown 1971) |
ocas_poly::resultant | 结式(Brown PRS) |
ocas_poly::rational | 有理函数分式域 |
ocas_calc::partial_fraction | 部分分式分解 |
类型别名
#![allow(unused)] fn main() { /// 无平方分解结果:(因子, 重数) 对列表。 pub type SquareFreeFactors<D> = Vec<(DenseUnivariatePolynomial<D>, usize)>; /// 完全因式分解结果:(不可约因子, 重数) 对列表。 pub type Factors<D> = Vec<(DenseUnivariatePolynomial<D>, usize)>; }
一、一元整数多项式因式分解 $\mathbb{Z}[x]$
DenseUnivariatePolynomial::factor
签名:pub fn factor(&self) -> Factors<IntegerDomain>
功能:将一元整数多项式分解为不可约因子(在 $\mathbb{Q}$ 上不可约的本原多项式)及其重数。
参数:无(self 为待分解多项式)。
返回值:Vec<(DenseUnivariatePolynomial<IntegerDomain>, usize)> —— 不可约因子与重数对列表。各因子为本原多项式且首项系数为正。所有因子的乘积(取各自重数幂)等于 self 的本原部分 primitive_part(self);输入必须为本原多项式(content = 1),否则内容会被丢弃。
算法流程:
- 输入须为本原多项式(content = 1,可用
primitive_part()预处理)。 - 无平方分解(Yun 算法):$\gcd(f, f')$ 分离重数。
- 对每个无平方分量调用
factor_square_free(内部对非首一情形使用首项系数变换,再调用factor_square_free_monic):- 选取素数 $p$($p \nmid \mathrm{lc}(f)$,$f \bmod p$ 无平方)。
- 在 $\mathbb{F}_p[x]$ 中分解为首一不可约因子(Cantor–Zassenhaus)。
- 计算 Mignotte 界 $B = 2^n |f|_2$。
- 线性 Hensel 提升 $p \to p^k$($p^k > 2B$)。
- Zassenhaus 子集重组:枚举提升因子的子集,试除验证。
- 非首一多项式通过首项系数变换 $a^{d-1} f(x/a)$ 处理。
示例:
#![allow(unused)] fn main() { use ocas_domain::{Integer, IntegerDomain}; use ocas_poly::DenseUnivariatePolynomial; let d = IntegerDomain; // x^2 - 1 = (x-1)(x+1) let p = DenseUnivariatePolynomial::from_coeffs(d, vec![ Integer::from(-1), Integer::from(0), Integer::from(1), ]); let factors = p.factor(); assert_eq!(factors.len(), 2); // 每个因子次数为 1,重数为 1 for (g, m) in &factors { assert_eq!(g.degree(), Some(1)); assert_eq!(*m, 1); } // 输出:[(x - 1, 1), (x + 1, 1)] }
参见:square_free_factorization、factor_over_finite_field、多项式 GCD 与因式分解
DenseUnivariatePolynomial::square_free_factorization
签名:pub fn square_free_factorization(&self) -> SquareFreeFactors<D>
功能:计算多项式的无平方分解。返回互不相同的无平方因子及其在原多项式中的重数。
参数:无(self 为待分解多项式)。
返回值:Vec<(DenseUnivariatePolynomial<D>, usize)> —— 无平方因子与重数对列表。
算法:Yun 算法(特征 0)。令 $g = \gcd(f, f')$,$w = f/g$,迭代 $h = \gcd(w, g)$,$z = w/h$ 收集重数 $k$ 的因子。
示例:
#![allow(unused)] fn main() { use ocas_domain::{IntegerDomain, Integer}; use ocas_poly::DenseUnivariatePolynomial; let d = IntegerDomain; // (x+1)^2 * (x-1) = x^3 + x^2 - x - 1 let p = DenseUnivariatePolynomial::from_coeffs(d, vec![ Integer::from(-1), Integer::from(-1), Integer::from(1), Integer::from(1), ]); let factors = p.square_free_factorization(); assert_eq!(factors.len(), 2); // 输出:[(x - 1, 1), (x + 1, 2)](Yun 算法先收集重数 1 的因子) }
DenseUnivariatePolynomial::is_square_free
签名:pub fn is_square_free(&self) -> bool
功能:判断多项式是否无平方(即 $\gcd(f, f') = 1$)。
参数:无。
返回值:bool —— 无平方返回 true。
示例:
#![allow(unused)] fn main() { use ocas_domain::{IntegerDomain, Integer}; use ocas_poly::DenseUnivariatePolynomial; let d = IntegerDomain; // x^2 - 1 无平方 let p = DenseUnivariatePolynomial::from_coeffs(d, vec![ Integer::from(-1), Integer::from(0), Integer::from(1), ]); assert!(p.is_square_free()); // (x+1)^2 不是无平方的 let q = DenseUnivariatePolynomial::from_coeffs(d, vec![ Integer::from(1), Integer::from(2), Integer::from(1), ]); assert!(!q.is_square_free()); }
二、一元有限域多项式因式分解 $\mathbb{F}_p[x]$
factor_over_finite_field
签名:pub fn factor_over_finite_field(f: &FpPoly) -> Vec<(FpPoly, usize)>
功能:将 $\mathbb{F}_p[x]$ 中的多项式分解为首一不可约因子及重数。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
f | &DenseUnivariatePolynomial<FiniteField> | 待分解多项式 |
返回值:Vec<(FpPoly, usize)> —— 首一不可约因子与重数对列表。首项系数作为常数因子(重数 1)单独列出。
算法流程:
- 无平方分解(Musser/Bernardin 算法,处理特征 $p$ 的 $p$ 次根情况)。
- 对每个无平方分量:
- DDF(分次分解):利用 Frobenius 映射 $x \mapsto x^{p^d} \bmod f$,将因子按次数分组。
- EDF(等次分解):对奇特征 $p$,随机选取 $a$,计算 $\gcd(f, a^{(p^d-1)/2} - 1)$;对特征 2,使用迹映射。
- 小素数时也可使用 Berlekamp 算法(Frobenius 矩阵 $Q^T - I$ 的核空间)。
示例:
#![allow(unused)] fn main() { use num_bigint::BigInt; use ocas_domain::{Domain, FiniteField}; use ocas_poly::DenseUnivariatePolynomial; use ocas_poly::factor::finite_field::factor_over_finite_field; let f = FiniteField::new(BigInt::from(5)); // x^2 - 1 = (x-1)(x+1) over F_5 let p = DenseUnivariatePolynomial::from_coeffs( f.clone(), vec![f.element(4), f.element(0), f.element(1)]); let factors = factor_over_finite_field(&p); let linear_count = factors.iter() .filter(|(g, _)| g.degree() == Some(1)).count(); assert_eq!(linear_count, 2); // 输出:两个一次首一因子 }
DenseUnivariatePolynomial::factor (FiniteField)
签名:pub fn factor(&self) -> Factors<FiniteField>
功能:DenseUnivariatePolynomial<FiniteField> 上的 factor() 方法,等价于调用 factor_over_finite_field。
示例:
#![allow(unused)] fn main() { use num_bigint::BigInt; use ocas_domain::{Domain, FiniteField}; use ocas_poly::DenseUnivariatePolynomial; let f = FiniteField::new(BigInt::from(5)); // x^2 - 1 over F_5 let p = DenseUnivariatePolynomial::from_coeffs( f.clone(), vec![f.element(4), f.element(0), f.element(1)]); let factors = p.factor(); assert!(!factors.is_empty()); }
berlekamp
签名:pub fn berlekamp(f: &FpPoly) -> Vec<FpPoly>
功能:Berlekamp 因式分解算法,适用于小素数域。构造 Frobenius 矩阵 $Q$($Q[i][j]$ 为 $x^{ip} \bmod f$ 的 $x^j$ 系数),求 $Q^T - I$ 的核空间;每个非零核向量 $v$ 满足 $v^p \equiv v \pmod{f}$,通过 $\gcd(f, v - a)$($a \in \mathbb{F}_p$)分裂因子。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
f | &FpPoly | 首一无平方多项式 |
返回值:Vec<FpPoly> —— 首一不可约因子列表。
参见:Berlekamp (1970)
cantor_zassenhaus
签名:pub fn cantor_zassenhaus(f: &FpPoly) -> Vec<FpPoly>
功能:Cantor–Zassenhaus 因式分解算法。先 DDF(分次分解),再 EDF(等次分解)。返回首一无平方多项式的首一不可约因子列表。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
f | &FpPoly | 首一无平方多项式 |
返回值:Vec<FpPoly> —— 首一不可约因子列表。
参见:Cantor & Zassenhaus (1981)
poly_pow_mod
签名:pub fn poly_pow_mod(base: &FpPoly, exp: &BigInt, modulus: &FpPoly) -> FpPoly
功能:计算 $\text{base}^{\text{exp}} \bmod \text{modulus}$,使用快速幂(重复平方),每次乘法后取模。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
base | &FpPoly | 底数多项式 |
exp | &BigInt | 指数(非负) |
modulus | &FpPoly | 模多项式(非零) |
返回值:FpPoly —— 模幂结果。
示例:
#![allow(unused)] fn main() { use num_bigint::BigInt; use ocas_domain::{Domain, FiniteField}; use ocas_poly::DenseUnivariatePolynomial; use ocas_poly::factor::finite_field::poly_pow_mod; let f = FiniteField::new(BigInt::from(7)); let m = DenseUnivariatePolynomial::from_coeffs( f.clone(), vec![f.element(1), f.element(0), f.element(1)]); // x^2 + 1 let base = DenseUnivariatePolynomial::from_coeffs( f.clone(), vec![f.element(0), f.element(1)]); // x // x^2 mod (x^2+1) = -1 = 6 in F_7 let r = poly_pow_mod(&base, &BigInt::from(2), &m); assert_eq!(r.coeff(0).cloned(), Some(f.element(6))); // 输出:6(即 -1 mod 7) }
三、多元整数多项式因式分解 $\mathbb{Z}[x_1,\dots,x_n]$
SparseMultivariatePolynomial::factor (IntegerDomain, Lex)
签名:pub fn factor(&self) -> Vec<(Self, usize)>
功能:将稀疏多元整数多项式分解为不可约因子及重数。
参数:无(self 为待分解的 SparseMultivariatePolynomial<IntegerDomain, Lex>)。
返回值:Vec<(SparseMultivariatePolynomial<IntegerDomain, Lex>, usize)> —— 不可约因子与重数对列表。
算法选择:
- $n \geq 3$ 变量:使用 EEZ Hensel 提升(Wang 首项系数重建 + p-adic 系数 Hensel 提升 + Zassenhaus 重组)。
- 双变量 + 常数首项系数:使用双变量 Hensel 提升(评估–提升路径)。
- 双变量 + 非常数首项系数:回退到 EEZ 路径。
算法流程(EEZ 路径,$n \geq 3$):
- 提取主变量内容,无平方分解。
- 选取样本点 $(a_1, \dots, a_n)$,使单变量像 $f(x_0, a_1, \dots)$ 次数保持且无平方。
- 在 $\mathbb{Z}$ 上分解单变量像。
- Wang 首项系数重建:将总首项系数的不可约因子分配给各单变量因子。
- EEZ 逐变量 Hensel 提升:依次恢复 $x_1, x_2, \dots$,每步解多元 Diophantine 方程。
- p-adic 系数 Hensel 提升:从模 $p$ 提升到足够大的 $p^k$。
- Zassenhaus 子集重组:枚举模因子子集,试除验证。
示例:
#![allow(unused)] fn main() { use ocas_domain::{Integer, IntegerDomain}; use ocas_poly::SparseMultivariatePolynomial; use ocas_poly::Lex; // (x^2 + y + 1)(x + y + 2) 在 Z[x,y] 中 let f = SparseMultivariatePolynomial::<_, Lex>::from_terms( IntegerDomain, 2, vec![ (vec![3, 0], Integer::from(1)), // x^3 (vec![2, 1], Integer::from(1)), // x^2 y (vec![2, 0], Integer::from(2)), // 2x^2 (vec![1, 1], Integer::from(1)), // xy (vec![1, 0], Integer::from(1)), // x (vec![0, 2], Integer::from(1)), // y^2 (vec![0, 1], Integer::from(3)), // 3y (vec![0, 0], Integer::from(2)), // 2 ], ); let factors = f.factor(); assert!(factors.len() >= 2); // 输出:[(x^2 + y + 1, 1), (x + y + 2, 1)] }
参见:Wang EEZ 算法、bivariate_factor_z
bivariate_factor_z
签名:pub fn bivariate_factor_z(f: &ZMPoly, x_var: usize, y_var: usize) -> Vec<(ZMPoly, usize)>
功能:将双变量整数多项式分解为不可约因子及重数。要求 $x$ 的首项系数为非零整数常数。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
f | &SparseMultivariatePolynomial<IntegerDomain, Lex> | 待分解多项式 |
x_var | usize | 主变量索引 |
y_var | usize | 次变量索引 |
返回值:Vec<(ZMPoly, usize)> —— 不可约因子与重数对。
算法:
- 双变量无平方分解(启发式双变量 GCD)。
- 选取 $y = \alpha$ 使单变量像 $f(x, \alpha)$ 无平方且因子数最少。
- Hensel 提升:从 $f(x, \alpha)$ 的因子提升回双变量,使用 Taylor 展开逐次修正。
参见:Wang (1978)
multivariate_factor_z
签名:pub fn multivariate_factor_z(f: &ZmPoly) -> Vec<(ZmPoly, usize)>
功能:EEZ Hensel 提升多元因式分解入口。支持非恒定首项系数(通过 Wang 首项系数重建)。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
f | &SparseMultivariatePolynomial<IntegerDomain, Lex> | 待分解多项式 |
返回值:Vec<(ZmPoly, usize)> —— 不可约因子与重数对。
四、多元有限域多项式因式分解 $\mathbb{F}_p[x_1,\dots,x_n]$
SparseMultivariatePolynomial::factor (FiniteField, Lex)
签名:pub fn factor(&self) -> Vec<(Self, usize)>
功能:将稀疏多元有限域多项式分解为不可约因子及重数。
参数:无。
返回值:Vec<(SparseMultivariatePolynomial<FiniteField, Lex>, usize)> —— 不可约因子与重数对列表。
算法选择:
- $n \geq 3$ 变量:EEZ Hensel 提升。
- 双变量:评估–Hensel 路径。
示例:
#![allow(unused)] fn main() { use num_bigint::BigInt; use ocas_domain::{Domain, FiniteField}; use ocas_poly::SparseMultivariatePolynomial; use ocas_poly::Lex; let fp = FiniteField::new(BigInt::from(7)); // x*y + 1 over F_7[x,y] — 已不可约 let f = SparseMultivariatePolynomial::<_, Lex>::from_terms( fp.clone(), 2, vec![ (vec![1, 1], fp.element(1)), // xy (vec![0, 0], fp.element(1)), // 1 ], ); let factors = f.factor(); assert_eq!(factors.len(), 1); assert_eq!(factors[0].1, 1); // 重数 1 }
bivariate_factor_fp
签名:pub fn bivariate_factor_fp(f: &FpMPoly, x_var: usize, y_var: usize) -> Vec<(FpMPoly, usize)>
功能:将双变量 $\mathbb{F}_p$ 多项式分解为不可约因子及重数。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
f | &SparseMultivariatePolynomial<FiniteField, Lex> | 待分解多项式 |
x_var | usize | 主变量索引 |
y_var | usize | 次变量索引 |
返回值:Vec<(FpMPoly, usize)> —— 不可约因子与重数对。
multivariate_factor_fp
签名:pub fn multivariate_factor_fp(f: &FpMPoly) -> Vec<(FpMPoly, usize)>
功能:多元 $\mathbb{F}_p$ 因式分解入口,支持非恒定首项系数(Wang 首项系数重建 + EEZ 提升)。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
f | &SparseMultivariatePolynomial<FiniteField, Lex> | 待分解多项式 |
返回值:Vec<(FpMPoly, usize)> —— 不可约因子与重数对。
五、代数数域因式分解 $\mathbb{Q}(\alpha)[x]$
DenseUnivariatePolynomial::factor (AlgebraicNumberField)
签名:pub fn factor(&self) -> Factors<AlgebraicNumberField>
功能:将代数数域 $\mathbb{Q}(\alpha)$ 上的多项式分解为首一不可约因子及重数(Trager 算法)。
参数:无(self 为 DenseUnivariatePolynomial<AlgebraicNumberField>)。
返回值:Vec<(DenseUnivariatePolynomial<AlgebraicNumberField>, usize)> —— 首一不可约因子与重数对。各因子的乘积(取重数幂)等于 self 除以首项系数($K$ 的单位)。
算法流程(Trager):
- 无平方分解(Yun 算法,使用模 GCD
gcd_anf避免系数爆炸)。 - 对每个无平方分量调用
factor_square_free_anf:- Trager 平移:找 $s \geq 0$ 使 $f(x - s\alpha)$ 的范数无平方。
- 范数计算:$\operatorname{Res}_\alpha(m(\alpha), f(x, \alpha))$,通过求值–插值。
- $\mathbb{Q}$ 上分解范数:使用 Hensel 路径。
- 恢复 $K$ 上因子:对每个范数因子 $g_i$ 计算 $\gcd_K(f, g_i(\alpha))$。
- GCD 使用模方法(Encarnación):映射到 $\mathrm{GF}(p^d)$,CRT 合并,有理重构,试除验证。
示例:
#![allow(unused)] fn main() { use ocas_domain::{AlgebraicNumberField, Domain, Rational, RationalDomain}; use ocas_poly::DenseUnivariatePolynomial; // 构造 Q(√2),极小多项式 x^2 - 2 let field = AlgebraicNumberField::new( RationalDomain, vec![Rational::new(-2, 1), Rational::new(0, 1), Rational::new(1, 1)], ); // x^2 - 2 在 Q(√2) 上可分解为 (x - √2)(x + √2) let f = DenseUnivariatePolynomial::from_coeffs( field.clone(), vec![ field.from_base(Rational::new(-2, 1)), field.zero(), field.one(), ], ); let factors = f.factor(); assert_eq!(factors.len(), 2); assert!(factors.iter().all(|(g, m)| *m == 1 && g.degree() == Some(1))); // 输出:[(x - √2, 1), (x + √2, 1)] }
norm_with_shift
签名:pub(crate) fn norm_with_shift(field: &AlgebraicNumberField, f: &UP<AlgebraicNumberField>) -> Option<(u64, UP<AlgebraicNumberField>, UP<RationalDomain>)>
功能:Trager 平移:找 $s \geq 0$ 使 $f(x - s\alpha)$ 的范数在 $\mathbb{Q}$ 上无平方。返回 (s, g, norm)。最多尝试 MAX_TRAGER_SHIFTS(100)次。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
field | &AlgebraicNumberField | 代数数域 $\mathbb{Q}(\alpha)$ |
f | &DenseUnivariatePolynomial<AlgebraicNumberField> | 待分解多项式 |
返回值:Option<(u64, UP<AlgebraicNumberField>, UP<RationalDomain>)> —— (s, f(x - s\alpha), \operatorname{Norm}(f(x - s\alpha)))。
gcd_anf
签名:pub(crate) fn gcd_anf(field: &AlgebraicNumberField, a: &UP<AlgebraicNumberField>, b: &UP<AlgebraicNumberField>) -> UP<AlgebraicNumberField>
功能:代数数域上两个一元多项式的 GCD。使用模方法(Encarnación):映射到 $\mathrm{GF}(p^d)$,组合首一模 GCD(CRT),有理重构系数,试除验证。最多使用 MAX_ANF_GCD_PRIMES(1000)个素数,超出后回退到稠密 Euclid GCD。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
field | &AlgebraicNumberField | 代数数域 |
a | &UP<AlgebraicNumberField> | 第一个多项式 |
b | &UP<AlgebraicNumberField> | 第二个多项式 |
返回值:首一 GCD 多项式。
六、多项式 GCD
DenseUnivariatePolynomial::gcd
签名:pub fn gcd(&self, other: &Self) -> Self
功能:计算两个一元多项式的最大公因子。对非域系数使用伪余式 Euclid 算法。结果为本原多项式。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
other | &Self | 另一个多项式 |
返回值:本原 GCD 多项式。
示例:
#![allow(unused)] fn main() { use ocas_domain::{IntegerDomain, Integer}; use ocas_poly::DenseUnivariatePolynomial; let d = IntegerDomain; // x^2 - 1 = (x-1)(x+1) let a = DenseUnivariatePolynomial::from_coeffs(d, vec![ Integer::from(-1), Integer::from(0), Integer::from(1), ]); // x^2 + 2x + 1 = (x+1)^2 let b = DenseUnivariatePolynomial::from_coeffs(d, vec![ Integer::from(1), Integer::from(2), Integer::from(1), ]); let g = a.gcd(&b); assert_eq!(g.coeffs(), &[Integer::from(1), Integer::from(1)]); // 输出:x + 1 }
gcd_modular_z
签名:pub fn gcd_modular_z(a: &ZPoly, b: &ZPoly) -> ZPoly
功能:Brown 模 GCD 算法。在多个素数 $p$ 上计算 $\mathbb{F}_p[x]$ 中的首一 GCD,用 CRT 合并为 $\mathbb{Z}[x]$ 中的本原 GCD,试除验证。比伪余式 GCD 在大系数时高效得多。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
a | &DenseUnivariatePolynomial<IntegerDomain> | 第一个多项式 |
b | &DenseUnivariatePolynomial<IntegerDomain> | 第二个多项式 |
返回值:本原 GCD 多项式。
算法细节:
- 选取素数 $p > 2^{30}$(避免小素数问题)。
- 在 $\mathbb{F}_p[x]$ 中计算首一 GCD,缩放 $\gamma = \gcd(\mathrm{lc}(a), \mathrm{lc}(b))$。
- CRT 合并(对称代表),丢弃"不幸素数"(GCD 次数高于真值)。
- 精确试除验证。最多尝试
MAX_PRIMES(10000)个素数,超出后回退到伪余式。
示例:
#![allow(unused)] fn main() { use ocas_domain::{IntegerDomain, Integer}; use ocas_poly::DenseUnivariatePolynomial; use ocas_poly::gcd::modular::gcd_modular_z; let d = IntegerDomain; let i = |v: i64| Integer::from(v); let a = DenseUnivariatePolynomial::from_coeffs(d, vec![i(-1), i(0), i(1)]); let b = DenseUnivariatePolynomial::from_coeffs(d, vec![i(1), i(2), i(1)]); let g = gcd_modular_z(&a, &b); assert_eq!(g.coeffs(), &[i(1), i(1)]); // x + 1 // 输出:x + 1 }
DenseUnivariatePolynomial::content
签名:pub fn content(&self) -> D::Element
功能:计算多项式的内容(所有系数的 GCD)。零多项式的内容为零。
DenseUnivariatePolynomial::primitive_part
签名:pub fn primitive_part(&self) -> Self
功能:返回本原部分(多项式除以内容)。
七、结式
DenseUnivariatePolynomial::resultant
签名:pub fn resultant(&self, other: &Self) -> D::Element
功能:使用 Brown PRS(多项式余式序列)算法计算两个一元多项式的结式 $\operatorname{Res}(a, b)$。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
other | &Self | 另一个多项式 |
返回值:D::Element —— 结式标量。当且仅当 $\gcd(a, b)$ 非常数时为零。
算法:子结式 PRS(Brown),每步精确除以 $\beta$(在 UFD 中子结式定理保证整除)。
示例:
#![allow(unused)] fn main() { use ocas_domain::{IntegerDomain, Integer}; use ocas_poly::DenseUnivariatePolynomial; let d = IntegerDomain; // Res(x - 1, x - 2) = 1 - 2 = -1 let a = DenseUnivariatePolynomial::from_coeffs(d, vec![ Integer::from(-1), Integer::from(1), ]); let b = DenseUnivariatePolynomial::from_coeffs(d, vec![ Integer::from(-2), Integer::from(1), ]); assert_eq!(a.resultant(&b), Integer::from(-1)); // 输出:-1 }
示例(有公共根):
#![allow(unused)] fn main() { use ocas_domain::{IntegerDomain, Integer}; use ocas_poly::DenseUnivariatePolynomial; let d = IntegerDomain; // x - 1 和 x^2 - 1 共享根 x = 1 let a = DenseUnivariatePolynomial::from_coeffs(d, vec![ Integer::from(-1), Integer::from(1), ]); let b = DenseUnivariatePolynomial::from_coeffs(d, vec![ Integer::from(-1), Integer::from(0), Integer::from(1), ]); assert_eq!(a.resultant(&b), Integer::from(0)); // 输出:0(有公共因子 x - 1) }
参见:结式与子结式 PRS
八、有理函数分式域
RationalPolynomial
签名:
#![allow(unused)] fn main() { pub struct RationalPolynomial<D: Domain, O: MonomialOrder = Grevlex> { pub numerator: SparseMultivariatePolynomial<D, O>, pub denominator: SparseMultivariatePolynomial<D, O>, } }
功能:多项式分式域元素 $\frac{n}{d}$,始终维持规范形式:分子与分母互素,分母首项系数为正(有序域)或 1(有限域)。
构造方法
RationalPolynomial::new
#![allow(unused)] fn main() { pub fn new( numerator: SparseMultivariatePolynomial<D, O>, denominator: SparseMultivariatePolynomial<D, O>, ) -> Self }
创建有理多项式。不自动约简——需要规范形式时使用 from_num_den。
RationalPolynomial::from_num_den
#![allow(unused)] fn main() { pub fn from_num_den( numerator: SparseMultivariatePolynomial<D, O>, denominator: SparseMultivariatePolynomial<D, O>, ) -> Self }
从分子分母创建并自动约简(GCD 化简 + 分母首项系数归一化)。
RationalPolynomial::from_polynomial
#![allow(unused)] fn main() { pub fn from_polynomial(poly: SparseMultivariatePolynomial<D, O>) -> Self }
从多项式创建(分母 = 1)。
查询方法
| 方法 | 签名 | 说明 |
|---|---|---|
is_zero | &self -> bool | 分子是否为零 |
is_one | &self -> bool | 是否为 1/1 |
n_vars | &self -> usize | 变量数 |
domain | &self -> &D | 系数域引用 |
算术运算
| 方法 | 签名 | 说明 |
|---|---|---|
add | (&self, &Self) -> Self | 加法(分母 GCD 策略,减少中间膨胀) |
sub | (&self, &Self) -> Self | 减法 |
mul | (&self, &Self) -> Self | 乘法(交叉消去) |
div | (&self, &Self) -> Option<Self> | 除法(除数分子为零时返回 None) |
neg | &self -> Self | 取反 $-\frac{n}{d}$ |
inv | &self -> Option<Self> | 取逆(分子为零时返回 None) |
pow | (&self, k: u32) -> Self | 幂 $\left(\frac{n}{d}\right)^k$ |
示例:
#![allow(unused)] fn main() { use ocas_domain::{IntegerDomain, Integer}; use ocas_poly::SparseMultivariatePolynomial; use ocas_poly::rational::RationalPolynomial; use ocas_poly::Grevlex; let d = IntegerDomain; let n_vars = 2; // x / y let num: SparseMultivariatePolynomial<_, Grevlex> = SparseMultivariatePolynomial::from_terms(d, n_vars, vec![(vec![1, 0], Integer::from(1))]); let den: SparseMultivariatePolynomial<_, Grevlex> = SparseMultivariatePolynomial::from_terms(d, n_vars, vec![(vec![0, 1], Integer::from(1))]); let f = RationalPolynomial::from_num_den(num, den); // y / x let num2: SparseMultivariatePolynomial<_, Grevlex> = SparseMultivariatePolynomial::from_terms(d, n_vars, vec![(vec![0, 1], Integer::from(1))]); let den2: SparseMultivariatePolynomial<_, Grevlex> = SparseMultivariatePolynomial::from_terms(d, n_vars, vec![(vec![1, 0], Integer::from(1))]); let g = RationalPolynomial::from_num_den(num2, den2); // (x/y) * (y/x) = 1 let h = f.mul(&g); assert!(h.is_one()); // 输出:1 }
九、部分分式分解
apart
签名:
#![allow(unused)] fn main() { pub fn apart<D: EuclideanDomain>( num: &DenseUnivariatePolynomial<D>, den: &DenseUnivariatePolynomial<D>, ) -> ( Option<DenseUnivariatePolynomial<D>>, Vec<PartialFractionTerm<D>>, ) }
功能:对 $\frac{\text{num}}{\text{den}}$ 进行部分分式分解。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
num | &DenseUnivariatePolynomial<D> | 分子多项式 |
den | &DenseUnivariatePolynomial<D> | 分母多项式(非零) |
返回值:(Option<poly_part>, Vec<PartialFractionTerm<D>>) —— 可选的多项式部分与部分分式项列表,满足:
$$\frac{\text{num}}{\text{den}} = \text{poly_part} + \sum_i \frac{\text{numer}_i}{\text{denom}_i^{\text{exp}_i}}$$
示例:
#![allow(unused)] fn main() { use ocas_domain::{RationalDomain, Rational}; use ocas_poly::DenseUnivariatePolynomial; use ocas_calc::partial_fraction::apart; let d = RationalDomain; // 1 / (x^2 - 1) let num = DenseUnivariatePolynomial::from_coeffs(d, vec![Rational::new(1, 1)]); let den = DenseUnivariatePolynomial::from_coeffs(d, vec![ Rational::new(-1, 1), Rational::new(0, 1), Rational::new(1, 1), ]); let (poly_part, terms) = apart(&num, &den); assert!(poly_part.is_none()); // 真分式,无多项式部分 // x^2 - 1 = (x-1)(x+1) 无平方 → 输出按因子分组 // 输出:poly_part = None, terms = [...] }
PartialFractionTerm
签名:
#![allow(unused)] fn main() { pub struct PartialFractionTerm<D: EuclideanDomain> { pub numer: DenseUnivariatePolynomial<D>, // 分子多项式 pub denom: DenseUnivariatePolynomial<D>, // 不可约(无平方)分母因子 pub exp: usize, // 该因子在原分母中的重数 } }
功能:部分分式分解中的单项,表示 $\frac{\text{numer}}{\text{denom}^{\text{exp}}}$。
together
签名:
#![allow(unused)] fn main() { pub fn together<D: EuclideanDomain>( poly_part: Option<&DenseUnivariatePolynomial<D>>, terms: &[PartialFractionTerm<D>], ) -> (DenseUnivariatePolynomial<D>, DenseUnivariatePolynomial<D>) }
功能:将多项式部分和部分分式项合并为单个有理函数 $\frac{n}{d}$。是 apart 的逆操作。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
poly_part | Option<&DenseUnivariatePolynomial<D>> | 多项式部分(可选) |
terms | &[PartialFractionTerm<D>] | 部分分式项列表 |
返回值:(numerator, denominator) —— 合并后的分子分母。
十、单变量多项式辅助方法
DenseUnivariatePolynomial::extended_gcd_poly
签名:pub fn extended_gcd_poly(&self, other: &Self) -> (Self, Self, Self)
功能:扩展 Euclid 算法。返回 $(g, s, t)$ 满足 $s \cdot a + t \cdot b = g = \gcd(a, b)$。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
other | &Self | 另一个多项式 |
返回值:(gcd, s, t)。
DenseUnivariatePolynomial::div_rem
签名:pub fn div_rem(&self, divisor: &Self) -> Option<(Self, Self)>
功能:带余除法。返回 (商, 余式),使 self = quotient * divisor + remainder。除数为零时返回 None。
DenseUnivariatePolynomial::pow
签名:pub fn pow(&self, n: u32) -> Self
功能:重复平方计算 $\text{self}^n$。
DenseUnivariatePolynomial::p_adic_expansion
签名:pub fn p_adic_expansion(&self, p: &Self) -> Vec<Self>
功能:以多项式 $p$ 为基展开 $p$-adic 表示。返回系数列表 $[c_0, c_1, \dots]$ 使 $\text{self} = \sum_i c_i \cdot p^i$,其中 $\deg(c_i) < \deg(p)$。
十一、单项式工具函数
monomial_divides
签名:pub fn monomial_divides(a: &[usize], b: &[usize]) -> bool
功能:判断单项式 $b$ 是否整除 $a$(即 $a$ 是 $b$ 的倍式:$a_i \geq b_i$ 对所有 $i$)。
monomial_lcm
签名:pub fn monomial_lcm(a: &[usize], b: &[usize]) -> SmallVec<[usize; 4]>
功能:计算两个单项式的最小公倍数(逐分量取最大值)。
monomial_are_coprime
签名:pub fn monomial_are_coprime(a: &[usize], b: &[usize]) -> bool
功能:判断两个单项式是否互素(无变量同时出现在两者中)。
十二、内部算法概览
Hensel 提升与 Zassenhaus 重组
文件:ocas-poly/src/factor/hensel.rs
mignotte_bound
#![allow(unused)] fn main() { pub(crate) fn mignotte_bound(f: &ZPoly) -> Integer }
Landau–Mignotte 界:对 $n$ 次多项式 $f$,其任何因子 $g$ 满足 $|g|_\infty \leq 2^n |f|_2$。
factor_square_free_monic
#![allow(unused)] fn main() { pub fn factor_square_free_monic(f: &ZPoly) -> Vec<ZPoly> }
将首一无平方 $\mathbb{Z}[x]$ 多项式分解为首一不可约因子。
factor_square_free
#![allow(unused)] fn main() { pub fn factor_square_free(f: &ZPoly) -> Vec<ZPoly> }
将无平方本原 $\mathbb{Z}[x]$ 多项式分解为不可约因子。非首一输入通过首项系数变换 $a^{d-1} f(x/a)$ 处理。
factor_primitive
#![allow(unused)] fn main() { pub fn factor_primitive(f: &ZPoly) -> Vec<(ZPoly, usize)> }
将本原 $\mathbb{Z}[x]$ 多项式分解为不可约因子及重数(Yun 无平方分解 + factor_square_free_monic)。这是 DenseUnivariatePolynomial::factor() 的内部实现。
EEZ Hensel 提升
文件:ocas-poly/src/factor/eez.rs
关键内部函数:
| 函数 | 说明 |
|---|---|
eez_lift | 泛型 EEZ 提升(域上,首一) |
eez_lift_imposed | 非首一 EEZ 提升(Wang 首项系数施加) |
eez_lift_z | 整数 EEZ 提升($\mathbb{Q}$ 上求解 + 积分性检查) |
coefficient_hensel_lift_z | p-adic 系数 Hensel 提升 |
diophantine | 递归多元 Diophantine 方程求解器 |
sparse_diophantine_fp | 骨架插值稀疏 Diophantine 求解器 |
wang_reconstruct_lcoeffs | Wang 首项系数重建 |
zassenhaus_multivariate | 多元 Zassenhaus 子集重组 |
有限域因式分解
文件:ocas-poly/src/factor/finite_field.rs
| 函数 | 说明 |
|---|---|
factor_over_finite_field | 顶层入口:无平方 + DDF + EDF |
distinct_degree_factorization | DDF:按不可约因子次数分组 |
equal_degree_factorization | EDF:随机分裂等次因子积 |
berlekamp | Berlekamp 算法(小素数) |
cantor_zassenhaus | DDF + EDF 组合 |
square_free_factorization_ff | 特征 $p$ 无平方分解(Musser/Bernardin) |
pth_root_prime | $\mathbb{F}_p$ 上的 $p$ 次根 |
代数数域因式分解
文件:ocas-poly/src/factor/algebraic.rs
| 函数 | 说明 |
|---|---|
factor_anf | 顶层入口:无平方 + Trager |
factor_square_free_anf | Trager:范数 → $\mathbb{Q}$ 分解 → GCD 恢复 |
norm_with_shift | Trager 平移找无平方范数 |
norm_eval_interp | 求值–插值计算范数 |
gcd_anf | 模 GCD over $\mathbb{Q}(\alpha)$ |
square_free_anf | Yun 无平方分解(模 GCD) |
factor_square_free_rationals | $\mathbb{Q}$ 上无平方因子分解(清除分母 → $\mathbb{Z}$ Hensel) |
参见
- 多项式 GCD 与因式分解 — 数学理论与算法详解
- 代数数域与 Galois 理论 — Trager 算法的数学背景
- 多项式 API —
DenseUnivariatePolynomial与SparseMultivariatePolynomial完整 API - 系数域 API —
Integer、FiniteField、AlgebraicNumberField定义
Rust API 参考:数值积分
本章涵盖 ocas-eval crate 中的数值积分功能,核心是 Vegas 自适应蒙特卡洛积分器(Lepage 1978)。所有接口通过 ocas_eval::numeric 模块导出。
模块概览
#![allow(unused)] fn main() { use ocas_eval::numeric::{ Vegas, // 自适应蒙特卡洛积分器 VegasOptions, // 配置参数 IntegrateResult, // 积分结果 Integrator, // 积分器 trait integrate_1d, // 一维便捷函数 StatisticsAccumulator, // 逆方差加权统计累加器 }; }
IntegrateResult
签名:
#![allow(unused)] fn main() { #[derive(Debug, Clone, Copy)] pub struct IntegrateResult { pub integral: f64, pub error: f64, } }
功能:数值积分的返回结果,包含积分估计值和标准误差。
字段:
| 字段 | 类型 | 说明 |
|---|---|---|
integral | f64 | 积分的最佳估计值 |
error | f64 | integral 的标准误差估计 |
示例:
#![allow(unused)] fn main() { use ocas_eval::numeric::integrate_1d; let r = integrate_1d(|x| x * x, 0.0, 1.0, Default::default()); println!("∫₀¹ x² dx ≈ {:.6} ± {:.6}", r.integral, r.error); // 输出:∫₀¹ x² dx ≈ 0.3334 ± 0.0003 (精确值 1/3) }
Integrator
签名:
#![allow(unused)] fn main() { pub trait Integrator { fn integrate<F: Fn(&[f64]) -> f64>(&mut self, f: &F) -> IntegrateResult; } }
功能:数值积分器的统一 trait。被积函数 f 接收单位超立方体 $[0,1]^d$ 中的一个点,返回标量值。物理域上的积分需要在闭包内手动进行线性变量替换。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
f | &F where F: Fn(&[f64]) -> f64 | 被积函数,输入为 $[0,1]^d$ 中的坐标点 |
返回值:IntegrateResult——积分估计值与标准误差。
说明:integrate 接收 &F 而非 F,因此可以多次调用同一闭包而无需重新分配。
VegasOptions
签名:
#![allow(unused)] fn main() { #[derive(Debug, Clone, Copy)] pub struct VegasOptions { pub n_bins: usize, pub n_samples: usize, pub iterations: usize, pub learning_rate: f64, pub seed: u64, } }
功能:Vegas 积分器的调优参数。
字段:
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
n_bins | usize | 64 | 每个维度的分箱数(总网格为各维 1-D 网格的直积) |
n_samples | usize | 10_000 | 每次迭代的采样数 |
iterations | usize | 10 | 自适应迭代次数 |
learning_rate | f64 | 1.5 | 网格平滑/学习率(典型范围 1.0–2.0;值越大,网格更新越平缓) |
seed | u64 | 0x0C45 | 随机数种子(跨运行确定性) |
实现细节:
Default实现提供上述默认值。- 学习率的作用:在网格更新时,每个 bin 的重要性值 $d_i$ 被变换为 $d_i^{1/\text{learning_rate}}$。
learning_rate = 1.0为完整步长,更大的值使网格变化更平滑、更稳定。 - 使用
Xoshiro256PlusPlus伪随机数生成器(通过seed初始化),确保结果可复现。
示例:
#![allow(unused)] fn main() { use ocas_eval::numeric::VegasOptions; // 高精度配置 let opts = VegasOptions { n_bins: 100, n_samples: 100_000, iterations: 20, learning_rate: 1.5, seed: 42, }; // 使用默认值 let default_opts = VegasOptions::default(); }
参见:Vegas::new
Vegas
签名:
#![allow(unused)] fn main() { pub struct Vegas { /* 私有字段 */ } }
功能:基于 Lepage Vegas 算法的自适应蒙特卡洛积分器,在单位超立方体 $[0,1]^d$ 上工作。
设计原理:Vegas 维护一个乘积网格(每个维度独立的 1-D 分箱),通过迭代精炼使每个 bin 捕获被积函数方差的近似相等份额。多轮迭代的估计值通过逆方差加权组合(见 StatisticsAccumulator)。
Vegas::new
签名:
#![allow(unused)] fn main() { pub fn new(n_dims: usize, opts: VegasOptions) -> Self }
功能:创建一个 n_dims 维的 Vegas 积分器。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
n_dims | usize | 积分维度数(≥ 1) |
opts | VegasOptions | 调优参数 |
返回值:初始化的 Vegas 实例,每个维度使用均匀分箱。
示例:
#![allow(unused)] fn main() { use ocas_eval::numeric::{Vegas, VegasOptions}; // 二维积分器,使用默认参数 let mut vegas = Vegas::new(2, VegasOptions::default()); }
Vegas::integrate
签名:
#![allow(unused)] fn main() { pub fn integrate<F: Fn(&[f64]) -> f64>(&mut self, f: &F) -> IntegrateResult }
功能:执行积分计算。对每个迭代:在单位超立方体上采样 n_samples 个点,计算被积函数值,累加统计量,然后更新网格边界以优化采样效率。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
f | &F where F: Fn(&[f64]) -> f64 | 被积函数,输入为 $[0,1]^d$ 中的坐标 |
返回值:IntegrateResult——所有迭代的逆方差加权平均估计及其标准误差。
算法步骤(每次迭代):
- 采样:对每个维度,均匀选择一个 bin,再在 bin 内均匀采样;Jacobian =
n_bins × bin_width(即逆 pdf 贡献)。 - 求值:计算 $f(\mathbf{x})$ 并以总 Jacobian 为权重累加到
StatisticsAccumulator。 - 训练信号:将 $f^2 \cdot w$ 累加到对应 bin 的训练槽。
- 统计收尾:调用
finalize_iteration()将本轮估计折叠到跨轮逆方差加权平均中。 - 网格更新:对每个维度,执行平滑累积弧长重分布——3-bin 平滑、均值归一化、阻尼 $d^{1/\text{lr}}$、在等弧长分位数重分布边界、单调性钳制。
示例:
#![allow(unused)] fn main() { use ocas_eval::numeric::{Vegas, VegasOptions}; // 计算 ∫₀¹∫₀¹ sin(x·y) dx dy let opts = VegasOptions { n_samples: 50_000, iterations: 15, ..Default::default() }; let mut vegas = Vegas::new(2, opts); let r = vegas.integrate(&|x: &[f64]| (x[0] * x[1]).sin()); println!("结果:{:.6} ± {:.6}", r.integral, r.error); // 结果:≈ 0.2398 ± 0.0002 }
注意:被积函数接收的是 $[0,1]^d$ 中的坐标。若需在物理域 $[a,b]$ 上积分,需在闭包内进行线性变换 $x_{\text{phys}} = a + u \cdot (b-a)$ 并乘以 Jacobian $(b-a)$。一维情况下可直接使用 integrate_1d。
Vegas::result
签名:
#![allow(unused)] fn main() { pub fn result(&self) -> IntegrateResult }
功能:获取最近一次 integrate 调用后的累积估计结果。
返回值:IntegrateResult——当前最佳积分估计和标准误差。
Vegas::iterations
签名:
#![allow(unused)] fn main() { pub fn iterations(&self) -> usize }
功能:返回已完成的迭代次数。
integrate_1d
签名:
#![allow(unused)] fn main() { pub fn integrate_1d<F: Fn(f64) -> f64>( f: F, a: f64, b: f64, opts: VegasOptions, ) -> IntegrateResult }
功能:一维数值积分的便捷函数。在物理区间 $[a, b]$ 上积分函数 $f(x)$,内部自动处理变量替换 $u \mapsto a + u(b-a)$ 并将 Jacobian $(b-a)$ 折入结果。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
f | F where F: Fn(f64) -> f64 | 被积函数,直接接收物理坐标 $x \in [a, b]$ |
a | f64 | 积分下限 |
b | f64 | 积分上限(可小于 a,此时结果为负) |
opts | VegasOptions | Vegas 参数(使用 Default::default() 获取默认值) |
返回值:IntegrateResult——积分估计值和标准误差。
实现细节:创建一维 Vegas 实例,将 f 包装为 |u| f(a + u[0] * width) * width,然后调用 integrate。
示例:
#![allow(unused)] fn main() { use ocas_eval::numeric::{integrate_1d, VegasOptions}; // ∫₀¹ x dx = 1/2 let r = integrate_1d(|x| x, 0.0, 1.0, Default::default()); assert!((r.integral - 0.5).abs() < 0.01); // ∫₀² x² dx = 8/3 ≈ 2.667 let r = integrate_1d(|x| x * x, 0.0, 2.0, Default::default()); assert!((r.integral - 8.0 / 3.0).abs() < 0.05); // 高精度配置 let opts = VegasOptions { n_samples: 100_000, iterations: 20, ..Default::default() }; let r = integrate_1d(|x| (-x * x).exp(), -5.0, 5.0, opts); // ∫₋₅⁵ e^{-x²} dx ≈ √π ≈ 1.7725 assert!((r.integral - std::f64::consts::PI.sqrt()).abs() < 0.01); }
参见:Vegas::new、Vegas::integrate
StatisticsAccumulator
签名:
#![allow(unused)] fn main() { #[derive(Debug, Clone)] pub struct StatisticsAccumulator { /* 私有字段 */ } }
功能:逆方差加权统计累加器,用于跨迭代合并蒙特卡洛积分估计。实现 Lepage 原始 Vegas 论文中的加权方案。
设计原理:每轮迭代产生一个积分估计 $I_i$ 和标准误差 $\sigma_i$。跨轮合并使用逆方差权重 $w_i = 1/\sigma_i^2$:
$$ I_{\text{combined}} = \frac{\sum_i w_i I_i}{\sum_i w_i}, \quad \sigma_{\text{combined}} = \frac{1}{\sqrt{\sum_i w_i}} $$
同时维护 $\chi^2$ 统计量 $\sum_i w_i (I_i - I_{\text{combined}})^2$ 用于诊断网格分层质量——$\chi^2 \approx \text{iterations} - 1$ 表示各轮估计一致。
内部字段(私有):
| 字段 | 类型 | 说明 |
|---|---|---|
sum_w | f64 | 当前迭代:$\sum \text{weight}$ |
sum_wf | f64 | 当前迭代:$\sum \text{weight} \cdot f$ |
sum_wf2 | f64 | 当前迭代:$\sum \text{weight} \cdot f^2$ |
integral | f64 | 跨迭代加权平均积分估计 |
error | f64 | 积分估计的标准误差 |
chi_square | f64 | 跨迭代 $\chi^2$ 统计量 |
iterations | usize | 已完成迭代数 |
StatisticsAccumulator::new
签名:
#![allow(unused)] fn main() { pub fn new() -> Self }
功能:创建空累加器。初始状态:integral = 0.0,error = INFINITY,iterations = 0。
StatisticsAccumulator::add_sample
签名:
#![allow(unused)] fn main() { pub fn add_sample(&mut self, weight: f64, f: f64) }
功能:添加一个采样点。在 Vegas 中,weight 是该点的 Jacobian(逆 pdf),f 是被积函数值。该采样对本轮积分估计的贡献为 weight * f。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
weight | f64 | 采样权重(Vegas 中为 Jacobian = n_bins × bin_width 各维度之积) |
f | f64 | 被积函数在该点的值 |
内部累加:sum_w += weight,sum_wf += weight * f,sum_wf2 += weight * f²。
StatisticsAccumulator::finalize_iteration
签名:
#![allow(unused)] fn main() { pub fn finalize_iteration(&mut self) }
功能:收尾当前迭代——计算本轮均值和方差,通过逆方差加权折叠到跨轮平均中,然后重置本轮累加器。
本轮均值:$\bar{f} = \sum(w \cdot f) / \sum w$
本轮方差:$\text{Var} = \sum(w \cdot f^2) / \sum w - \bar{f}^2$
退化处理:若 sum_w ≤ 0 或方差为负(数值问题),跳过本轮但仍重置。
误差钳制:标准误差被钳制到不小于 $10^{-150}$(避免逆方差权重溢出)。
StatisticsAccumulator::samples
签名:
#![allow(unused)] fn main() { pub fn samples(&self) -> usize }
功能:返回当前(未收尾)迭代的近似采样数。
注意:内部通过 sum_w 推导(sum_w as usize),当权重为 1 时精确;Vegas 的 Jacobian 权重使此值为近似值。
StatisticsAccumulator::integral
签名:
#![allow(unused)] fn main() { pub fn integral(&self) -> f64 }
功能:当前最佳积分估计(跨迭代逆方差加权平均)。
StatisticsAccumulator::error
签名:
#![allow(unused)] fn main() { pub fn error(&self) -> f64 }
功能:积分估计的标准误差,即 $\sigma_{\text{combined}} = 1/\sqrt{\sum w_i}$。
StatisticsAccumulator::chi_square
签名:
#![allow(unused)] fn main() { pub fn chi_square(&self) -> f64 }
功能:跨迭代的 $\chi^2$ 统计量。值接近 iterations - 1 表示各轮估计一致(网格分层良好);显著偏大则表明被积函数存在未被网格捕获的结构。
StatisticsAccumulator::iterations
签名:
#![allow(unused)] fn main() { pub fn iterations(&self) -> usize }
功能:已完成的迭代次数。
完整示例:
#![allow(unused)] fn main() { use ocas_eval::numeric::StatisticsAccumulator; let mut acc = StatisticsAccumulator::new(); // 模拟两轮迭代,每轮三个采样 // 第一轮 acc.add_sample(1.0, 1.5); acc.add_sample(1.0, 1.8); acc.add_sample(1.0, 2.1); acc.finalize_iteration(); assert_eq!(acc.iterations(), 1); // 第二轮 acc.add_sample(1.0, 1.6); acc.add_sample(1.0, 1.9); acc.add_sample(1.0, 2.0); acc.finalize_iteration(); assert_eq!(acc.iterations(), 2); // 跨轮加权平均 println!("积分:{:.4} ± {:.4}", acc.integral(), acc.error()); println!("χ² = {:.2}", acc.chi_square()); }
参见:Vegas::integrate(内部使用此累加器)
与 ExpressionEvaluator 的配合
Vegas 积分器可与 ExpressionEvaluator 配合,对符号表达式进行数值积分:
#![allow(unused)] fn main() { use ocas_eval::numeric::{Vegas, VegasOptions}; use ocas_eval::ExpressionEvaluator; use ocas_atom::AtomArena; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let expr = ctx.fun("sin", &[ctx.var("x")]); let evaluator: ExpressionEvaluator<f64> = ExpressionEvaluator::compile(expr).unwrap(); let mut vegas = Vegas::new(1, VegasOptions::default()); let r = vegas.integrate(&|x: &[f64]| { evaluator.evaluate(&[x[0]]).unwrap()[0] }); }
对于一维情况,也可直接使用 integrate_1d 包装:
#![allow(unused)] fn main() { use ocas_eval::numeric::integrate_1d; use ocas_eval::ExpressionEvaluator; use ocas_atom::AtomArena; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let expr = ctx.fun("sin", &[ctx.var("x")]); let evaluator: ExpressionEvaluator<f64> = ExpressionEvaluator::compile(expr).unwrap(); let r = integrate_1d( |x| evaluator.evaluate(&[x]).unwrap()[0], 0.0, std::f64::consts::PI, Default::default(), ); // ∫₀^π sin(x) dx ≈ 2.0 }
Python API 参考
本章记录 oCAS Python 绑定 (ocas 模块) 的完整 API。每个类和函数均包含签名、参数说明、返回值、异常和完整示例。
版本:本文档对应 oCAS 0.24.x。
导入
import ocas
模块顶层导出所有类和函数,无需子模块导入。
表达式
Expression
符号表达式类。从字符串解析构造,支持算术运算、微积分、化简等操作。
签名:
ocas.Expression(input: str)
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
input | str | 符号表达式字符串,使用 ^ 表示幂次,* 表示乘法 |
返回值:Expression 实例。
异常:ValueError — 表达式字符串解析失败。
支持的运算:
| 运算 | 方法 | 说明 |
|---|---|---|
| 加法 | __add__ | e1 + e2 |
| 减法 | __sub__ | e1 - e2 |
| 乘法 | __mul__ | e1 * e2 |
| 幂 | __pow__ | e1 ** e2 |
| 取负 | __neg__ | -e |
| 相等比较 | __eq__ | e1 == e2(基于规范化形式比较) |
| 哈希 | __hash__ | 可用作字典键或集合元素 |
| 字符串化 | __str__ | 输出内部表示 |
| 表示 | __repr__ | Expression("...") 格式 |
方法:
Expression.clone
Expression.clone() -> Expression
返回表达式的深拷贝。
示例:
>>> e = ocas.Expression("x + 1")
>>> f = e.clone()
>>> e == f
True
Expression.simplify
Expression.simplify() -> Expression
使用内置规则集对表达式进行化简(不动点迭代)。
返回值:化简后的 Expression。
异常:ValueError — 化简过程中发生内部错误。
示例:
>>> e = ocas.Expression("x + x")
>>> print(e.simplify())
2*x
Expression.diff
Expression.diff(var: str) -> Expression
对表达式关于变量 var 求导。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
var | str | 微分变量名 |
返回值:导数表达式。
示例:
>>> e = ocas.Expression("x^3 + 2*x")
>>> print(e.diff("x"))
2 + (3*(x^2))
Expression.integrate
Expression.integrate(var: str) -> Expression
对表达式关于变量 var 进行符号积分。采用分层积分管线:快速查表 → 有理函数 → Risch → 三角重写+realify → 特殊函数 → 启发式 → 未求值形式。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
var | str | 积分变量名 |
返回值:积分结果表达式。若无法求出闭式解,返回未求值的 Integral(expr, var) 形式。结果未经认证;需要可机检的证书时用 integrate_outcome。
示例:
>>> e = ocas.Expression("x^2")
>>> print(e.integrate("x"))
(3^-1)*(x^3)
Expression.integrate_outcome
Expression.integrate_outcome(var: str, rules: bool = True) -> dict
认证积分(0.28.0 新增):结果分三值,found 一定带可机检的符号证书(D(F) − f ≡ 0),unknown 诚实返回未求值形式。经精确检查器证伪的结果永远不会以 found 返回。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
var | str | 积分变量名 |
rules | bool | 是否启用规则表引擎(默认 True) |
返回值:字典,键为:
| 键 | 说明 |
|---|---|
kind | "found" / "proved_nonelementary" / "unknown" |
value | 原函数;unknown 时为未求值 Integral(expr, var) |
certificate | 证书层("structural" / "field" / "radical"),未认证为 None |
uncertified | 管线算出的未认证候选(仅诊断用,明确不是答案),没有则为 None |
示例:
>>> e = ocas.Expression("2*x")
>>> e.integrate_outcome("x")["kind"]
'found'
>>> ocas.Expression("exp(x^3)").integrate_outcome("x")["kind"]
'unknown'
Expression.integrate_heuristic
Expression.integrate_heuristic(var: str) -> Expression
仅使用启发式方法积分(分部积分、三角替换等),不调用完整 Risch 算法。成功时返回闭式解,失败时返回未求值形式。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
var | str | 积分变量名 |
返回值:积分结果或未求值 Integral(expr, var)。
示例:
>>> e = ocas.Expression("sin(x)")
>>> print(e.integrate_heuristic("x"))
-1*(cos(x))
Expression.taylor
Expression.taylor(var: str, point: Expression, order: int) -> Expression
计算表达式在 point 处关于 var 的 Taylor 展开至 order 阶。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
var | str | 展开变量名 |
point | Expression | 展开中心点 |
order | int | 展开阶数(非负整数) |
返回值:Taylor 多项式。
示例:
>>> e = ocas.Expression("exp(x)")
>>> p = ocas.Expression("0")
>>> print(e.taylor("x", p, 4))
1 + x + ((2^-1)*(x^2)) + ((6^-1)*(x^3)) + ((24^-1)*(x^4))
Expression.substitute
Expression.substitute(var: str, replacement: Expression) -> Expression
将表达式中所有 var 的出现替换为 replacement。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
var | str | 被替换的变量名 |
replacement | Expression | 替换表达式 |
返回值:替换后的表达式。
示例:
>>> e = ocas.Expression("x^2 + y")
>>> r = ocas.Expression("3")
>>> print(e.substitute("x", r))
y + (3^2)
多项式
Polynomial
稠密一元多项式,支持整数 ℤ、有理数 ℚ 和有限域 GF(p) 三种系数域。
签名:
ocas.Polynomial(coeffs, domain=None)
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
coeffs | list | 系数列表,从低次到高次排列 |
domain | str 或 FiniteField 或 None | 系数域:"integer"(默认)、"rational"、FiniteField(p) |
系数格式:
- 整数域:整数列表,如
[1, 2, 1]表示 $1 + 2x + x^2$ - 有理数域:整数列表或
(分子, 分母)元组列表 - 有限域:整数列表,运算自动取模
异常:TypeError — 系数类型不匹配所选域。
示例:
>>> from ocas import Polynomial
>>> p = Polynomial([1, 2, 1])
>>> print(p.degree())
2
Polynomial.coeffs
Polynomial.coeffs() -> list[str]
返回系数列表(字符串形式),从低次到高次。
示例:
>>> p = Polynomial([1, 2, 1])
>>> p.coeffs()
['1', '2', '1']
Polynomial.degree
Polynomial.degree() -> int | None
返回多项式的次数。零多项式返回 None。
示例:
>>> Polynomial([1, 2, 1]).degree()
2
>>> Polynomial([0]).degree() is None
True
Polynomial.len
Polynomial.len() -> int
返回存储的系数个数。
Polynomial.is_zero
Polynomial.is_zero() -> bool
是否为零多项式。
Polynomial.eval
Polynomial.eval(x) -> str
在点 x 处求值。整数域下 x 为整数;有理数域下 x 为整数或 (分子, 分母) 元组。
返回值:求值结果的字符串表示。
示例:
>>> p = Polynomial([1, 2, 1])
>>> p.eval(2)
'9'
Polynomial.derivative
Polynomial.derivative() -> Polynomial
返回形式导数。
示例:
>>> p = Polynomial([1, 2, 1])
>>> q = p.derivative()
>>> q.coeffs()
['2', '2']
Polynomial.integral
Polynomial.integral() -> Polynomial
返回形式积分(常数项为零)。三个系数域均可调用:在有理数域上精确;在整数域和有限域上,当系数除以 $i+1$ 不能整除时该系数被置零(不抛异常)。
Polynomial.primitive_part
Polynomial.primitive_part() -> Polynomial
返回本原部分(去除内容)。仅支持整数域。
异常:ValueError — 非整数域多项式。
Polynomial.factor
Polynomial.factor() -> list[PolynomialFactor]
完整因式分解。返回 (因子, 重数) 对的列表:整数域上每个因子为本原多项式,有限域上为首一多项式。
因式分解策略:
- ℤ[x]:无平方分解 + Berlekamp–Zassenhaus + Hensel 提升
- GF(p)[x]:Berlekamp 算法
示例:
>>> p = Polynomial([1, 2, 1])
>>> for f in p.factor():
... print(f.factor.coeffs(), f.multiplicity)
['1', '1'] 2
Polynomial.square_free_factorization
Polynomial.square_free_factorization() -> list[PolynomialFactor]
无平方分解。返回互不相同的无平方因子及其重数。
示例:
>>> p = Polynomial([1, 2, 1])
>>> for f in p.square_free_factorization():
... print(f.factor.coeffs(), f.multiplicity)
['1', '1'] 2
Polynomial.is_square_free
Polynomial.is_square_free() -> bool
是否无平方(无重复不可约因子)。
Polynomial.gcd
Polynomial.gcd(other: Polynomial) -> Polynomial
计算与 other 的最大公因式。两个多项式必须在同一系数域上。
异常:TypeError — 系数域不匹配。
示例:
>>> a = Polynomial([0, 1]) # x
>>> b = Polynomial([0, 0, 1]) # x^2
>>> a.gcd(b).coeffs()
['0', '1']
Polynomial.div_rem
Polynomial.div_rem(other: Polynomial) -> tuple[Polynomial, Polynomial] | None
带余除法,返回 (商, 余式)。other 为零时返回 None。
异常:TypeError — 系数域不匹配。
示例:
>>> a = Polynomial([1, 2, 1])
>>> b = Polynomial([1, 1])
>>> q, r = a.div_rem(b)
>>> q.coeffs()
['1', '1']
>>> r.is_zero()
True
Polynomial 算术运算
| 运算 | 语法 | 说明 |
|---|---|---|
| 加法 | p + q | 多项式加法 |
| 减法 | p - q | 多项式减法 |
| 乘法 | p * q | 多项式乘法 |
| 取负 | -p | 系数取负 |
| 相等 | p == q | 归一化系数比较 |
PolynomialFactor
因式分解结果中的单个因子。
属性:
| 属性 | 类型 | 说明 |
|---|---|---|
factor | Polynomial | 因子多项式(整数域上本原,有限域上首一) |
multiplicity | int | 重数 |
MultivariatePolynomial
稀疏多元多项式($\mathbb{Q}[x_1, \dots, x_n]$),使用字典序 (Lex)。
签名:
ocas.MultivariatePolynomial(terms: dict, n_vars: int)
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
terms | dict[tuple[int,...], int] | 键为指数元组(长度为 n_vars),值为系数 |
n_vars | int | 变量个数 |
示例:
>>> from ocas import MultivariatePolynomial
>>> # x² + y² - 1
>>> p = MultivariatePolynomial({(2, 0): 1, (0, 2): 1, (0, 0): -1}, n_vars=2)
>>> p.n_vars()
2
MultivariatePolynomial.n_vars
MultivariatePolynomial.n_vars() -> int
返回变量个数。
矩阵
Matrix
域上的稠密矩阵,支持 ℤ、ℚ 和 GF(p)。
签名:
ocas.Matrix(rows, domain=None)
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
rows | list[list] | 行优先的二维列表 |
domain | str 或 FiniteField 或 None | 系数域:"integer"(默认)、"rational"、FiniteField(p) |
异常:TypeError — 元素类型不匹配所选域。
示例:
>>> from ocas import Matrix
>>> a = Matrix([[1, 2], [3, 4]])
>>> a.determinant()
'-2'
Matrix.nrows
Matrix.nrows -> int # property
行数。
Matrix.ncols
Matrix.ncols -> int # property
列数。
Matrix.shape
Matrix.shape() -> tuple[int, int]
返回 (nrows, ncols)。
Matrix.getitem
Matrix[i, j] -> str
返回 (i, j) 位置的元素(字符串形式)。
异常:ValueError — 索引越界。
示例:
>>> a = Matrix([[1, 2], [3, 4]])
>>> a[1, 0]
'3'
Matrix.rows
Matrix.rows() -> list[list[str]]
返回所有行的二维列表(字符串形式)。
Matrix.transpose
Matrix.transpose() -> Matrix
返回转置矩阵。
Matrix.trace
Matrix.trace() -> str
返回方阵的迹(对角线元素之和)。
异常:ValueError — 非方阵。
Matrix.rank
Matrix.rank() -> int
返回矩阵的秩。
Matrix.determinant
Matrix.determinant() -> str
返回方阵的行列式(Bareiss 无分数算法)。
异常:ValueError — 非方阵。
示例:
>>> a = Matrix([[1, 2], [3, 4]])
>>> a.determinant()
'-2'
Matrix.inverse
Matrix.inverse() -> Matrix
返回逆矩阵。
异常:ValueError — 奇异矩阵或非方阵。
Matrix.solve
Matrix.solve(rhs) -> list[str]
求解线性方程组 self · x = rhs。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
rhs | list | 右端向量 |
返回值:解向量(字符串列表)。
异常:ValueError — 无解或不唯一。
示例:
>>> a = Matrix([[2, 0], [0, 3]])
>>> a.solve([4, 9])
['2', '3']
Matrix 算术运算
| 运算 | 语法 | 说明 |
|---|---|---|
| 矩阵乘法 | a @ b | __matmul__ |
| 加法 | a + b | 逐元素加法 |
| 减法 | a - b | 逐元素减法 |
| 相等 | a == b | 逐元素比较 |
系数域
IntegerDomain
整数域 ℤ 的选择器类。
签名:
ocas.IntegerDomain()
注意:
Polynomial和Matrix的domain参数目前接受字符串("integer"/"rational")或FiniteField实例;IntegerDomain()/RationalDomain()实例尚不能直接作为该参数传递。
示例:
>>> from ocas import IntegerDomain, Polynomial
>>> d = IntegerDomain()
>>> repr(d)
'IntegerDomain()'
>>> p = Polynomial([1, 2, 1], domain="integer")
>>> p.coeffs()
['1', '2', '1']
RationalDomain
有理数域 ℚ 的选择器。
签名:
ocas.RationalDomain()
示例:
>>> from ocas import RationalDomain, Polynomial
>>> d = RationalDomain()
>>> repr(d)
'RationalDomain()'
>>> p = Polynomial([(1, 2), (3, 4)], domain="rational")
>>> p.coeffs()
['1/2', '3/4']
FiniteField
有限域 GF(p),$p$ 为素数。
签名:
ocas.FiniteField(modulus: int)
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
modulus | int | 素数模数,$p \geq 2$ |
返回值:FiniteField 实例。
异常:ValueError — modulus < 2。
属性:
| 属性 | 类型 | 说明 |
|---|---|---|
modulus | str | 素数模数的十进制字符串 |
示例:
>>> from ocas import FiniteField, Polynomial
>>> gf5 = FiniteField(5)
>>> p = Polynomial([1, 2, 1], domain=gf5)
>>> print(repr(gf5))
FiniteField(5)
求解器
solve_linear_rational
ocas.solve_linear_rational(a: list[list[int]], b: list[int]) -> list[tuple[int, int]]
在 ℚ 上求解线性方程组 $A \mathbf{x} = \mathbf{b}$。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
a | list[list[int]] | 系数矩阵(行优先,整数) |
b | list[int] | 右端向量(整数) |
返回值:解向量,每个分量为 (分子, 分母) 元组。
异常:ValueError — 无唯一解。
示例:
>>> ocas.solve_linear_rational([[2, 0], [0, 3]], [4, 9])
[(2, 1), (3, 1)]
solve_linear_integer
ocas.solve_linear_integer(a: list[list[int]], b: list[int]) -> list[int]
在 ℤ 上求解线性方程组 $A \mathbf{x} = \mathbf{b}$。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
a | list[list[int]] | 系数矩阵(行优先,整数) |
b | list[int] | 右端向量(整数) |
返回值:整数解向量。
异常:ValueError — 无整数解。
示例:
>>> ocas.solve_linear_integer([[2, 0], [0, 3]], [4, 9])
[2, 3]
solve_diophantine
ocas.solve_diophantine(a: int, b: int, c: int) -> DiophantineSolution | None
求解线性丢番图方程 $ax + by = c$。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
a | int | $x$ 的系数 |
b | int | $y$ 的系数 |
c | int | 右端常数 |
返回值:DiophantineSolution(有解时)或 None(无整数解时)。
示例:
>>> sol = ocas.solve_diophantine(3, 5, 1)
>>> sol.particular
(2, -1)
>>> sol.general
(5, -3)
DiophantineSolution
丢番图方程的解。
属性:
| 属性 | 类型 | 说明 |
|---|---|---|
particular | tuple[int, int] | 特解 $(x_0, y_0)$ |
general | tuple[int, int] | 通解方向 $(t_x, t_y)$;通解为 $(x_0 + k \cdot t_x, ; y_0 + k \cdot t_y)$,$k \in \mathbb{Z}$ |
ODE
classify_ode
ocas.classify_ode(equation: Expression, func: str, var: str) -> list[str]
分类常微分方程,返回可用求解方法名列表。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
equation | Expression | ODE 表达式(等于零的形式),如 Derivative(y(x), x) - y(x) |
func | str | 未知函数名,如 "y" |
var | str | 自变量名,如 "x" |
返回值:方法名列表,如 ["LinearFirst", "PowerSeries"]。
支持的 ODE 类型:Separable、LinearFirst、Bernoulli、Exact、Homogeneous、LinearConstantCoeff、CauchyEuler、ReductionOfOrder、PowerSeries。
示例:
>>> eq = ocas.Expression("Derivative(y(x), x) - y(x)")
>>> ocas.classify_ode(eq, "y", "x")
['LinearFirst', 'PowerSeries']
dsolve
ocas.dsolve(equation: Expression, func: str, var: str, hint: str | None = None) -> str
符号求解 ODE。
签名(含默认值):dsolve(equation, func, var, hint=None)
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
equation | Expression | ODE 表达式(等于零的形式) |
func | str | 未知函数名 |
var | str | 自变量名 |
hint | str 或 None | 指定求解方法(classify_ode 返回的名称之一) |
返回值:解的字符串表示(显式、隐式、级数或未求值形式)。
示例:
>>> eq = ocas.Expression("Derivative(y(x), x) - y(x)")
>>> ocas.dsolve(eq, "y", "x")
'y = C1*exp(x)'
dsolve_ivp
ocas.dsolve_ivp(equation: Expression, func: str, var: str, y0: str, y1: str | None = None) -> str
通过 Laplace 变换求解常系数线性 ODE 的初值问题。
签名(含默认值):dsolve_ivp(equation, func, var, y0, y1=None)
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
equation | Expression | ODE 表达式(等于零的形式) |
func | str | 未知函数名 |
var | str | 自变量名 |
y0 | str | $y(0)$ 的值(字符串表达式,如 "1") |
y1 | str 或 None | $y'(0)$ 的值(二阶问题需要) |
返回值:不含自由常数的显式解字符串。
示例:
>>> eq = ocas.Expression("Derivative(y(x), x, 2) + y(x)")
>>> ocas.dsolve_ivp(eq, "y", "x", "0", "1")
'y = sin(x)'
数论
factorint
ocas.factorint(n: int) -> list[tuple[str, int]]
将 $|n|$ 分解为素因子。负输入的首项为 ("-1", 1)。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
n | int | 待分解的整数(任意精度) |
返回值:(素数, 指数) 元组列表,按素数升序排列。
示例:
>>> ocas.factorint(60)
[('2', 2), ('3', 1), ('5', 1)]
>>> ocas.factorint(-12)
[('-1', 1), ('2', 2), ('3', 1)]
isprime
ocas.isprime(n: int) -> bool
BPSW 概率素性测试。对 $n < 2^{64}$ 为确定性判定;目前已知对任意大小无合数通过。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
n | int | 待测整数(任意精度) |
返回值:True 表示(可能是)素数。
示例:
>>> ocas.isprime(97)
True
>>> ocas.isprime(100)
False
isprime_u64
ocas.isprime_u64(n: int) -> bool
对 $n < 2^{64}$ 的确定性素性判定(u64 快速路径)。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
n | int | 待测整数(必须在 u64 范围内) |
返回值:True 表示素数。
示例:
>>> ocas.isprime_u64(2**61 - 1)
True
nextprime
ocas.nextprime(n: int) -> int
返回严格大于 $n$ 的最小素数。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
n | int | 起始整数 |
返回值:下一个素数(任意精度)。
示例:
>>> ocas.nextprime(10)
11
>>> ocas.nextprime(11)
13
discrete_log
ocas.discrete_log(p: int, base: int, target: int) -> int
求解离散对数 $\text{base}^x \equiv \text{target} \pmod{p}$。对素数 $p$ 使用 Pohlig–Hellman 算法,否则使用 BSGS。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
p | int | 模数 |
base | int | 底数 |
target | int | 目标值 |
返回值:离散对数 $x$。
异常:ValueError — 对数不存在。
示例:
>>> ocas.discrete_log(13, 2, 9)
8
>>> pow(2, 8, 13)
9
crt
ocas.crt(moduli: list[int], residues: list[int]) -> tuple[int, int]
中国剩余定理:求解同余方程组 $x \equiv r_i \pmod{m_i}$。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
moduli | list[int] | 模数列表(不要求互素) |
residues | list[int] | 余数列表 |
返回值:(r, m) 表示 $x \equiv r \pmod{m}$。
异常:ValueError — 方程组不相容。
示例:
>>> ocas.crt([3, 5, 7], [2, 3, 2])
(23, 105)
jacobi_symbol
ocas.jacobi_symbol(a: int, n: int) -> int
计算 Jacobi 符号 $\left(\frac{a}{n}\right)$,其中 $n$ 为正奇数。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
a | int | 分子 |
n | int | 分母(正奇数) |
返回值:$-1$、$0$ 或 $1$。
异常:ValueError — $n$ 非正奇数。
示例:
>>> ocas.jacobi_symbol(2, 7)
1
>>> ocas.jacobi_symbol(3, 7)
-1
totient
ocas.totient(n: int) -> int
Euler totient 函数 $\varphi(n)$。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
n | int | 正整数 |
返回值:$\varphi(n)$(任意精度)。
示例:
>>> ocas.totient(12)
4
mobius
ocas.mobius(n: int) -> int
Möbius 函数 $\mu(n)$。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
n | int | 正整数 |
返回值:$-1$、$0$ 或 $1$。
示例:
>>> ocas.mobius(6)
1
>>> ocas.mobius(4)
0
divisor_count
ocas.divisor_count(n: int) -> int
正因数个数 $\tau(n)$。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
n | int | 正整数 |
返回值:正因数个数。
示例:
>>> ocas.divisor_count(12)
6
divisor_sigma
ocas.divisor_sigma(n: int, k: int = 1) -> int
因数的 $k$ 次幂之和 $\sigma_k(n) = \sum_{d \mid n} d^k$。
签名(含默认值):divisor_sigma(n, k=1)
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
n | int | 正整数 |
k | int | 幂次(默认 1,即因数之和 $\sigma_1$) |
返回值:$\sigma_k(n)$。
示例:
>>> ocas.divisor_sigma(12) # 1+2+3+4+6+12
28
>>> ocas.divisor_sigma(12, 2) # 1+4+9+16+36+144
210
liouville_lambda
ocas.liouville_lambda(n: int) -> int
Liouville 函数 $\lambda(n) = (-1)^{\Omega(n)}$,其中 $\Omega(n)$ 是 $n$ 的素因子个数(计重数)。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
n | int | 正整数 |
返回值:$-1$ 或 $1$。
示例:
>>> ocas.liouville_lambda(12) # Ω(12)=3
-1
>>> ocas.liouville_lambda(9) # Ω(9)=2
1
Gröbner 基与理想
注意:以下函数保留
py_前缀(Rust 端未使用#[pyo3(name=...)]重命名)。在 Python 中的实际调用名为ocas.py_groebner_basis、ocas.py_ideal_contains等。
py_groebner_basis
ocas.py_groebner_basis(generators, n_vars: int = 1, algorithm: str = "auto") -> GroebnerBasis
计算多项式理想的 Gröbner 基。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
generators | list | 生成元列表(MultivariatePolynomial 或 Polynomial 对象) |
n_vars | int | 变量个数(默认 1;若生成元为 MultivariatePolynomial 则自动检测) |
algorithm | str | 算法选择:"auto"(默认)、"f4"、"f5"、"buchberger" |
返回值:GroebnerBasis 对象。
异常:ValueError — 算法名称无效。
示例:
>>> from ocas import MultivariatePolynomial, py_groebner_basis
>>> f = MultivariatePolynomial({(2, 0): 1, (0, 0): -1}, n_vars=2) # x² - 1
>>> g = MultivariatePolynomial({(0, 2): 1, (0, 0): -1}, n_vars=2) # y² - 1
>>> gb = py_groebner_basis([f, g], n_vars=2)
>>> len(gb)
2
py_ideal_contains
ocas.py_ideal_contains(generators, f, n_vars: int = 1, algorithm: str = "auto") -> bool
判断多项式 $f$ 是否属于由 generators 生成的理想。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
generators | list | 理想的生成元列表 |
f | MultivariatePolynomial 或 Polynomial | 待检测的多项式 |
n_vars | int | 变量个数(默认 1) |
algorithm | str | 算法选择(默认 "auto") |
返回值:True 表示 $f \in I$。
示例:
>>> from ocas import MultivariatePolynomial, py_ideal_contains
>>> f = MultivariatePolynomial({(2, 0): 1, (0, 0): -1}, n_vars=2)
>>> g = MultivariatePolynomial({(0, 2): 1, (0, 0): -1}, n_vars=2)
>>> x = MultivariatePolynomial({(1, 0): 1}, n_vars=2)
>>> py_ideal_contains([f, g], f, n_vars=2)
True
>>> py_ideal_contains([f, g], x, n_vars=2)
False
py_solve_polynomial_system
ocas.py_solve_polynomial_system(equations, n_vars: int = 1, algorithm: str = "auto") -> PolynomialSystemSolution
求解多项式方程组。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
equations | list | 方程列表(每个方程为等于零的多项式) |
n_vars | int | 变量个数(默认 1) |
algorithm | str | 算法选择(默认 "auto") |
返回值:PolynomialSystemSolution 对象。
示例:
>>> from ocas import MultivariatePolynomial, py_solve_polynomial_system
>>> f = MultivariatePolynomial({(2, 0): 1, (0, 2): 1, (0, 0): -1}, n_vars=2)
>>> g = MultivariatePolynomial({(1, 0): 1, (0, 0): -1}, n_vars=2)
>>> sol = py_solve_polynomial_system([f, g], n_vars=2)
>>> sol.kind
'zero_dimensional'
>>> for s in sol.solutions():
... print(s.values, s.multiplicity)
[1.0, 0.0] 1
py_hilbert_series
ocas.py_hilbert_series(gb: GroebnerBasis) -> HilbertSeries
计算 Gröbner 基对应的 Hilbert 级数。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
gb | GroebnerBasis | Gröbner 基 |
返回值:HilbertSeries 对象。
示例:
>>> from ocas import MultivariatePolynomial, py_groebner_basis, py_hilbert_series
>>> f = MultivariatePolynomial({(2, 0): 1, (0, 0): -1}, n_vars=2)
>>> gb = py_groebner_basis([f], n_vars=2)
>>> hs = py_hilbert_series(gb)
>>> hs.dimension
1
py_ideal_radical
ocas.py_ideal_radical(generators, n_vars: int = 1) -> GroebnerBasis
计算理想的根式 $\sqrt{I}$。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
generators | list | 理想的生成元列表 |
n_vars | int | 变量个数(默认 1) |
返回值:$\sqrt{I}$ 的 Gröbner 基。
算法:零维使用无平方分解;正维使用 Jacobian 饱和 $\sqrt{I} = I : h^\infty$。
py_primary_decomposition
ocas.py_primary_decomposition(generators, n_vars: int = 1) -> list[PrimaryComponent]
计算理想的准素分解。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
generators | list | 理想的生成元列表 |
n_vars | int | 变量个数(默认 1) |
返回值:PrimaryComponent 列表。
示例:
>>> from ocas import MultivariatePolynomial, py_primary_decomposition
>>> f = MultivariatePolynomial({(2, 0): 1, (0, 0): -1}, n_vars=1)
>>> comps = py_primary_decomposition([f], n_vars=1)
>>> len(comps)
2
py_is_zero_dimensional
ocas.py_is_zero_dimensional(gb: GroebnerBasis) -> bool
判断 Gröbner 基对应的理想是否为零维的。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
gb | GroebnerBasis | Gröbner 基 |
返回值:True 表示零维。
py_eliminate
ocas.py_eliminate(generators, elim_vars: int, n_vars: int = 1, algorithm: str = "auto") -> GroebnerBasis
消元:计算消元理想 $I \cap k[x_{e+1}, \dots, x_n]$ 的 Gröbner 基。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
generators | list | 理想的生成元列表 |
elim_vars | int | 消除的变量数(从前向后) |
n_vars | int | 总变量个数(默认 1) |
algorithm | str | 算法选择(默认 "auto") |
返回值:消元理想的 Gröbner 基。
MultivariatePolynomial(Gröbner 辅助类)
已在多项式一节介绍。Gröbner 函数同时接受 MultivariatePolynomial 和 Polynomial 作为生成元;后者自动映射为单变量多元多项式。
GroebnerBasis
Gröbner 基计算结果。
属性:
| 属性 | 类型 | 说明 |
|---|---|---|
n_vars | int | 变量个数 |
方法:
| 方法 | 返回值 | 说明 |
|---|---|---|
__len__() | int | 基中多项式个数 |
is_groebner_basis() | bool | 验证是否确实为 Gröbner 基 |
RealSolution
多项式方程组的一个实数解。
属性:
| 属性 | 类型 | 说明 |
|---|---|---|
values | list[float] | 解的坐标值 |
multiplicity | int | 重数 |
PolynomialSystemSolution
多项式方程组的求解结果。
属性:
| 属性 | 类型 | 说明 |
|---|---|---|
kind | str | "zero_dimensional"、"positive_dimensional" 或 "empty" |
vector_space_dimension | int 或 None | 向量空间维数(仅零维时有值) |
方法:
| 方法 | 返回值 | 说明 |
|---|---|---|
solutions() | list[RealSolution] | 实数解列表(仅零维时非空) |
HilbertSeries
Hilbert 级数。
属性:
| 属性 | 类型 | 说明 |
|---|---|---|
dimension | int | Krull 维数 |
degree | int | 重数(degree) |
numerator | list[int] | 分子多项式系数 |
方法:
| 方法 | 参数 | 返回值 | 说明 |
|---|---|---|---|
hilbert_function(d) | d: int | int | 在次数 $d$ 处的 Hilbert 函数值 |
PrimaryComponent
准素分解的一个分量。
属性:
| 属性 | 类型 | 说明 |
|---|---|---|
n_vars | int | 变量个数 |
代数数
AlgebraicExtension
代数数域 $K = \mathbb{Q}(\alpha)$,由首一极小多项式定义。
签名:
ocas.AlgebraicExtension(min_poly: list)
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
min_poly | list[int] 或 list[tuple[int,int]] | 极小多项式系数(升幂排列),末项(首项系数)必须为 1 |
异常:ValueError — 系数列表过短、首项系数不为 1 或多项式不可约性验证失败。
示例:
>>> from ocas import AlgebraicExtension
>>> # α² - 2(即 ℚ(√2))
>>> field = AlgebraicExtension([-2, 0, 1])
>>> field.extension_degree()
2
AlgebraicExtension.extension_degree
AlgebraicExtension.extension_degree() -> int
返回扩张次数 $[K:\mathbb{Q}] = \deg(m)$。
AlgebraicExtension.alpha
AlgebraicExtension.alpha() -> AlgebraicElement
返回扩张的生成元 $\alpha$。
示例:
>>> field = AlgebraicExtension([-2, 0, 1])
>>> a = field.alpha()
>>> a.coeffs()
['0', '1']
AlgebraicExtension.from_base
AlgebraicExtension.from_base(c) -> AlgebraicElement
将有理数 $c$ 嵌入为域中的常量元素。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
c | int 或 tuple[int, int] | 有理数(整数或 (分子, 分母) 元组) |
AlgebraicExtension.element
AlgebraicExtension.element(coeffs: list) -> AlgebraicElement
从 $\alpha$-多项式系数(升幂)创建元素。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
coeffs | list[int] 或 list[tuple[int,int]] | 有理系数列表(升幂) |
示例:
>>> field = AlgebraicExtension([-2, 0, 1])
>>> # 3 + 2α
>>> e = field.element([3, 2])
>>> e.coeffs()
['3', '2']
AlgebraicElement
代数数域 $\mathbb{Q}(\alpha)$ 中的一个元素,存储为 $\alpha$ 的多项式。
AlgebraicElement.coeffs
AlgebraicElement.coeffs() -> list[str]
返回 $\alpha$-多项式系数(字符串列表,升幂)。
示例:
>>> field = AlgebraicExtension([-2, 0, 1])
>>> e = field.element([1, 1]) # 1 + α = 1 + √2
>>> e.coeffs()
['1', '1']
AlgebraicPolynomial
代数数域 $\mathbb{Q}(\alpha)$ 上的稠密一元多项式。
签名:
ocas.AlgebraicPolynomial(field: AlgebraicExtension, coeffs: list)
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
field | AlgebraicExtension | 系数所在的代数数域 |
coeffs | list | 系数列表(升幂),每个元素为 int、(int, int) 元组或 AlgebraicElement |
示例:
>>> from ocas import AlgebraicExtension, AlgebraicPolynomial
>>> field = AlgebraicExtension([-2, 0, 1])
>>> # x² - 2 over ℚ(√2)
>>> p = AlgebraicPolynomial(field, [-2, 0, 1])
>>> p.degree()
2
AlgebraicPolynomial.degree
AlgebraicPolynomial.degree() -> int | None
返回次数。零多项式返回 None。
AlgebraicPolynomial.len
AlgebraicPolynomial.len() -> int
返回存储的系数个数。
AlgebraicPolynomial.is_zero
AlgebraicPolynomial.is_zero() -> bool
是否为零多项式。
AlgebraicPolynomial.coeffs
AlgebraicPolynomial.coeffs() -> list[list[str]]
返回系数列表。每个系数为一个有理数列表(字符串形式),表示 $\alpha$-多项式系数。
AlgebraicPolynomial.factor
AlgebraicPolynomial.factor() -> list[AlgebraicFactor]
在代数数域上进行因式分解(Trager 算法)。
返回值:AlgebraicFactor 列表,每个包含不可约因子和重数。
示例:
>>> field = AlgebraicExtension([-2, 0, 1])
>>> p = AlgebraicPolynomial(field, [-2, 0, 1]) # x² - 2 = (x-α)(x+α)
>>> facs = p.factor()
>>> len(facs)
2
>>> [f.multiplicity for f in facs]
[1, 1]
AlgebraicFactor
代数数域多项式因式分解中的一个因子。
属性:
| 属性 | 类型 | 说明 |
|---|---|---|
factor | AlgebraicPolynomial | 不可约因子 |
multiplicity | int | 重数 |
自动微分
DualShape
一阶对偶数布局描述,声明追踪的微分变量个数。构建一次,多处共享。
签名:
# 不直接构造,使用静态方法
shape = DualShape.first_order(n_vars)
DualShape.first_order(静态方法)
DualShape.first_order(n_vars: int) -> DualShape
创建包含 n_vars 个微分变量的布局。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
n_vars | int | 微分变量个数($\geq 1$) |
异常:ValueError — n_vars == 0。
示例:
>>> from ocas import DualShape
>>> shape = DualShape.first_order(3)
>>> shape.n_vars
3
>>> shape.n_components
4
DualShape.n_vars
DualShape.n_vars -> int # property
微分变量个数。
DualShape.n_components
DualShape.n_components -> int # property
总分量数(值 + 导数槽位)。
HyperDual
超对偶数:一个标量值加上对 DualShape 中各变量的偏导数。使用有理数精确计算。
限制:仅支持多项式/有理算术(+、-、*、/、取负)。超越函数(sin/exp/log)未实现;整数幂请用重复乘法。
HyperDual.variable(静态方法)
HyperDual.variable(shape: DualShape, i: int, value) -> HyperDual
创建独立变量 $x_i = \text{value}$(对第 $i$ 个变量的导数为 1,其余为 0)。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
shape | DualShape | 对偶数布局 |
i | int | 变量索引 |
value | int 或 tuple[int, int] | 变量值(整数或有理数元组) |
异常:ValueError — 索引越界。
HyperDual.constant(静态方法)
HyperDual.constant(shape: DualShape, value) -> HyperDual
创建常量(所有导数为零)。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
shape | DualShape | 对偶数布局 |
value | int 或 tuple[int, int] | 常量值 |
HyperDual.value
HyperDual.value() -> str
返回标量值(字符串形式,如 "5" 或 "3/7")。
HyperDual.deriv
HyperDual.deriv(i: int) -> str | None
返回对第 $i$ 个变量的偏导数。若 i 越界返回 None。
HyperDual.n_vars
HyperDual.n_vars -> int # property
微分变量个数。
HyperDual 算术运算
| 运算 | 语法 | 说明 |
|---|---|---|
| 加法 | a + b | 逐分量加法 |
| 减法 | a - b | 逐分量减法 |
| 乘法 | a * b | 乘积法则 |
| 除法 | a / b | 商法则 |
| 取负 | -a | 逐分量取负 |
所有运算要求两个操作数共享同一 DualShape。不同形状会抛出 ValueError。
完整示例:
>>> from ocas import DualShape, HyperDual
>>> shape = DualShape.first_order(2)
>>> x = HyperDual.variable(shape, 0, 3)
>>> y = HyperDual.variable(shape, 1, 5)
>>> f = x * y
>>> f.value()
'15'
>>> f.deriv(0) # ∂f/∂x = y
'5'
>>> f.deriv(1) # ∂f/∂y = x
'3'
张量
Tensor
命名张量对象,带指标槽位和可选对称性。
签名:
ocas.Tensor(name: str, slots: list[tuple[str, str]], symmetry: str = "none")
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
name | str | 张量名称 |
slots | list[tuple[str, str]] | 指标槽位列表,每项为 (标签, 位置)。位置取 "upper"(逆变)或 "lower"(协变) |
symmetry | str | 对称性:"none"(默认)、"symmetric"、"antisymmetric" |
异常:ValueError — 位置或对称性字符串无效。
示例:
>>> from ocas import Tensor
>>> t = Tensor("T", [("i", "upper"), ("j", "lower")])
>>> t.name
'T'
>>> t.rank
2
Tensor.name
Tensor.name -> str # property
张量名称。
Tensor.rank
Tensor.rank -> int # property
张量秩(槽位数)。
Tensor.symmetry
Tensor.symmetry -> str # property
对称性字符串("none"、"symmetric"、"antisymmetric")。
Tensor.slots
Tensor.slots() -> list[tuple[str, str]]
返回槽位列表,每项为 (标签, 位置)。
Tensor.dummy_labels
Tensor.dummy_labels() -> list[str]
返回当前哑指标(出现两次的指标)标签列表。
Tensor.to_string_atom
Tensor.to_string_atom() -> str
将张量渲染为表达式函数节点 name(slot, slot, ...) 的字符串形式。
contract_tensors
ocas.contract_tensors(a: Tensor, b: Tensor) -> tuple[str, list | str]
对两个张量进行缩并:对共享哑指标(相同标签、相反变异性)求和。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
a | Tensor | 第一个张量 |
b | Tensor | 第二个张量 |
返回值:(kind, payload) 元组:
kind = "product"时,payload为缩并后张量列表(自由指标拼接)kind = "scalar"时,payload为缩并后标量表达式的字符串
示例:
>>> from ocas import Tensor, contract_tensors
>>> t = Tensor("T", [("i", "upper"), ("j", "lower")])
>>> u = Tensor("U", [("j", "upper"), ("k", "lower")])
>>> kind, payload = contract_tensors(t, u)
>>> kind
'product'
tensor_symmetrise_sign
ocas.tensor_symmetrise_sign(tensor: Tensor) -> int
返回张量的对称化符号($+1$ 或 $-1$)。"none" 和 "symmetric" 永远返回 $+1$;"antisymmetric" 返回槽位排列的奇偶性。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
tensor | Tensor | 输入张量 |
返回值:$+1$ 或 $-1$。
示例:
>>> from ocas import Tensor, tensor_symmetrise_sign
>>> eps = Tensor("eps", [("a", "lower"), ("b", "lower")], symmetry="antisymmetric")
>>> tensor_symmetrise_sign(eps) in (1, -1)
True
canonicalize_tensors
ocas.canonicalize_tensors(expr: str, specs: dict[str, str], index_groups: dict[str, int] | None = None) -> str
使用图同构引擎对张量表达式进行规范化。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
expr | str | 张量表达式字符串 |
specs | dict[str, str] | 张量名 → 对称性规格("none"、"symmetric"、"antisymmetric") |
index_groups | dict[str, int] 或 None | 指标维度分组(可选) |
返回值:规范化后的表达式字符串。
异常:ValueError — 解析或规范化失败。
young_project
ocas.young_project(expr: str, tableau: list[int]) -> str
对张量表达式应用 Young 投影。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
expr | str | 张量表达式字符串 |
tableau | list[int] | Young 图各行长度,如 [2, 1] 表示 □□/□ |
返回值:投影后的表达式字符串。
示例:
>>> proj = ocas.young_project("f(a,b,c)", [1, 1, 1]) # □/□/□ 全反对称投影
>>> "f(a,b,c)" in proj
True
refresh_dummies
ocas.refresh_dummies(expr: str, specs: dict[str, str]) -> str
重命名(刷新)张量表达式中的哑指标。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
expr | str | 张量表达式字符串 |
specs | dict[str, str] | 张量名 → 对称性规格 |
返回值:哑指标重命名后的表达式字符串。
求值
ExpressionEvaluator
编译表达式以进行快速数值求值。编译一次,多次求值。
签名:
ocas.ExpressionEvaluator(input: str, param_names: list[str])
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
input | str | 表达式字符串 |
param_names | list[str] | 参数名列表(求值时的顺序) |
异常:ValueError — 解析或编译错误。
示例:
>>> from ocas import ExpressionEvaluator
>>> ev = ExpressionEvaluator("x^2 + y", ["x", "y"])
>>> ev.evaluate([3.0, 1.0])
[10.0]
>>> ev.evaluate([2.0, 0.0])
[4.0]
ExpressionEvaluator.evaluate
ExpressionEvaluator.evaluate(values: list[float]) -> list[float]
用给定参数值求值。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
values | list[float] | 浮点数值列表,长度必须与参数个数匹配 |
返回值:结果浮点数列表。
异常:ValueError — 参数个数不匹配或求值错误。
ExpressionEvaluator.n_params
ExpressionEvaluator.n_params -> int # property
参数个数。
数值积分
Vegas
自适应蒙特卡洛积分器(Vegas 算法),支持多维积分。
签名:
ocas.Vegas(n_dims: int, *, n_bins=None, n_samples=None, iterations=None, learning_rate=None, seed=None)
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
n_dims | int | 积分维度 |
n_bins | int 或 None | 每维的 bin 数(默认 64) |
n_samples | int 或 None | 每次迭代的采样数(默认 10000) |
iterations | int 或 None | 迭代次数(默认 10) |
learning_rate | float 或 None | 网格自适应学习率(默认 1.5) |
seed | int 或 None | RNG 种子(默认 0x0C45) |
示例:
>>> from ocas import Vegas
>>> v = Vegas(2, n_samples=20000, iterations=8, seed=1)
>>> r = v.integrate(lambda xs: xs[0] * xs[1])
>>> abs(r.integral - 0.25) < 0.01
True
Vegas.integrate
Vegas.integrate(f: Callable[[list[float]], float]) -> IntegrateResult
对可调用对象 $f$ 在单位超立方体 $[0, 1]^n$ 上积分。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
f | Callable | 接受 n_dims 个 [0, 1] 内浮点数的列表,返回一个浮点数 |
返回值:IntegrateResult。
Vegas.result
Vegas.result -> IntegrateResult # property
最新的累计估计值和误差。
Vegas.iterations
Vegas.iterations -> int # property
已完成的迭代次数。
integrate_1d
ocas.integrate_1d(f: Callable[[float], float], a: float, b: float, *, n_bins=None, n_samples=None, iterations=None, learning_rate=None, seed=None) -> IntegrateResult
一维数值积分便捷函数。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
f | Callable | 一元浮点函数 |
a | float | 积分下限 |
b | float | 积分上限 |
n_bins 等 | 同 Vegas | 可选调参 |
返回值:IntegrateResult。
示例:
>>> import ocas
>>> r = ocas.integrate_1d(lambda x: x, 0.0, 1.0)
>>> abs(r.integral - 0.5) < 0.01
True
IntegrateResult
数值积分结果。
属性:
| 属性 | 类型 | 说明 |
|---|---|---|
integral | float | 积分估计值 |
error | float | 估计标准误差 |
支持索引访问:result[0] = integral,result[1] = error;支持解包:integral, error = result。
示例:
>>> import ocas
>>> r = ocas.integrate_1d(lambda x: x**2, 0.0, 1.0, seed=42)
>>> r[0] # integral
0.333...
>>> r[1] # error
0.00...
>>> i, e = r # unpacking
双精度浮点
DoubleF64
双精度浮点数运算(~31 位有效数字,~84 二进制位),使用 Dekker/Knuth "double-float" 算法。
签名:
ocas.DoubleF64(hi: float, lo: float = 0.0)
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
hi | float | 主值 |
lo | float | 误差项(默认 0.0) |
示例:
>>> from ocas import DoubleF64
>>> a = DoubleF64(1.0)
>>> b = DoubleF64(2.0)
>>> str(a + b)
'3'
DoubleF64.to_f64
DoubleF64.to_f64() -> float
转换为标准 float(丢失误差项精度)。
DoubleF64.components
DoubleF64.components() -> tuple[float, float]
返回 (hi, lo) 元组。
DoubleF64 算术运算
| 运算 | 语法 | 说明 |
|---|---|---|
| 加法 | a + b | 双精度加法 |
| 减法 | a - b | 双精度减法 |
| 乘法 | a * b | 双精度乘法 |
| 除法 | a / b | 双精度除法 |
| 取负 | -a | 逐分量取负 |
| 绝对值 | abs(a) | 双精度绝对值 |
| 幂 | a ** n | 整数幂(n 为 int) |
| 比较 | ==, <, <=, >, >= | 全序比较 |
异常:ValueError — 除以零(__truediv__)。
DoubleF64 超越函数
| 方法 | 说明 | 异常 |
|---|---|---|
sin() | 正弦 | — |
cos() | 余弦 | — |
tan() | 正切 | — |
exp() | 自然指数 $e^x$ | — |
ln() | 自然对数 | ValueError:非正数 |
sqrt() | 平方根 | ValueError:负数 |
完整示例:
>>> from ocas import DoubleF64
>>> DoubleF64(0.0).sin().to_f64()
0.0
>>> DoubleF64(4.0).sqrt().components()
(2.0, 0.0)
函数速查表
| 函数 | 签名 | 说明 |
|---|---|---|
solve_linear_rational | (a, b) -> list[tuple] | ℚ 上求解 Ax=b |
solve_linear_integer | (a, b) -> list[int] | ℤ 上求解 Ax=b |
solve_diophantine | (a, b, c) -> DiophantineSolution | None | ax+by=c |
classify_ode | (equation, func, var) -> list[str] | ODE 分类 |
dsolve | (equation, func, var, hint=None) -> str | 符号求解 ODE |
dsolve_ivp | (equation, func, var, y0, y1=None) -> str | Laplace IVP |
factorint | (n) -> list[tuple] | 整数素因子分解 |
isprime | (n) -> bool | BPSW 素性测试 |
isprime_u64 | (n) -> bool | 确定性素性(u64) |
nextprime | (n) -> int | 下一个素数 |
discrete_log | (p, base, target) -> int | 离散对数 |
crt | (moduli, residues) -> tuple | 中国剩余定理 |
jacobi_symbol | (a, n) -> int | Jacobi 符号 |
totient | (n) -> int | Euler totient |
mobius | (n) -> int | Möbius 函数 |
divisor_count | (n) -> int | 因数个数 |
divisor_sigma | (n, k=1) -> int | 因数幂和 |
liouville_lambda | (n) -> int | Liouville 函数 |
py_groebner_basis | (generators, n_vars=1, algorithm="auto") | Gröbner 基 |
py_ideal_contains | (generators, f, n_vars=1, algorithm="auto") | 理想成员判定 |
py_solve_polynomial_system | (equations, n_vars=1, algorithm="auto") | 多项式方程组求解 |
py_hilbert_series | (gb) -> HilbertSeries | Hilbert 级数 |
py_ideal_radical | (generators, n_vars=1) | 理想根式 |
py_primary_decomposition | (generators, n_vars=1) | 准素分解 |
py_is_zero_dimensional | (gb) -> bool | 零维判定 |
py_eliminate | (generators, elim_vars, n_vars=1, algorithm="auto") | 消元 |
contract_tensors | (a, b) -> tuple | 张量缩并 |
tensor_symmetrise_sign | (tensor) -> int | 对称化符号 |
canonicalize_tensors | (expr, specs, index_groups=None) | 张量规范化 |
young_project | (expr, tableau) -> str | Young 投影 |
refresh_dummies | (expr, specs) -> str | 哑指标刷新 |
integrate_1d | (f, a, b, **opts) -> IntegrateResult | 一维数值积分 |
类速查表
| 类 | 构造函数 | 说明 |
|---|---|---|
Expression | Expression(input) | 符号表达式 |
Polynomial | Polynomial(coeffs, domain=None) | 一元多项式 |
MultivariatePolynomial | MultivariatePolynomial(terms, n_vars) | 多元多项式 |
Matrix | Matrix(rows, domain=None) | 域上矩阵 |
IntegerDomain | IntegerDomain() | ℤ 选择器 |
RationalDomain | RationalDomain() | ℚ 选择器 |
FiniteField | FiniteField(modulus) | GF(p) |
AlgebraicExtension | AlgebraicExtension(min_poly) | 代数数域 |
AlgebraicElement | (由 AlgebraicExtension 创建) | 域元素 |
AlgebraicPolynomial | AlgebraicPolynomial(field, coeffs) | 代数域上多项式 |
DualShape | DualShape.first_order(n_vars) | 对偶数布局 |
HyperDual | HyperDual.variable(...) / .constant(...) | 超对偶数 |
Tensor | Tensor(name, slots, symmetry="none") | 命名张量 |
ExpressionEvaluator | ExpressionEvaluator(input, param_names) | 数值求值器 |
Vegas | Vegas(n_dims, **opts) | Monte Carlo 积分器 |
IntegrateResult | IntegrateResult(integral, error) | 积分结果 |
DoubleF64 | DoubleF64(hi, lo=0.0) | 双精度浮点 |
GroebnerBasis | (由函数返回) | Gröbner 基结果 |
RealSolution | (由函数返回) | 实数解 |
PolynomialSystemSolution | (由函数返回) | 方程组求解结果 |
HilbertSeries | (由函数返回) | Hilbert 级数 |
PrimaryComponent | (由函数返回) | 准素分解分量 |
DiophantineSolution | (由函数返回) | 丢番图方程解 |
PolynomialFactor | (由函数返回) | 因式分解因子 |
AlgebraicFactor | (由函数返回) | 代数域因式分解因子 |
C/C++ API 参考
⚠️ 重要提示:头文件
ocas.h已过时由 cbindgen 自动生成的
ocas-c/include/ocas.h缺少以下 9 个函数:
ocas_expr_integrate_heuristic(表达式域)- 8 个 Gröbner 域导出(
ocas_groebner_basis、ocas_groebner_basis_free、ocas_groebner_basis_len、ocas_is_zero_dimensional、ocas_solve_polynomial_system、ocas_system_solution_count、ocas_system_solution_value、ocas_system_solution_free)本文档基于
ocas-c/src/*.rs中的 实际导出,而非头文件。使用缺失函数时需自行声明函数原型。
⚠️ Gröbner 模块注意事项
Gröbner 模块使用非标准
-1错误码(而非OCAS_*常量),且OcasGroebnerBasis和OcasSystemSolution句柄未标记#[repr(C)]——它们在当前 Rust 版本中工作正常,但 ABI 稳定性不由编译器保证。
⚠️ C++ 包装覆盖范围
ocas.hpp仅覆盖三个域(共 22 个包装函数):
- 表达式:
ocas::Expression类(8 个成员:2 个构造函数 + 6 个方法)- 数论:
ocas::ntheory::*(11 个内联函数)- 张量:
ocas::tensor::*(3 个内联函数)其余 69 个导出无 C++ RAII 包装,需直接调用 C API。
通用约定
错误处理
所有 fallible 函数遵循以下模式:
int err = 0;
OcasExpr *e = ocas_expr_parse("x^2 + 1", &err);
if (e == NULL) {
fprintf(stderr, "错误 %d: %s\n", err, ocas_error_last_message());
ocas_error_clear();
return 1;
}
err_out参数可为NULL(忽略错误码)。- 返回指针的函数:失败返回
NULL。 - 返回
int的函数:失败返回非零值(具体含义见各函数文档)。 - 返回
size_t的函数:失败返回0。 - 返回
int的数论函数使用特殊哨兵值-1(isprime)或-2(jacobi/mobius/liouville)表示错误。
内存管理
| 返回类型 | 释放函数 |
|---|---|
char*(字符串) | ocas_string_free() |
OcasExpr* | ocas_expr_free() |
OcasPolyZ* | ocas_poly_z_free() |
OcasPolyFp* | ocas_poly_fp_free() |
OcasPolyFactorArray | ocas_poly_factor_array_free()(仅释放数组结构,不释放内部多项式句柄) |
OcasAlgebraicField* | ocas_algebraic_field_free() |
OcasAlgebraicPoly* | ocas_algebraic_poly_free() |
OcasAlgebraicFactorArray | ocas_algebraic_factor_array_free()(同上,仅释放数组) |
OcasVegas* | ocas_vegas_free() |
OcasTensor* | ocas_tensor_free() |
OcasTensorContraction | ocas_tensor_contraction_free()(释放数组/字符串,不释放内部张量句柄) |
OcasDualShape* | ocas_dual_shape_free() |
OcasHyperDual* | ocas_hyperdual_free() |
OcasGroebnerBasis* | ocas_groebner_basis_free() |
OcasSystemSolution* | ocas_system_solution_free() |
所有 *_free 函数对 NULL 参数安全(no-op)。
1. 错误/工具(3 个函数)
ocas_version
签名:
const char *ocas_version(void);
功能:返回 oCAS 版本字符串。
返回值:程序生命周期内有效的静态字符串指针。调用方不得释放或修改。
示例:
printf("oCAS 版本: %s\n", ocas_version());
// 输出:oCAS 版本: 0.24.x
ocas_error_last_message
签名:
const char *ocas_error_last_message(void);
功能:返回调用线程上最后一次错误的消息,无错误时返回 NULL。
返回值:库拥有的字符串指针。不得释放或修改。在同一线程上调用任何可能设置错误的函数或 ocas_error_clear() 后失效。
示例:
ocas_expr_parse("invalid !!!", NULL);
const char *msg = ocas_error_last_message();
if (msg) printf("错误: %s\n", msg);
ocas_error_clear();
ocas_error_clear
签名:
void ocas_error_clear(void);
功能:清除调用线程上的最后错误状态。
示例:
ocas_error_clear(); // 重置错误状态
2. 表达式(12 个函数)
表达式是 oCAS 的核心类型。每个 OcasExpr* 句柄拥有私有 arena、一个 AtomArena 和一个规范化的根 Atom。句柄可移动;堆地址在句柄生命周期内不变。
ocas_expr_parse
签名:
OcasExpr *ocas_expr_parse(const char *input, int *err_out);
功能:将输入字符串解析为新的表达式句柄。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
input | const char* | 以 null 结尾的表达式字符串,如 "x^2 + 2*x + 1" |
err_out | int* | 可为 NULL;非空时写入错误码 |
返回值:成功返回 OcasExpr*;失败返回 NULL(OCAS_ERROR_PARSE)。
内存:调用方负责用 ocas_expr_free() 释放。
示例:
int err = 0;
OcasExpr *e = ocas_expr_parse("sin(x)^2 + cos(x)^2", &err);
assert(err == OCAS_OK && e != NULL);
ocas_expr_free(e);
ocas_expr_free
签名:
void ocas_expr_free(OcasExpr *handle);
功能:释放表达式句柄。NULL 安全(no-op)。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
handle | OcasExpr* | 由 ocas_expr_parse 或其他表达式函数返回的句柄 |
示例:
ocas_expr_free(e); // 释放
ocas_expr_free(NULL); // no-op
ocas_expr_clone
签名:
OcasExpr *ocas_expr_clone(const OcasExpr *handle, int *err_out);
功能:克隆表达式到新 arena。返回独立副本。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
handle | const OcasExpr* | 非空表达式句柄 |
err_out | int* | 可为 NULL |
返回值:新句柄或 NULL(OCAS_ERROR_RUNTIME)。
示例:
OcasExpr *copy = ocas_expr_clone(original, NULL);
// copy 和 original 独立,互不影响
ocas_expr_free(copy);
ocas_expr_to_string
签名:
char *ocas_expr_to_string(const OcasExpr *handle, int *err_out);
功能:将表达式渲染为 null 结尾的 C 字符串。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
handle | const OcasExpr* | 非空表达式句柄 |
err_out | int* | 可为 NULL |
返回值:堆分配的字符串(调用方用 ocas_string_free() 释放),或 NULL。
示例:
char *s = ocas_expr_to_string(e, NULL);
printf("表达式: %s\n", s);
ocas_string_free(s);
ocas_string_free
签名:
void ocas_string_free(char *s);
功能:释放由 ocas_expr_to_string 或其他返回 char* 的函数分配的字符串。NULL 安全。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
s | char* | 堆分配的字符串指针 |
ocas_expr_normalize
签名:
int ocas_expr_normalize(OcasExpr *handle, int *err_out);
功能:就地重新规范化表达式句柄。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
handle | OcasExpr* | 非空表达式句柄(修改) |
err_out | int* | 可为 NULL |
返回值:OCAS_OK(0)成功;非零失败。
ocas_expr_diff
签名:
OcasExpr *ocas_expr_diff(const OcasExpr *handle, const char *var, int *err_out);
功能:对表达式关于变量 var 求导。返回新句柄。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
handle | const OcasExpr* | 非空表达式句柄 |
var | const char* | 变量名,如 "x" |
err_out | int* | 可为 NULL |
返回值:导数表达式句柄或 NULL。
示例:
OcasExpr *e = ocas_expr_parse("x^3 + 2*x", NULL);
OcasExpr *de = ocas_expr_diff(e, "x", NULL);
char *s = ocas_expr_to_string(de, NULL);
printf("d/dx = %s\n", s); // 输出:2 + (3*(x^2))
ocas_string_free(s);
ocas_expr_free(de);
ocas_expr_free(e);
ocas_expr_integrate
签名:
OcasExpr *ocas_expr_integrate(const OcasExpr *handle, const char *var, int *err_out);
功能:对表达式关于变量 var 积分。若无法解析求解,返回未求值形式 Integral(expr, var)。结果未经认证;需要可机检证书时用 ocas_expr_integrate_outcome。
参数:同 ocas_expr_diff。
返回值:积分表达式句柄或 NULL。
示例:
OcasExpr *e = ocas_expr_parse("3*x^2", NULL);
OcasExpr *ie = ocas_expr_integrate(e, "x", NULL);
char *s = ocas_expr_to_string(ie, NULL);
printf("∫ = %s\n", s); // 输出:3*(3^-1)*(x^3)
ocas_string_free(s);
ocas_expr_free(ie);
ocas_expr_free(e);
ocas_expr_integrate_outcome
签名:
OcasExpr *ocas_expr_integrate_outcome(const OcasExpr *handle, const char *var,
int *outcome_out, int *err_out);
功能:认证积分(0.28.0 新增)。*outcome_out 取 ocas_OCAS_INTEGRATION_FOUND、
ocas_OCAS_INTEGRATION_PROVED_NONELEMENTARY、ocas_OCAS_INTEGRATION_UNKNOWN 之一。
FOUND 的返回值一定带可机检的符号证书(D(F) − f ≡ 0);UNKNOWN 诚实返回未求值
Integral(expr, var);被精确检查器证伪的结果永远不会以 FOUND 返回。
PROVED_NONELEMENTARY 在 0.28.0 无生产者(变体为后续非初等层预留)。
参数:
| 参数 | 说明 |
|---|---|
handle | 被积表达式 |
var | 积分变量名 |
outcome_out | 输出三值判定;可为 NULL |
err_out | 错误码输出 |
返回值:新表达式句柄(调用者负责释放)或 NULL。
示例:
int outcome = -1;
OcasExpr *ie = ocas_expr_integrate_outcome(e, "x", &outcome, NULL);
if (outcome == ocas_OCAS_INTEGRATION_FOUND) {
/* ie 的原函数带证书:D(ie) == 被积函数 */
}
ocas_expr_free(ie);
ocas_expr_integrate_heuristic
签名:
OcasExpr *ocas_expr_integrate_heuristic(const OcasExpr *handle, const char *var, int *err_out);
功能:使用启发式技术(分部积分、三角替换、Weierstrass 有理化、Euler 替换)积分。若无启发式成功,返回未求值形式。
⚠️ 注意:此函数未包含在
ocas.h头文件中。使用时需自行声明:extern OcasExpr *ocas_expr_integrate_heuristic(const OcasExpr *handle, const char *var, int *err_out);
参数:同 ocas_expr_diff。
返回值:积分表达式句柄或 NULL。
ocas_expr_taylor
签名:
OcasExpr *ocas_expr_taylor(const OcasExpr *handle, const char *var,
const OcasExpr *point, uint32_t order, int *err_out);
功能:计算表达式在 point 处关于 var 的 order 阶 Taylor 展开。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
handle | const OcasExpr* | 非空表达式句柄 |
var | const char* | 展开变量 |
point | const OcasExpr* | 展开点(表达式句柄) |
order | uint32_t | 展开阶数 |
err_out | int* | 可为 NULL |
返回值:Taylor 展开句柄或 NULL。
示例:
OcasExpr *f = ocas_expr_parse("exp(x)", NULL);
OcasExpr *zero = ocas_expr_parse("0", NULL);
OcasExpr *t = ocas_expr_taylor(f, "x", zero, 4, NULL);
char *s = ocas_expr_to_string(t, NULL);
printf("Taylor = %s\n", s); // 输出:1 + x + ((2^-1)*(x^2)) + ((6^-1)*(x^3)) + ((24^-1)*(x^4))
ocas_string_free(s);
ocas_expr_free(t);
ocas_expr_free(zero);
ocas_expr_free(f);
ocas_expr_simplify
签名:
OcasExpr *ocas_expr_simplify(const OcasExpr *handle, int *err_out);
功能:使用默认规则集化简表达式。返回新句柄。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
handle | const OcasExpr* | 非空表达式句柄 |
err_out | int* | 可为 NULL |
返回值:化简后的表达式句柄或 NULL。
示例:
OcasExpr *e = ocas_expr_parse("x + x", NULL);
OcasExpr *simplified = ocas_expr_simplify(e, NULL);
char *s = ocas_expr_to_string(simplified, NULL);
printf("化简 = %s\n", s); // 输出:2*x
ocas_string_free(s);
ocas_expr_free(simplified);
ocas_expr_free(e);
ocas_expr_substitute
签名:
OcasExpr *ocas_expr_substitute(const OcasExpr *handle, const char *var,
const OcasExpr *replacement, int *err_out);
功能:将表达式中所有 var 的出现替换为 replacement。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
handle | const OcasExpr* | 非空表达式句柄 |
var | const char* | 被替换的变量名 |
replacement | const OcasExpr* | 替换表达式(非空) |
err_out | int* | 可为 NULL |
返回值:替换后的表达式句柄或 NULL。
示例:
OcasExpr *e = ocas_expr_parse("x^2 + y", NULL);
OcasExpr *val = ocas_expr_parse("3", NULL);
OcasExpr *result = ocas_expr_substitute(e, "x", val, NULL);
char *s = ocas_expr_to_string(result, NULL);
printf("替换后 = %s\n", s); // 输出:y + (3^2)
ocas_string_free(s);
ocas_expr_free(result);
ocas_expr_free(val);
ocas_expr_free(e);
C++ 包装:ocas::Expression
ocas.hpp 提供 RAII 包装类,自动管理句柄生命周期:
#include <ocas.h>
#include <ocas.hpp>
try {
ocas::Expression e("x^2 + 1");
ocas::Expression d = e.diff("x");
std::cout << "d/dx = " << d.to_string() << std::endl;
// 自动释放所有句柄
} catch (const ocas::Error& ex) {
std::cerr << "错误: " << ex.what() << std::endl;
}
| C++ 方法 | 对应 C 函数 |
|---|---|
Expression(input) | ocas_expr_parse |
Expression(other) | ocas_expr_clone |
to_string() | ocas_expr_to_string + ocas_string_free |
diff(var) | ocas_expr_diff |
integrate(var) | ocas_expr_integrate |
simplify() | ocas_expr_simplify |
substitute(var, rep) | ocas_expr_substitute |
raw() | 获取原始 OcasExpr*(不转移所有权) |
3. 整数多项式(6 个函数)
处理双变量(x, y)整数多项式 ℤ[x,y]。从字符串解析,支持因式分解。
ocas_poly_z_create
签名:
OcasPolyZ *ocas_poly_z_create(const char *input, int *err);
功能:从字符串创建双变量整数多项式。输入可含变量 x、y,整数系数,加法、乘法和非负整数幂。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
input | const char* | 多项式字符串,如 "x^2*y + 3*x - 1" |
err | int* | 可为 NULL |
返回值:OcasPolyZ* 句柄或 NULL(OCAS_ERROR_PARSE)。
内存:调用方用 ocas_poly_z_free() 释放。
示例:
int err = 0;
OcasPolyZ *p = ocas_poly_z_create("x^2 + y^2 - 1", &err);
assert(p != NULL);
ocas_poly_z_free(p);
ocas_poly_z_free
签名:
void ocas_poly_z_free(OcasPolyZ *poly);
功能:释放整数多项式句柄。NULL 安全。
ocas_poly_z_clone
签名:
OcasPolyZ *ocas_poly_z_clone(const OcasPolyZ *poly);
功能:克隆整数多项式。返回新句柄(调用方释放)。
返回值:新 OcasPolyZ* 或 NULL(输入为 NULL 或内存不足)。
ocas_poly_z_degree
签名:
size_t ocas_poly_z_degree(const OcasPolyZ *poly);
功能:返回多项式的总次数。零多项式返回 0。NULL 句柄返回 0。
ocas_poly_z_to_string
签名:
char *ocas_poly_z_to_string(const OcasPolyZ *poly, int *err);
功能:返回多项式的堆分配字符串表示。调用方用 ocas_string_free() 释放。
返回值:字符串或 NULL。
示例:
char *s = ocas_poly_z_to_string(p, NULL);
printf("多项式: %s\n", s);
ocas_string_free(s);
ocas_poly_z_factor
签名:
int ocas_poly_z_factor(const OcasPolyZ *poly, OcasPolyFactorArray *out, int *err);
功能:对双变量整数多项式进行因式分解。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
poly | const OcasPolyZ* | 非空多项式句柄 |
out | OcasPolyFactorArray* | 输出结构体(成功时填充) |
err | int* | 可为 NULL |
返回值:OCAS_OK(0)成功;非零失败(out 不变)。
内存:成功时,out.factors[i].poly 是 void*,需转换为 OcasPolyZ* 并用 ocas_poly_z_free() 释放。数组本身用 ocas_poly_factor_array_free() 释放。
示例:
OcasPolyZ *p = ocas_poly_z_create("x^2 - y^2", NULL);
OcasPolyFactorArray arr = {0};
if (ocas_poly_z_factor(p, &arr, NULL) == OCAS_OK) {
for (size_t i = 0; i < arr.len; i++) {
OcasPolyZ *f = (OcasPolyZ *)arr.factors[i].poly;
char *s = ocas_poly_z_to_string(f, NULL);
printf("因子: %s (重数 %zu)\n", s, arr.factors[i].multiplicity);
ocas_string_free(s);
ocas_poly_z_free(f);
}
ocas_poly_factor_array_free(&arr);
}
ocas_poly_z_free(p);
4. 有限域多项式(6 个函数)
处理双变量多项式 $𝔽_p[x,y]$。
ocas_poly_fp_create
签名:
OcasPolyFp *ocas_poly_fp_create(const char *input, const char *prime, int *err);
功能:在素数域 $𝔽_p$ 上创建双变量多项式。字符串中的系数自动取模 $p$。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
input | const char* | 多项式字符串 |
prime | const char* | 素数 $p$ 的十进制字符串 |
err | int* | 可为 NULL |
返回值:OcasPolyFp* 句柄或 NULL。
内存:调用方用 ocas_poly_fp_free() 释放。
示例:
OcasPolyFp *p = ocas_poly_fp_create("x^2 + 2*x + 1", "7", NULL);
// 系数在 𝔽₇ 中:x² + 2x + 1
ocas_poly_fp_free(p);
ocas_poly_fp_free
签名:
void ocas_poly_fp_free(OcasPolyFp *poly);
功能:释放有限域多项式句柄。NULL 安全。
ocas_poly_fp_clone
签名:
OcasPolyFp *ocas_poly_fp_clone(const OcasPolyFp *poly);
功能:克隆有限域多项式。返回新句柄。
ocas_poly_fp_degree
签名:
size_t ocas_poly_fp_degree(const OcasPolyFp *poly);
功能:返回多项式的总次数。NULL 返回 0。
ocas_poly_fp_to_string
签名:
char *ocas_poly_fp_to_string(const OcasPolyFp *poly, int *err);
功能:返回多项式的堆分配字符串。调用方用 ocas_string_free() 释放。
ocas_poly_fp_factor
签名:
int ocas_poly_fp_factor(const OcasPolyFp *poly, OcasPolyFactorArray *out, int *err);
功能:对有限域多项式进行因式分解。返回值和内存管理同 ocas_poly_z_factor,但 out.factors[i].poly 需转换为 OcasPolyFp*。
示例:
OcasPolyFp *p = ocas_poly_fp_create("x^2 - 1", "5", NULL);
OcasPolyFactorArray arr = {0};
if (ocas_poly_fp_factor(p, &arr, NULL) == OCAS_OK) {
for (size_t i = 0; i < arr.len; i++) {
OcasPolyFp *f = (OcasPolyFp *)arr.factors[i].poly;
char *s = ocas_poly_fp_to_string(f, NULL);
printf("因子: %s (重数 %zu)\n", s, arr.factors[i].multiplicity);
ocas_string_free(s);
ocas_poly_fp_free(f);
}
ocas_poly_factor_array_free(&arr);
}
ocas_poly_fp_free(p);
5. 因子数组(1 个函数)
ocas_poly_factor_array_free
签名:
void ocas_poly_factor_array_free(OcasPolyFactorArray *arr);
功能:释放因子数组结构本身及内部的 OcasPolyFactor 对象,但不释放各因子的多项式句柄。
⚠️ 内存陷阱:必须先用
ocas_poly_z_free()/ocas_poly_fp_free()释放每个arr->factors[i].poly,再调用此函数。顺序颠倒会导致 use-after-free。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
arr | OcasPolyFactorArray* | 由 ocas_poly_z_factor 或 ocas_poly_fp_factor 填充的数组 |
6. 代数数域(9 个函数)
处理代数数域 $ℚ(α)$ 上的多项式。支持 Trager 因式分解算法。
数据格式
极小多项式:单变量字符串,变量必须为 x,如 "x^2 - 2" 表示 $ℚ(\sqrt{2})$。
系数列表:分号分隔(常数项在前),每个系数是逗号分隔的有理数列表($α$-多项式,升序)。例如在 $ℚ(\sqrt{2})$ 上:
"-2;0;1"— $x^2 - 2$(所有系数在基域中)"0;0;0,1"— $x^2 - α$($x^2$ 系数为 $0 + 1 \cdot α$)
单个有理数可省略逗号。
ocas_algebraic_field_create
签名:
OcasAlgebraicField *ocas_algebraic_field_create(const char *min_poly, int *err);
功能:从首一极小多项式创建代数数域。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
min_poly | const char* | 极小多项式字符串,如 "x^2 - 2" |
err | int* | 可为 NULL |
返回值:OcasAlgebraicField* 句柄或 NULL。
内存:调用方用 ocas_algebraic_field_free() 释放。
示例:
OcasAlgebraicField *q_sqrt2 = ocas_algebraic_field_create("x^2 - 2", NULL);
printf("扩张次数: %zu\n", ocas_algebraic_field_degree(q_sqrt2));
// 输出:扩张次数: 2
ocas_algebraic_field_free(q_sqrt2);
ocas_algebraic_field_free
签名:
void ocas_algebraic_field_free(OcasAlgebraicField *field);
功能:释放代数数域句柄。NULL 安全。
ocas_algebraic_field_degree
签名:
size_t ocas_algebraic_field_degree(const OcasAlgebraicField *field);
功能:返回扩张次数 $\deg(m) = [ℚ(α):ℚ]$。NULL 返回 0。
ocas_algebraic_poly_create
签名:
OcasAlgebraicPoly *ocas_algebraic_poly_create(const OcasAlgebraicField *field,
const char *coeffs, int *err);
功能:在代数数域上创建多项式。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
field | const OcasAlgebraicField* | 非空数域句柄 |
coeffs | const char* | 系数列表字符串(见上文格式) |
err | int* | 可为 NULL |
返回值:OcasAlgebraicPoly* 句柄或 NULL。
示例:
OcasAlgebraicField *fld = ocas_algebraic_field_create("x^2 - 2", NULL);
// 创建多项式 x² - α(即 x² - √2)
OcasAlgebraicPoly *p = ocas_algebraic_poly_create(fld, "0;0;0,1", NULL);
printf("次数: %zu\n", ocas_algebraic_poly_degree(p));
ocas_algebraic_poly_free(p);
ocas_algebraic_field_free(fld);
ocas_algebraic_poly_free
签名:
void ocas_algebraic_poly_free(OcasAlgebraicPoly *poly);
功能:释放代数域多项式句柄。NULL 安全。
ocas_algebraic_poly_degree
签名:
size_t ocas_algebraic_poly_degree(const OcasAlgebraicPoly *poly);
功能:返回多项式次数。零多项式返回 0。
ocas_algebraic_poly_to_string
签名:
char *ocas_algebraic_poly_to_string(const OcasAlgebraicPoly *poly, int *err);
功能:返回多项式的堆分配字符串。格式为 [c0] + [c1]*x + [c2]*x^2 + ...,其中 [ci] 是逗号分隔的 $α$-多项式有理数。调用方用 ocas_string_free() 释放。
ocas_algebraic_poly_factor
签名:
int ocas_algebraic_poly_factor(const OcasAlgebraicPoly *poly,
OcasAlgebraicFactorArray *out, int *err);
功能:使用 Trager 算法在代数数域上因式分解多项式。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
poly | const OcasAlgebraicPoly* | 非空多项式句柄 |
out | OcasAlgebraicFactorArray* | 输出数组(成功时填充) |
err | int* | 可为 NULL |
返回值:OCAS_OK(0)成功。
内存:out->factors[i].poly(void*)需转换为 OcasAlgebraicPoly* 并用 ocas_algebraic_poly_free() 释放。数组用 ocas_algebraic_factor_array_free() 释放。
示例:
OcasAlgebraicField *fld = ocas_algebraic_field_create("x^2 - 2", NULL);
OcasAlgebraicPoly *p = ocas_algebraic_poly_create(fld, "-2;0;1", NULL);
OcasAlgebraicFactorArray arr = {0};
if (ocas_algebraic_poly_factor(p, &arr, NULL) == OCAS_OK) {
for (size_t i = 0; i < arr.len; i++) {
OcasAlgebraicPoly *f = (OcasAlgebraicPoly *)arr.factors[i].poly;
char *s = ocas_algebraic_poly_to_string(f, NULL);
printf("因子: %s (重数 %zu)\n", s, arr.factors[i].multiplicity);
ocas_string_free(s);
ocas_algebraic_poly_free(f);
}
ocas_algebraic_factor_array_free(&arr);
}
ocas_algebraic_poly_free(p);
ocas_algebraic_field_free(fld);
ocas_algebraic_factor_array_free
签名:
void ocas_algebraic_factor_array_free(OcasAlgebraicFactorArray *arr);
功能:释放因子数组的存储。不释放各因子的多项式句柄——必须先单独释放。
7. 数值积分(6 个函数)
Vegas 自适应蒙特卡洛积分器。
积分子函数签名
typedef double (*ocas_integrand_t)(double x, void *user_data);
user_data 从调用方原样传递到积分子。
⚠️ 注意:当前 C API 的积分子只接收采样点的第一个坐标(一维积分友好接口)。即使以
n_dims > 1创建积分器,积分子的x参数也仅代表第一个维度;完整的多维const double*/size_t签名留待后续版本。
ocas_vegas_create
签名:
OcasVegas *ocas_vegas_create(size_t n_dims, const OcasVegasOptions *opts, int *err);
功能:创建 n_dims 维 Vegas 积分器。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
n_dims | size_t | 积分维度 |
opts | const OcasVegasOptions* | 可为 NULL(使用默认值) |
err | int* | 可为 NULL |
OcasVegasOptions 字段:
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
n_bins | size_t | 64 | 每维度 bin 数 |
n_samples | size_t | 10000 | 每次迭代采样数 |
iterations | size_t | 10 | 自适应迭代次数 |
learning_rate | double | 1.5 | 网格平滑/学习率 |
seed | uint64_t | 0x0C45 | 随机数种子 |
返回值:OcasVegas* 句柄或 NULL。
内存:调用方用 ocas_vegas_free() 释放。
示例:
OcasVegasOptions opts = {
.n_bins = 50, .n_samples = 20000, .iterations = 8,
.learning_rate = 1.5, .seed = 42
};
int err = 0;
OcasVegas *v = ocas_vegas_create(1, &opts, &err);
ocas_vegas_free
签名:
void ocas_vegas_free(OcasVegas *v);
功能:释放 Vegas 积分器句柄。NULL 安全。
ocas_vegas_integrate
签名:
OcasIntegrateResult ocas_vegas_integrate(OcasVegas *v, ocas_integrand_t f,
void *user_data, int *err);
功能:在单位超立方体上对 f 积分(积分子仅接收第一个坐标,见上文注意)。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
v | OcasVegas* | 非空积分器句柄 |
f | ocas_integrand_t | 积分子函数 |
user_data | void* | 传递给积分子的用户数据 |
err | int* | 可为 NULL |
返回值:OcasIntegrateResult { double integral; double error; }。
示例:
static double f(double x, void *ud) { return x * x; }
OcasIntegrateResult r = ocas_vegas_integrate(v, f, NULL, &err);
printf("∫₀¹ x² dx ≈ %g ± %g\n", r.integral, r.error);
// 输出(示例值,随种子与采样数而定):∫₀¹ x² dx ≈ 0.333 ± 0.001
ocas_vegas_result
签名:
OcasIntegrateResult ocas_vegas_result(const OcasVegas *v);
功能:返回最近一次 ocas_vegas_integrate 的累计估计值和误差。
ocas_vegas_iterations
签名:
size_t ocas_vegas_iterations(const OcasVegas *v);
功能:返回已完成的迭代次数。NULL 返回 0。
ocas_integrate_1d
签名:
OcasIntegrateResult ocas_integrate_1d(ocas_integrand_t f, void *user_data,
double a, double b,
const OcasVegasOptions *opts, int *err);
功能:一维数值积分的便捷函数——在 $[a, b]$ 上使用 Vegas 积分。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
f | ocas_integrand_t | 积分子 |
user_data | void* | 用户数据 |
a | double | 积分下限 |
b | double | 积分上限 |
opts | const OcasVegasOptions* | 可为 NULL |
err | int* | 可为 NULL |
返回值:OcasIntegrateResult。
示例:
OcasIntegrateResult r = ocas_integrate_1d(f, NULL, 0.0, 1.0, NULL, &err);
printf("结果: %g ± %g\n", r.integral, r.error);
8. ODE(3 个函数)
符号常微分方程求解。所有输入输出均为字符串。
ocas_ode_classify
签名:
char *ocas_ode_classify(const char *equation, const char *func,
const char *var, int *err);
功能:分类 ODE 并返回适用的求解方法名列表(逗号分隔)。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
equation | const char* | ODE 等于零的表达式,如 "Derivative(y(x), x) - y(x)" |
func | const char* | 未知函数名,如 "y" |
var | const char* | 自变量,如 "x" |
err | int* | 可为 NULL |
返回值:堆分配字符串如 "LinearFirst,PowerSeries",调用方用 ocas_string_free() 释放。失败返回 NULL。
示例:
char *methods = ocas_ode_classify("Derivative(y(x),x) - y(x)", "y", "x", NULL);
printf("可用方法: %s\n", methods);
// 输出:可用方法: LinearFirst,PowerSeries
ocas_string_free(methods);
ocas_ode_dsolve
签名:
char *ocas_ode_dsolve(const char *equation, const char *func,
const char *var, const char *hint, int *err);
功能:符号求解 ODE。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
equation | const char* | ODE 表达式 |
func | const char* | 未知函数名 |
var | const char* | 自变量 |
hint | const char* | 可为 NULL(自动分类);或 ocas_ode_classify 返回的方法名之一 |
err | int* | 可为 NULL |
返回值:解字符串如 "y = C1*exp(x)" 或 "unsolved"。调用方用 ocas_string_free() 释放。
示例:
char *sol = ocas_ode_dsolve("Derivative(y(x),x) - y(x)", "y", "x", NULL, NULL);
printf("解: %s\n", sol); // 输出:解: y = C1*exp(x)
ocas_string_free(sol);
ocas_ode_dsolve_ivp
签名:
char *ocas_ode_dsolve_ivp(const char *equation, const char *func,
const char *var, const char *y0,
const char *y1, int *err);
功能:通过 Laplace 变换求解一阶或二阶线性常系数 ODE 的初值问题。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
equation | const char* | ODE 表达式 |
func | const char* | 未知函数名 |
var | const char* | 自变量 |
y0 | const char* | $y(0)$ 的表达式字符串,如 "1" |
y1 | const char* | $y'(0)$(可为 NULL,仅二阶需要) |
err | int* | 可为 NULL |
返回值:无自由常数的显式解字符串。调用方用 ocas_string_free() 释放。
示例:
// y'' + y = 0, y(0) = 1, y'(0) = 0 → y = cos(x)
char *sol = ocas_ode_dsolve_ivp(
"Derivative(y(x),x,2) + y(x)", "y", "x", "1", "0", NULL);
printf("IVP 解: %s\n", sol);
ocas_string_free(sol);
9. 数论(11 个函数)
任意精度整数通过十进制字符串传递。字符串结果由调用方用 ocas_string_free() 释放。
ocas_ntheory_factorint
签名:
char *ocas_ntheory_factorint(const char *n, int *err_out);
功能:对 $|n|$ 进行素因数分解。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
n | const char* | 十进制整数字符串 |
err_out | int* | 可为 NULL |
返回值:"p1:e1,p2:e2,..."(升序),负数开头含 "-1:1"。失败返回 NULL。
示例:
char *f = ocas_ntheory_factorint("360", NULL);
printf("360 = %s\n", f); // 输出:360 = 2:3,3:2,5:1
ocas_string_free(f);
f = ocas_ntheory_factorint("-12", NULL);
printf("-12 = %s\n", f); // 输出:-12 = -1:1,2:2,3:1
ocas_string_free(f);
ocas_ntheory_isprime
签名:
int ocas_ntheory_isprime(const char *n, int *err_out);
功能:BPSW 概率素性测试。
返回值:1 = (很可能) 素数,0 = 合数,-1 = 错误。
示例:
int r = ocas_ntheory_isprime("997", NULL);
printf("997 是素数?%s\n", r == 1 ? "是" : "否");
// 输出:997 是素数?是
ocas_ntheory_nextprime
签名:
char *ocas_ntheory_nextprime(const char *n, int *err_out);
功能:返回严格大于 n 的最小素数。
返回值:十进制字符串或 NULL。
示例:
char *p = ocas_ntheory_nextprime("100", NULL);
printf("100 之后的素数: %s\n", p); // 输出:101
ocas_string_free(p);
ocas_ntheory_discrete_log
签名:
char *ocas_ntheory_discrete_log(const char *p, const char *base,
const char *target, int *err_out);
功能:求解 $\text{base}^x \equiv \text{target} \pmod{p}$。素数 $p$ 使用 Pohlig–Hellman,否则使用 BSGS。
返回值:对数的十进制字符串,或 NULL(无解时设 OCAS_ERROR_RUNTIME)。
示例:
char *x = ocas_ntheory_discrete_log("7", "3", "5", NULL);
// 3^x ≡ 5 (mod 7) → x = 5
printf("离散对数: %s\n", x);
ocas_string_free(x);
ocas_ntheory_crt
签名:
char *ocas_ntheory_crt(const char *moduli, const char *residues, int *err_out);
功能:中国剩余定理。模数无需互素;不一致返回 NULL。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
moduli | const char* | 逗号分隔模数,如 "3,5,7" |
residues | const char* | 逗号分隔余数,如 "2,3,2" |
返回值:"r,m"($r \equiv \text{residues}[i] \pmod{\text{moduli}[i]}$),或 NULL。
示例:
char *r = ocas_ntheory_crt("3,5,7", "2,3,2", NULL);
printf("CRT: %s\n", r); // 输出:CRT: 23,105
// 23 ≡ 2 (mod 3), 23 ≡ 3 (mod 5), 23 ≡ 2 (mod 7)
ocas_string_free(r);
ocas_ntheory_jacobi
签名:
int ocas_ntheory_jacobi(const char *a, const char *n, int *err_out);
功能:计算 Jacobi 符号 $(a/n)$,要求 $n$ 为正奇数。
返回值:$-1$、$0$ 或 $1$;$-2$ 表示输入无效。
ocas_ntheory_totient
签名:
char *ocas_ntheory_totient(const char *n, int *err_out);
功能:Euler totient 函数 $\varphi(n)$。
返回值:十进制字符串或 NULL。
示例:
char *t = ocas_ntheory_totient("12", NULL);
printf("φ(12) = %s\n", t); // 输出:φ(12) = 4
ocas_string_free(t);
ocas_ntheory_mobius
签名:
int ocas_ntheory_mobius(const char *n, int *err_out);
功能:Möbius 函数 $\mu(n)$。
返回值:$-1$、$0$ 或 $1$;$-2$ 表示错误。
ocas_ntheory_divisor_count
签名:
char *ocas_ntheory_divisor_count(const char *n, int *err_out);
功能:正因数个数 $\tau(n)$。
返回值:十进制字符串或 NULL。
示例:
char *d = ocas_ntheory_divisor_count("12", NULL);
printf("τ(12) = %s\n", d); // 输出:τ(12) = 6
// 12 的因数:1,2,3,4,6,12
ocas_string_free(d);
ocas_ntheory_divisor_sigma
签名:
char *ocas_ntheory_divisor_sigma(const char *n, uint32_t k, int *err_out);
功能:正因数的 $k$ 次幂和 $\sigma_k(n)$。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
n | const char* | 正整数 |
k | uint32_t | 幂次 |
err_out | int* | 可为 NULL |
返回值:十进制字符串或 NULL。
示例:
char *s = ocas_ntheory_divisor_sigma("6", 1, NULL);
printf("σ₁(6) = %s\n", s); // 输出:σ₁(6) = 12
// 1+2+3+6 = 12
ocas_string_free(s);
ocas_ntheory_liouville
签名:
int ocas_ntheory_liouville(const char *n, int *err_out);
功能:Liouville 函数 $\lambda(n) = (-1)^{\Omega(n)}$。
返回值:$-1$、$0$ 或 $1$;$-2$ 表示错误。
C++ 包装:ocas::ntheory::*
所有数论函数均有 C++ 内联包装,自动管理字符串内存:
#include <ocas.h>
#include <ocas.hpp>
std::string f = ocas::ntheory::factorint("360");
// f == "2:3,3:2,5:1"
bool prime = ocas::ntheory::isprime("997");
// prime == true
std::string p = ocas::ntheory::nextprime("100");
// p == "101"
std::string tot = ocas::ntheory::totient("12");
// tot == "4"
| C++ 函数 | C 函数 | 说明 |
|---|---|---|
factorint(n) | ocas_ntheory_factorint | 素因数分解 |
isprime(n) | ocas_ntheory_isprime | 返回 bool,错误抛异常 |
nextprime(n) | ocas_ntheory_nextprime | 下一个素数 |
discrete_log(p, base, target) | ocas_ntheory_discrete_log | 离散对数,无解抛异常 |
crt(moduli, residues) | ocas_ntheory_crt | 中国剩余定理 |
jacobi(a, n) | ocas_ntheory_jacobi | Jacobi 符号,无效输入抛异常 |
totient(n) | ocas_ntheory_totient | Euler totient |
mobius(n) | ocas_ntheory_mobius | Möbius 函数,无效输入抛异常 |
divisor_count(n) | ocas_ntheory_divisor_count | 因数个数 |
divisor_sigma(n, k=1) | ocas_ntheory_divisor_sigma | 因数幂和 |
liouville(n) | ocas_ntheory_liouville | Liouville 函数,无效输入抛异常 |
所有包装在 C API 返回错误时抛出 ocas::Error 异常。
10. 张量(12 个函数)
命名指标张量,支持指标缩并和对称性。
槽位字符串格式
槽位以分号分隔,每个条目为 label,position,其中 position 为 "upper" 或 "lower"(别名:up/down/contravariant/covariant)。
例如:"i,upper;j,lower" 表示两个槽位 $i^j{}_{}$。
ocas_tensor_create
签名:
OcasTensor *ocas_tensor_create(const char *name, const char *slots,
const char *symmetry, int *err);
功能:创建张量。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
name | const char* | 张量名,如 "T" |
slots | const char* | 槽位字符串 |
symmetry | const char* | 可为 NULL(= "none");或 "none"/"symmetric"/"antisymmetric" |
err | int* | 可为 NULL |
返回值:OcasTensor* 句柄或 NULL。
示例:
OcasTensor *T = ocas_tensor_create("T", "i,upper;j,lower", "symmetric", NULL);
printf("秩: %zu\n", ocas_tensor_rank(T)); // 输出:秩: 2
ocas_tensor_free(T);
ocas_tensor_free
签名:
void ocas_tensor_free(OcasTensor *t);
功能:释放张量句柄。NULL 安全。
ocas_tensor_name
签名:
char *ocas_tensor_name(const OcasTensor *t, int *err);
功能:返回张量名称的堆分配字符串。调用方用 ocas_string_free() 释放。
ocas_tensor_rank
签名:
size_t ocas_tensor_rank(const OcasTensor *t);
功能:返回张量的秩(槽位数)。NULL 返回 0。
ocas_tensor_symmetry
签名:
int ocas_tensor_symmetry(const OcasTensor *t);
功能:返回对称性代码。
返回值:0 = none,1 = symmetric,2 = antisymmetric,-1 = null 句柄。
ocas_tensor_to_string
签名:
char *ocas_tensor_to_string(const OcasTensor *t, int *err);
功能:返回张量的字符串表示 name(slot, slot, ...)。调用方用 ocas_string_free() 释放。
ocas_tensor_symmetrise_sign
签名:
int64_t ocas_tensor_symmetrise_sign(const OcasTensor *t);
功能:返回反对称化符号($+1$ 或 $-1$)。NULL 返回 0。
ocas_tensor_contract
签名:
int ocas_tensor_contract(const OcasTensor *a, const OcasTensor *b,
OcasTensorContraction *out, int *err);
功能:对两个张量的共享哑指标(相同标签、相反变异性)求和缩并。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
a | const OcasTensor* | 非空张量 |
b | const OcasTensor* | 非空张量 |
out | OcasTensorContraction* | 输出结构体 |
err | int* | 可为 NULL |
返回值:OCAS_OK(0)成功。
OcasTensorContraction 字段:
| 字段 | 类型 | 说明 |
|---|---|---|
kind | int | 0 = product(有自由指标),1 = scalar(完全缩并) |
tensors | OcasTensor** | kind == 0 时有效,张量句柄数组 |
n_tensors | size_t | tensors 数组长度 |
scalar_str | char* | kind == 1 时有效,标量结果字符串 |
内存:
kind == 0:每个tensors[i]用ocas_tensor_free()释放,数组用ocas_tensor_contraction_free()释放。kind == 1:scalar_str用ocas_string_free()释放,结构体用ocas_tensor_contraction_free()释放。
示例:
OcasTensor *A = ocas_tensor_create("A", "i,upper;j,lower", NULL, NULL);
OcasTensor *B = ocas_tensor_create("B", "j,upper;k,lower", NULL, NULL);
OcasTensorContraction result = {0};
if (ocas_tensor_contract(A, B, &result, NULL) == OCAS_OK) {
if (result.kind == 0) {
// 自由指标剩余
for (size_t i = 0; i < result.n_tensors; i++) {
char *s = ocas_tensor_to_string(result.tensors[i], NULL);
printf("结果张量: %s\n", s);
ocas_string_free(s);
ocas_tensor_free(result.tensors[i]);
}
}
ocas_tensor_contraction_free(&result);
}
ocas_tensor_free(B);
ocas_tensor_free(A);
ocas_tensor_contraction_free
签名:
void ocas_tensor_contraction_free(OcasTensorContraction *c);
功能:释放缩并结果的 tensors 数组和 scalar_str。不释放各张量句柄。NULL 安全。
ocas_tensor_canonicalize
签名:
char *ocas_tensor_canonicalize(const char *expr_str, const char *specs_str,
const char *groups_str, int *err);
功能:通过图同构规范化张量表达式(0.22.0)。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
expr_str | const char* | 张量积字符串,如 "T(i,j)*U(j,k)" |
specs_str | const char* | 逗号分隔的 name:sym 对,如 "T:none,U:none" |
groups_str | const char* | 可为 NULL;或逗号分隔的 label:group 对 |
err | int* | 可为 NULL |
返回值:堆分配的规范形式字符串。调用方用 ocas_string_free() 释放。
示例:
char *canon = ocas_tensor_canonicalize(
"T(i,j)*T(j,i)", "T:symmetric", NULL, NULL);
printf("规范形式: %s\n", canon);
ocas_string_free(canon);
ocas_young_project
签名:
char *ocas_young_project(const char *expr_str, const char *tableau_str, int *err);
功能:对张量表达式应用 Young 投影子(0.22.0)。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
expr_str | const char* | 张量表达式,如 "f(a,b)" |
tableau_str | const char* | 逗号分隔的行长度,如 "2,1" 表示 □□/□ |
err | int* | 可为 NULL |
返回值:堆分配的展开表达式字符串。调用方用 ocas_string_free() 释放。
示例:
// 全反对称投影((1,1,1) Young 表,三指标)
char *proj = ocas_young_project("f(a,b,c)", "1,1,1", NULL);
printf("投影结果: %s\n", proj);
ocas_string_free(proj);
ocas_tensor_refresh_dummies
签名:
char *ocas_tensor_refresh_dummies(const char *expr_str, const char *specs_str, int *err);
功能:重命名张量表达式中的哑指标(恰好出现两次的标签)为 d0、d1、… 以避免冲突(0.22.0)。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
expr_str | const char* | 张量表达式 |
specs_str | const char* | name:sym 对 |
err | int* | 可为 NULL |
返回值:堆分配字符串。调用方用 ocas_string_free() 释放。
C++ 包装:ocas::tensor::*
std::string canon = ocas::tensor::canonicalize("T(i,j)*T(j,i)", "T:symmetric");
std::string proj = ocas::tensor::young_project("f(a,b,c)", "2,1");
std::string fresh = ocas::tensor::refresh_dummies("T(i,j)*U(j,i)", "T:none,U:none");
所有包装在错误时抛出 ocas::Error。
11. 双数(14 个函数)
超对偶数(前向自动微分),仅支持 Rational 系数。多项式/有理算术。
系数字符串格式
有理数以 "num"(分母为 1)或 "num/den" 传递。返回字符串使用相同格式。
ocas_dual_shape_new
签名:
OcasDualShape *ocas_dual_shape_new(size_t n_vars, int *err);
功能:创建一阶形状,跟踪 n_vars 个变量的导数。
返回值:OcasDualShape* 句柄或 NULL。
示例:
OcasDualShape *shape = ocas_dual_shape_new(2, NULL); // 2 个变量
printf("变量数: %zu, 分量数: %zu\n",
ocas_dual_shape_n_vars(shape),
ocas_dual_shape_n_components(shape));
// 输出:变量数: 2, 分量数: 3
ocas_dual_shape_free(shape);
ocas_dual_shape_free
签名:
void ocas_dual_shape_free(OcasDualShape *s);
功能:释放形状句柄。NULL 安全。
ocas_dual_shape_n_vars
签名:
size_t ocas_dual_shape_n_vars(const OcasDualShape *s);
功能:返回微分变量数。NULL 返回 0。
ocas_dual_shape_n_components
签名:
size_t ocas_dual_shape_n_components(const OcasDualShape *s);
功能:返回总分量数(值 + 各偏导)。NULL 返回 0。
ocas_dual_variable
签名:
OcasHyperDual *ocas_dual_variable(const OcasDualShape *shape, size_t i,
const char *coeff, int *err);
功能:创建独立变量 $x_i = \text{coeff}$(对变量 $i$ 的导数为 1)。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
shape | const OcasDualShape* | 非空形状句柄 |
i | size_t | 变量索引($0 \le i < \text{n_vars}$) |
coeff | const char* | 系数字符串 "num" 或 "num/den" |
err | int* | 可为 NULL |
返回值:OcasHyperDual* 句柄或 NULL。
ocas_dual_constant
签名:
OcasHyperDual *ocas_dual_constant(const OcasDualShape *shape,
const char *coeff, int *err);
功能:创建常量双数(所有导数为零)。
ocas_hyperdual_free
签名:
void ocas_hyperdual_free(OcasHyperDual *d);
功能:释放超对偶数句柄。NULL 安全。
ocas_dual_value
签名:
char *ocas_dual_value(const OcasHyperDual *d, int *err);
功能:返回标量值分量的堆分配字符串。调用方用 ocas_string_free() 释放。
ocas_dual_deriv
签名:
char *ocas_dual_deriv(const OcasHyperDual *d, size_t i, int *err);
功能:返回关于变量 $i$ 的偏导数的堆分配字符串。形状无 $i$ 的一阶分量时返回 NULL。
示例:
OcasDualShape *shape = ocas_dual_shape_new(2, NULL);
OcasHyperDual *x = ocas_dual_variable(shape, 0, "3", NULL); // x₀ = 3
OcasHyperDual *y = ocas_dual_variable(shape, 1, "5", NULL); // x₁ = 5
OcasHyperDual *prod = ocas_dual_mul(x, y, NULL); // f = x₀·x₁
char *val = ocas_dual_value(prod, NULL);
char *df_dx = ocas_dual_deriv(prod, 0, NULL); // ∂f/∂x₀ = x₁ = 5
char *df_dy = ocas_dual_deriv(prod, 1, NULL); // ∂f/∂x₁ = x₀ = 3
printf("f = %s, ∂f/∂x₀ = %s, ∂f/∂x₁ = %s\n", val, df_dx, df_dy);
// 输出:f = 15, ∂f/∂x₀ = 5, ∂f/∂x₁ = 3
ocas_string_free(val);
ocas_string_free(df_dx);
ocas_string_free(df_dy);
ocas_hyperdual_free(prod);
ocas_hyperdual_free(y);
ocas_hyperdual_free(x);
ocas_dual_shape_free(shape);
ocas_dual_add
签名:
OcasHyperDual *ocas_dual_add(const OcasHyperDual *a, const OcasHyperDual *b, int *err);
功能:计算 $a + b$。两个操作数必须共享同一形状。
返回值:新句柄或 NULL。
ocas_dual_sub
签名:
OcasHyperDual *ocas_dual_sub(const OcasHyperDual *a, const OcasHyperDual *b, int *err);
功能:计算 $a - b$。
ocas_dual_mul
签名:
OcasHyperDual *ocas_dual_mul(const OcasHyperDual *a, const OcasHyperDual *b, int *err);
功能:计算 $a \times b$。通过乘积法则自动传播导数。
ocas_dual_div
签名:
OcasHyperDual *ocas_dual_div(const OcasHyperDual *a, const OcasHyperDual *b, int *err);
功能:计算 $a / b$。若 $b$ 的值分量为零,设置 OCAS_ERROR_DIVISION_BY_ZERO 并返回 NULL。
ocas_dual_neg
签名:
OcasHyperDual *ocas_dual_neg(const OcasHyperDual *a, int *err);
功能:计算 $-a$。
12. Gröbner(8 个函数)
Gröbner 基计算和多项式系统求解。
⚠️ 非标准约定
- 这些函数未包含在
ocas.h头文件中,需自行声明原型。- 错误码使用
-1而非OCAS_*常量。OcasGroebnerBasis和OcasSystemSolution句柄未标记#[repr(C)]。
多项式数据格式
多项式通过系数数组传递:每个多项式由以下部分指定:
n_vars— 变量数n_terms— 项数exponents— 展平的指数矩阵(n_terms × n_vars,行优先)coeff_nums/coeff_dens— 有理系数的分子/分母数组
ocas_groebner_basis
签名:
OcasGroebnerBasis *ocas_groebner_basis(
size_t n_polys,
const size_t *n_vars_array,
const size_t *n_terms_array,
const size_t *exponents,
const int64_t *coeff_nums,
const int64_t *coeff_dens,
int32_t algorithm,
int32_t *err
);
功能:从多项式数据数组计算 Gröbner 基。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
n_polys | size_t | 多项式个数 |
n_vars_array | const size_t* | 每个多项式的变量数 |
n_terms_array | const size_t* | 每个多项式的项数 |
exponents | const size_t* | 展平指数矩阵 |
coeff_nums | const int64_t* | 有理系数分子 |
coeff_dens | const int64_t* | 有理系数分母 |
algorithm | int32_t | 算法选择:0 = Auto, 1 = F4, 2 = F5, 3 = Buchberger |
err | int32_t* | 错误码(-1 表示失败)。注意:与其余 C API 不同,此指针不可为 NULL——为 NULL 时函数直接返回 NULL 且不计算 |
返回值:OcasGroebnerBasis* 句柄或 NULL。
内存:调用方用 ocas_groebner_basis_free() 释放。
示例:
// 计算 {x² + y - 1, x + y² - 1} 的 Gröbner 基
size_t n_vars[] = {2, 2};
size_t n_terms[] = {2, 2};
// x² + y - 1: exponents = [[2,0],[0,1],[0,0]]
// x + y² - 1: exponents = [[1,0],[0,2],[0,0]]
size_t exps[] = {2,0, 0,1, 0,0,
1,0, 0,2, 0,0};
int64_t nums[] = {1, 1, -1, 1, 1, -1};
int64_t dens[] = {1, 1, 1, 1, 1, 1};
int32_t err = 0;
OcasGroebnerBasis *gb = ocas_groebner_basis(
2, n_vars, n_terms, exps, nums, dens, 0, &err);
if (gb) {
printf("基有 %zu 个元素\n", ocas_groebner_basis_len(gb));
printf("零维?%s\n", ocas_is_zero_dimensional(gb) ? "是" : "否");
ocas_groebner_basis_free(gb);
}
ocas_groebner_basis_free
签名:
void ocas_groebner_basis_free(OcasGroebnerBasis *gb);
功能:释放 Gröbner 基句柄。NULL 安全。
ocas_groebner_basis_len
签名:
size_t ocas_groebner_basis_len(const OcasGroebnerBasis *gb);
功能:返回 Gröbner 基中的元素数。NULL 返回 0。
ocas_is_zero_dimensional
签名:
bool ocas_is_zero_dimensional(const OcasGroebnerBasis *gb);
功能:检查理想是否为零维。NULL 返回 false。
ocas_solve_polynomial_system
签名:
OcasSystemSolution *ocas_solve_polynomial_system(
size_t n_polys,
const size_t *n_vars_array,
const size_t *n_terms_array,
const size_t *exponents,
const int64_t *coeff_nums,
const int64_t *coeff_dens,
int32_t algorithm,
int32_t *err
);
功能:求解多项式方程组。返回句柄或 NULL。
参数:同 ocas_groebner_basis。
内存:调用方用 ocas_system_solution_free() 释放。
ocas_system_solution_count
签名:
size_t ocas_system_solution_count(const OcasSystemSolution *sol);
功能:返回解的个数。非零维或空解返回 0。NULL 返回 0。
ocas_system_solution_value
签名:
double ocas_system_solution_value(const OcasSystemSolution *sol,
size_t sol_idx, size_t var_idx);
功能:获取特定解的特定变量值。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
sol | const OcasSystemSolution* | 非空解句柄 |
sol_idx | size_t | 解的索引 |
var_idx | size_t | 变量的索引 |
返回值:f64 值。越界或错误返回 0.0。
示例:
OcasSystemSolution *sol = ocas_solve_polynomial_system(
2, n_vars, n_terms, exps, nums, dens, 0, &err);
if (sol) {
size_t count = ocas_system_solution_count(sol);
printf("找到 %zu 个解\n", count);
for (size_t i = 0; i < count; i++) {
printf("解 %zu: x = %g, y = %g\n", i,
ocas_system_solution_value(sol, i, 0),
ocas_system_solution_value(sol, i, 1));
}
ocas_system_solution_free(sol);
}
ocas_system_solution_free
签名:
void ocas_system_solution_free(OcasSystemSolution *sol);
功能:释放解句柄。NULL 安全。
完整示例
C:符号微分与求值
#include <ocas.h>
#include <stdio.h>
#include <assert.h>
int main(void) {
int err = 0;
// 解析表达式
OcasExpr *f = ocas_expr_parse("x^3 - 6*x^2 + 11*x - 6", &err);
assert(f != NULL);
// 求导
OcasExpr *df = ocas_expr_diff(f, "x", &err);
assert(df != NULL);
// 化简
OcasExpr *simplified = ocas_expr_simplify(df, &err);
assert(simplified != NULL);
char *s = ocas_expr_to_string(simplified, &err);
printf("f'(x) = %s\n", s); // 输出:f'(x) = 11 + (-12*x) + (3*(x^2))
// 清理
ocas_string_free(s);
ocas_expr_free(simplified);
ocas_expr_free(df);
ocas_expr_free(f);
return 0;
}
编译:
gcc example.c -locas -o example
C:自动微分(双数)
#include <ocas.h>
#include <stdio.h>
int main(void) {
int err = 0;
// f(x,y) = x² + 2xy + y²,在 (3, 5) 处求值与偏导
OcasDualShape *shape = ocas_dual_shape_new(2, &err);
OcasHyperDual *x = ocas_dual_variable(shape, 0, "3", &err); // x₀ = 3
OcasHyperDual *y = ocas_dual_variable(shape, 1, "5", &err); // x₁ = 5
OcasHyperDual *x2 = ocas_dual_mul(x, x, &err); // x², ∂/∂x = 6
OcasHyperDual *xy = ocas_dual_mul(x, y, &err); // xy, ∂/∂x = 5
OcasHyperDual *y2 = ocas_dual_mul(y, y, &err); // y², ∂/∂y = 10
OcasHyperDual *two_xy = ocas_dual_add(xy, xy, &err);
OcasHyperDual *a = ocas_dual_add(x2, two_xy, &err);
OcasHyperDual *f = ocas_dual_add(a, y2, &err);
char *v = ocas_dual_value(f, &err);
char *dfx = ocas_dual_deriv(f, 0, &err);
char *dfy = ocas_dual_deriv(f, 1, &err);
printf("f(3,5) = %s, ∂f/∂x = %s, ∂f/∂y = %s\n", v, dfx, dfy);
ocas_string_free(v);
ocas_string_free(dfx);
ocas_string_free(dfy);
ocas_hyperdual_free(f);
ocas_hyperdual_free(a);
ocas_hyperdual_free(two_xy);
ocas_hyperdual_free(y2);
ocas_hyperdual_free(xy);
ocas_hyperdual_free(x2);
ocas_hyperdual_free(y);
ocas_hyperdual_free(x);
ocas_dual_shape_free(shape);
return 0;
}
注意:双数域在
ocas.hpp中没有 C++ RAII 包装,必须直接使用上述 C API。
C:数论
#include <ocas.h>
#include <stdio.h>
int main(void) {
int err = 0;
// 素因数分解
char *f = ocas_ntheory_factorint("360", &err);
printf("360 = %s\n", f); // 2:3,3:2,5:1
ocas_string_free(f);
// 素性测试
printf("997 是素数?%s\n", ocas_ntheory_isprime("997", &err) ? "是" : "否");
// 中国剩余定理
char *r = ocas_ntheory_crt("3,5,7", "2,3,2", &err);
printf("CRT: %s\n", r); // 23,105
ocas_string_free(r);
// Euler totient
char *t = ocas_ntheory_totient("100", &err);
printf("φ(100) = %s\n", t); // 40
ocas_string_free(t);
return 0;
}
参见
- Rust API 参考 — Rust 公共 API 总览
- Python API 参考 — Python 绑定文档
- 架构 — 项目架构概览
数学基础总览
本章系统介绍 oCAS 所涉及的数学理论,从基础概念到高阶算法,为理解源码实现提供完整的数学背景。每篇文章遵循"前提知识 → 基础概念 → 核心理论 → oCAS 实现 → 进阶话题 → 参考文献"的渐进式结构。
知识图谱
下图展示各数学主题之间的依赖关系。箭头表示"建议先学习"。
graph TD
PA[多项式代数] --> FA[有限域与<br>模算术]
LA[线性代数] --> FA
PA --> GCD[多项式 GCD<br>与因式分解]
FA --> GCD
LA --> GCD
FA --> NT[数论算法]
PA --> GB[Gröbner 基<br>理论]
LA --> GB
GCD --> GB
GCD --> ANF[代数数域与<br>Galois 理论]
GB --> ANF
GB --> FGLM[FGLM 与<br>消元理论]
NT --> ANF
FA --> ANF
SC[符号微积分] --> ODE[ODE 求解<br>理论]
PA --> ODE
LA --> ODE
SC --> RISCH[Risch 积分<br>算法]
ODE --> RISCH
PA --> RISCH
FA --> RISCH
TENSOR[张量代数与<br>规范化]
MC[蒙特卡洛<br>积分]
style PA fill:#e8f5e9,stroke:#2e7d32
style FA fill:#e3f2fd,stroke:#1565c0
style LA fill:#fff3e0,stroke:#e65100
style SC fill:#fce4ec,stroke:#c62828
style GCD fill:#e8f5e9,stroke:#2e7d32
style NT fill:#e3f2fd,stroke:#1565c0
style GB fill:#f3e5f5,stroke:#6a1b9a
style ODE fill:#fce4ec,stroke:#c62828
style RISCH fill:#fce4ec,stroke:#c62828
style FGLM fill:#f3e5f5,stroke:#6a1b9a
style ANF fill:#e3f2fd,stroke:#1565c0
style TENSOR fill:#fffde7,stroke:#f57f17
style MC fill:#fffde7,stroke:#f57f17
推荐学习路径
根据你的兴趣方向,选择以下路径之一进行系统学习。
路径 A:多项式代数与符号计算
适合希望理解符号计算核心算法(多项式运算、消元、求解方程组)的读者。
$$\text{多项式代数} ;\to; \text{GCD/因式分解} ;\to; \text{Gröbner 基} ;\to; \text{FGLM/消元}$$
| 步骤 | 主题 | 核心收获 | 文件 |
|---|---|---|---|
| 1 | 多项式代数 | 多项式环、单项式序、除法算法 | polynomial-algebra.md |
| 2 | 线性代数 | 矩阵运算、Bareiss 算法、高斯消元 | linear-algebra.md |
| 3 | 有限域与模算术 | $\mathbb{F}_p$ 构造、模逆、NTT | finite-fields.md |
| 4 | 多项式 GCD 与因式分解 | Euclid 算法、Hensel 提升、Berlekamp 算法 | poly-gcd-factoring.md |
| 5 | Gröbner 基理论 | Buchberger 算法、F4/F5、Hilbert 函数 | groebner-theory.md |
| 6 | FGLM 与消元理论 | 换序算法、理想运算、准素分解 | fglm-elimination.md |
路径 B:数论与代数数域
适合希望理解整数分解、素性判定、离散对数、代数数域构造的读者。
$$\text{有限域} ;\to; \text{数论算法} ;\to; \text{代数数域}$$
| 步骤 | 主题 | 核心收获 | 文件 |
|---|---|---|---|
| 1 | 有限域与模算术 | $\mathbb{F}p$、$\mathbb{F}{p^d}$ 构造、乘法群循环性 | finite-fields.md |
| 2 | 数论算法 | BPSW 素性、ECM 分解、BSGS 离散对数、Tonelli–Shanks | number-theory-algorithms.md |
| 3 | 代数数域与 Galois 理论 | $\mathbb{Q}(\alpha)$ 表示、Trager 范数算法、$\mathrm{GF}(p^d)$ | algebraic-number-fields.md |
路径 C:符号微积分与微分方程
适合希望理解符号求导、积分、ODE 求解、Risch 算法的读者。
$$\text{符号微积分} ;\to; \text{ODE 求解} ;\to; \text{Risch 积分}$$
| 步骤 | 主题 | 核心收获 | 文件 |
|---|---|---|---|
| 1 | 符号微积分 | 求导规则、Taylor 展开、表达式树变换 | symbolic-calculus.md |
| 2 | ODE 求解理论 | 可分离/线性/Bernoulli/常系数/级数解/Laplace 变换 | ode-theory.md |
| 3 | Risch 积分算法 | 微分域塔、Hermite 约化、对数导数恒等式、RDE | risch-algorithm.md |
主题速查表
下表列出每个数学主题对应的 oCAS 源码模块和本文档中的章节文件。
| 主题 | 难度 | oCAS 模块 | 数学章节 |
|---|---|---|---|
| 多项式代数 | 基础 | ocas-poly(dense.rs, sparse.rs) | polynomial-algebra.md |
| 有限域与模算术 | 基础 | ocas-domain(finite_field.rs) | finite-fields.md |
| 线性代数 | 基础 | ocas-poly(matrix.rs) | linear-algebra.md |
| 多项式 GCD 与因式分解 | 进阶 | ocas-poly(gcd/, factor/) | poly-gcd-factoring.md |
| Gröbner 基理论 | 进阶 | ocas-poly(groebner/mod.rs, f4.rs, f5.rs, hilbert.rs) | groebner-theory.md |
| 数论算法 | 进阶 | ocas-domain(number_theory/) | number-theory-algorithms.md |
| 符号微积分 | 进阶 | ocas-calc(lib.rs) | symbolic-calculus.md |
| ODE 求解理论 | 进阶 | ocas-calc(ode/) | ode-theory.md |
| Risch 积分算法 | 高阶 | ocas-calc(integral/) | risch-algorithm.md |
| FGLM 与消元理论 | 高阶 | ocas-poly(groebner/fglm.rs, ideal.rs) | fglm-elimination.md |
| 代数数域与 Galois 理论 | 高阶 | ocas-domain(algebraic.rs) | algebraic-number-fields.md |
| 张量代数与规范化 | 高阶 | ocas-atom(tensor/canon.rs, young.rs, spec.rs) | tensor-canonicalization.md |
| 蒙特卡洛积分 | 高阶 | ocas-eval(numeric/vegas.rs) | monte-carlo-integration.md |
数学分支与 oCAS 的对应关系
oCAS 的设计遵循计算代数(Computer Algebra)的传统,各模块与数学分支的对应如下:
- 交换代数(多项式环、理想论)→
ocas-poly:多项式表示、GCD、因式分解、Gröbner 基、理想运算 - 数论(素性、分解、离散对数)→
ocas-domain/number_theory:整数分解、素性判定、二次剩余 - 线性代数(矩阵、行列式)→
ocas-poly/matrix:Bareiss 行列式、高斯消元、线性方程组 - 微积分与微分代数(符号求导、积分、ODE)→
ocas-calc:微分、Taylor 展开、ODE 分类求解、Risch 算法 - 张量代数(指标缩并、对称化)→
ocas-atom/tensor:图编码、McKay 算法、Young 投影 - 数值分析(自适应积分)→
ocas-eval/numeric:Vegas 蒙特卡洛、自适应网格
基础:多项式代数
多项式代数是符号计算的基石。本章从多项式环的基本定义出发,逐步引入单项式序、多元除法算法和 Hilbert 基定理,并说明 oCAS 如何在 Rust 中实现这些概念。
前提知识
阅读本章前,建议具备以下基础:
- 抽象代数:群、环、域的基本概念(单位元、零因子、整环)
- 线性代数:向量空间、线性无关、基
- 数学归纳法:用于证明除法算法的终止性
如需复习,可参考 数学基础总览 中的学习路径推荐。
基础概念
一元多项式环
设 $\mathbb{F}$ 为一个域(如 $\mathbb{Q}$、$\mathbb{F}_p$)。一元多项式环 $\mathbb{F}[x]$ 是所有形如
$$f(x) = a_0 + a_1 x + a_2 x^2 + \cdots + a_n x^n$$
的表达式构成的集合,其中 $a_i \in \mathbb{F}$,$n \geq 0$。
定义(次数)。对于非零多项式 $f = \sum_{i=0}^n a_i x^i$($a_n \neq 0$),其次数(degree)为
$$\deg(f) = n$$
零多项式 $f = 0$ 的次数约定为 $-\infty$(或在实现中表示为 None)。
定义(首项系数)。$f$ 的首项系数(leading coefficient)为 $\text{lc}(f) = a_n$。当 $\text{lc}(f) = 1$ 时,称 $f$ 为首一多项式(monic)。
性质:
| 运算 | 次数关系 |
|---|---|
| $f + g$ | $\deg(f+g) \leq \max(\deg f, \deg g)$ |
| $f \cdot g$ | $\deg(f \cdot g) = \deg f + \deg g$ |
| $f / g$(带余除法) | $\deg(r) < \deg(g)$ |
多元多项式环
设 $\mathbb{F}$ 为域,$x_1, x_2, \ldots, x_n$ 为 $n$ 个不定元。多元多项式环 $\mathbb{F}[x_1, \ldots, x_n]$ 是所有有限和
$$f = \sum_{\alpha \in \mathbb{N}^n} c_\alpha , \mathbf{x}^\alpha$$
构成的集合,其中 $\alpha = (\alpha_1, \ldots, \alpha_n) \in \mathbb{N}^n$ 称为指数向量,$\mathbf{x}^\alpha = x_1^{\alpha_1} x_2^{\alpha_2} \cdots x_n^{\alpha_n}$ 称为单项式(monomial),$c_\alpha \in \mathbb{F}$ 称为系数。
定义(全次数)。单项式 $\mathbf{x}^\alpha$ 的全次数(total degree)为 $|\alpha| = \alpha_1 + \cdots + \alpha_n$。多项式 $f$ 的全次数为 $\deg(f) = \max{|\alpha| : c_\alpha \neq 0}$。
定义(偏次数)。多项式 $f$ 关于变量 $x_i$ 的偏次数为 $\deg_{x_i}(f) = \max{\alpha_i : c_\alpha \neq 0}$。
重要区别:一元多项式环 $\mathbb{F}[x]$ 是主理想整环(PID),每个理想都由单个多项式生成。多元多项式环 $\mathbb{F}[x_1, \ldots, x_n]$($n \geq 2$)不再是 PID,这是 Gröbner 基理论的出发点——参见 Gröbner 基理论。
多项式的内部表示
多项式有两种基本的计算机表示方式:
| 表示 | 存储方式 | 适用场景 |
|---|---|---|
| 稠密表示 | 系数向量 $[a_0, a_1, \ldots, a_n]$ | 一元、中等次数 |
| 稀疏表示 | 仅存储非零项的 ${(\alpha, c_\alpha)}$ 对 | 多元、大量项为零 |
在 oCAS 中,DenseUnivariatePolynomial 使用稠密表示,SparseMultivariatePolynomial 使用稀疏表示。详见 § 在 oCAS 中的实现。
单项式序
在多元多项式环中,"谁是首项"不再一目了然——我们需要一种比较单项式的规则。
定义(单项式序)。$\mathbb{N}^n$ 上的单项式序(monomial ordering)是一个全序 $\succ$,满足:
- 良序性:$\mathbb{N}^n$ 的每个非空子集都有最小元(等价地:$0 \preceq \alpha$ 对所有 $\alpha$ 成立)
- 相容性:若 $\alpha \succ \beta$,则 $\alpha + \gamma \succ \beta + \gamma$ 对所有 $\gamma \in \mathbb{N}^n$ 成立
这两个条件保证了多项式除法的终止性。下面介绍三种最常用的单项式序。
字典序(Lex)
$x^\alpha \succ_{\text{lex}} x^\beta$ 当且仅当 $\alpha - \beta$ 的最左非零分量为正。
等价描述:从左到右逐分量比较指数向量,首次不等处决定大小。
示例:在 $\mathbb{F}[x, y, z]$ 中,按字典序排列:
$$x^2 y^3 z \succ_{\text{lex}} x^2 y^2 z^3 \succ_{\text{lex}} x y^5 \succ_{\text{lex}} y^{100} \succ_{\text{lex}} z^{100}$$
字典序的特点是"消除性":若 $x_1 \succ x_2 \succ \cdots \succ x_n$,则按字典序的首项关于 $x_1$ 的次数最大。这使得字典序基天然具有消元性质。
分次字典序(Grlex)
$x^\alpha \succ_{\text{grlex}} x^\beta$ 当且仅当:
- $|\alpha| > |\beta|$,或
- $|\alpha| = |\beta|$ 且 $\alpha \succ_{\text{lex}} \beta$
即先按全次数排,同次数内按字典序排。
示例:在 $\mathbb{F}[x, y]$ 中,$\deg(x^2) = 2$、$\deg(xy) = 2$、$\deg(y^3) = 3$,故
$$y^3 \succ_{\text{grlex}} x^2 \succ_{\text{grlex}} xy$$
分次反字典序(Grevlex)
$x^\alpha \succ_{\text{grevlex}} x^\beta$ 当且仅当:
- $|\alpha| > |\beta|$,或
- $|\alpha| = |\beta|$ 且 $\alpha - \beta$ 的最右非零分量为负
即先按全次数排,同次数内从右往左比较,小的一方更大。
示例:在 $\mathbb{F}[x, y, z]$ 中,$x^2 y$ 和 $x y^2$ 的全次数都是 3。$\alpha - \beta = (1, -1, 0)$,最右非零分量是 $-1$(负),故
$$x^2 y \succ_{\text{grevlex}} x y^2$$
Grevlex 的关键优势在于计算:对相同全次数的单项式,Grevlex 倾向于让低次变量的指数更小,这在 Gröbner 基计算中产生更小的中间多项式。
三种序的比较
| 单项式 | Lex | Grlex | Grevlex |
|---|---|---|---|
| $x^2$ ($\alpha = (2,0)$) | 1 | 2 | 2 |
| $xy$ ($\alpha = (1,1)$) | 2 | 3 | 3 |
| $y^3$ ($\alpha = (0,3)$) | 3 | 1(次数最高) | 1(次数最高) |
注意:上表中排名 1 表示在对应序下"最大"。Grlex 与 Grevlex 都先按 全次数排序,因此 $y^3$(全次数 3)排在 $x^2$、$xy$(全次数 2)之前。 在二元情形下,同次数内的平局决胜规则使 Grlex 与 Grevlex 重合: 对 $x^2$ 与 $xy$,$(2,0) - (1,1) = (1, -1)$ 的最右非零分量为 $-1 < 0$, 故两种序下都有 $x^2 \succ xy$。两者的差异要到 $n \geq 3$ 个变量时才 显现,例如 $xz^2$ 与 $y^3$(全次数均为 3):Grlex 下 $xz^2 \succ y^3$ (字典序平局决胜:$(1,0,2) > (0,3,0)$),而 Grevlex 下 $(0,3,0) - (1,0,2) = (-1,3,-2)$ 的最右非零分量为 $-2 < 0$,故 $y^3 \succ_{\text{grevlex}} xz^2$。
再验证 Grevlex 的平局决胜:$x^2 y$ 与 $x y^2$:$(2,1) - (1,2) = (1, -1)$, 最右非零分量是 $-1 < 0$,所以 $x^2 y \succ_{\text{grevlex}} xy^2$。
扩展序
除了上述三种基本序,oCAS 还支持以下参数化序:
加权序(WeightOrder):给每个变量赋予权重 $w_i$,按 $\sum w_i \alpha_i$ 降序排列。当权重向量为 $(1, 1, \ldots, 1)$ 时退化为全次数序。
矩阵序(MatrixOrder):给定 $n \times n$ 整数矩阵 $M$,$\alpha \succ \beta$ 当且仅当 $M\alpha >_{\text{lex}} M\beta$。矩阵序是所有线性单项式序的完全一般化。特别地,消元序(elimination order)可通过 MatrixOrder::elimination_order(elim_vars, n_vars) 构造,用于从多项式系统中消除变量。
分块序(BlockOrder):将变量划分为连续块,每块内使用独立的子序(Lex、Grlex 或 Grevlex)。消元序的标准形式是"前 $k$ 个变量用 Lex,其余用 Grevlex"。
核心理论
一元带余除法
定理(带余除法)。设 $\mathbb{F}$ 为域,$f, g \in \mathbb{F}[x]$,$g \neq 0$。存在唯一的 $q, r \in \mathbb{F}[x]$ 使得
$$f = q \cdot g + r, \quad \deg(r) < \deg(g)$$
算法(逐次消首项):
输入: f, g (g ≠ 0)
输出: 商 q, 余数 r
q ← 0, r ← f
while r ≠ 0 and deg(r) ≥ deg(g):
t ← lc(r) / lc(g) · x^(deg(r) - deg(g))
q ← q + t
r ← r - t · g
return (q, r)
终止性:每一步 $\deg(r)$ 严格递减(或 $r$ 变为零),故算法必然终止。
整环上的推广:当系数环是欧几里得整环(如 $\mathbb{Z}$)时,带余除法仍然存在,但需要伪除法(pseudo-division)来避免分数。oCAS 中
DenseUnivariatePolynomial::div_rem通过EuclideanDomain::div_rem对首项系数执行带余除法来实现。
多元带余除法
多元情况远比一元复杂——商和余数不唯一,依赖于单项式序和除法顺序。
定理(多元除法算法)。设 $\mathbb{F}$ 为域,$f, g_1, \ldots, g_s \in \mathbb{F}[x_1, \ldots, x_n]$,$g_i \neq 0$。固定一个单项式序。存在算法计算 $q_1, \ldots, q_s, r \in \mathbb{F}[x_1, \ldots, x_n]$ 使得
$$f = q_1 g_1 + q_2 g_2 + \cdots + q_s g_s + r$$
其中 $r$ 的每个非零项都不被任何 $\text{lt}(g_i)$ 整除。这样的 $r$ 称为 $f$ 对 ${g_1, \ldots, g_s}$ 的余数(或正规形,normal form)。
算法:
输入: f, G = [g1, ..., gs]
输出: q1, ..., qs, r
r ← 0, p ← f
while p ≠ 0:
if ∃ i 使得 lt(gi) | lt(p):
选择最小的这样的 i
qi ← qi + lt(p) / lt(gi)
p ← p - (lt(p) / lt(gi)) · gi
else:
r ← r + lt(p)
p ← p - lt(p)
return (q1, ..., qs, r)
关键性质:
- 终止性:由单项式序的良序性保证
- 不唯一性:余数 $r$ 依赖于除法顺序 $g_1, \ldots, g_s$。不同的顺序可能产生不同的余数
- Gröbner 基的关键作用:当 ${g_1, \ldots, g_s}$ 构成 Gröbner 基时,余数 $r$ 是唯一的——这正是 Gröbner 基的核心价值之一
示例:取 $f = x^2 y + x y^2 + y^2$,$g_1 = xy - 1$,$g_2 = y^2 - 1$,使用 Grevlex 序。
第一步:$\text{lt}(f) = x^2 y$,$\text{lt}(g_1) = xy$ 整除 $x^2 y$。
$$f \leftarrow f - x \cdot g_1 = x^2 y + x y^2 + y^2 - x(xy - 1) = x y^2 + y^2 + x$$
第二步:$\text{lt}(p) = x y^2$,$\text{lt}(g_1) = xy$ 整除 $x y^2$。
$$p \leftarrow x y^2 + y^2 + x - y \cdot g_1 = x y^2 + y^2 + x - y(xy - 1) = y^2 + x + y$$
第三步:$\text{lt}(p) = y^2$,$\text{lt}(g_2) = y^2$ 整除 $y^2$。
$$p \leftarrow y^2 + x + y - 1 \cdot g_2 = y^2 + x + y - (y^2 - 1) = x + y + 1$$
此时 $\text{lt}(p) = x$ 不被 $xy$ 或 $y^2$ 整除,移入余数:$r = x + y + 1$。
S-多项式
S-多项式是 Gröbner 基算法的核心构件。
定义。设 $f, g$ 为非零多项式,$\gamma = \text{lcm}(\text{lm}(f), \text{lm}(g))$(指数向量逐分量取最大值)。$f$ 和 $g$ 的 S-多项式为
$$S(f, g) = \frac{\gamma}{\text{lt}(f)} \cdot f - \frac{\gamma}{\text{lt}(g)} \cdot g = \frac{x^\gamma}{\text{lm}(f)} \cdot f - \frac{x^\gamma}{\text{lm}(g)} \cdot g$$
S-多项式的直觉是"消除 $f$ 和 $g$ 的首项"。Buchberger 算法反复构造 S-多项式并化简,直到所有 S-多项式化简为零——此时的多项式集合即为 Gröbner 基。
Dickson 引理
Dickson 引理是单项式理想的有限生成性定理,也是 Hilbert 基定理的基础。
引理(Dickson)。$\mathbb{N}^n$ 的每个子集 $S$ 都有有限的"最小元"集合 $M \subseteq S$,使得对每个 $\alpha \in S$,存在 $\beta \in M$ 满足 $\beta \leq \alpha$(逐分量)。
等价表述:$\mathbb{F}[x_1, \ldots, x_n]$ 中由单项式生成的每个理想都由有限个单项式生成。
直觉:在 $\mathbb{N}^n$ 中,"向下封闭"的集合只能由有限个"极小元"生成。这与 $\mathbb{N}$ 中的良序性类似,但推广到高维。
Hilbert 基定理
定理(Hilbert 基定理)。$\mathbb{F}[x_1, \ldots, x_n]$ 中的每个理想 $I$ 都是有限生成的。即存在 $f_1, \ldots, f_s \in I$ 使得 $I = \langle f_1, \ldots, f_s \rangle$。
证明思路:设 $J = \langle f_1, \ldots, f_s \rangle$ 为待证对象。考虑首项理想 $L(I) = \langle \text{lm}(f) : f \in I \rangle$。由 Dickson 引理,$L(I) = \langle \text{lm}(f_1), \ldots, \text{lm}(f_s) \rangle$,故 $L(J) \supseteq L(I)$。若 $J \subsetneq I$,取 $f \in I \setminus J$ 使 $\text{lm}(f)$ 在 $\succ$ 下极小。由于 $\text{lm}(f) \in L(I) \subseteq L(J)$,单项式理想的性质保证存在 $f_i$ 使 $\text{lm}(f_i) \mid \text{lm}(f)$;用 $f_i$ 消去 $f$ 的首项得到 $f' = f - c, \mathbf{x}^\gamma f_i \in I \setminus J$,且 $\text{lm}(f') \prec \text{lm}(f)$——与 $\text{lm}(f)$ 的极小性矛盾(单项式序的良序性禁止无限下降链)。故 $J = I$。
意义:Hilbert 基定理保证了 Gröbner 基的存在性——每个理想的 Gröbner 基都是有限集。Buchberger 算法进一步提供了构造性的证明。
在 oCAS 中的实现
稠密一元多项式
DenseUnivariatePolynomial<D> 将系数从常数项到首项系数存储在一个连续向量中:
#![allow(unused)] fn main() { pub struct DenseUnivariatePolynomial<D: Domain> { coeffs: Vec<D::Element>, // [a0, a1, ..., an] domain: D, } }
设计决策:
- 尾零裁剪:构造时自动移除尾部零系数,零多项式用空向量表示
- 系数域内联:
domain字段存储在多项式内部,避免每次运算时传入 - 多项式类型参数:
D: Domain泛型允许在整数环 $\mathbb{Z}$、有理数域 $\mathbb{Q}$、有限域 $\mathbb{F}_p$ 等上统一实现
乘法策略:
| 条件 | 算法 | 复杂度 |
|---|---|---|
| $\min(\deg f, \deg g) + 1 < 32$(系数个数) | Schoolbook(逐项相乘) | $O(n \cdot m)$ |
| $\min(\deg f, \deg g) + 1 \geq 32$ | Karatsuba | $O(n^{1.585})$ |
有限域 + NTT 友好素数 + 系数个数 $\geq 256$(mul_ntt) | 数论变换 | $O(n \log n)$ |
注意:通用
mul走前两行(Karatsuba/Schoolbook,阈值按系数个数KARATSUBA_THRESHOLD = 32判断,即次数 $\geq 31$)。NTT 通过DenseUnivariatePolynomial<FiniteField>::mul_ntt启用:它内部检测NTT_THRESHOLD = 256(系数个数)与素数是否 NTT-friendly,否则回退到 Karatsuba/Schoolbook。
Karatsuba 的核心思想是将多项式在中点 $m = n/2$ 处拆分:
$$a = a_0 + a_1 x^m, \quad b = b_0 + b_1 x^m$$
通过三次半规模乘法(而非四次)计算乘积:
$$z_0 = a_0 b_0, \quad z_2 = a_1 b_1, \quad z_1 = (a_0 + a_1)(b_0 + b_1) - z_0 - z_2$$
$$a \cdot b = z_0 + z_1 x^m + z_2 x^{2m}$$
EuclideanDomain 方法(当 $D: \text{EuclideanDomain}$ 时可用):
div_rem(&self, divisor)— 带余除法,返回Option<(quotient, remainder)>gcd(&self, other)— 多项式 GCD(通过伪余式 PRS 链)extended_gcd_poly(&self, other)— 扩展 GCD,返回 $(g, s, t)$ 满足 $s \cdot f + t \cdot g = \text{gcd}(f, g)$content()— 内容(所有系数的 GCD)primitive_part()— 本原部分 $f / \text{content}(f)$square_free_factorization()— 无平方因式分解resultant(&self, other)— 结式(Brown PRS 算法)pow(n)— 快速幂(重复平方法)p_adic_expansion(p)— $p$-进展开
一元多项式求值使用 Horner 法则,$O(n)$ 时间:
$$f(x) = a_0 + x(a_1 + x(a_2 + \cdots + x \cdot a_n))$$
#![allow(unused)] fn main() { // Horner's method let mut result = domain.zero(); for coeff in self.coeffs.iter().rev() { result = domain.mul(&result, x); result = domain.add(&result, coeff); } }
稀疏多元多项式
SparseMultivariatePolynomial<D, O> 仅存储非零项,用 HashMap 从指数向量映射到系数:
#![allow(unused)] fn main() { pub struct SparseMultivariatePolynomial<D: Domain, O: MonomialOrder = Grevlex> { terms: HashMap<SmallVec<[usize; 4]>, D::Element>, domain: D, n_vars: usize, pub order: O, } }
设计要点:
- 指数向量:
SmallVec<[usize; 4]>——小变量数时内联存储(≤4 个变量不分配堆内存),大变量数时溢出到堆 - 默认序:
O = Grevlex——这是 Gröbner 基计算最常用的序,因为它产生最小的中间表达式 - 序作为类型参数:单项式序在编译时确定,零开销抽象。
Lex、Grlex、Grevlex是零大小类型(ZST),不占运行时空间;WeightOrder、MatrixOrder、BlockOrder携带运行时配置
MonomialOrder trait:
#![allow(unused)] fn main() { pub trait MonomialOrder: Clone + PartialEq + Eq + Debug + Default { fn cmp(&self, lhs: &[usize], rhs: &[usize]) -> std::cmp::Ordering; } }
单一方法 cmp 比较两个指数向量。各序的实现:
#![allow(unused)] fn main() { // 字典序:直接比较指数向量 impl MonomialOrder for Lex { fn cmp(&self, lhs: &[usize], rhs: &[usize]) -> Ordering { lhs.cmp(rhs) } } // 分次反字典序:先比全次数(降序),再反字典序 impl MonomialOrder for Grevlex { fn cmp(&self, lhs: &[usize], rhs: &[usize]) -> Ordering { let deg_lhs: usize = lhs.iter().sum(); let deg_rhs: usize = rhs.iter().sum(); deg_rhs.cmp(°_lhs) .then_with(|| rhs.iter().rev().cmp(lhs.iter().rev())) } } // 分次字典序:先比全次数(降序),再字典序 impl MonomialOrder for Grlex { fn cmp(&self, lhs: &[usize], rhs: &[usize]) -> Ordering { let deg_lhs: usize = lhs.iter().sum(); let deg_rhs: usize = rhs.iter().sum(); deg_rhs.cmp(°_lhs) .then_with(|| lhs.cmp(rhs)) } } }
注意实现细节:
deg_rhs.cmp(°_lhs)而非deg_lhs.cmp(°_rhs)——因为 Rust 的Ordering中Less表示"排在前面",我们需要更高次数排在前面(即Less),所以用deg_rhs对deg_lhs比较。类似地,Grevlex 中rhs.iter().rev().cmp(lhs.iter().rev())实现了"反"字典序。
多元除法通过 reduce(&self, basis: &[Self]) 实现:
#![allow(unused)] fn main() { pub fn reduce(&self, basis: &[Self]) -> Self { let mut remainder = self.clone(); let mut result = self.zero(); // 缓存每个基元素的首项 let basis_lts: Vec<_> = basis .iter() .filter_map(|g| g.leading_term().map(|(e, c)| (g, e.clone(), c.clone()))) .collect(); let max_iter = 10000; for _ in 0..max_iter { if remainder.is_zero() { break; } let (rm, rc) = match remainder.leading_term() { Some((e, c)) => (e, c), None => break, }; let mut reduced = false; for (g, lm, lc) in &basis_lts { // lm | rm(逐分量 rm[i] ≥ lm[i]) if monomial_divides(&rm, lm) { // 计算商单项式 let qm: SmallVec<[usize; 4]> = rm.iter().zip(lm.iter()).map(|(a, b)| a - b).collect(); let qc = match self.domain.div(&rc, lc) { Some(q) => q, None => break, }; remainder = remainder.sub(&g.mul_monomial(&qm).mul_scalar(&qc)); reduced = true; break; } } if !reduced { result.terms.insert(rm, rc); remainder.terms.remove(&rm); } } result } }
辅助函数(单项式操作):
#![allow(unused)] fn main() { // 返回 x^b 是否整除 x^a(逐分量 a[i] ≥ b[i]) pub fn monomial_divides(a: &[usize], b: &[usize]) -> bool // 最小公倍单项式(逐分量取 max) pub fn monomial_lcm(a: &[usize], b: &[usize]) -> SmallVec<[usize; 4]> // 两个单项式是否互素(无共同变量) pub fn monomial_are_coprime(a: &[usize], b: &[usize]) -> bool }
稠密 vs 稀疏:选择指南
| 考虑因素 | 稠密 DenseUnivariatePolynomial | 稀疏 SparseMultivariatePolynomial |
|---|---|---|
| 变量数 | 一元 | 多元($n$ 个变量) |
| 存储 | 连续 Vec,$O(\deg f + 1)$ | HashMap,$O(\text{非零项数})$ |
| 乘法 | Karatsuba / NTT | 逐项展开 |
| 除法 | 唯一商和余数 | 依赖单项式序,余数不唯一 |
| GCD | Euclid 算法 / PRS | 需要 Gröbner 基技术 |
| 适用系数域 | EuclideanDomain | Domain(除法需要域) |
经验法则:一元多项式用稠密表示;两个以上变量用稀疏表示。即使多元多项式只有少数非零项(如 $x^{100} + y^{100}$),稀疏表示也只需存储两项,而稠密表示需要 $100 \times 100$ 的系数矩阵。
进阶话题
单项式序的等价与统一
所有标准单项式序都可以用矩阵序统一表示。设 $n$ 个变量:
- Lex $=$ $M = I_n$(单位矩阵)
- Grlex $=$ 第一行全 1,其余行为字典序单位向量($e_1, e_2, \ldots, e_n$,按序排列——先比全次数,再从左到右比较各分量)
- Grevlex $=$ 第一行全 1,其余行为反字典序的负单位向量($-e_n, -e_{n-1}, \ldots, -e_1$——先比全次数,再从右到左、取较小者更大)
MatrixOrder::elimination_order(k, n) 构造的消元序等价于 BlockOrder 中前 $k$ 个变量用 Lex、其余用 Grevlex,但表达为矩阵形式更为简洁。
除法的局限性
多元带余除法的一个重要局限:余数依赖于除子的顺序。例如,对 $f$ 用 $(g_1, g_2)$ 除和用 $(g_2, g_1)$ 除可能得到不同的余数。Gröbner 基的引入正是为了解决这个问题——对 Gröbner 基的除法余数是唯一的。
整数环上的多项式
当系数环是 $\mathbb{Z}$(而非域)时,带余除法需要调整。DenseUnivariatePolynomial<IntegerDomain> 的 div_rem 通过在首项系数上执行整数带余除法来实现伪除法(pseudo-division)。具体地,对于 $f = q \cdot g + r$,$q$ 和 $r$ 的系数可能比 $f$ 的系数大,因为除法在 $\mathbb{Z}$ 中不总是精确的。GCD 计算则使用伪余式序列(Pseudo-Remainder Sequence, PRS)来避免分数膨胀;对很大的整数系数可改用模 GCD(gcd/modular.rs 的 Brown 算法)。结式(resultant)的计算才使用子结式 PRS(resultant.rs)。
参考文献
-
Cox, D., Little, J., O'Shea, D. Ideals, Varieties, and Algorithms (4th ed.). Springer, 2015.
- 第 1 章:多项式环的基本性质
- 第 2 章:Gröbner 基的引入与 Buchberger 算法
-
Gathen, J. von zur, Gerhard, J. Modern Computer Algebra (3rd ed.). Cambridge University Press, 2013.
- 第 6 章:多项式乘法算法(Karatsuba、FFT、NTT)
- 第 21 章:多元多项式 GCD
-
Adams, W. W., Loustaunau, P. An Introduction to Gröbner Bases. AMS, 1994.
- 第 1 章:单项式序与多元除法
参见
- Gröbner 基理论 — 除法不唯一性的解决方案
- 多项式 GCD 与因式分解 — 一元和多元 GCD 算法
- 有限域与模算术 — 有限域上的多项式运算
- 线性代数 — Bareiss 无分数行列式与高斯消元
- Rust API:多项式 —
DenseUnivariatePolynomial和SparseMultivariatePolynomial的完整 API - Rust API:系数域 —
Domain和EuclideanDomaintrait
基础:有限域与模算术
前提知识
- 集合论与基本代数结构(群、环、域的定义)
- 整数除法与余数
- 多项式的基本运算
基础概念
模算术
给定正整数 $n \geq 2$,整数 $a$ 与 $b$ 模 $n$ 同余(记为 $a \equiv b \pmod{n}$)当且仅当 $n \mid (a - b)$。模运算将整数划分为 $n$ 个等价类:
$$ \mathbb{Z}/n\mathbb{Z} = {\overline{0},, \overline{1},, \dots,, \overline{n-1}} $$
其中 $\overline{a} = {a + kn \mid k \in \mathbb{Z}}$。加法和乘法按代表元运算后再取模:
$$ \overline{a} + \overline{b} = \overline{a + b}, \quad \overline{a} \cdot \overline{b} = \overline{a \cdot b} $$
这使得 $\mathbb{Z}/n\mathbb{Z}$ 成为一个交换环。
模逆与扩展 Euclid 算法
元素 $\overline{a} \in \mathbb{Z}/n\mathbb{Z}$ 有乘法逆元当且仅当 $\gcd(a, n) = 1$。此时存在整数 $x, y$ 满足 Bézout 等式:
$$ ax + ny = \gcd(a, n) = 1 $$
取模 $n$ 即得 $ax \equiv 1 \pmod{n}$,故 $\overline{x} = \overline{a}^{-1}$。
扩展 Euclid 算法通过迭代求解此等式:
- 初始化:$(r_0, r_1) = (a, n)$,$(s_0, s_1) = (1, 0)$,$(t_0, t_1) = (0, 1)$。
- 每步计算 $q = \lfloor r_{i-1}/r_i \rfloor$,更新:
$$ \begin{pmatrix} r_{i+1} \ s_{i+1} \ t_{i+1} \end{pmatrix} = \begin{pmatrix} r_{i-1} - q \cdot r_i \ s_{i-1} - q \cdot s_i \ t_{i-1} - q \cdot t_i \end{pmatrix} $$
- 当 $r_{i+1} = 0$ 时终止,此时 $r_i = \gcd(a, n)$,$s_i \cdot a + t_i \cdot n = r_i$。
素域 $\mathbb{Z}/p\mathbb{Z}$
当 $n = p$ 为素数时,$\mathbb{Z}/p\mathbb{Z}$ 的结构发生根本变化:每个非零元素都与 $p$ 互素,因此都有逆元。这使得 $\mathbb{Z}/p\mathbb{Z}$ 成为一个域(field),记为 $\mathbb{F}_p$。
定理(Fermat 小定理):设 $p$ 为素数,$a \not\equiv 0 \pmod{p}$,则
$$ a^{p-1} \equiv 1 \pmod{p} $$
由此立即得到 $a^{p-2} \equiv a^{-1} \pmod{p}$,这为有限域求逆提供了一种无需扩展 Euclid 算法的实现方式——只需一次模幂运算。当使用 GMP 后端的 pow_mod 时,这比逐次除法快得多。
域的基本性质:
- 加法群 $(\mathbb{F}_p, +)$ 是 $p$ 阶循环群,同构于 $(\mathbb{Z}/p\mathbb{Z}, +)$。
- 乘法群 $(\mathbb{F}_p^, \times)$ 是 $p-1$ 阶循环群——存在原根(primitive root)$g$ 使得 ${g^0, g^1, \dots, g^{p-2}} = \mathbb{F}_p^$。
- 特征(characteristic)为 $p$:$p \cdot 1 = \underbrace{1 + \cdots + 1}_{p} = 0$,且 $p$ 是使此式成立的最小正整数。
核心理论
有限域 $\mathbb{F}_{p^d}$ 的构造
素域 $\mathbb{F}_p$ 并非唯一的有限域。有限域分类定理指出:对每个素数幂 $q = p^d$($d \geq 1$),存在唯一的(在同构意义下)$q$ 元有限域 $\mathbb{F}_q$,且所有有限域都是这种形式。
构造 $\mathbb{F}_{p^d}$($d > 1$)的标准方法是不可约多项式扩张:
- 选取 $\mathbb{F}_p[x]$ 中一个 $d$ 次不可约多项式 $m(x)$。
- 定义 $\mathbb{F}_{p^d} = \mathbb{F}_p[x] / (m(x))$——多项式环对理想 $(m(x))$ 的商环。
- 元素是次数 $< d$ 的多项式 $a_0 + a_1 \alpha + \cdots + a_{d-1}\alpha^{d-1}$,其中 $\alpha = \overline{x}$ 是 $m$ 的根。
- 加法按系数逐项模 $p$;乘法先做多项式乘法,再对 $m(x)$ 取模。
不可约多项式的存在性:$\mathbb{F}_p[x]$ 中首一(monic)$d$ 次不可约多项式的个数为
$$ N_p(d) = \frac{1}{d}\sum_{k \mid d} \mu(k) \cdot p^{d/k} $$
其中 $\mu$ 是 Möbius 函数。这保证了对任意 $d \geq 1$,不可约多项式都存在。
例:$\mathbb{F}_4 = \mathbb{F}_2[x]/(x^2 + x + 1)$。设 $\alpha$ 为 $x^2 + x + 1$ 的根,则 $\alpha^2 = \alpha + 1$(在 $\mathbb{F}_2$ 中 $-1 = 1$)。四个元素为 ${0, 1, \alpha, \alpha+1}$。
循环乘法群
定理:有限域的乘法群 $\mathbb{F}_{p^d}^*$ 是循环群,阶为 $p^d - 1$。
这意味着存在本原元(primitive element)$g \in \mathbb{F}_{p^d}^*$ 使得
$$ \mathbb{F}_{p^d}^* = {g^0, g^1, \dots, g^{p^d - 2}} $$
循环性在密码学和编码理论中有深远应用:离散对数问题(DLP)、伪随机数生成、纠错码的构造都依赖于此。
特征与素子域
有限域 $\mathbb{F}_{p^d}$ 的特征为 $p$。由特征定义:
- $p \cdot a = 0$ 对所有 $a \in \mathbb{F}_{p^d}$ 成立。
- $\mathbb{F}p$ 是 $\mathbb{F}{p^d}$ 的素子域(prime subfield),即包含 $1$ 的最小子域。
- $\mathbb{F}_{p^d}$ 作为 $\mathbb{F}_p$-向量空间,维数为 $d$。
Frobenius 自同态 $\varphi: x \mapsto x^p$ 是 $\mathbb{F}_{p^d}$ 上的重要结构映射。其 $d$ 次迭代 $\varphi^d = \mathrm{id}$,不动点恰好是 $\mathbb{F}_p$ 中的元素。
在 oCAS 中的实现
oCAS 的有限域实现在 ocas-domain/src/finite_field.rs 中,支持素域 $\mathbb{Z}/p\mathbb{Z}$。扩展域 $\mathbb{F}_{p^d}$ 通过 AlgebraicExtension<FiniteField> 实现(见 ocas-domain/src/algebraic.rs)。
核心结构体
FiniteField 表示一个素域 $\mathbb{F}_p$:
#![allow(unused)] fn main() { pub struct FiniteField { prime: BigInt, prime_minus_two: BigInt, // 缓存 p-2,用于 Fermat 求逆 #[cfg(feature = "gmp")] prime_gmp: rug::Integer, // GMP 后端加速模运算 #[cfg(feature = "gmp")] prime_minus_two_gmp: rug::Integer, } }
prime字段存储模数 $p$,使用num-bigint的任意精度整数。prime_minus_two在构造时预计算,避免每次求逆时重复计算。- 启用
gmpfeature 时,额外缓存 GMP 表示以利用rug的原生模幂。
FiniteFieldElement 表示域中元素:
#![allow(unused)] fn main() { pub struct FiniteFieldElement { value: BigInt, // 始终保持在 [0, p-1] 范围内 } }
元素始终以规范代表元(canonical representative)存储——即 $[0, p-1]$ 范围内的非负整数。这保证了语义相等即结构相等(PartialEq 直接比较值),无需每次比较时取模。
构造与元素创建
#![allow(unused)] fn main() { // 创建 F_7 let f = FiniteField::new(BigInt::from(7)); // 从任意整数创建元素(自动归约到 [0, p-1]) let a = f.element(10); // a = 10 mod 7 = 3 let b = f.element(-3); // b = -3 mod 7 = 4 }
element() 方法使用 mod_floor(而非简单的 %),确保负数也被正确映射到 $[0, p-1]$。
算术运算
所有运算通过 Domain trait 统一接口:
| 运算 | 方法 | 实现策略 |
|---|---|---|
| 加法 | f.add(&a, &b) | $(a + b) \bmod p$ |
| 减法 | f.sub(&a, &b) | $(a - b) \bmod p$(使用 mod_floor 处理负数) |
| 乘法 | f.mul(&a, &b) | $(a \cdot b) \bmod p$ |
| 取反 | f.neg(&a) | $(-a) \bmod p$ |
| 求逆 | f.inv(&a) | $a^{p-2} \bmod p$(Fermat 小定理);$a = 0$ 时返回 None |
| 除法 | f.div(&a, &b) | $a \cdot b^{-1}$;$b = 0$ 时返回 None |
| 幂运算 | f.pow(&a, n) | 快速模幂(二进制取幂) |
求逆实现细节:
#![allow(unused)] fn main() { fn inv(&self, a: &FiniteFieldElement) -> Option<FiniteFieldElement> { if a.value.is_zero() { return None; // 零没有逆元 } // Fermat: a^(p-2) ≡ a^{-1} (mod p) Some(self.normalize(a.value.modpow(&self.prime_minus_two, &self.prime))) } }
使用 Fermat 小定理而非扩展 Euclid 算法,因为 modpow 的二进制取幂只需 $O(\log p)$ 次乘法,且实现简单。当启用 GMP 后端时,rug::Integer::pow_mod 使用底层 GMP 的高度优化实现。
EuclideanDomain 实现
有限域同时实现了 EuclideanDomain trait,但域上的欧几里得除法是退化的:
#![allow(unused)] fn main() { // 域上除法总是精确的,余数恒为零 fn div_rem(&self, a, b) -> Option<(Self::Element, Self::Element)> { self.div(a, b).map(|q| (q, self.zero())) } // 域上的 GCD:两者均为零时返回 0,否则返回 1 fn gcd(&self, a, b) -> Self::Element { if self.is_zero(a) && self.is_zero(b) { self.zero() } else { self.one() } } }
这是因为域中每个非零元素都是单位(unit),因此 GCD 的概念退化为平凡情况。
扩展域 $\mathbb{F}_{p^d}$
通过 AlgebraicExtension<FiniteField> 构造:
#![allow(unused)] fn main() { use ocas_domain::{AlgebraicExtension, Domain, FiniteField}; use num_bigint::BigInt; // 构造 F_4 = F_2[x]/(x^2 + x + 1) let f2 = FiniteField::new(BigInt::from(2)); let f4 = AlgebraicExtension::new( f2, vec![ f2.element(1), // 常数项 f2.element(1), // x 系数 f2.element(1), // x^2 系数(首项) ], ); let alpha = f4.alpha(); // α = x mod (x^2+x+1) // α^2 = α + 1(在 F_2 中) let alpha_sq = f4.mul(&alpha, &alpha); }
AlgebraicElement<E> 内部用系数向量表示,按升幂排列、尾部零裁剪:
#![allow(unused)] fn main() { pub struct AlgebraicElement<E> { coeffs: Vec<E>, // [a_0, a_1, ..., a_{d-1}] 表示 a_0 + a_1*α + ... } }
求逆使用扩展 Euclid 算法(在基域的稠密多项式上自包含实现),求得 $s(x) \cdot a(x) + t(x) \cdot m(x) = 1$,则 $s(x) \bmod m(x) = a(x)^{-1}$。
进阶话题
数论变换(NTT)与快速多项式乘法
NTT 是 FFT 在有限域上的类比。标准 FFT 在复数域 $\mathbb{C}$ 上使用单位根 $\omega_n = e^{2\pi i/n}$,NTT 则在 $\mathbb{F}_p$ 上使用模 $p$ 的 $n$ 次单位根。
存在条件:$\mathbb{F}_p$ 中存在 $n$ 次本原单位根当且仅当 $n \mid (p - 1)$。这是因为 $\mathbb{F}_p^*$ 是 $p-1$ 阶循环群,$n$ 次单位根存在 $\iff$ $n$ 整除群的阶。
算法流程(给定多项式 $f, g \in \mathbb{F}_p[x]$,次数各 $< n$):
- 补零:将 $f, g$ 的系数向量补到长度 $N = 2^{\lceil \log_2(2n) \rceil}$。
- 正变换:$\hat{f} = \mathrm{NTT}(f)$,$\hat{g} = \mathrm{NTT}(g)$。
- 逐点乘:$\hat{h}_i = \hat{f}_i \cdot \hat{g}_i$(逐分量模 $p$)。
- 逆变换:$h = \mathrm{NTT}^{-1}(\hat{h})$,最后乘以 $N^{-1} \bmod p$。
总复杂度 $O(N \log N)$,对比朴素卷积的 $O(n^2)$ 和 Karatsuba 的 $O(n^{1.585})$。
oCAS 中的 NTT 实现(ocas-poly/src/ntt.rs):
- 算法:Cooley-Tukey 基 2 DIT(时间抽取),bit-reversal 置换。
- Montgomery 算术:所有中间值以 Montgomery 形式 $x \cdot R \bmod p$($R = 2^{64}$)存储,避免昂贵的
u128 % p运算。入口和出口各做一次转换。 - 触发条件:当多项式系数个数 $\geq 256$(
NTT_THRESHOLD,即次数 $\geq 255$)且 $p$ 为 NTT-friendly($p-1$ 含足够大的 2-幂因子)时自动激活,否则回退到 Karatsuba。 - 限制:当前仅支持能放入
u64的素数($p < 2^{64}$,try_ntt_mul_fp中检查);Montgomery 约减使用 128 位中间值,$p \leq 2^{63}$ 时可保证不溢出。
#![allow(unused)] fn main() { // NTT-friendly 检查 pub fn is_ntt_friendly(p: u64, n: usize) -> bool { // n 必须整除 p-1 if n == 0 { return true; } let pm1 = p - 1; pm1 % (n as u64) == 0 } }
NTT-friendly 素数的构造:实践中常用形如 $p = k \cdot 2^m + 1$ 的素数(如 NTT Prime $998244353 = 119 \cdot 2^{23} + 1$),保证 $2^{23} \mid (p-1)$,从而可处理长度达 $2^{23}$ 的变换。
Montgomery 乘法的核心思想是用乘法和右移代替取模:
$$ \mathrm{MontMul}(a, b) = a \cdot b \cdot R^{-1} \bmod p $$
其中 $R = 2^{64}$,$R^{-1} \bmod p$ 和 $p' = -p^{-1} \bmod R$ 预计算。乘法步骤为:
- $t = a \cdot b$(128 位中间值)
- $m = (t \bmod R) \cdot p' \bmod R$
- $u = (t + m \cdot p) / R$
- 若 $u \geq p$ 则 $u = u - p$
全程仅用加法、乘法和移位——无昂贵的除法。
NTT 在多项式 GCD 中的应用
NTT 不仅加速乘法,还间接加速了多项式 GCD 和因式分解。在 Hensel 提升过程中,大量小系数多项式的乘法是瓶颈,NTT 将其从 $O(n^2)$ 降为 $O(n \log n)$。oCAS 的 DenseUnivariatePolynomial<FiniteField>::mul_ntt 在系数个数 $\geq 256$ 且素数 NTT-friendly 时自动启用 NTT(否则回退 Karatsuba/Schoolbook);通用 mul 目前仍走 Karatsuba/Schoolbook 路径。
参考文献
- Shoup, V. A Computational Introduction to Number Theory and Algebra. Cambridge University Press, 2nd edition, 2009. — 第 4–5 章涵盖有限域构造、循环群和不可约多项式。
- Lidl, R. & Niederreiter, H. Introduction to Finite Fields and Their Applications. Cambridge University Press, 1994. — 有限域理论的经典教材。
- Gathen, J. von zur & Gerhard, J. Modern Computer Algebra. Cambridge University Press, 3rd edition, 2013. — 第 8 章(快速多项式乘法与 NTT)和第 14 章(有限域算术)。
- Menezes, A., van Oorschot, P. & Vanstone, S. Handbook of Applied Cryptography. CRC Press, 1996. — 第 2 章涵盖模算术和扩展 Euclid 算法。
基础:线性代数
本章介绍 oCAS 中矩阵运算的数学基础。oCAS 的 Matrix 类型工作在
EuclideanDomain 上——不只是浮点数或有理数,而是任意满足欧几里得除法
的代数结构。这意味着我们需要一种不产生分数的消元算法。
前提知识
在阅读本章之前,你需要了解:
- 环与域的基本概念(参见有限域与模算术)
- 欧几里得域:支持
add、mul、sub、neg、div(带余除法)、gcd、rem的代数结构 - 向量空间的基本直觉:向量加法与标量乘法
不需要线性代数的系统训练——本章从零开始构建。
基础概念
矩阵
一个 $m \times n$ 矩阵是一个按 $m$ 行 $n$ 列排列的元素矩形阵列:
$$ A = \begin{pmatrix} a_{11} & a_{12} & \cdots & a_{1n} \ a_{21} & a_{22} & \cdots & a_{2n} \ \vdots & \vdots & \ddots & \vdots \ a_{m1} & a_{m2} & \cdots & a_{mn} \end{pmatrix} $$
元素 $a_{ij}$ 位于第 $i$ 行第 $j$ 列。在 oCAS 中,元素存储在行优先的
一维数组中:data[i * ncols + j] 对应 $a_{i+1,,j+1}$(零索引)。
矩阵运算
加法:两个同形矩阵逐元素相加:
$$ (A + B){ij} = a{ij} + b_{ij} $$
标量乘法:
$$ (cA){ij} = c \cdot a{ij} $$
矩阵乘法:$A$($m \times n$)与 $B$($n \times p$)的积 $C = AB$ 是 $m \times p$ 矩阵:
$$ c_{ij} = \sum_{k=1}^{n} a_{ik} \cdot b_{kj} $$
乘法要求 $A$ 的列数等于 $B$ 的行数。注意矩阵乘法一般不满足交换律 ($AB \neq BA$),但满足结合律($(AB)C = A(BC)$)。
转置:交换行与列:
$$ (A^\top){ij} = a{ji} $$
性质:$(AB)^\top = B^\top A^\top$,$(A^\top)^\top = A$。
迹(trace):方阵对角元素之和:
$$ \operatorname{tr}(A) = \sum_{i=1}^{n} a_{ii} $$
性质:$\operatorname{tr}(AB) = \operatorname{tr}(BA)$(当乘积有定义时)。
行列式
行列式是方阵的一个标量值,是线性代数中最核心的概念之一。对 $n \times n$ 矩阵 $A$,行列式 $\det(A)$ 有以下等价定义:
递归定义(Laplace 展开):沿第 $i$ 行展开:
$$ \det(A) = \sum_{j=1}^{n} (-1)^{i+j} , a_{ij} , M_{ij} $$
其中 $M_{ij}$ 是删去第 $i$ 行第 $j$ 列后的 $(n-1)\times(n-1)$ 子矩阵 的行列式(余子式)。对 $1\times 1$ 矩阵,$\det(a_{11}) = a_{11}$。
几何定义:$|\det(A)|$ 是矩阵列向量所张成的平行多面体的"有向体积"。 $\det(A) > 0$ 表示保持定向,$\det(A) < 0$ 表示翻转定向。
行列式的关键性质
- 乘法性:$\det(AB) = \det(A) \cdot \det(B)$
- 转置不变性:$\det(A^\top) = \det(A)$
- 行交换变号:交换两行,行列式变号
- 行倍乘:某行乘以 $c$,行列式乘以 $c$
- 行加法不变:将一行的倍数加到另一行,行列式不变
- 奇异判定:$\det(A) = 0$ 当且仅当 $A$ 不可逆(奇异)
- 上/下三角矩阵:$\det(A) = \prod_{i=1}^{n} a_{ii}$(对角元素之积)
这些性质直接引出了用消元法计算行列式的思路:通过行变换将矩阵化为 三角矩阵,行列式就是对角元素之积(乘以行交换的符号因子)。
核心理论
问题:域上的分数膨胀
经典的高斯消元在有理数域上工作良好——我们可以自由地进行除法。但 oCAS 的
Matrix<D: EuclideanDomain> 要求在任意欧几里得域上工作,包括:
- 整数环 $\mathbb{Z}$:$3 \div 2$ 无意义(不存在整数商)
- 多项式环 $\mathbb{F}[x]$:$x^2 \div (x+1)$ 不是多项式
- 有限域 $\mathbb{F}_p$:可以除,但每次除法产生模逆运算,且中间结果可能溢出
即使在有理数上,经典消元也会产生越来越大的分母。例如对整数矩阵:
$$ A = \begin{pmatrix} 2 & 3 \ 5 & 7 \end{pmatrix} $$
经典消元需要计算 $7 - \frac{5 \cdot 3}{2} = 7 - \frac{15}{2} = -\frac{1}{2}$—— 出现了分数。对 $n \times n$ 矩阵,分数的分子分母可能增长到 $O(n!)$ 量级。
Bareiss 无分数行列式算法
Bareiss 算法(1968)的核心洞察:在整数(或更一般的欧几里得域)上, 消元过程中的所有中间值都可以通过除以前一个主元来保持为整数。
算法描述
对 $n \times n$ 矩阵 $A$,Bareiss 算法产生上三角矩阵,其最后一个对角 元素 $a_{nn}^{(n-1)}$ 就是 $\det(A)$(可能需要乘以 $-1$ 来补偿行交换)。
初始化:令 $a_{ij}^{(0)} = a_{ij}$,$p_0 = 1$。
对 $k = 0, 1, \ldots, n-2$:
-
选主元:若 $a_{kk}^{(k)} = 0$,寻找 $i > k$ 使 $a_{ik}^{(k)} \neq 0$, 交换第 $k$ 行与第 $i$ 行(记录符号翻转)。若找不到,$\det(A) = 0$。
-
消元:对 $i = k+1, \ldots, n-1$ 和 $j = k+1, \ldots, n-1$:
$$ a_{ij}^{(k+1)} = \frac{a_{ij}^{(k)} \cdot a_{kk}^{(k)} - a_{ik}^{(k)} \cdot a_{kj}^{(k)}}{p_k} $$
- 更新除数:$p_{k+1} = a_{kk}^{(k)}$
最终:
$$ \det(A) = \begin{cases} a_{nn}^{(n-1)} & \text{若无行交换或偶数次交换} \ -a_{nn}^{(n-1)} & \text{若有奇数次行交换} \end{cases} $$
为什么除法是精确的
定理(Bareiss 1968):上述公式中,$p_k$ 精确整除分子 $a_{ij}^{(k)} \cdot a_{kk}^{(k)} - a_{ik}^{(k)} \cdot a_{kj}^{(k)}$。
直觉上,这来自 Sylvester 行列式恒等式。消元矩阵的 $(k+1)$ 步更新 等价于计算一个 $(k+2) \times (k+2)$ 子矩阵的行列式,而 Schur 补公式 保证除以前一个主元的结果仍是整数。
复杂度与数值行为
- 时间复杂度:$O(n^3)$ 次域运算——与经典高斯消元相同
- 中间值大小:对整数矩阵,中间值 $a_{ij}^{(k)}$ 的位数增长为 $O(n \log n)$——远优于经典消元的 $O(n!)$ 量级增长
- 无舍入误差:在整数和多项式上,所有运算精确,无浮点近似
高斯消元与行阶梯形
高斯消元将矩阵化为行阶梯形(Row Echelon Form, REF):
$$ \begin{pmatrix} \boxed{a_{11}} & a_{12} & a_{13} & a_{14} \ 0 & \boxed{a_{22}} & a_{23} & a_{24} \ 0 & 0 & 0 & \boxed{a_{34}} \ 0 & 0 & 0 & 0 \end{pmatrix} $$
带框的元素是主元(pivots)。行阶梯形满足:
- 所有非零行在所有零行之上
- 每个主元所在列在其下方行中全为零
- 每个主元严格在其上方行的主元右侧
继续消元可得简化行阶梯形(Reduced Row Echelon Form, RREF)—— 每个主元为 1,且主元所在列的其余元素全为 0。
主元选取
欧几里得域上未必有"绝对值"概念(例如 $\mathbb{F}[x]$),因此 oCAS 的消元不使用数值线性代数中的部分主元选取(partial pivoting)。row_echelon 与 determinant 都选择当前列中第一个非零元素作为主元;若当前列全为零则跳过该列(不交换行)。中间值的膨胀由内容剥离(content stripping,见下文)控制——消元后立即提取主元行的 GCD 并整行除以它。
分数无关回代
消元后需要回代(back-substitution)来求解线性系统。oCAS 的回代也是 分数无关的:从最后一行开始向上,用 GCD 缩放避免引入分数。
对行阶梯形中的第 $i$ 行(主元在第 $j$ 列)和第 $k < i$ 行(第 $j$ 列 有非零元素 $a_{kj}$):
$$ g = \gcd(a_{ij}, a_{kj}), \quad s_p = \frac{a_{kj}}{g}, \quad s_r = \frac{a_{ij}}{g} $$
更新第 $k$ 行:对每个 $l > j$:
$$ a_{kl} \leftarrow s_r \cdot a_{kl} - s_p \cdot a_{il} $$
$$ a_{kj} \leftarrow 0 $$
这与 Bareiss 消元的无分数性质完全一致。
内容剥离
消元过程中,行的系数可能包含公共因子。oCAS 在每一步主元处理后执行 内容剥离(content stripping):计算主元行从主元列到最后一列的 GCD,然后整行除以该 GCD。
$$ g = \gcd(a_{ij}, a_{i,j+1}, \ldots, a_{i,n-1}) $$
$$ a_{il} \leftarrow \frac{a_{il}}{g}, \quad l = j, j+1, \ldots, n-1 $$
这个优化对于多项式环尤其关键——否则中间多项式的次数会指数增长。
逆矩阵
方阵 $A$ 的逆矩阵 $A^{-1}$ 满足:
$$ AA^{-1} = A^{-1}A = I $$
其中 $I$ 是单位矩阵。逆存在的充要条件是 $\det(A) \neq 0$($A$ 非奇异)。
计算方法:将 $[A \mid I]$ 做行变换化为 $[I \mid A^{-1}]$。在 oCAS 中,
inverse() 通过对单位矩阵的每一列 $e_j$ 求解 $Ax_j = e_j$ 来得到
$A^{-1}$ 的第 $j$ 列。
在 oCAS 中的实现
Matrix<D: EuclideanDomain> 结构
oCAS 的矩阵是泛型的——它接受任何 EuclideanDomain 作为系数域:
#![allow(unused)] fn main() { use ocas_domain::{EuclideanDomain, IntegerDomain, Integer}; use ocas_poly::matrix::Matrix; let d = IntegerDomain; let a = Matrix::new(2, 2, vec![ Integer::from(2), Integer::from(3), Integer::from(5), Integer::from(7), ], d); }
内部存储为行优先的 Vec<D::Element>,加上行数、列数和系数域实例
(domain: D 按值存储,而非引用)。这使得所有算术操作通过域的 trait
方法(add、mul、sub、div、gcd)进行,完全不依赖具体类型。
为什么选择 Bareiss 而非 Laplace 展开
| 方法 | 时间复杂度 | 空间复杂度 | 分数膨胀 | 域兼容性 |
|---|---|---|---|---|
| Laplace 展开 | $O(n!)$ | $O(n^2)$(递归深度 $O(n)$) | 无 | 任意交换环(实际不可行) |
| 经典高斯消元 | $O(n^3)$ | $O(n^2)$ | 严重 | 仅域 |
| Bareiss | $O(n^3)$ | $O(n^2)$ | 无 | 欧几里得域 |
- Laplace 展开的 $O(n!)$ 复杂度使其对 $n > 10$ 完全不可用
- 经典消元需要分数运算,不适用于 $\mathbb{Z}$ 或 $\mathbb{F}[x]$
- Bareiss 算法兼得两者之长:$O(n^3)$ 效率 + 无分数 + 支持任意欧几里得域
这是 oCAS 选择 Bareiss 的根本原因——它是一个通用符号计算系统, 矩阵元素可能是整数、多项式、有限域元素,甚至代数数域元素。
行阶梯化:row_echelon
#![allow(unused)] fn main() { // 分数无关高斯消元,返回秩 pub fn row_echelon(&mut self, max_col: usize) -> usize }
row_echelon 对前 max_col 列执行分数无关消元,返回矩阵的秩。算法步骤:
- 对每列 $j$,选择当前列中第一个非零元素作为主元(若全列为零则跳过该列)
- 内容剥离:提取主元行的 GCD 并整行除以它
- 消元:对主元下方的每行,用 GCD 缩放后的减法消除第 $j$ 列($a_{ij}$ 为主元,$a_{kj}$ 为被消元元素,$s_r$、$s_p$ 须在清零前计算):
$$ g = \gcd(a_{ij}, a_{kj}), \quad s_r = \frac{a_{ij}}{g}, \quad s_p = \frac{a_{kj}}{g} $$
$$ a_{kj} \leftarrow 0, \qquad a_{kl} \leftarrow s_r \cdot a_{kl} - s_p \cdot a_{il} \quad (l > j) $$
这保证所有中间值保持在域内,无分数产生。
行列式:determinant
#![allow(unused)] fn main() { use ocas_domain::{Integer, IntegerDomain}; use ocas_poly::matrix::Matrix; let d = IntegerDomain; let a = Matrix::from_rows(vec![ vec![Integer::from(1), Integer::from(2)], vec![Integer::from(3), Integer::from(4)], ], d); assert_eq!(a.determinant().unwrap(), Integer::from(-2)); }
determinant 内部直接实现 Bareiss 算法:
- 初始化:复制数据到工作数组,$p_0 = 1$,符号标记 $\epsilon = 1$
- 对 $k = 0, \ldots, n-2$:
- 若主元为零,寻找下方非零行并交换(翻转 $\epsilon$)
- 更新:$a_{ij}^{(k+1)} = (a_{ij}^{(k)} \cdot a_{kk}^{(k)} - a_{ik}^{(k)} \cdot a_{kj}^{(k)}) / p_k$
- 更新除数:$p_{k+1} = a_{kk}^{(k)}$
- 返回 $\epsilon \cdot a_{n-1,n-1}^{(n-1)}$
特殊情况:$n = 0$ 返回 $1$(空矩阵行列式),$n = 1$ 直接返回唯一元素。
线性系统求解:solve
#![allow(unused)] fn main() { let a = Matrix::from_rows(vec![ vec![Integer::from(1), Integer::from(1)], vec![Integer::from(1), Integer::from(-1)], ], d); let b = vec![Integer::from(3), Integer::from(-1)]; let x = a.solve(&b).unwrap(); assert_eq!(x, vec![Integer::from(1), Integer::from(2)]); }
solve 的完整流程:
- 增广:将 $b$ 拼接为 $[A \mid b]$
- 消元:
row_echelon(nvars)化为行阶梯形 - 一致性检查:若主元行之后有非零的 $b$ 分量,系统矛盾,返回
MatrixError::Inconsistent - 回代:
back_substitution(nvars)分数无关回代 - 秩检查:若秩 $< n$,系统欠定,返回
MatrixError::Underdetermined - 最终除法:每个解分量除以其对应主元
错误类型
| 错误 | 含义 |
|---|---|
ShapeMismatch | $A$ 不是方阵,或 $b$ 的长度不等于行数 |
RightHandSideIsNotVector | $b$ 不是列向量 |
Inconsistent | 系统无解(增广矩阵秩 > 系数矩阵秩) |
Underdetermined { rank } | 系统有无穷多解(秩 < 未知数个数) |
ResultNotInDomain | 解不在域中(整数矩阵可能产生有理数解) |
秩与逆矩阵
#![allow(unused)] fn main() { // 秩:行阶梯形的主元个数 let rank = a.rank(); // 逆矩阵:逐列求解 Ax_j = e_j let inv = a.inverse()?; }
rank 将矩阵克隆后调用 row_echelon,返回主元个数。
inverse 对单位矩阵的每一列求解线性系统,得到逆矩阵的各列。这要求
矩阵非奇异且解在域内——例如整数矩阵的逆一般不在 $\mathbb{Z}$ 中,
除非 $\det(A) = \pm 1$(幺模矩阵):
#![allow(unused)] fn main() { // 幺模矩阵:det = 1,整数逆存在 let a = Matrix::from_rows(vec![ vec![Integer::from(1), Integer::from(2)], vec![Integer::from(0), Integer::from(1)], ], d); let inv = a.inverse().unwrap(); assert_eq!(inv[(0, 0)], Integer::from(1)); assert_eq!(inv[(0, 1)], Integer::from(-2)); assert_eq!(inv[(1, 0)], Integer::from(0)); assert_eq!(inv[(1, 1)], Integer::from(1)); }
矩阵乘法与转置
#![allow(unused)] fn main() { // 乘法:C = A · B let c = a.matmul(&b)?; // 转置 let at = a.transpose(); // 迹(方阵对角元素之和) let tr = a.trace()?; }
matmul 使用标准的三重循环 $O(n^3)$ 算法,所有运算通过域的 trait 方法。
transpose 通过重新排列数据实现。trace 对方阵求对角元素之和。
完整示例:求解整数线性系统
#![allow(unused)] fn main() { use ocas_domain::{IntegerDomain, Integer}; use ocas_poly::matrix::Matrix; let d = IntegerDomain; // 方程组: // 2x + 3y = 8 // 5x + 7y = 19 let a = Matrix::from_rows(vec![ vec![Integer::from(2), Integer::from(3)], vec![Integer::from(5), Integer::from(7)], ], d); let b = vec![Integer::from(8), Integer::from(19)]; let x = a.solve(&b).unwrap(); // x = [1, 2] assert_eq!(x[0], Integer::from(1)); assert_eq!(x[1], Integer::from(2)); // 验证:Ax = b let x_matrix = Matrix::new(2, 1, x, d); let result = a.matmul(&x_matrix).unwrap(); assert_eq!(result[(0, 0)], Integer::from(8)); assert_eq!(result[(1, 0)], Integer::from(19)); // 行列式:2·7 - 3·5 = -1 assert_eq!(a.determinant().unwrap(), Integer::from(-1)); }
消元过程中的中间值全部保持为整数——Bareiss 算法保证了这一点。
进阶话题
Bareiss 算法的推广
Bareiss 算法可以推广到计算子矩阵的行列式(所有 $k \times k$ 阶子式), 这在计算 Smith 标准形和 Hermite 标准形时很有用。
对多项式矩阵,Bareiss 通过 GCD 缩放有效地控制了系数膨胀,使得 多项式矩阵的行列式计算在实践中可行。
与 LU 分解的关系
Bareiss 算法可以看作是无分数 LU 分解的变体。经典 LU 分解将矩阵 分解为 $A = LU$,其中 $L$ 是下三角、$U$ 是上三角。Bareiss 产生 的是类似的分解,但所有中间值保持在域内。
$\det(A) = \det(L) \cdot \det(U) = \prod_{i} l_{ii} \cdot \prod_{i} u_{ii}$。
符号计算中的矩阵
在符号计算中,矩阵元素可能是多项式或有理函数。此时 Bareiss 算法的 GCD 缩放尤为关键——它将多项式系数的次数增长控制在 $O(n)$ 而非 指数增长。
oCAS 的 Gröbner 基 F4 算法内部大量使用矩阵行阶梯化——将多元多项式的
消元问题转化为系数域上的矩阵问题,再用 Bareiss 或其 ℤ_p 快速路径
(惰性模算术)来高效处理。
参考文献
-
Axler, S. Linear Algebra Done Right, 3rd ed. Springer, 2015. Chapters 4–6: 行列式、特征值与内积空间。
-
Bareiss, E. H. "Sylvester's Identity and Multistep Integer-Preserving Gaussian Elimination." Mathematics of Computation, 22(103):565–578, 1968. Bareiss 算法的原始论文。
-
Cox, D., Little, J., O'Shea, D. Ideals, Varieties, and Algorithms, 4th ed. Springer, 2015. 多项式矩阵与 Gröbner 基中的线性代数。
-
Geddes, K. O., Czapor, S. R., Labahn, G. Algorithms for Computer Algebra. Kluwer, 1992. 第 5 章:矩阵上的符号消元算法。
进阶:多项式 GCD 与因式分解
本章系统讲解多项式最大公因式(GCD)计算和因式分解的核心算法。这些算法是计算机代数系统的基础构件——Gröbner 基计算、有理函数化简、ODE 求解中的部分分式分解都依赖于它们。我们从经典的 Euclidean 算法出发,逐步引入模方法、Hensel 提升和多元扩展,最终覆盖有限域和代数数域上的因式分解。
前提知识
阅读本章前,建议先学习:
- 基础:多项式代数 — 多项式环、次数、单项式序
- 基础:有限域与模算术 — $\mathbb{F}_p$ 构造、模逆、CRT
- 基础:线性代数 — 矩阵运算、行列式
Euclidean 算法回顾
设 $\mathbb{F}$ 为域,$f, g \in \mathbb{F}[x]$,$g \ne 0$。Euclidean 算法通过反复带余除法计算 GCD:
$$f = q_1 g + r_1, \quad g = q_2 r_1 + r_2, \quad r_1 = q_3 r_2 + r_3, \quad \ldots, \quad r_{k-2} = q_k r_{k-1} + r_k$$
最后一个非零余式 $r_k$ 就是 $\gcd(f, g)$(精确到常数倍)。
在 $\mathbb{Z}[x]$ 中,系数不再是域元素,带余除法可能不可行。我们需要伪除法(pseudo-division):
$$\text{lc}(g)^{d+1} \cdot f = q \cdot g + r, \quad d = \deg(f) - \deg(g)$$
其中 $\text{lc}(g)$ 是 $g$ 的首项系数。这保证了商和余式都在 $\mathbb{Z}[x]$ 中,但代价是余式的系数可能膨胀。
伪余式序列(PRS)
直接对 $\mathbb{Z}[x]$ 使用伪除法的 Euclidean 算法会导致系数膨胀:在度约 16 以上时,中间结果的系数可能达到数百位。为控制膨胀,文献中提出了多种伪余式序列(Pseudo-Remainder Sequences):
- 原始 PRS(primitive PRS):每步取伪余式的本原部分(除以系数的内容),使中间余式互素、避免不必要的系数膨胀
- 子结式 PRS(Subresultant PRS):通过精心选择的缩放因子,使中间系数的大小恰好等于子结式行列式的理论界,避免了原始 PRS 的过度膨胀
在 oCAS 中,DenseUnivariatePolynomial::gcd 使用带伪余式的 Euclidean 算法(ocas-poly/src/gcd.rs),返回本原 GCD。伪余式在度数较高时(实践中约 $\deg > 16$)系数急剧膨胀,此时应改用 gcd_modular_z(Brown 模 GCD,ocas-poly/src/gcd/modular.rs);目前两者由调用方显式选择,没有自动的度数调度。
结式(Resultant)
两个多项式 $f, g \in \mathbb{F}[x]$ 的结式 $\text{Res}(f, g)$ 是一个标量,满足:
$$\text{Res}(f, g) = 0 \iff \gcd(f, g) \ne 1$$
结式可以通过 Sylvester 矩阵的行列式计算,也可以通过 PRS 高效计算(Brown 的 PRS 算法)。在 oCAS 中,resultant() 方法使用 Brown PRS 算法实现。
基础概念
GCD 的定义
设 $D$ 为唯一分解整环(UFD),$f, g \in D[x]$。$f$ 和 $g$ 的最大公因式 $\gcd(f, g)$ 是满足以下条件的首一多项式 $d$:
- $d \mid f$ 且 $d \mid g$(公因式)
- 对任意公因式 $c$,有 $c \mid d$(最大性)
在 $\mathbb{F}[x]$($\mathbb{F}$ 为域)中,GCD 总是首一的(乘以首项系数的逆)。在 $\mathbb{Z}[x]$ 中,GCD 通常取为本原的(primitive),即系数的 GCD 为 1。
内容与本原化
多项式 $f = \sum a_i x^i \in \mathbb{Z}[x]$ 的内容(content)定义为系数的 GCD:
$$\text{cont}(f) = \gcd(a_0, a_1, \ldots, a_n)$$
本原部分(primitive part)定义为:
$$\text{pp}(f) = f / \text{cont}(f)$$
Gauss 引理:两个本原多项式的乘积仍是本原的。由此可得:
$$\gcd(f, g) = \gcd(\text{cont}(f),, \text{cont}(g)) \cdot \gcd(\text{pp}(f),, \text{pp}(g))$$
这允许我们将内容(整数 GCD)和本原部分(多项式 GCD)分开计算。
在 oCAS 中:
#![allow(unused)] fn main() { use ocas_poly::DenseUnivariatePolynomial; use ocas_domain::IntegerDomain; let f = DenseUnivariatePolynomial::from_coeffs( IntegerDomain, vec![Integer::from(6), Integer::from(4), Integer::from(2)], ); assert_eq!(f.content(), Integer::from(2)); // cont(f) = gcd(6,4,2) = 2 assert_eq!(f.primitive_part().coeffs(), // pp(f) = 3 + 2x + x² &[Integer::from(3), Integer::from(2), Integer::from(1)]); }
核心理论
无平方分解(Square-free Factorization)
动机
完全因式分解的第一步是将多项式分解为无平方因子的幂之积:
$$f = c \cdot g_1^1 \cdot g_2^2 \cdot g_3^3 \cdots$$
其中 $g_i$ 互素且无平方因子($\gcd(g_i, g_i') = 1$)。这大幅简化了后续的不可约分解——每个 $g_i$ 只需分解一次。
Yun 算法
对于特征 0 的域(如 $\mathbb{Q}$ 或 $\mathbb{Z}$),Yun 算法基于以下观察:若 $f = g_1 g_2^2 g_3^3 \cdots$,则
$$\gcd(f, f') = g_2 g_3^2 g_4^3 \cdots$$
算法步骤:
输入:f ∈ ℤ[x](已本原化)
输出:[(g₁, 1), (g₂, 2), …] 使得 f = ∏ gₖᵏ
1. f ← pp(f), f' ← derivative(f)
2. g ← gcd(f, f')
3. w ← f / g
4. k ← 1
5. while w ≠ 1:
6. h ← gcd(w, g)
7. z ← w / h // z = gₖ(重数为 k 的无平方因子)
8. if z ≠ 1: 输出 (z, k)
9. g ← g / h
10. w ← h
11. k ← k + 1
关键性质:每步迭代的 GCD 计算精确消去了更高重数的因子,使得 $z = w/h$ 恰好包含重数 $k$ 的因子。
复杂度:$O(n^2)$ 次系数运算($n = \deg f$),瓶颈在于 GCD 计算。
有限域上的特殊情况
在 $\mathbb{F}_p[x]$ 中,当 $p \mid \deg f$ 时,形式导数 $f'$ 可能为零(例如 $f(x) = x^p - 1$ 在 $\mathbb{F}_p$ 上有 $f' = 0$)。此时 $f$ 是某个多项式的 $p$ 次幂:$f(x) = g(x^p) = g(x)^p$。Musser/Bernardin 算法检测此情况,取 $p$ 次根后递归,再将重数乘以 $p$。
在 oCAS 中,DenseUnivariatePolynomial::square_free_factorization() 实现了 Yun 算法(ocas-poly/src/factor/mod.rs),square_free_factorization_ff() 实现了有限域版本(ocas-poly/src/factor/finite_field.rs)。
模 GCD(Brown 1971)
问题
直接在 $\mathbb{Z}[x]$ 上用伪除法计算 GCD 会导致系数指数级膨胀。Brown 的模算法将问题"投影"到多个 $\mathbb{F}_p[x]$ 上,利用域上的高效 GCD,再用 CRT 重建。
算法核心思想
设 $a, b \in \mathbb{Z}[x]$,令 $g = \gcd(a, b)$(本原)。关键观察:
$$\text{lc}(g) \mid \gcd(\text{lc}(a), \text{lc}(b)) \equiv \gamma$$
因此 $\gamma^{-1} \cdot \text{lc}(g) \mid 1$,即模 $p$ 的 monic GCD 乘以 $\gamma$ 后给出 $\mathbb{Z}[x]$ 上 GCD 的一个缩放版本。
算法步骤
输入:a, b ∈ ℤ[x](非零)
输出:gcd(a, b) ∈ ℤ[x](本原)
1. a_p ← pp(a), b_p ← pp(b)
2. γ ← gcd(lc(a_p), lc(b_p)) // GCD 首项系数的倍数
3. images ← [], best_deg ← ∞
4. for p in primes_from(2³⁰): // 从大于 2³⁰ 的素数开始
5. if p | γ: continue
6. field ← FiniteField(p)
7. g_p ← monic(gcd(a_p mod p, b_p mod p))
8. g_scaled ← g_p · (γ mod p) · (lc(g_p)⁻¹ mod p)
9.
10. if deg(g_scaled) > best_deg: continue // 不幸素数,丢弃
11. if deg(g_scaled) < best_deg: // 发现更小的 GCD
12. best_deg ← deg(g_scaled)
13. images ← [(p, g_scaled)]
14. else: images.append((p, g_scaled))
15.
16. // CRT 重建 + 试除验证
17. candidate ← primitive_part(CRT_reconstruct(images))
18. if candidate divides a_p AND candidate divides b_p:
19. return candidate
20.
21. if 已尝试的素数个数 > MAX_PRIMES: break
22.
23. return fallback_pseudo_remainder_gcd(a, b) // 极端回退
关键细节
素数选择:素数从 $> 2^{30}$ 开始。每个素数在 CRT 重建中贡献约 30 比特的精度,因此系数位长为 $L$ 的多项式通常只需约 $L/30$ 个素数即可重建(实践中一般不超过几十个)。
Landau-$\gamma$ 缩放:monic 模 GCD $g_p$ 需要乘以 $\gamma = \gcd(\text{lc}(a), \text{lc}(b))$ 才能与 $\mathbb{Z}[x]$ 上的 GCD 对齐。具体地,$\text{lc}(g_p)^{-1} \cdot \gamma \bmod p$ 是缩放因子。
CRT 对称代表:重建时使用对称剩余 $(-p/2, p/2]$ 而非 $[0, p)$,使系数绝对值最小化。
不幸素数检测:若某素数 $p$ 上的模 GCD 次数高于当前最优值,该素数是"不幸的"($p$ 整除真 GCD 的某个系数的分母),直接丢弃。若次数更低,说明之前的素数都是不幸的,清空重来。
试除验证:CRT 重建的候选 $c$ 必须满足 $c \mid a_p$ 且 $c \mid b_p$(精确除法),否则继续收集更多素数。
安全回退:尝试最多 10000 个素数后(实践中极少超过数十个),回退到伪余式 GCD 保证终止。
在 oCAS 中,gcd_modular_z() 实现在 ocas-poly/src/gcd/modular.rs:
#![allow(unused)] fn main() { use ocas_poly::gcd::modular::gcd_modular_z; let a = DenseUnivariatePolynomial::from_coeffs(d, vec![i(-1), i(0), i(1)]); // x² - 1 let b = DenseUnivariatePolynomial::from_coeffs(d, vec![i(1), i(2), i(1)]); // x² + 2x + 1 let g = gcd_modular_z(&a, &b); assert_eq!(g.coeffs(), &[i(1), i(1)]); // x + 1 }
多元 GCD(Brown 求值—插值)
问题
给定 $f, g \in \mathbb{Z}[x_1, \ldots, x_n]$,计算 $\gcd(f, g)$。多元 GCD 的挑战在于:(1) 多元多项式没有规范的"带余除法";(2) 系数结构更复杂。
Brown 求值—插值策略(二元情形)
设 $f, g \in \mathbb{Z}[x, y]$,视为主变量 $x$ 的多项式,系数在 $\mathbb{Z}[y]$ 中。
步骤:
- 求值:选 $\alpha \in \mathbb{Z}$,计算 $f(x, \alpha)$ 和 $g(x, \alpha)$(一元多项式)
- 一元 GCD:$d_\alpha(x) = \gcd(f(x, \alpha), g(x, \alpha))$
- 验证:检查 $d_\alpha(x)$ 的次数是否等于真 GCD 的次数(对多个 $\alpha$ 取最小一致次数)
- Lagrange 插值:对足够多的 $\alpha_1, \ldots, \alpha_k$ 计算 $d_{\alpha_i}(x)$,将每个 $x$ 系数视为 $y$ 的多项式,通过 Lagrange 插值恢复 $d(x, y)$
关键条件:$\alpha$ 必须是"幸运的"——即 $\gcd(f(x, \alpha), g(x, \alpha))$ 的次数等于真 GCD 的次数。若 $\alpha$ 不幸(例如使首项系数消失),需重试。
多元情形的递归
$n$ 变量的 GCD 递归地减少一个变量:
- 选 $x_n$ 的求值点 $\alpha$,计算 $f|{x_n = \alpha}$ 和 $g|{x_n = \alpha}$
- 递归计算 $(n-1)$ 变量的 GCD
- 插值恢复 $x_n$ 的依赖
$\mathbb{Z}$ 到 $\mathbb{Q}$ 的嵌入
在 $\mathbb{Z}[x_1, \ldots, x_n]$ 上计算 GCD 时,内容的处理至关重要:
- 先提取关于主变量 $x_0$ 的内容(content in $x_0$),即所有 $x_0^k$ 系数的 GCD
- 对本原部分进行多元 GCD
- 最终 GCD = $\gcd(\text{cont}_x(f), \text{cont}_x(g)) \cdot \gcd(\text{pp}_x(f), \text{pp}_x(g))$
Hensel 提升
核心思想
Hensel 提升是将模 $p$ 的因式分解"提升"到模 $p^k$(或 $\mathbb{Z}$)的关键技术。设 $f \in \mathbb{Z}[x]$ 是首一多项式,且
$$f \equiv g_0 \cdot h_0 \pmod{p}, \quad \gcd(g_0, h_0) = 1 \pmod{p}$$
目标是找到 $g, h \in \mathbb{Z}[x]$ 使得 $f = g \cdot h$,且 $g \equiv g_0 \pmod{p}$,$h \equiv h_0 \pmod{p}$。
线性 Hensel 提升(两因子情形)
设 $s, t$ 是 $\mathbb{F}_p[x]$ 上的 Bézout 系数:$s \cdot g_0 + t \cdot h_0 = 1 \pmod{p}$。
迭代步骤(从 $m = p$ 到 $m > B$):
1. e ← f - g·h // 误差
2. if e = 0: return (g, h) // 精确分解
3. ē ← (e / m) mod p // 误差的"导数"
4. Δg ← (t · ē) mod g₀ // g 的修正(deg < deg g₀)
5. Δh ← (ē - Δg · h₀) / g₀ // h 的修正(精确除法)
6. g ← g + Δg · m
7. h ← h + Δh · m
8. m ← m · p
每步迭代将精度从 $\bmod m$ 提升到 $\bmod m \cdot p$。
Mignotte 界
提升到何时停止?我们需要一个因子系数大小的上界。Landau–Mignotte 界给出:
$$|g|_\infty \le 2^n |f|_2$$
其中 $n = \deg f$,$|f|_2 = \sqrt{\sum a_i^2}$ 是 2-范数。因此只需提升到 $p^k > 2 \cdot 2^n |f|_2$。
多因子 Hensel 提升
对于 $r > 2$ 个因子,采用逐因子剥离策略:
输入:f, [g₁, g₂, …, gᵣ] mod p
输出:[G₁, G₂, …, Gᵣ] 使得 f = ∏ Gᵢ
1. 对 i = 1, …, r-1:
2. h₀ ← g_{i+1} · g_{i+2} · … · gᵣ (剩余因子之积)
3. (Gᵢ, H) ← hensel_lift_pair(f_current, gᵢ, h₀, p, bound)
4. f_current ← H
5. Gᵣ ← f_current
每步都是两因子的 Hensel 提升。
Zassenhaus 因子重组
Hensel 提升给出 $r$ 个模 $p^k$ 的因子 $\tilde{g}_1, \ldots, \tilde{g}_r$。$\mathbb{Z}[x]$ 上的真因子是这些 $\tilde{g}_i$ 的某个子集的乘积(模 $p^k$ 约化到对称范围 $(-p^k/2, p^k/2]$)。
重组策略:
1. remaining ← [g̃₁, …, g̃ᵣ]
2. result ← []
3. for size = 1, 2, …, r:
4. for each subset S ⊂ remaining of size |S|:
5. candidate ← primitive_part(lc(rest) · ∏_{i∈S} g̃ᵢ)
6. reduce candidate to symmetric range mod p^k
7. if candidate divides f:
8. result.append(candidate)
9. remaining ← remaining \ S
10. rest ← f / (∏ result)
11. break // 从 size=1 重新开始
12. return result
关键优化:乘以当前余因子的首项系数后再取本原部分,这是 Zassenhaus 的经典技巧——真因子 $h$ 满足 $\text{lc}(\text{rest}) \cdot \prod S = c \cdot h$。
复杂度:最坏情况 $2^r$ 个子集($r$ 为 mod-$p$ 因子数),但实践中因子数通常很少。
在 oCAS 中,Hensel 提升和 Zassenhaus 重组实现在 ocas-poly/src/factor/hensel.rs:
#![allow(unused)] fn main() { // 完整的 ℤ[x] 因式分解管线: // 1. 无平方分解 // 2. 对每个无平方分量: // a. 选素数 p(不整除首项系数,且 f mod p 无平方) // b. 在 𝔽_p 上因式分解(Cantor-Zassenhaus) // c. Hensel 提升到 ℤ // d. Zassenhaus 重组 // 入口函数:DenseUnivariatePolynomial::factor() }
Berlekamp 算法
适用场景
在小素数 $p$ 的有限域 $\mathbb{F}_p$ 上分解无平方多项式。当 $p$ 较小时($p \le 1000$),矩阵方法比 Cantor–Zassenhaus 的随机算法更高效。
理论基础
设 $f \in \mathbb{F}_p[x]$ 是无平方的 $n$ 次多项式。定义 Frobenius 矩阵 $Q \in \mathbb{F}_p^{n \times n}$:
$$Q_{ij} = [x^j] \text{ 的系数在 } x^{ip} \bmod f \text{ 中}$$
即 $Q$ 的第 $i$ 行是 $x^{ip} \bmod f$ 的系数向量。
关键定理:$v \in \mathbb{F}_p^n$ 满足 $Q^T v = v$(即 $v$ 是 $Q^T - I$ 的零空间向量)当且仅当对应的多项式 $v(x) = \sum v_i x^i$ 满足
$$v(x)^p \equiv v(x) \pmod{f}$$
这意味着 $v(x)$ 在 $\mathbb{F}_p$ 中取值,且
$$f \mid \prod_{a \in \mathbb{F}_p} (v(x) - a)$$
因此 $\gcd(f, v(x) - a)$ 对某个 $a \in \mathbb{F}_p$ 给出 $f$ 的非平凡因子。
算法步骤
输入:monic square-free f ∈ 𝔽_p[x],deg f = n
输出:f 的不可约因子列表
1. 构造 Frobenius 矩阵 Q
2. 计算 Q^T - I 的零空间基 {v₁, v₂, …, vᵣ}
3. factors ← [f]
4. for each vⱼ (r ≥ 1):
5. new_factors ← []
6. for each factor g in factors:
7. if deg(g) ≤ 1:
8. new_factors.append(g); continue
9. for a = 0, 1, …, p-1:
10. d ← gcd(g, vⱼ - a)
11. if 0 < deg(d) < deg(g):
12. new_factors.append(d)
13. g ← g / d
14. new_factors.append(g)
15. factors ← new_factors
16. return factors
零空间维数:若 $f$ 有 $r$ 个不可约因子,则零空间维数为 $r$。因此只需 $r - 1$ 个非平凡零空间向量即可完全分裂。
复杂度:$O(n^3)$(矩阵消元)+ $O(r \cdot n^2 \cdot p)$(GCD 分裂)。当 $p$ 很大时,$a$-循环的代价过高,应改用 Cantor–Zassenhaus。
在 oCAS 中,berlekamp() 实现在 ocas-poly/src/factor/finite_field.rs。当 $p \le 1000$ 时自动使用。
Cantor–Zassenhaus 算法
适用场景
大素数 $p$ 的有限域 $\mathbb{F}_p$ 上的无平方多项式分解。分为两个阶段:不同次分解(DDF)和等次分解(EDF)。
不同次分解(DDF)
目标:将 $f$ 分解为 $f = g_1 \cdot g_2 \cdots g_s$,其中 $g_d$ 的每个不可约因子的次数恰好为 $d$。
理论基础:$\mathbb{F}_{p^d}$ 的元素恰好是 $x^{p^d} - x = 0$ 的根。因此 $f$ 的次数为 $d$ 的不可约因子恰好整除
$$\gcd(f,, x^{p^d} - x)$$
但不整除之前的 $x^{p^{d'}} - x$($d' < d$)。
算法:
输入:monic square-free f ∈ 𝔽_p[x]
输出:[(g₁, 1), (g₂, 2), …] 其中 gₖ 的因子次数为 k
1. current ← f, h ← x, degree ← 1
2. while deg(current) ≥ 2·degree:
3. h ← h^p mod current // Frobenius 迭代:h = x^(p^degree)
4. g ← gcd(current, h - x)
5. if deg(g) > 0:
6. 输出 (monic(g), degree)
7. current ← current / g
8. h ← h mod current
9. degree ← degree + 1
10. if deg(current) > 0:
11. 输出 (monic(current), deg(current))
Frobenius 迭代的优化:不需要直接计算 $x^{p^d}$(指数巨大),而是逐步迭代 $h \leftarrow h^p \bmod f$,每次使用快速幂取模。
复杂度:$O(n^2 \log p)$(每步 Frobenius 迭代),总共 $O(n^3 \log p)$。
等次分解(EDF)
目标:将 $f = g_1 g_2 \cdots g_r$(每个 $g_i$ 不可约且次数为 $d$)完全分裂。
奇特征情形($p > 2$):
利用 $\mathbb{F}_{p^d}$ 中恰好一半元素是平方剩余的事实。对随机多项式 $a$,计算
$$b = a^{(p^d - 1)/2} \bmod f$$
则 $b$ 在每个不可约因子 $g_i$ 上的取值要么是 $1$($a$ 是该因子域中的平方剩余),要么是 $-1$(非剩余)。因此
$$\gcd(f,, b - 1) = \prod_{i:, a \text{ 是 } g_i \text{ 中的平方剩余}} g_i$$
给出 $f$ 的非平凡分裂(除非所有 $g_i$ 的取值一致,概率为 $2^{1-r}$)。
特征 2 情形($p = 2$):
$b - 1$ 的技巧不适用($1 = -1$)。改用迹映射(trace map):
$$T(a) = a + a^2 + a^{2^2} + \cdots + a^{2^{d-1}} \bmod f$$
$T(a)$ 在每个 $\mathbb{F}_{2^d}$ 上取值为 $\mathbb{F}_2$ 中的元素(迹的性质),且对随机 $a$ 的取值在不同因子上独立。因此 $\gcd(f, T(a))$ 给出非平凡分裂。
输入:f(DDF 输出的 d-次因子之积)
输出:f 的不可约因子列表
1. factors ← [f]
2. while 存在 deg > d 的因子:
3. for each factor g with deg(g) > d:
4. 选取随机多项式 a(deg < deg(g))
5. if p = 2:
6. b ← T(a) = Σᵢ₌₀^{d-1} a^{2^i} mod g
7. else:
8. b ← a^{(p^d-1)/2} mod g
9. d₁ ← gcd(g, b - 1) // 或 gcd(g, b) for char 2
10. if 0 < deg(d₁) < deg(g):
11. replace g by d₁ and g/d₁ in factors
12. return factors
在 oCAS 中,cantor_zassenhaus() 实现在 ocas-poly/src/factor/finite_field.rs,顶层入口 factor_over_finite_field() 根据 $p$ 的大小自动选择 Berlekamp($p \le 1000$)或 Cantor–Zassenhaus。
Wang EEZ 多元因式分解
问题
给定 $f \in \mathbb{Z}[x_1, \ldots, x_n]$(或 $\mathbb{F}_p[x_1, \ldots, x_n]$),分解为不可约因子。
策略概述
Wang(1978)的 EEZ(Evaluation, Exact division, Zassenhaus)算法将一元 Hensel 提升推广到多元:
- 求值:在辅助变量上选求值点,得到一元像 $f(x_1, \alpha_2, \ldots, \alpha_n)$
- 一元分解:在 $\mathbb{Z}$(或 $\mathbb{F}_p$)上分解一元像
- 逐变量 Hensel 提升:一个变量一个变量地提升回多元分解
- Zassenhaus 重组:组合提升后的因子
Wang 首项系数预处理
多元因式分解的难点之一是非常数首项系数。设 $f$ 的主变量 $x_0$ 的首项系数为 $\ell(x_1, \ldots, x_n)$。若 $\ell$ 不是常数,每个因子 $f_i$ 的首项系数 $\ell_i$ 也不一定是常数,且 $\prod \ell_i = \ell$。
Wang 的贪心分配:
- 将 $\ell$ 分解为不可约因子的幂之积:$\ell = \prod g_j^{e_j}$
- 在求值点 $\alpha = (\alpha_2, \ldots, \alpha_n)$ 处,$\ell$ 的像分解为一元因子的像
- 将 $\ell$ 的非平凡因子 $g_j$ 分配给对应的 $f_i$,使得 $g_j(\alpha) = \text{lc}(u_i)$($u_i$ 是一元像因子)
- 通过逐对互素条件($\alpha_j = |g_j(\alpha)| > 1$)确保分配的一致性
逐变量 Hensel 提升
设 $f$ 有 $n$ 个变量,一元像因子为 $u_1, \ldots, u_r$。
提升变量 $x_k$($k = 1, 2, \ldots, n-1$,按变量序号逐个提升):
- 对当前因子 $F_i^{(k-1)}$(在变量 $x_1, \ldots, x_{k-1}$ 中),求值 $x_k = \alpha_k$ 得到 $u_i$
- 计算 Bézout 系数 $b_i$ 使得 $\sum b_i \prod_{j \ne i} u_j = 1$
- 对 $t = 1, 2, \ldots$ 逐次求解多元 Diophantine 方程:
$$\sum_i \sigma_i \cdot \prod_{j \ne i} F_j = e_t$$
其中 $e_t$ 是当前误差 $f - \prod F_i$ 的 Taylor 展开第 $t$ 项
- 修正 $F_i \leftarrow F_i + \sigma_i \cdot (x_k - \alpha_k)^t$
多元 Diophantine 方程求解器
提升变量 $x_k$ 时需要求解的方程形式为:
$$\sum_{i=1}^{r} \sigma_i \cdot \prod_{j \ne i} u_j = e \pmod{(x_k - \alpha_k)^t}$$
其中 $\deg_{x_0}(\sigma_i) < \deg_{x_0}(u_i)$。这是一个线性方程组(关于 $\sigma_i$ 的系数),可以通过以下方式递归求解:
- 当 $k = 1$(单变量):扩展 Euclid 算法
- 当 $k > 1$:递归到 $k-1$ 个变量,在 $\alpha_k$ 处求值,求解更小的系统,再插值
p-adic 系数 Hensel 提升
对于 $\mathbb{Z}[x_1, \ldots, x_n]$ 上的非常数首项系数情形,Wang EEZ 之后还需要p-adic 系数提升:
- 将多元因子的系数模 $p$ 得到 $\mathbb{F}_p$ 上的骨架
- 迭代求解 mod-$p$ Diophantine 方程,逐步将系数从 $\bmod p$ 提升到 $\bmod p^k$
- 直到误差为零或 $p^k$ 超过系数界(Gelfond 界)
Gelfond 系数界:
$$B = \left(\sqrt{\prod_v (d_v + 1) \cdot 2^{2 \sum d_v - n}} + 1\right) \cdot |f|_\infty \cdot |\text{lc}(f)|$$
其中 $d_v$ 是变量 $v$ 的次数,$n$ 是变量数。
稀疏 Diophantine 求解
当 Diophantine 修正项具有稀疏结构时,可以避免密集递归求解。oCAS 实现了骨架插值(skeleton interpolation)策略:
- 从误差 $e$ 的项中提取骨架(skeleton)——修正项的可能指数模式
- 在多个随机基点上求值,得到单变量 Diophantine 方程
- 通过 Vandermonde 系统插值系数
- 验证插值结果
这在因子具有许多变量但每个变量的度很低时特别有效。
在 oCAS 中,通用 EEZ 算法实现在 ocas-poly/src/factor/eez.rs,二元特化版本在 ocas-poly/src/factor/multivariate.rs(目前要求主变量首项系数为常数):
#![allow(unused)] fn main() { // ℤ 上的 n 元因式分解入口(eez.rs): pub fn multivariate_factor_z(f: &ZmPoly) -> Vec<(ZmPoly, usize)> // 𝔽_p 上的 n 元因式分解入口(eez.rs): pub fn multivariate_factor_fp(f: &FpMPoly) -> Vec<(FpMPoly, usize)> // 二元特化入口(multivariate.rs): pub fn bivariate_factor_z(f: &ZMPoly, x_var: usize, y_var: usize) -> Vec<(ZMPoly, usize)> pub fn bivariate_factor_fp(f: &FpMPoly, x_var: usize, y_var: usize) -> Vec<(FpMPoly, usize)> }
Trager 代数数域因式分解
问题
给定 $f \in K[x]$,其中 $K = \mathbb{Q}(\alpha)$ 是代数数域($\alpha$ 是极小多项式 $m(\alpha) = 0$ 的根),分解 $f$ 为 $K[x]$ 中的不可约因子。
范数下降
Trager 算法的核心思想是通过范数将问题从 $K[x]$ 降到 $\mathbb{Q}[x]$:
$$N(f) = \text{Res}_\alpha(m(\alpha),, f(x, \alpha))$$
范数 $N(f) \in \mathbb{Q}[x]$ 的次数为 $\deg_x(f) \cdot [K:\mathbb{Q}]$,且若 $f$ 在 $K[x]$ 中可约,则 $N(f)$ 在 $\mathbb{Q}[x]$ 中可约。
求值—插值计算范数
直接构造 Sylvester 矩阵计算结式代价高昂。oCAS 使用求值—插值:
- 在 $\deg_x(f) \cdot [K:\mathbb{Q}] + 1$ 个有理点 $x_j$ 处计算标量结式 $\text{Res}_\alpha(m, f(x_j, \alpha))$
- 每个标量结式是 $\mathbb{Q}(\alpha)$ 中元素的范数,通过在 $[K:\mathbb{Q}]$ 个点处求值 $m$ 并取乘积得到
- 通过 Newton 差商插值恢复 $N(f) \in \mathbb{Q}[x]$
Trager 平移
若 $N(f)$ 有重因子,$f$ 的因子信息会丢失。Trager 平移通过替换 $x \mapsto x - s\alpha$($s \ge 0$)使范数无平方:
$$N(f(x - s\alpha)) \text{ 无平方}$$
这样的 $s$ 总是存在的(坏的 $s$ 只有有限多个),实践中 $s = 0$ 或很小的值即可。
分解步骤
输入:f ∈ K[x](无平方)
输出:f 在 K[x] 中的不可约因子
1. for s = 0, 1, 2, …:
2. g ← f(x - s·α)
3. N ← Res_α(m, g(x, α)) via 求值-插值
4. if N 无平方: break
5.
6. N₁, …, Nₖ ← factor_over_Q(N) // ℚ 上因式分解
7. for each Nᵢ:
8. N̂ᵢ ← embed_Q_to_K(Nᵢ)
9. hᵢ ← gcd_K(g, N̂ᵢ) // K 上的 GCD
10. output ← compose_linear(hᵢ, +s·α) // 逆平移
11. return [monic(output₁), …]
代数数域上的模 GCD
步骤 9 中的 $K[x]$ GCD 通过模方法高效计算(gcd_anf):
- 选素数 $p$ 使得 $m$ 在 $\mathbb{F}_p$ 上不可约(确保 $\mathbb{F}_p[\alpha]/(m) \cong \text{GF}(p^d)$)
- 将 $a, b \in K[x]$ 映射到 $\text{GF}(p^d)[x]$
- 计算 $\text{GF}(p^d)[x]$ 上的 monic GCD
- CRT 合并 + 有理重构 + 试除验证
不幸素数(模 GCD 次数偏大)被丢弃,最多尝试 1000 个素数后回退到密集 Euclid GCD。
在 oCAS 中,Trager 算法实现在 ocas-poly/src/factor/algebraic.rs:
#![allow(unused)] fn main() { // K = ℚ(α) 上的因式分解入口: impl DenseUnivariatePolynomial<AlgebraicNumberField> { pub fn factor(&self) -> Factors<AlgebraicNumberField> } // 示例:x² - 2 在 ℚ(√2) 上分解为 (x - √2)(x + √2) let field = AlgebraicNumberField::new( RationalDomain, vec![Rational::new(-2, 1), Rational::new(0, 1), Rational::new(1, 1)], ); let f = DenseUnivariatePolynomial::from_coeffs(field.clone(), vec![ field.from_base(Rational::new(-2, 1)), field.zero(), field.one(), ]); let factors = f.factor(); assert_eq!(factors.len(), 2); // (x - √2) 和 (x + √2) }
在 oCAS 中的实现
文件映射
| 源文件 | 算法 | 说明 |
|---|---|---|
ocas-poly/src/dense.rs | Euclidean GCD、Karatsuba 乘法 | 一元多项式的核心数据结构 |
ocas-poly/src/gcd/modular.rs | Brown 模 GCD | $\mathbb{Z}[x]$ 上的高效 GCD |
ocas-poly/src/factor/mod.rs | 顶层入口、Yun 无平方分解 | factor() 和 square_free_factorization() |
ocas-poly/src/factor/finite_field.rs | Berlekamp、Cantor–Zassenhaus(DDF + EDF) | $\mathbb{F}_p[x]$ 因式分解 |
ocas-poly/src/factor/hensel.rs | Hensel 提升、Zassenhaus 重组、Mignotte 界 | $\mathbb{Z}[x]$ 因式分解管线 |
ocas-poly/src/factor/multivariate.rs | 二元因式分解(Wang EEZ 特化;主变量首项系数需为常数) | $\mathbb{Z}[x,y]$ 和 $\mathbb{F}_p[x,y]$ |
ocas-poly/src/factor/eez.rs | 通用多元 EEZ Hensel 提升 | $\mathbb{Z}[x_1,\ldots,x_n]$ 和 $\mathbb{F}_p[x_1,\ldots,x_n]$ |
ocas-poly/src/factor/algebraic.rs | Trager 算法(代数数域因式分解) | $\mathbb{Q}(\alpha)[x]$ |
ocas-poly/src/multivariate_gcd.rs | 多元 GCD(求值—插值) | 辅助多元因式分解 |
算法选择策略
oCAS 根据输入自动选择最优算法:
factor(f ∈ ℤ[x]):
1. 无平方分解(Yun 算法)
2. 对每个无平方分量 g:
a. if deg(g) ≤ 1: 直接返回
b. 选素数 p(不整除 lc(g),g mod p 无平方)
c. 在 𝔽_p 上分解(Cantor–Zassenhaus;通用的 factor_over_finite_field 入口则在 p ≤ 1000 时自动改用 Berlekamp)
d. if mod-p 因子数 = 1: g 不可约,返回
e. Hensel 提升(Mignotte 界决定精度)
f. Zassenhaus 因子重组
g. 非首一情况:首项系数变换 a^{d-1}·f(x/a)
factor(f ∈ 𝔽_p[x]):
1. 提取首项系数
2. 无平方分解(Musser/Bernardin,处理特征 p)
3. 对每个无平方分量:Berlekamp(小 p)或 Cantor-Zassenhaus
factor(f ∈ ℚ(α)[x]):
1. Yun 无平方(使用模 GCD gcd_anf)
2. 对每个分量:Trager 范数 + ℚ 分解 + K-GCD 恢复
factor(f ∈ ℤ[x₁,…,xₙ]):
1. 提取内容(主变量 x₀)
2. 无平方分解
3. Wang 首项系数预处理
4. 选求值点,一元分解
5. EEZ 逐变量 Hensel 提升
6. p-adic 系数提升(非常数首项系数时)
7. Zassenhaus 重组
性能特征
| 算法 | 时间复杂度 | 空间复杂度 | 适用场景 |
|---|---|---|---|
| Euclidean GCD(PRS) | $O(n^2)$ | $O(n)$ | 低度($\lesssim 16$) |
| Brown 模 GCD | $O(n^2 \cdot k)$ | $O(n \cdot k)$ | 高度 $\mathbb{Z}[x]$,$k$ = 素数个数 |
| Hensel 提升 | $O(n^2 \cdot \log B)$ | $O(n \cdot r)$ | $r$ 因子,$B$ = Mignotte 界 |
| Zassenhaus 重组 | $O(2^r \cdot n)$ | $O(n \cdot r)$ | $r$ 个 mod-$p$ 因子 |
| Berlekamp | $O(n^3 + r \cdot n^2 p)$ | $O(n^2)$ | 小 $p$ |
| Cantor–Zassenhaus | $O(n^3 \log p)$ | $O(n)$ | 大 $p$ |
| Wang EEZ | 指数(变量数) | 多元多项式 | 多元分解 |
| Trager | $O(n^3 [K:\mathbb{Q}]^2)$ | $O(n \cdot [K:\mathbb{Q}])$ | 代数数域 |
参考文献
- [Gathen–Gerhard] J. von zur Gathen and J. Gerhard, Modern Computer Algebra, 3rd ed., Cambridge University Press, 2013. Chapters 14–15 (factoring), 18 (GCD).
- [Brown] W. S. Brown, "On Euclid's Algorithm and the Computation of Polynomial Greatest Common Divisors," J. ACM, 18(4):478–504, 1971.
- [Zassenhaus] H. Zassenhaus, "On Hensel Factorization I," J. Number Theory, 1(3):291–311, 1969.
- [Wang] P. S. Wang, "An Improved Multivariate Polynomial Factoring Algorithm," Math. Comp., 32(144):1215–1231, 1978.
- [Berlekamp] E. R. Berlekamp, "Factoring Polynomials over Large Finite Fields," Math. Comp., 24(111):713–735, 1970.
- [Cantor–Zassenhaus] D. G. Cantor and H. Zassenhaus, "A New Algorithm for Factoring Polynomials over Finite Fields," Math. Comp., 36(154):587–592, 1981.
- [Trager] B. M. Trager, "Algebraic Factoring and Rational Function Integration," Proc. SYMSAC '76, pp. 219–226, 1976.
- [Mignotte] M. Mignotte, Mathematics for Computer Algebra, Springer, 1992.
- [Geddes–Czapor–Labahn] K. O. Geddes, S. R. Czapor, and G. Labahn, Algorithms for Computer Algebra, Kluwer, 1992. Chapters 6 (multivariate factoring), 7 (GCD).
- [Knuth] D. E. Knuth, The Art of Computer Programming, Vol. 2, §4.6.2, Addison-Wesley, 1997.
进阶:Gröbner 基理论
Gröbner 基是多元多项式理想论的核心工具。本章从单项式理想和 S-多项式的 基本定义出发,系统讲解 Buchberger 算法、F4 矩阵方法和 F5 签名方法的理论 基础,并说明 oCAS 如何在 Rust 中实现这些算法。
前提知识
阅读本章前,建议具备以下基础:
- 多项式代数:多元多项式环 $\mathbb{F}[x_1, \ldots, x_n]$、单项式序(Lex / Grlex / Grevlex)、多元带余除法——参见多项式代数
- 线性代数:矩阵运算、高斯消元、行阶梯形——参见线性代数
- 有限域:$\mathbb{F}_p$ 的构造与基本运算——参见有限域与模算术
- 理想与商环:理想 $I \subseteq \mathbb{F}[x_1, \ldots, x_n]$、商环 $R/I$ 的基本概念
如需系统学习路径,请参考数学基础总览中的路径 A。
基础概念
单项式理想
定义。设 $R = \mathbb{F}[x_1, \ldots, x_n]$。由单项式生成的理想
$$M = \langle x^{\alpha^{(1)}}, x^{\alpha^{(2)}}, \ldots, x^{\alpha^{(s)}} \rangle$$
称为单项式理想(monomial ideal)。$f \in M$ 当且仅当 $f$ 的每个非零项的单项式都被某个 $x^{\alpha^{(i)}}$ 整除。
例。$M = \langle x^2, xy \rangle \subseteq \mathbb{F}[x, y]$。多项式 $f = x^2 y + x y^2$ 的各项:$x^2 y$ 被 $x^2$ 整除,$xy^2$ 被 $xy$ 整除,故 $f \in M$。而 $g = y^3$ 的单项式 $y^3$ 不被 $x^2$ 或 $xy$ 整除,故 $g \notin M$。
Dickson 引理
引理(Dickson)。$\mathbb{N}^n$ 的每个子集 $S$ 都有有限的极小元集合 $M \subseteq S$,使得对每个 $\alpha \in S$,存在 $\beta \in M$ 满足 $\beta \leq \alpha$(逐分量比较)。
等价表述:$\mathbb{F}[x_1, \ldots, x_n]$ 中的每个单项式理想都由有限个单项式生成。
证明思路(对 $n$ 归纳)。
- $n = 1$:$\mathbb{N}$ 的非空子集有最小元,取该最小元即得 $M$。
- $n \to n+1$:对 $S \subseteq \mathbb{N}^{n+1}$,对每个 $a \in \mathbb{N}$ 考虑截面 $S_a = {(\alpha_2, \ldots, \alpha_{n+1}) : (a, \alpha_2, \ldots, \alpha_{n+1}) \in S}$。由归纳假设,每个 $S_a$ 有有限极小元。进一步分析"被更小的第一分量支配"的情况,可构造 $S$ 的有限极小元集。
意义:Dickson 引理保证了单项式理想的有限生成性,这直接推出 Hilbert 基定理——每个多项式理想都是有限生成的。Gröbner 基理论正是在此基础上建立了"找到合适生成元"的系统方法。
S-多项式
定义。设 $f, g \in R$ 为非零多项式。令 $\gamma = \text{lcm}(\text{lm}(f), \text{lm}(g))$——指数向量逐分量取最大值。$f$ 和 $g$ 的 S-多项式(S-polynomial)为
$$S(f, g) = \frac{x^\gamma}{\text{lt}(f)} \cdot f - \frac{x^\gamma}{\text{lt}(g)} \cdot g = \frac{x^\gamma}{\text{lm}(f)} \cdot f - \frac{x^\gamma}{\text{lm}(g)} \cdot g$$
S-多项式的关键性质:它的首项被消去了。直觉上,$S(f,g)$ 度量了 $f$ 和 $g$ 在首项之外的"不一致性"。当 $f$ 和 $g$ 的首项互素(即 $\text{lcm} = \text{lm}(f) \cdot \text{lm}(g)$)时,$S(f,g)$ 通常可以直接化简为零——这是 Buchberger 的第一条优化判据。
例。在 $\mathbb{F}[x, y]$ 中,取 $f = x^2 - y$,$g = xy - 1$(Grevlex 序)。
$$\gamma = \text{lcm}(x^2, xy) = x^2 y$$
$$S(f, g) = \frac{x^2 y}{x^2} \cdot (x^2 - y) - \frac{x^2 y}{xy} \cdot (xy - 1) = y(x^2 - y) - x(xy - 1) = -y^2 + x$$
Gröbner 基的定义
定义。固定单项式序 $\succ$。理想 $I$ 的生成集 ${g_1, \ldots, g_t}$ 称为 $I$ 关于 $\succ$ 的 Gröbner 基(Gröbner basis),如果
$$\langle \text{lt}(g_1), \ldots, \text{lt}(g_t) \rangle = \langle \text{lt}(f) : f \in I \rangle$$
即生成元的首项生成的理想恰好等于 $I$ 中所有多项式首项生成的理想($I$ 的首项理想 $L(I)$)。
等价刻画(Buchberger 判据)。${g_1, \ldots, g_t}$ 是 $I$ 的 Gröbner 基当且仅当对每对 $i \neq j$,$S(g_i, g_j)$ 对 ${g_1, \ldots, g_t}$ 的余数为零。
Gröbner 基的核心价值:
- 成员判定:$f \in I$ 当且仅当 $f$ 对 Gröbner 基的余数为零(正规形唯一性)
- 理想相等判定:两个理想 $I = J$ 当且仅当它们的(约化)Gröbner 基相同
- 消元:Lex 序下的 Gröbner 基天然具有消元性质(消元理想的生成元出现在基中)
- 商环结构:$R/I$ 的 $k$-向量空间基由不被首项理想整除的单项式给出(阶梯)
约化 Gröbner 基。Gröbner 基 ${g_1, \ldots, g_t}$ 是约化的(reduced),如果:
- 每个 $g_i$ 是首一的($\text{lc}(g_i) = 1$)
- 对 $i \neq j$,$g_i$ 的任何非零项都不被 $\text{lm}(g_j)$ 整除
约化 Gröbner 基是唯一的(固定序和域下)。oCAS 的所有算法都输出约化 Gröbner 基。
核心理论
Buchberger 算法
Buchberger 算法(1965)是 Gröbner 基计算的经典方法。其思想朴素而直接: 反复构造 S-多项式、化简,直到所有 S-多项式化简为零。
输入: F = {f1, ..., fs}(理想生成元)
输出: G(Gröbner 基)
G ← F
P ← {(fi, fj) | i < j} // 初始临界对集合
while P ≠ ∅:
从 P 中取出一对 (f, g)
h ← NF(S(f, g), G) // S-多项式对 G 的余数
if h ≠ 0:
P ← P ∪ {(h, g') | g' ∈ G} // 新增临界对
G ← G ∪ {h}
return G
终止性:由 Buchberger 判据保证——每增加一个新元素,首项理想严格扩大,而 Hilbert 基定理保证链终止。实际实现中还需 minimize(移除冗余元素)和 auto_reduce(互相约化),得到约化基。
效率问题:朴素 Buchberger 算法的主要瓶颈在于——
- 临界对数量:$O(|G|^2)$ 个临界对,大部分是冗余的
- 逐个处理:每次只化简一个 S-多项式,无法利用批处理
- 反复扫描:寻找约化器需要线性扫描整个基
以下三节分别介绍解决这些问题的方法:Gebauer–Moeller 筛选(Buchberger 加速)、F4(批处理)、F5(避免零约化)。
Buchberger 判据与优化
Buchberger 的两个判据可以大幅减少需要处理的临界对数量。
第一判据(首项互素判据)。若 $\text{lm}(f_i)$ 和 $\text{lm}(f_j)$ 互素(即 $\gcd = 1$),则
$$S(f_i, f_j) \xrightarrow{G} 0$$
即该 S-多项式直接化简为零,可以从临界对集合中删除。
证明:设 $\text{lm}(f_i) = m_i$,$\text{lm}(f_j) = m_j$,$\gcd(m_i, m_j) = 1$。则 $\text{lcm}(m_i, m_j) = m_i m_j$,且 $S(f_i, f_j) = m_j f_i - m_i f_j$。对其逐项化简时,每个非零项 $c \cdot x^\alpha$ 满足 $x^\alpha$ 被 $m_i$ 或 $m_j$ 之一整除,因此可以被对应的 $f_i$ 或 $f_j$ 化简,最终余数为零。$\square$
第二判据(链判据 / Buchberger 判据的推广)。设 $f_k \in G$。若 $\text{lm}(f_k)$ 整除 $\text{lcm}(\text{lm}(f_i), \text{lm}(f_j))$,且 $\text{lcm}(\text{lm}(f_i), \text{lm}(f_k))$ 和 $\text{lcm}(\text{lm}(f_j), \text{lm}(f_k))$ 都严格整除 $\text{lcm}(\text{lm}(f_i), \text{lm}(f_j))$,则
$$S(f_i, f_j) \xrightarrow{G} 0$$
可以不处理临界对 $(f_i, f_j)$。
冗余对:更一般地,若临界对 $(f_i, f_j)$ 的 $\text{lcm}$ 被另一个已有临界对的 $\text{lcm}$ 整除,则 $(f_i, f_j)$ 是冗余的——化简另一个已隐含此对的处理。
Gebauer–Moeller 临界对管理
Gebauer 和 Moeller(1988)将 Buchberger 判据系统化,提供了高效的临界对管理框架。在新多项式 $f_{\text{new}}$ 加入基 $G = {f_1, \ldots, f_m}$ 时:
- 生成候选对:$P_{\text{new}} = {(f_{\text{new}}, f_i) \mid 1 \leq i \leq m}$
- 第一判据过滤:删除 $\text{lm}(f_{\text{new}})$ 与 $\text{lm}(f_i)$ 互素的对
- 第二判据过滤:删除 $\text{lcm}$ 被其他(仍存活)对的 $\text{lcm}$ 整除的对(整除关系含相等,重复对会被丢弃)
- 冗余对清理:将被新对的 $\text{lcm}$ 整除的旧对标记为冗余
- 合并:将筛选后的 $P_{\text{new}}$ 合并到主临界对集合
关键优化:按 $\text{lcm}$ 的大小(用单项式序比较)对临界对排序,优先处理 小的——这使得更大的对更可能被第二判据过滤。
F4 算法:矩阵行阶梯批处理
F4 算法(Faugère 1999)的核心思想是:将多个 S-多项式的化简批处理为一次稀疏矩阵的行阶梯运算。
符号预处理
F4 不直接对 S-多项式做除法,而是先进行符号预处理(symbolic preprocessing):对选定的临界对集合,找出所有需要的约化器(基元素的单项式倍),将它们的非零项展开并映射到列编号。
具体步骤:
- 选择临界对:从队列中选取一批临界对(按 $\text{lcm}$ 排序)
- 收集单项式:对每个选出的临界对,计算 S-多项式涉及的单项式;对每个需要约化的单项式,查找基中的约化器并将其乘以相应的单项式倍展开
- 建立列映射:将所有出现的单项式按序排列,编号为 $0, 1, \ldots, N-1$
- 构造矩阵:每行对应一个多项式(S-多项式或约化器的倍),每列对应一个单项式,矩阵元素为系数
这产生一个稀疏矩阵 $M$,其中每行的非零元素对应多项式的非零项。
行阶梯运算
对矩阵 $M$ 执行高斯消元(行阶梯化):
- 按行的首非零列排序
- 逐列消元:找到首非零元素为当前列的行作为主元,将其余行的该列消为零
- 消元后的行中,非零行对应的多项式即为新基元素
ℓ_p 原生快速路径。当系数域为有限域 $\mathbb{F}_p$($p < 2^{31}$)时,所有矩阵运算使用 i64 模算术——无 BigInt 开销。这包括:
- 稀疏行减法
sub_scaled_fp:归并两列升序行,跳过首列(已消去),逐项 $(row_j - c \cdot pivot_j) \bmod p$,丢弃零项 - 模逆查找
mod_inv:扩展 Euclid - 模归约
norm_mod:确保系数在 $[0, p)$
提取新基元素
消元后,矩阵的每个非零行 $r$ 对应一个多项式 $h_r$。对 $h_r$:
- 若 $h_r$ 的首项是"新"的(不在之前已收集的首项集合中),则加入基
- 用尾部约化(tail reduction)进一步简化:用已有基元素化简 $h_r$ 的尾项
F4 的复杂度
F4 的主要开销在于行阶梯运算——对 $m$ 行 $N$ 列的矩阵,高斯消元需要 $O(m \cdot N \cdot \text{nnz_per_row})$ 次域运算。实际中矩阵是稀疏的($\text{nnz_per_row} \ll N$),加上 Gebauer–Moeller 筛选减少了临界对数量,F4 比朴素 Buchberger 算法快一到两个数量级。
F5 算法:基于签名的 Gröbner 基
F5 算法(Faugère 2002)的革命性贡献在于:在矩阵构造之前就识别并拒绝会产生零约化的 S-多项式。这通过给每个多项式附加一个签名(signature)实现。
签名与模表示
将理想生成问题转化为模元素问题。设 $I = \langle f_1, \ldots, f_s \rangle$。考虑自由模 $R^s$ 的标准基 $e_1, \ldots, e_s$。每个多项式 $h = \sum q_i f_i$ 对应一个模元素
$$\vec{h} = (q_1, \ldots, q_s) = \sum q_i e_i$$
$h$ 的签名定义为模元素中"最高"的单项式倍:
$$\text{sig}(h) = \max_\succ {x^{\alpha_i} e_i : q_i \neq 0}$$
其中比较使用 pot(position-over-term)序:先比较模块位置($e_i$ 的索引 $i$,索引小者签名更小、更先被处理),再比较单项式。即 $x^\alpha e_i \succ x^\beta e_j$ 当且仅当 $i > j$,或 $i = j$ 且 $\alpha \succ \beta$。
直觉:签名记录了多项式 $h$ 的"来源"——它是由哪个生成元($f_i$)的哪个倍($x^\alpha$)开始推导的。多项式的签名是其推导历史的最简表示。
F5 准则
签名判据(Signature Criterion)。若一个候选 S-多项式的签名是某个已知 syzygy 的单项式倍,则该 S-多项式必化简为零——可直接拒绝,无需进入矩阵。
syzygy 的含义:当矩阵中某行化简为零时,该行对应多项式 $h = 0$,但其模表示 $(q_1, \ldots, q_s) \neq 0$。这给出一个 syzygy(关系)$\sum q_i f_i = 0$。该行的签名 $\text{sig}(h)$ 记录了这个 syzygy。
SyzygySet 数据结构:对每个模块位置 $k$,维护已知 syzygy 的首单项式集合。判断签名 $(k, t)$ 是否为 syzygy 时,检查是否有已存储的 $k$-位置 syzygy 的首单项式整除 $t$。
正则序列与零约化的完全避免
定理(Faugère 2002)。若输入生成元 $f_1, \ldots, f_s$ 构成一个正则序列(regular sequence),则 F5 的签名判据足以拒绝全部零约化——即矩阵中不会出现化简为零的行。
正则序列的定义:$f_1, \ldots, f_s$ 是正则序列,如果 $f_1$ 不是零因子,且对 $i > 1$,$f_i$ 在 $R/\langle f_1, \ldots, f_{i-1} \rangle$ 中不是零因子。
实践中,即使生成元不是正则序列,F5 的签名判据也能过滤掉大量零约化。文献中在困难的 benchmark 理想上,F5 相比 F4 可获得数量级的加速(oCAS 的 F5 实现仍在持续优化中)。
F5 的矩阵构造
F5 的矩阵构造与 F4 类似,但每行额外携带签名信息:
- 增量处理:逐个添加生成元 $f_1, f_2, \ldots, f_s$
- 带标签的 S-多项式:每个 S-多项式都带着签名进入矩阵
- 符号预处理:与 F4 相同,但约化器的签名也需传播——当用基元素 $g$ 的倍 $x^\delta g$ 约化时,新行的签名为 $x^\delta \cdot \text{sig}(g)$
- syzygy 更新:化简为零的行的签名加入 SyzygySet
标签行(LabeledRow):矩阵中每行存储为 (Signature, Vec<(coefficient, column)>)——签名与稀疏行数据的配对。行阶梯运算时,签名不变(因为行运算是行之间的线性组合,签名取诸参与行中的最大者)。
F5 的复杂度优势
理论上,F5 的签名判据保证了:
- 对正则序列:矩阵中无零行,矩阵大小最小化
- 对一般理想:大幅减少零行,节省行阶梯运算开销
实践中的主要瓶颈是签名比较和 SyzygySet 查询。oCAS 的实现通过 SmallVec 和 HashMap 优化了这些操作。
单项式序的等价与换序
不同的计算任务需要不同的单项式序:
| 任务 | 需要的序 |
|---|---|
| 消元 | Lex |
| 求解多项式系统 | Lex(结合 Sturm 根隔离) |
| 理想成员判定 | 任意序均可 |
| 高效计算 | Grevlex(中间多项式最小) |
标准策略是"先快后换":
- 用 Grevlex 计算基(最快——中间多项式次数最低)
- 转换为 Lex(消元、求解需要)
两种转换方法:
方法一:重新计算(reorder)。将 Grevlex 基的多项式重新解释为 Lex 序,然后在 Lex 序下重跑 F4。对一般理想,这是默认方法——因为基的元素在新序下可能需要额外约化。
方法二:FGLM(零维理想专用)。FGLM 算法(下一小节详述)对零维理想提供 $O(n \cdot D^3)$ 的换序,远快于重新计算。
FGLM 算法
FGLM(Faugère–Gianni–Lazard–Mora 1993)对零维理想提供高效的换序算法。
前提条件:理想 $I$ 是零维的,即 $R/I$ 作为 $k$-向量空间是有限维的。
阶梯与正规形
阶梯(staircase):不被任何首项单项式 $\text{lm}(g_i)$ 整除的单项式集合。对零维理想,阶梯是有限集,大小 $D = \dim_k(R/I)$。
阶梯中的单项式构成 $R/I$ 的一组 $k$-向量空间基。任何多项式 $f$ 的正规形(normal form)$f \bmod I$ 可以唯一表示为阶梯单项式的 $k$-线性组合。
算法思路
FGLM 按目标序递增遍历单项式,计算其正规形,并检测线性关系:
输入: GB_src(源序下的约化 Gröbner 基)
输出: GB_tgt(目标序下的约化 Gröbner 基)
S ← {1} // 已处理的单项式(阶梯中)
B ← {x1, x2, ..., xn} // 边界:已处理单项式的"邻居"
GB_tgt ← ∅
seen_nf ← {} // 已见正规形
for m in 目标序递增:
nf ← normal_form(m, GB_src) // 在源序基下求正规形
if nf ∈ span(seen_nf): // 线性相关?
coeffs ← find_relation(seen_nf, nf)
new_poly ← m - Σ coeffs_i · m_i
GB_tgt ← GB_tgt ∪ {new_poly}
else:
S ← S ∪ {m}
seen_nf ← seen_nf ∪ {nf}
B ← B ∪ {m · xi : i = 1..n} // 扩展边界
if |S| = D: // 阶梯满了
break
return minimize(GB_tgt)
关键性质:
- 每个线性相关产生目标序基中的一个多项式
- 每个线性无关扩展阶梯,直到阶梯大小达到 $D$
- 总操作数 $O(n \cdot D^3)$——$D$ 次正规形计算(每次 $O(D^2)$),加上 $D$ 次高斯消元(每次 $O(D^2)$)
非零维情况:当阶梯无限时,FGLM 无法终止——fglm 返回 None,此时必须使用 reorder。
Hilbert 函数与正则性
Hilbert 函数是描述分次环结构的强大工具,在 Gröbner 基计算中提供提前终止界。
Hilbert 函数与 Hilbert 级数
定义。设 $I \subseteq R = \mathbb{F}[x_1, \ldots, x_n]$ 是齐次理想。Hilbert 函数
$$H_{R/I}(d) = \dim_\mathbb{F} (R/I)_d$$
度量商环在次数 $d$ 处的齐次分量的向量空间维数。
Hilbert 级数是 Hilbert 函数的生成函数:
$$\text{HS}{R/I}(t) = \sum{d=0}^{\infty} H_{R/I}(d) \cdot t^d$$
定理(Macaulay)。$R/I$ 的 Hilbert 级数只依赖于首项理想 $\langle \text{lm}(g) : g \in \text{GB}(I) \rangle$,即
$$\text{HS}{R/I}(t) = \text{HS}{R/\langle\text{LM}(I)\rangle}(t)$$
这意味着我们可以仅从 Gröbner 基的首项计算 Hilbert 级数,无需知道理想的其他信息。
单项式理想的 Hilbert 级数
对由单项式 $m_1, \ldots, m_s$ 生成的理想 $M$,容斥原理给出:
$$\text{HS}_{R/M}(t) = \frac{N(t)}{(1-t)^n}$$
其中分子 $N(t)$ 为
$$N(t) = \sum_{k=0}^{s} (-1)^k \sum_{\substack{S \subseteq {1,\ldots,s} \ |S| = k}} t^{\deg \text{lcm}(S)}$$
这里 $\text{lcm}(\emptyset) = 1$(次数 0),$\text{lcm}({m_i}) = m_i$。
展开:
$$N(t) = 1 - \sum_{i} t^{\deg m_i} + \sum_{i < j} t^{\deg \text{lcm}(m_i, m_j)} - \sum_{i < j < k} t^{\deg \text{lcm}(m_i, m_j, m_k)} + \cdots$$
示例。$M = \langle x^2, xy \rangle \subseteq \mathbb{F}[x, y]$($n = 2$)。
- $|S| = 0$: $t^0 = 1$
- $|S| = 1$: $-t^2 - t^2 = -2t^2$($\deg(x^2) = \deg(xy) = 2$)
- $|S| = 2$: $+t^{\deg \text{lcm}(x^2, xy)} = +t^{\deg(x^2 y)} = +t^3$
$$N(t) = 1 - 2t^2 + t^3$$
$$\text{HS}_{R/M}(t) = \frac{1 - 2t^2 + t^3}{(1-t)^2}$$
正则性与提前终止
正则性(regularity)定义为 Hilbert 分子 $N(t)$ 的最高次非零项的次数:
$$\text{reg}(M) = \max{d : [t^d] N(t) \neq 0}$$
定理(Bayer–Stillman)。对零维理想,Gröbner 基中所有多项式的次数不超过 $\text{reg}(M)$。
这意味着 F4 可以安全地忽略次数超过正则性界的临界对——它们必然化简为零。oCAS 的 hilbert 模块通过 regularity_bound 计算此界,作为 F4 的提前终止提示(advisory bound,不影响正确性)。
完整 Hilbert 级数
对一般(非齐次)理想的 Gröbner 基,Hilbert 级数也可计算:
$$\text{HS}_{R/I}(t) = \frac{N(t)}{(1-t)^n}$$
其中 $N(t)$ 由首项理想的容斥公式给出。oCAS 提供 HilbertSeries 结构体,支持:
hilbert_function(d):$\dim_k (R/I)_d$(通过展开 $\frac{N(t)}{(1-t)^n}$ 的 $t^d$ 系数)dimension():$N(1) \neq 0$ 时维数为 $n - \text{ord}_{t=1}(N(t))$,即 $(1-t)$ 的消去次数degree():射影簇的次数 $= h(1)$,其中 $h(t) = N(t)/(1-t)^{n-\dim}$ 是约去 $(1-t)^{n-\dim}$ 后的分子;对零维理想($\dim = 0$)这等于 $\dim_k(R/I)$(点的个数)hilbert_polynomial():对充分大的 $d$,$H_{R/I}(d)$ 是 $d$ 的多项式,通过 Lagrange 插值计算
在 oCAS 中的实现
oCAS 在 ocas-poly/src/groebner/ 中实现三种 Gröbner 基算法和 Hilbert 级数计算。统一入口 groebner_basis(ideal, Algorithm::Auto) 目前默认路由到 F4。
f4.rs:F4 矩阵行阶梯算法
Gebauer–Moeller 临界对管理
update_pairs 函数在每次新多项式加入基时执行 Gebauer–Moeller 筛选:
- 生成候选:新多项式与基中每个现有元素配对
- 第一判据(
monomial_are_coprime):若首项互素,直接跳过(乘积判据) - 第二判据(链判据,顺序消去):若另一个(仍存活)候选对的 $\text{lcm}$ 整除当前对的 $\text{lcm}$(整除关系含相等,重复对被丢弃),当前对冗余、不保留
- 更新判据(针对旧对):仅当新首项整除旧对的 $\text{lcm}$,且 $\text{lcm}(i, \text{new})$、$\text{lcm}(j, \text{new})$ 都严格小于 $\text{lcm}(i,j)$ 时,旧对才被移除;等号情形必须保留(否则会丢掉完整性所需的 S-多项式)
CriticalPair 结构体预计算了 $\text{lcm}$ 和单项式差(S-多项式的构造信息),避免在后续步骤中重复计算。
DivisorIndex:O(1)-ish 约化器查找
符号预处理的核心操作是"查找基中首项整除给定单项式的元素"。朴素实现需要 $O(\text{monomials} \times |\text{basis}|)$ 的线性扫描。oCAS 使用支持位掩码(support bitmask)索引加速此操作。
support_mask:对指数向量 $\alpha = (\alpha_1, \ldots, \alpha_n)$,定义位掩码
$$\text{mask}(\alpha) = \sum_{v : \alpha_v > 0} 2^v$$
即第 $v$ 位被设置当且仅当变量 $x_v$ 出现在单项式中(变量索引不超过 63)。
关键观察:若 $\text{lm}(g)$ 整除 $x^\alpha$,则 $\text{support}(\text{lm}(g)) \subseteq \text{support}(x^\alpha)$。因此查询时只需枚举 $\text{mask}(x^\alpha)$ 的子掩码,检查对应桶中的基元素。
DivisorIndex 结构:
#![allow(unused)] fn main() { struct DivisorIndex { // support_mask → 拥有该 mask 的基元素索引列表 buckets: HashMap<u64, Vec<usize>>, } }
查询 find_reducer(index, basis, exp):
- 计算 $m = \text{mask}(\text{exp})$
- 枚举 $m$ 的所有子掩码 $s \subseteq m$
- 对
buckets[s]中的每个基元素 $g$,精确检查 $\text{lm}(g) \mid x^\text{exp}$ - 在所有满足条件的约化器中,选择项数最少的(加速约化)
子掩码枚举的复杂度 $O(3^v)$($v$ 为变量数),但实际中 $v$ 通常很小($\leq 10$),且桶内元素极少。
SimpCache:约化器倍的缓存
符号预处理中,同一基元素可能被多个单项式乘以不同的差来约化。SimpCache<P> 缓存已计算的多项式倍:
#![allow(unused)] fn main() { type SimpCache<P> = Vec<(SmallVec<[usize; 4]>, P)>; }
get_simplified(cache, diff, basis_poly) 先查缓存,命中则直接返回;否则计算 $x^{\text{diff}} \cdot \text{basis_poly}$ 并存入缓存。缓存跨轮次持久化。
ℤ_p 原生快速路径
当系数域为 FiniteField(p)($p < 2^{31}$)时,F4 使用 FpPoly 替代泛型多项式:
#![allow(unused)] fn main() { struct FpPoly { terms: Vec<(i64, SmallVec<[usize; 4]>)>, // (coeff, exponent) // coeff ∈ [0, p),按降序排列 } }
所有矩阵运算在 i64 上进行——无 BigInt 开销。sub_scaled_fp 通过双指针归并实现 O(nnz) 的稀疏行减法:
#![allow(unused)] fn main() { fn sub_scaled_fp( row: &mut Vec<(i64, usize)>, // 列升序 pivot: &[(i64, usize)], // 列升序 c: i64, // 乘数 p: i64, // 模数 scratch: &mut Vec<(i64, usize)>, // 临时缓冲 ) { // 两个指针归并:row -= c * pivot // 首列(主元列)自动消去(c = row[head_col] / pivot[head_col]) // 所有结果取 mod p,零系数丢弃 } }
register_row_fp 进一步缓存 S-多项式和约化器倍的 FpPoly 表示,跨轮次复用。
矩阵消元流程
完整 F4 流程:
- 选择临界对:从队列按 $\text{lcm}$ 大小排序选取
- 符号预处理:展开每个对的 S-多项式和所有约化器,建立列映射
- 行阶梯化:
echelonize_fp(ℤ_p 快速路径)或echelonize_generic(泛型) - 提取新元素:非零行转回多项式,检查首项是否为"新的"
- 更新基和对:新元素加入基,
update_pairs更新临界对队列
f5.rs:F5 签名算法
签名结构
#![allow(unused)] fn main() { struct Signature { module_pos: usize, // 生成元索引 k monomial: SmallVec<[usize; 4]>, // 单项式指数 } }
签名按 pot(position-over-term)序比较:先比较 module_pos(小者优先),再按单项式序 O 比较 monomial。
LabeledPoly:带签名的基元素
#![allow(unused)] fn main() { struct LabeledPoly<D: Domain, O: MonomialOrder> { poly: SparseMultivariatePolynomial<D, O>, sig: Signature, } }
每个基元素携带签名。当用 $g$ 的倍 $x^\delta g$ 约化时,新多项式的签名为 $x^\delta \cdot \text{sig}(g)$。
SyzygySet:零约化追踪
#![allow(unused)] fn main() { struct SyzygySet { // module_pos → 该位置已知 syzygy 的首单项式列表 lms: HashMap<usize, Vec<SmallVec<[usize; 4]>>>, } }
insert(sig):当矩阵行化简为零时,其签名 $(k, t)$ 被记录——在 lms[k] 中存入 $t$。
contains(sig):检查签名 $(k, t)$——若 lms[k] 中存在 $s$ 使 $s \mid t$,则为 syzygy。使用 monomial_divides(t, s) 做逐分量比较($t_i \geq s_i$ 对所有 $i$)。
矩阵构造与消元
F5 的 build_and_reduce 流程:
- 选择对:与 F4 类似的临界对选择(使用
update_pairs共享实现) - syzygy 过滤:在将对加入矩阵前,检查其签名——若为 syzygy 则跳过
- 带标签的行:每行携带
(Signature, Vec<(coefficient, column)>) - 符号预处理:与 F4 相同的列映射构建,但约化器的签名需传播
- 行阶梯化:按签名升序排列行,执行高斯消元
- 新元素提取:非零行提取为
LabeledPoly,签名保留 - syzygy 更新:零行的签名加入
SyzygySet
ℤ_p 快速路径
F5 复用 F4 的 FpPoly、CriticalPair、update_pairs 等基础设施,通过 LabeledFpPoly 和 LabeledFpRow 添加签名标签。f5_fp 的结构与泛型 f5 完全对称——仅多项式表示从泛型域切换为 i64 模算术。
hilbert.rs:Hilbert 级数计算
容斥原理实现
hilbert_numerator(generators) 计算单项式理想 $\langle m_1, \ldots, m_s \rangle$ 的 Hilbert 分子:
#![allow(unused)] fn main() { pub fn hilbert_numerator(generators: &[Vec<usize>]) -> Vec<(usize, i64)> { // 对所有子集 S ⊆ {0, ..., s-1} 遍历 // |S| = k 贡献 (-1)^k · t^{deg lcm(S)} // 返回 (degree, coefficient) 对的稀疏表示 } }
枚举所有 $2^s$ 个子集(实用上限约 $s \leq 20$),对每个子集计算 $\text{lcm}$ 的次数(逐分量取最大值后求和),按符号累加到对应次数。
正则性界
regularity_bound(generators) 返回 Hilbert 分子的最高次非零项次数:
$$\text{reg} = \max{d : [t^d] N(t) \neq 0}$$
F4 将此界用作提前终止提示——选择临界对时,若 $\deg(\text{lcm}) > \text{reg}$,可安全跳过。这不影响正确性(只影响效率),且仅对零维理想有效。
完整 Hilbert 级数
hilbert_series(gb) 从 Gröbner 基计算 $R/I$ 的完整 Hilbert 级数 $\frac{N(t)}{(1-t)^n}$:
- 提取首项:从基中取出每个多项式的首项指数向量
- 容斥计算:调用
hilbert_numerator得到 $N(t)$ - 展开级数:$\frac{N(t)}{(1-t)^n} = N(t) \cdot \sum_{k \geq 0} \binom{n+k-1}{k} t^k$
HilbertSeries 结构体的方法:
| 方法 | 计算方式 |
|---|---|
hilbert_function(d) | 展开 $\frac{N(t)}{(1-t)^n}$ 的 $t^d$ 系数 |
dimension() | $N(1) \neq 0$ 时为 $n$,否则递归检查 $(1-t)$ 的消去 |
degree() | 对 $N(t)$ 求 $(n-\dim)$ 阶导数并在 $t=1$ 求值,再除以 $(n-\dim)!$;零维($\dim=0$)时等于 $\dim_k(R/I) = \text{HS}(1)$(点的个数) |
hilbert_polynomial() | Lagrange 插值:在足够多的 $d$ 处计算 $H(d)$,拟合多项式 |
binomial_general(n, k) 计算广义二项式系数 $\binom{n+k-1}{k}$($n$ 可为负数或零),用于展开 $(1-t)^{-n}$。
fglm.rs:零维换序算法
阶梯计算
compute_staircase(lms, n_vars) 枚举不被任何首项单项式整除的单项式:
- 从 $1 = x_1^0 \cdots x_n^0$ 开始 BFS
- 对每个单项式 $m$,检查是否被某个 $\text{lm}(g_i)$ 整除
- 不被整除的加入阶梯,其所有"邻居"($m \cdot x_i$)加入队列
- 若阶梯大小超过阈值仍未停止,返回
None(正维理想)
正规形计算
normal_form_monomial(m, gb, staircase, domain) 在源序基下计算单项式 $m$ 的正规形,结果表示为阶梯坐标的 $k$-向量:
$$m \bmod I = \sum_{i=1}^{D} c_i \cdot s_i$$
其中 $s_1, \ldots, s_D$ 是阶梯单项式。计算方式是标准的多元除法算法。
线性相关检测
find_relation(seen, nf, domain) 检测新正规形 nf 是否在已见正规形的张成空间中。使用增量高斯消元:维护一个阶梯形矩阵,每次新向量尝试化简——化简为零则找到关系。
边界管理
mark_multiples(visited, m, n_vars, max_deg) 标记单项式 $m$ 的所有倍(至全次数 max_deg)为已访问,避免重复插入边界。使用递归枚举。
统一入口与算法选择
#![allow(unused)] fn main() { pub fn groebner_basis<D: Domain + 'static, O: MonomialOrder>( ideal: &[SparseMultivariatePolynomial<D, O>], algo: Algorithm, ) -> GroebnerBasis<D, O> }
Algorithm 枚举:
| 变体 | 行为 |
|---|---|
Auto | 当前路由到 F4;未来将基于 cyclic-n 基准校准 F5 交叉点 |
Buchberger | 经典 S-多项式迭代(教学用) |
F4 | 矩阵行阶梯批处理 |
F5 | 签名判据 + 矩阵构造 |
所有路径最终输出约化 Gröbner 基:先 minimize(移除被其他元素首项整除的冗余元素),再 auto_reduce(互相约化尾项)。
进阶话题
F4 vs Buchberger 的实际性能
oCAS 的基准(ocas-tests/benches/groebner.rs)在 cyclic-n 理想上比较 Buchberger 与 F4:
| 基准 | Buchberger | F4 | 说明 |
|---|---|---|---|
| cyclic-3($\mathbb{Q}$) | 可行 | 快 | Buchberger 在此规模已很慢 |
| cyclic-4($\mathbb{Q}$) | 过慢 | 可行 | 只有 F4 可行 |
| cyclic-3/4/5($\mathbb{Z}{13}$、$\mathbb{Z}{101}$) | — | 快 | i64 快速路径可延伸到 cyclic-5 |
F5 的签名判据在理论上能避免零约化,对困难理想有数量级加速的潜力,但 oCAS 的 F5 实现较新,尚无正式基准数据。
实践建议:对一般理想使用 Algorithm::Auto(当前 = F4);对已知为正则序列的系统,显式指定 Algorithm::F5 可能更快。
F5 的理论保证
定理。F5 算法在以下意义下是"最优"的:
- 无冗余约化:对正则序列,矩阵中无化简为零的行
- 完备性:产生与 F4 完全相同的约化 Gröbner 基
- 签名唯一性:每个多项式的签名唯一确定其来源
这些保证使 F5 成为处理困难理想的理论首选,尽管实际中 F4 的简洁实现常在中小规模问题上更快。
Hilbert 级数的应用
Hilbert 级数不仅是理论工具,在 oCAS 中有以下实际应用:
- 提前终止:正则性界 $\text{reg}(M)$ 为 F4 提供可靠的次数上界
- 维数判定:$R/I$ 的 Krull 维数 = $\text{ord}_{t=1} \text{HS}(t)$ 的阶数
- 次数计算:射影簇的次数 = $N(1)$(零维时为阶梯大小)
- 零维判定:$N(1) \neq 0$ 当且仅当理想为零维
计算代数几何中的 Gröbner 基
Gröbner 基是计算代数几何的通用工具。在 oCAS 中,它们支撑着以下应用(详见 FGLM 与消元理论 和 Gröbner 基实现):
- 多项式方程组求解:Gröbner 基 → 消元 → 一元方程 → 根隔离
- 理想运算:成员判定、和、积、商、饱和、交集、消元、根式、准素分解
- 代数簇的几何性质:维数、次数、不可约分量
参考文献
-
Cox, D., Little, J. & O'Shea, D. Ideals, Varieties, and Algorithms. 4th ed., Springer, 2015.
- 第 2 章:Gröbner 基的定义、Buchberger 算法、Buchberger 判据
- 第 3 章:消元定理与理想运算
- 第 8 章:FGLM 与换序
-
Adams, W.W. & Loustaunau, P. An Introduction to Gröbner Bases. AMS, 1994.
- 第 4–5 章:Buchberger 算法的详细分析、Gebauer–Moeller 优化
-
Faugère, J.-C. "A New Efficient Algorithm for Computing Gröbner Bases (F4)." Journal of Pure and Applied Algebra, 139(1–3):61–88, 1999.
- F4 算法的原始论文:矩阵行阶梯批处理
-
Faugère, J.-C. "A New Efficient Algorithm for Computing Gröbner Bases without Reduction to Zero (F5)." ISSAC 2002, pp. 75–83.
- F5 算法的原始论文:签名判据与正则序列
-
Faugère, J.-C., Gianni, P., Lazard, D. & Mora, T. "Efficient Computation of Zero-dimensional Gröbner Bases by Change of Ordering." Journal of Symbolic Computation, 16(4):329–344, 1993.
- FGLM 算法的原始论文
-
Gebauer, R. & Möller, H.M. "On an Installation of Buchberger's Algorithm." Journal of Symbolic Computation, 6(2–3):275–286, 1988.
- Gebauer–Moeller 临界对筛选准则
-
Eder, C. & Perry, J. "Signature-based Algorithms to Compute Gröbner Bases." ISSAC 2009, pp. 139–146.
- Eder–Perry 的 F5 形式化
-
Bayer, D. & Stillman, M. "A Criterion for Detecting m-Regularity." Inventiones Mathematicae, 87(1):1–11, 1987.
- 正则性界与 Hilbert 函数的关系
-
Gathen, J. von zur & Gerhard, J. Modern Computer Algebra. 3rd ed., Cambridge University Press, 2013.
- 第 21 章:Gröbner 基的全面综述
参见:
- 多项式代数 — 单项式序、多元除法算法的基础
- 线性代数 — Bareiss 算法与高斯消元
- FGLM 与消元理论 — FGLM 详细分析与理想运算
- Gröbner 基实现 — 使用指南、基准与理想运算 API
- Rust API:Gröbner 基与理想 — 函数签名与完整示例
进阶:数论算法
本章系统讲解 oCAS 中实现的核心数论算法:素性判定、整数分解、离散对数、中国剩余定理、二次剩余以及有理重构。每个算法从数学原理出发,逐步深入到 oCAS 的具体实现细节。
前提知识
阅读本章前,读者应熟悉以下概念:
- 素数与整除:素数定义、最大公因数、Euclid 算法、扩展 Euclid 算法
- 模算术:同余、模逆(扩展 Euclid 求 $a^{-1} \bmod m$)、快速模幂(binary exponentiation)
- Euler φ 函数:$\varphi(n) = n \prod_{p \mid n}(1 - 1/p)$,计数 $[1, n]$ 中与 $n$ 互素的整数个数
- 积性函数:$f(mn) = f(m)f(n)$ 当 $\gcd(m, n) = 1$;完全积性则对所有 $m, n$ 成立
- Fermat 小定理:$p$ 素数且 $\gcd(a, p) = 1$ 时 $a^{p-1} \equiv 1 \pmod{p}$
- 群论基础:循环群、元素的阶、生成元(原根)
推荐阅读:Shoup《A Computational Introduction to Number Theory and Algebra》Ch.1–9。
基础概念
素性判定问题
给定正整数 $n$,判断 $n$ 是否为素数。这是一个判定问题,但确定性算法(如 AKS)的实际速度远不如概率性算法。现代实践采用概率素数测试(probable prime test):合数通过测试的概率可压到极低,但无法绝对排除。
整数分解问题
给定合数 $n$,找到非平凡因子 $d$($1 < d < n$)。重复递归直到所有因子均为素数,得到 $n = p_1^{e_1} \cdots p_k^{e_k}$。分解的难度是 RSA 等公钥密码安全性的基础假设。
离散对数问题
在群 $\mathbb{Z}_p^*$ 中,给定 $g$ 和 $h$,求 $x$ 使得 $g^x \equiv h \pmod{p}$。当 $p - 1$ 光滑(只有小素因子)时,Pohlig–Hellman 算法可高效求解。
二次剩余
对奇素数 $p$ 和整数 $a$,若存在 $x$ 满足 $x^2 \equiv a \pmod{p}$,则称 $a$ 是模 $p$ 的二次剩余(quadratic residue)。Legendre 符号 $\left(\frac{a}{p}\right) = 1$ 表示是,$-1$ 表示非,$0$ 表示 $p \mid a$。
积性数论函数
| 函数 | 定义 | 积性 |
|---|---|---|
| $\varphi(n)$ | $#{k \in [1,n] : \gcd(k,n)=1}$ | 是 |
| $\mu(n)$ | $1$($n=1$),$(-1)^k$($n$ 恰好 $k$ 个不同素因子),$0$($n$ 有平方因子) | 是 |
| $\tau(n)$ | 正因子个数 $= \prod(e_i + 1)$ | 是 |
| $\sigma_k(n)$ | $\sum_{d \mid n} d^k$ | 是 |
| $\lambda(n)$ | Liouville 函数 $= (-1)^{\Omega(n)}$ | 是(完全积性) |
核心理论
BPSW 素性判定
BPSW 测试(Baillie–Pomerance–Selfridge–Wagstaff)将两个独立的概率素数测试串联:一个 base-2 强 Miller–Rabin 测试 + 一个强 Lucas 概率素数测试。自 1980 年提出以来,没有任何已知合数能通过 BPSW 测试($n < 2^{64}$ 范围内已被穷举验证)。
Miller–Rabin 测试
原理:设 $n - 1 = d \cdot 2^r$($d$ 为奇数)。对底数 $a$,计算 $a^d \bmod n$。若结果为 $1$ 或在序列 $a^d, a^{2d}, a^{4d}, \ldots, a^{d \cdot 2^{r-1}} \pmod{n}$ 中出现 $-1$,则 $n$ 通过该轮测试。
判定:若 $n$ 是素数,则对任意 $1 < a < n$ 都通过。若 $n$ 是合数,则至少 $3/4$ 的底数是证人(witness),即能揭穿 $n$。测试 $k$ 个随机底数后,合数通过全部的概率 $\leq 4^{-k}$。
确定性:对 $n < 3.317 \times 10^{24}$,使用固定底数集 ${2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37}$ 的 Miller–Rabin 测试是确定性的。oCAS 使用这 12 个底数作为 MR_WITNESSES 常量。
强 Lucas 概率素数测试
Lucas 序列:对参数 $(P, Q)$,定义
$$U_0 = 0,\quad U_1 = 1,\quad U_{m+1} = P \cdot U_m - Q \cdot U_{m-1}$$
$$V_0 = 2,\quad V_1 = P,\quad V_{m+1} = P \cdot V_m - Q \cdot V_{m-1}$$
二进制阶梯(binary ladder):高效计算 $(U_k, V_k, Q^k) \bmod n$。利用倍增公式:
$$U_{2m} = U_m \cdot V_m, \quad V_{2m} = V_m^2 - 2Q^m$$
和递增公式:
$$2U_{m+1} = P \cdot U_m + V_m, \quad 2V_{m+1} = D \cdot U_m + P \cdot V_m$$
其中 $D = P^2 - 4Q$。通过模 $n$ 的 half-mod 操作($n$ 为奇数时 $\text{half_mod}(x, n) = y$ 满足 $2y \equiv x \pmod{n}$)避免除法。
Selfridge 参数选择:选择第一个 $D \in {5, -7, 9, -11, 13, \ldots}$ 使得 Jacobi 符号 $\left(\frac{D}{n}\right) = -1$,然后令 $P = 1$,$Q = (1 - D)/4$。
强 Lucas 测试:写 $n + 1 = d \cdot 2^r$($d$ 为奇数),$n$ 通过当且仅当 $U_d \equiv 0 \pmod{n}$ 或存在 $0 \leq i < r$ 使得 $V_{d \cdot 2^i} \equiv 0 \pmod{n}$。
BPSW 组合
is_prime_bpsw(n):
1. 若 n < 2,返回 false;若 n 等于 {2,3,5,…,37} 中的素数,返回 true
2. 若 n 被 2..37 中的某个素数整除,返回 false
3. 若 n 是完全平方数,返回 false
4. 强 Miller–Rabin 测试(底数 2)
5. 若通过,执行强 Lucas PRP 测试(Selfridge 参数)
6. 两者都通过返回 true
oCAS 中 is_prime() 使用完整的 12 底数 Miller–Rabin,而 is_prime_bpsw() 使用单底数 Miller–Rabin + Lucas 测试。两者互补:is_prime() 在 $n < 3.317 \times 10^{24}$ 时确定性;is_prime_bpsw() 理论上更强(组合两种不同原理的测试)。
整数分解策略
oCAS 的 factor_integer 是一个分层驱动器(driver),将多种分解算法组合为一个递增策略。
试除法(Trial Division)
首先用 Eratosthenes 筛生成 $\leq 1000$ 的素数列表,逐一试除。这步的复杂度为 $O(\pi(1000)) = O(168)$,代价极小但能快速剥离小因子。
$$n = \prod_{p \leq 1000} p^{e_p} \cdot C$$
余因子 $C$ 的最小素因子 $> 1000$。
Pollard rho–Brent 变体
原理:基于生日悖论的随机碰撞检测。定义序列 $x_0, x_1, x_2, \ldots$ 满足 $x_{i+1} = x_i^2 + c \pmod{n}$($c$ 为随机常数)。序列模 $n$ 最终进入循环,而模真因子 $p$ 会更早碰撞。
Brent 变体的核心优化:
- 指数搜索:用 $2^k$ 步长检测碰撞(Floyd 的龟兔法每步都检测,Brent 只在 2 的幂处检测)
- 批量 GCD:积累若干个 $(x_i - y_i)$ 的乘积后一次性取 $\gcd$,摊销 GCD 的高代价
- 回溯(backtrack):当批量 GCD 返回 $n$ 本身时,在该批量内逐个重试
时间复杂度:期望 $O(n^{1/4})$ 次模乘。实践中对中等大小因子(< 20 位)非常高效。
Pollard $p - 1$ 方法(Stage 1)
原理:若 $n$ 有因子 $p$ 且 $p - 1$ 是 $B_1$-光滑的(即 $p - 1$ 的所有素因子幂 $\leq B_1$),则 $p - 1 \mid M$ 其中 $M = \prod_{q \leq B_1} q^{\lfloor \log_q B_1 \rfloor}$。由 Fermat 小定理,对任意 $\gcd(a, p) = 1$:
$$a^M \equiv 1 \pmod{p} \implies p \mid \gcd(a^M - 1, n)$$
实现细节:
- 用 Eratosthenes 筛生成 $q \leq B_1$ 的素数
- 逐素数做模幂:$a \leftarrow a^{q^e} \bmod n$($q^e \leq B_1 < q^{e+1}$)
- 每累积一定步数后检测 $\gcd(a - 1, n)$
- 若 $\gcd = n$(所有因子都 $p-1$ 光滑),方法失败
- 若 $1 < \gcd < n$,找到非平凡因子
局限:要求 $p - 1$ 光滑。若 $p - 1$ 有大素因子,方法无效。
Williams $p + 1$ 方法(Stage 1)
原理:与 $p - 1$ 方法互补。若 $p + 1$ 是 $B_1$-光滑的,利用 Lucas $V$ 序列代替模幂。
Lucas $V$ 序列:取 $Q = 1$,随机选择 $P$ 使得 $\left(\frac{P^2 - 4}{n}\right) = -1$(Jacobi 符号)。计算 $V_M \bmod n$,其中 $M = \prod_{q \leq B_1} q^{\lfloor \log_q B_1 \rfloor}$。若 $p + 1$ 是 $B_1$-光滑的,则 $V_M \equiv V_0 = 2 \pmod{p}$,即 $p \mid \gcd(V_M - 2, n)$。
参数选择:随机选 $P$,检查 $\text{jacobi}(P^2 - 4, n) = -1$。oCAS 实现中使用 lucas_uv_mod(与 BPSW 共享的 Lucas 链代码)。
局限:要求 $p + 1$ 光滑。与 $p - 1$ 方法互补(某些 $p$ 满足一个但不满足另一个)。
Lenstra 椭圆曲线方法(ECM)
原理:将整数分解转化为椭圆曲线群阶的光滑性问题。对随机椭圆曲线 $E/\mathbb{F}_p$,其群阶 $|E(\mathbb{F}_p)|$ 在 Hasse 区间 $[p + 1 - 2\sqrt{p}, p + 1 + 2\sqrt{p}]$ 内均匀分布(Sato–Tate)。ECM 成功的条件是某条曲线的群阶为 $B_1$-光滑的。
关键优势:与 rho 和 $p \pm 1$ 方法不同,ECM 不固定群阶区间——每换一条曲线就换一个群阶。这使得 ECM 对任何大小的因子都有概率成功,复杂度取决于最小因子 $p$ 的光滑性,而非 $n$ 本身。
Suyama 参数化:从随机 $\sigma \notin {0, 1, 5}$ 构造 Montgomery 曲线:
$$u = \sigma^2 - 5, \quad v = 4\sigma$$
$$A = \frac{(v - u)^3(3u + v)}{4u^3 v} - 2$$
曲线为 $B y^2 = x^3 + A x^2 + x$,基点为 $(u^3, v^3)$。Suyama 参数化保证基点总是有理的,且在构造过程中若分母的 GCD 暴露出 $n$ 的因子,则立即返回。
Montgomery 坐标:使用射影坐标 $(X : Z)$(省略 $Y$ 坐标),曲线方程为 $By^2 = x^3 + Ax^2 + x$,其中 $x = X/Z$。
- 倍点 $[2](X : Z)$:用常量 $a_{24} = (A + 2)/4$ 计算,只需 4 次模乘 + 4 次模加/减
- 差分加法 $P + Q$ 给定 $P - Q$:只需 6 次模乘
- Montgomery 阶梯:标量乘 $[k]P$ 通过交替倍点和加法计算,始终使用基点作为固定差分
Stage 1 算法:计算 $[M]P$ 其中 $M = \prod_{q \leq B_1} q^{\lfloor \log_q B_1 \rfloor}$。在计算过程中取 GCD,若发现非平凡因子则返回。
驱动器 factor_integer
factor_integer 将上述算法组合为一个递增策略:
factor_integer(n):
1. 试除法:剥离所有 p ≤ 1000 的因子
2. 对余因子 C(若 C > 1):
a. 若 is_prime_bpsw(C),记录为素因子
b. 否则调用 find_factor(C) 拆分,递归处理
find_factor(n) 的升级策略:
- 先做一次 Pollard rho–Brent(对小因子最廉价,无光滑性要求)
- 循环($B_1$ 从 2000 开始):
- Pollard $p - 1$(Stage 1,要求某因子 $p - 1$ 为 $B_1$-光滑)
- Williams $p + 1$(Stage 1,要求某因子 $p + 1$ 为 $B_1$-光滑,与 $p - 1$ 互补)
- ECM:曲线数 $\approx B_1 / 550$(限制在 10–300 之间)
- 全部失败后 $B_1 \leftarrow B_1 \times 4$,继续循环
曲线预算约为 $B_1 / 550$ 是 ECM 平滑度试探的标准经验比例,保证每档 $B_1$ 下有足够的随机曲线覆盖 Hasse 区间内的群阶。
离散对数
Baby-Step Giant-Step(BSGS)
问题:给定 $g, h, p$,求 $x$ 使得 $g^x \equiv h \pmod{p}$($0 \leq x < m$,$m$ 为 $g$ 的阶的上界)。
算法:令 $m = \lfloor \sqrt{\text{bound}} \rfloor + 1$。
- Baby steps:计算并存储 ${(j, g^j \bmod p) : 0 \leq j < m}$ 到 HashMap
- Giant steps:计算 $g^{-m} \bmod p$,对 $i = 0, 1, \ldots, m-1$,检查 $h \cdot (g^{-m})^i \bmod p$ 是否在表中
- 若找到匹配 $g^j = h \cdot g^{-im}$,则 $x = im + j$
复杂度:时间 $O(\sqrt{m})$ 次模乘,空间 $O(\sqrt{m})$。
oCAS 的 dlog_bsgs 使用 HashMap<Integer, Integer> 存储 baby steps,用 (j, g^j) 键值对实现 $O(1)$ 查找。
Pohlig–Hellman 算法
场景:$p$ 为素数,$g$ 的阶为 $n = p - 1$,且 $n$ 光滑:$n = q_1^{e_1} \cdots q_k^{e_k}$($q_i$ 为小素数)。
核心思想:将 $x \bmod n$ 的求解分解为 $x \bmod q_i^{e_i}$ 的子问题,然后用 CRT 合并。
逐素幂分解:对每个 $q = q_i$,$e = e_i$,求 $x \bmod q^e$:
写 $x = x_0 + x_1 q + x_2 q^2 + \cdots + x_{e-1} q^{e-1}$($0 \leq x_j < q$,逐"位"恢复)。
- 令 $g_0 = g^{n/q} \bmod p$,$h_0 = h^{n/q} \bmod p$。则 $g_0^{x_0} = h_0 \pmod{p}$。用 BSGS 求 $x_0 \bmod q$。
- 更新 $h \leftarrow h \cdot g^{-x_0}$,计算 $h_1 = (h \cdot g^{-x_0})^{n/q^2}$。求 $x_1$。
- 继续直到 $x_{e-1}$。
- $x \bmod q^e = x_0 + x_1 q + \cdots + x_{e-1} q^{e-1}$。
CRT 合并:对所有 $q_i^{e_i}$ 的结果用 crt_many 合并得到 $x \bmod n$。
复杂度:每个素幂的 BSGS 花费 $O(\sqrt{q_i})$,总计 $O(\sum e_i \sqrt{q_i})$。当所有 $q_i$ 都小时,这比直接 BSGS 的 $O(\sqrt{n})$ 快得多。
oCAS 的 dlog_pohlig_hellman 要求 $p$ 为素数。它使用 factor_integer 分解 $p - 1$(复用了整数分解模块),逐素幂执行 BSGS 数字恢复,最后 crt_many 合并并验证结果。
中国剩余定理
经典 CRT
定理:设 $m_1, \ldots, m_k$ 两两互素,给定同余方程组
$$x \equiv r_i \pmod{m_i}, \quad i = 1, \ldots, k$$
则存在唯一解 $x \bmod M$ 其中 $M = m_1 \cdots m_k$。
构造:$x = \sum_i r_i \cdot M_i \cdot M_i^{-1}$,其中 $M_i = M / m_i$,$M_i^{-1} = M_i^{-1} \bmod m_i$。
推广:模数不互素
oCAS 的 crt(成对合并)和 crt_many(多同余合并)不要求模数两两互素。
成对 CRT:对 $x \equiv r_1 \pmod{m_1}$ 和 $x \equiv r_2 \pmod{m_2}$:
- 计算 $g = \gcd(m_1, m_2)$
- 检查 $r_1 \equiv r_2 \pmod{g}$(一致性条件)
- 若不一致,返回
None - 否则用扩展 Euclid 算法求解,合并模为 $\text{lcm}(m_1, m_2)$
多同余合并 crt_many:左折叠——从第一个同余开始,逐个与下一个合并:
$$x \equiv r_1 \pmod{m_1} \xrightarrow{\text{合并}} x \equiv R_2 \pmod{\text{lcm}(m_1, m_2)} \xrightarrow{\text{合并}} \cdots$$
最终返回 $(R, M)$ 其中 $M = \text{lcm}(m_1, \ldots, m_k)$,$0 \leq R < M$。若任何一步发现不一致,整体返回 None。
复杂度:$k$ 次成对合并,每次 $O(\log M)$ 的 GCD 运算。
二次剩余
Legendre 符号与 Jacobi 符号
Legendre 符号:对奇素数 $p$,
$$\left(\frac{a}{p}\right) = \begin{cases} 0 & p \mid a \ 1 & a \text{ 是模 } p \text{ 二次剩余} \ -1 & a \text{ 是模 } p \text{ 二次非剩余} \end{cases}$$
Euler 判据:$\left(\frac{a}{p}\right) \equiv a^{(p-1)/2} \pmod{p}$。
Jacobi 符号:Legendre 符号对合数模的推广。若 $n = p_1^{e_1} \cdots p_k^{e_k}$,则
$$\left(\frac{a}{n}\right) = \prod_i \left(\frac{a}{p_i}\right)^{e_i}$$
关键性质:Jacobi 符号可通过二次互反律(quadratic reciprocity)快速计算,无需分解 $n$。
二次互反律
对奇素数 $p, q$:
$$\left(\frac{p}{q}\right) \left(\frac{q}{p}\right) = (-1)^{\frac{p-1}{2} \cdot \frac{q-1}{2}}$$
补充规则:
- $\left(\frac{2}{n}\right) = (-1)^{(n^2 - 1)/8}$,即 $n \equiv \pm 1 \pmod 8$ 时为 $1$,$n \equiv \pm 3 \pmod 8$ 时为 $-1$
- $\left(\frac{-1}{n}\right) = (-1)^{(n-1)/2}$,即 $n \equiv 1 \pmod 4$ 时为 $1$
计算算法(oCAS 的 jacobi 函数):
jacobi(a, n):
若 n 为偶数或非正,返回 0
反复:
1. 2-adic 剥离:提取 a 中所有因子 2,用 mod-8 规则累积符号
2. 互反律翻转:交换 a ↔ n,根据 a, n 的 mod-4 值调整符号
3. a = a mod n
4. 若 a == 0,返回 0(n > 1)或累积符号(n == 1)
复杂度:$O(\log^2 n)$,与 Euclid 算法同阶。
Tonelli–Shanks 算法
求 $x$ 使得 $x^2 \equiv a \pmod{p}$($p$ 为奇素数,$\left(\frac{a}{p}\right) = 1$)。
快速路径:当 $p \equiv 3 \pmod{4}$ 时,
$$x = a^{(p+1)/4} \bmod p$$
因为 $x^2 = a^{(p+1)/2} = a \cdot a^{(p-1)/2} = a \cdot \left(\frac{a}{p}\right) = a$。
一般情况($p \equiv 1 \pmod{4}$)——Tonelli–Shanks 算法:
- 分解:写 $p - 1 = Q \cdot 2^S$($Q$ 为奇数)
- 找非剩余:搜索 $z$ 使得 $\left(\frac{z}{p}\right) = -1$
- 初始化:
- $M = S$
- $c = z^Q \bmod p$($2^{S-1}$ 阶元素)
- $t = a^Q \bmod p$
- $R = a^{(Q+1)/2} \bmod p$
- 循环(当 $t \neq 1$):
- 找最小 $i > 0$ 使得 $t^{2^i} \equiv 1 \pmod{p}$
- 更新 $c \leftarrow c^{2^{M-i-1}}$,$t \leftarrow t \cdot c^2$,$R \leftarrow R \cdot c$,$M \leftarrow i$
- 返回 $R$
不变量:$R^2 = a \cdot t \pmod{p}$ 且 $c$ 的阶为 $2^M$。循环终止时 $t = 1$,故 $R^2 \equiv a$。
复杂度:$O(S^2 \log p)$,其中 $S = v_2(p - 1)$($p - 1$ 中 2 的幂次)。平均 $S$ 很小,算法高效。
有理重构
问题:给定 $a \in \mathbb{Z}$ 和模数 $m$,找到 $n, d \in \mathbb{Z}$ 使得
$$a \cdot d \equiv n \pmod{m}, \quad \gcd(n, d) = 1, \quad 2|n| \cdot |d| < m$$
应用:在模 GCD 算法中,通过 CRT 合并多个 $\mathbb{F}_p$ 上的 GCD 系数后,需要将模 $M$ 下的结果"重构"回 $\mathbb{Q}$ 上的有理数。
Wang/扩展 Euclid 算法:
使用扩展 Euclid 算法追踪序列 $(r_i, t_i)$:
- 初始化:$(r_0, r_1) = (m, a)$,$(t_0, t_1) = (0, 1)$
- 迭代:$q = \lfloor r_0 / r_1 \rfloor$,$(r_0, r_1) \leftarrow (r_1, r_0 - q \cdot r_1)$,$(t_0, t_1) \leftarrow (t_1, t_0 - q \cdot t_1)$
- 终止条件:当 $|r_1| \leq \sqrt{m/2}$ 且 $|t_1| \leq \sqrt{m/2}$ 时停止
- 验证:令 $n = r_1$,$d = t_1$(取正值),检查 $a \cdot d \equiv n \pmod{m}$ 和 $2|n| \cdot |d| < m$
- 若验证失败或 $t_1 = 0$,返回
None(不存在满足条件的有理重构)
唯一性定理:当 $2|n| \cdot |d| < m$ 时,满足条件的 $(n, d)$ 是唯一的。这个条件保证了重构结果是"最简"的有理表示。
复杂度:$O(\log m)$ 的 GCD 运算步数,与 Euclid 算法同阶。
在 oCAS 中的实现
oCAS 的数论算法实现在 ocas-domain crate 的 number_theory 模块和 ocas-poly 的有理重构模块中。
模块结构
ocas-domain/src/
├── number_theory.rs ← 素性测试、模逆、CRT、Legendre/Jacobi、mod_sqrt
└── number_theory/
├── primes.rs ← BPSW、Lucas 序列(lucas_uv_mod、strong_lucas_prp)
├── factor.rs ← 整数分解驱动器(factor_integer)及各方法
├── dlog.rs ← BSGS 和 Pohlig–Hellman
├── crt.rs ← 多同余 CRT(crt_many)
└── functions.rs ← φ、μ、τ、σ、λ
ocas-poly/src/
└── rational_reconstruction.rs ← 有理重构(扩展 Euclid 方法)
素性判定
| 函数 | 位置 | 说明 |
|---|---|---|
is_prime(n) | number_theory.rs | 12 底数 Miller–Rabin,$n < 3.317 \times 10^{24}$ 确定性 |
is_prime_bpsw(n) | primes.rs | base-2 MR + 强 Lucas PRP |
is_prime_u64(n) | primes.rs | u64 专用,委托给 is_prime |
next_prime(n) | number_theory.rs | 从 $n + 1$ 起逐个奇数测试 |
primes_from(n) | number_theory.rs | 素数迭代器 |
Lucas 序列计算由 lucas_uv_mod 实现,使用二进制阶梯法,返回 $(U_k, V_k, Q^k) \bmod n$。该函数同时被 BPSW 测试和 Williams $p + 1$ 分解方法共用。
整数分解
| 函数 | 位置 | 说明 |
|---|---|---|
factor_integer(n) | factor.rs | 入口:试除 + 递归拆分 |
factor_integer_with_rng(n, rng) | factor.rs | 带显式 RNG 的版本 |
factor_trial(n, limit) | factor.rs | 试除法,返回 (因子, 余因子) |
pollard_rho_brent(n, rng) | factor.rs | Brent 变体,带重试 |
pollard_pm1(n, b1, rng) | factor.rs | Pollard $p - 1$,Stage 1 |
williams_pp1(n, b1, rng) | factor.rs | Williams $p + 1$,Stage 1 |
ecm(n, b1, max_curves, rng) | factor.rs | Lenstra ECM,Suyama 参数化 |
内部实现细节:
primes_up_to(limit): Eratosthenes 筛,为试除法和光滑界计算生成素数表prime_power_le(q, bound): 计算 $q^e \leq \text{bound}$,用于构造光滑指数 $M$ProjPoint { x, z }: Montgomery 射影坐标点ecm_double、ecm_add、ecm_mul: Montgomery 曲线上的群运算suyama_curve(sigma, n): Suyama 参数化,返回Suyama枚举(Curve/Factor/Degenerate)find_factor(n, rng): 升级策略驱动器(rho → p−1 → p+1 → ECM,光滑界递增)
离散对数
| 函数 | 位置 | 说明 |
|---|---|---|
dlog_bsgs(base, target, modulus) | dlog.rs | Baby-step giant-step |
dlog_pohlig_hellman(base, target, p) | dlog.rs | Pohlig–Hellman(需 $p$ 为素数) |
内部:
bsgs_bounded(base, target, modulus, order_bound): BSGS 核心,给定阶上界搜索dlog_bsgs设置上界为modulus - 1并委托给bsgs_boundeddlog_pohlig_hellman使用factor_integer分解 $p - 1$,逐素幂 BSGS 恢复数字,crt_many合并
中国剩余定理
| 函数 | 位置 | 说明 |
|---|---|---|
crt(r1, m1, r2, m2) | number_theory.rs | 成对合并,模数无需互素 |
crt_many(congruences) | crt.rs | 多同余左折叠合并 |
crt_many 从第一个同余开始,逐个调用 crt 合并。任何一步不一致则整体返回 None。
二次剩余
| 函数 | 位置 | 说明 |
|---|---|---|
legendre(a, p) | number_theory.rs | Legendre 符号,委托给 jacobi |
jacobi(a, n) | number_theory.rs | Jacobi 符号(二次互反律 + 2-adic 剥离) |
mod_sqrt(a, p) | number_theory.rs | Tonelli–Shanks,含 $p \equiv 3 \pmod{4}$ 快速路径 |
mod_sqrt 实现:
- 先检查 $\left(\frac{a}{p}\right) = 1$(否则返回
None) - 若 $p \equiv 3 \pmod{4}$,走快速路径 $x = a^{(p+1)/4} \bmod p$
- 否则执行完整 Tonelli–Shanks 算法:找非剩余 $z$,分解 $p - 1 = Q \cdot 2^S$,维护 $(c, t, r, m)$ 不变量循环
辅助数论函数
| 函数 | 位置 | 说明 |
|---|---|---|
euler_phi(n) | functions.rs | $\varphi(n) = |
moebius_mu(n) | functions.rs | $\mu(n)$,基于 factor_integer |
divisor_tau(n) | functions.rs | $\tau(n) = \prod(e_i + 1)$ |
divisor_sigma(n, k) | functions.rs | $\sigma_k(n) = \prod \frac{p^{k(e+1)} - 1}{p^k - 1}$ |
liouville_lambda(n) | functions.rs | $\lambda(n) = (-1)^{\Omega(n)}$ |
所有积性函数都先通过 factor_integer 获取素因子分解,再用积性公式计算。
有理重构
| 函数 | 位置 | 说明 |
|---|---|---|
rational_reconstruction(a, m) | ocas-poly/src/rational_reconstruction.rs | Wang/扩展 Euclid 方法 |
内部使用 integer_sqrt 计算 $\lfloor\sqrt{m/2}\rfloor$ 作为终止界的阈值。
基础工具函数
| 函数 | 位置 | 说明 |
|---|---|---|
mod_inv(a, m) | number_theory.rs | 扩展 Euclid 求模逆 |
extended_gcd(a, b) | number_theory.rs | 返回 $(g, x, y)$ 满足 $ax + by = g$ |
symmetric_mod(a, m) | number_theory.rs | 约化到 $(-m/2, m/2]$ |
参考文献
-
Shoup, V. A Computational Introduction to Number Theory and Algebra. Cambridge University Press, 2nd edition, 2009.
- Ch.10: Primality testing (Miller–Rabin, randomized algorithms)
- Ch.11: Finding discrete logarithms (BSGS, Pohlig–Hellman)
- Ch.19: Factoring integers (Pollard rho, ECM)
-
Crandall, R. & Pomerance, C. Prime Numbers: A Computational Perspective. Springer, 2nd edition, 2005.
- Ch.3: Recognizing primes and composites (BPSW, Lucas tests)
- Ch.5: Exponential factoring algorithms (ECM, Pollard rho)
- Ch.6: Subexponential factoring (Pollard $p-1$, Williams $p+1$)
- Ch.7: Modern discrete logarithm algorithms
-
Brent, R. P. "An improved Monte Carlo factorization algorithm." BIT Numerical Mathematics, 20(2):176–184, 1980.
- Brent 变体的 Pollard rho 算法
-
Williams, H. C. "A p+1 method of factoring." Mathematics of Computation, 39(159):225–234, 1982.
- Williams $p + 1$ 方法
-
Lenstra, H. W. Jr. "Factoring integers with elliptic curves." Annals of Mathematics, 126(3):649–673, 1987.
- ECM 方法
-
Montgomery, P. L. "Speeding the Pollard and elliptic curve methods of factorization." Mathematics of Computation, 48(177):243–264, 1987.
- Montgomery 曲线坐标与阶梯优化
-
Shanks, D. & Tonelli, R. See Cohen, H. A Course in Computational Algebraic Number Theory, Algorithm 1.5.1.
- Tonelli–Shanks 模平方根算法
-
Baillie, R. & Wagstaff, S. S. "Lucas pseudoprimes." Mathematics of Computation, 35(152):1391–1417, 1980.
- BPSW 测试的 Lucas 部分
-
Pohlig, S. & Hellman, M. "An improved algorithm for computing logarithms over GF(p) and its cryptographic significance." IEEE Transactions on Information Theory, 24(1):106–110, 1978.
- Pohlig–Hellman 离散对数算法
-
Wang, P. S. "A p-adic algorithm for univariate partial fractions." Proceedings of SYMSAC '81, 212–217, 1981.
- 有理重构的扩展 Euclid 方法
进阶:符号微积分
前提知识
- 微积分基础:导数定义、基本求导公式、Taylor 级数概念
- 表达式树与递归数据结构(见 多项式代数)
- oCAS 表达式系统(
Atom、AtomNode、AtomArena)
基础概念
表达式树上的符号微积分
在符号计算中,函数 $f(x)$ 被表示为一棵表达式树(expression tree)。树的叶节点是常数或变量,内部节点是运算符(Add、Mul、Pow)或函数调用(sin、exp、log 等)。
例如,$f(x) = x^2 + \sin(x)$ 的表达式树为:
Add
├── Pow
│ ├── Var("x")
│ └── Num(2)
└── Fun("sin", [Var("x")])
符号微分的核心思想是模式匹配(pattern matching):对表达式树的每个节点,根据其类型应用对应的微分规则,递归地构造导数表达式树。
基本求导规则
给定可微函数 $f(x)$ 和 $g(x)$:
| 规则 | 公式 |
|---|---|
| 常数 | $\frac{d}{dx}[c] = 0$ |
| 恒等 | $\frac{d}{dx}[x] = 1$ |
| 和 | $\frac{d}{dx}[f + g] = f' + g'$ |
| 积 | $\frac{d}{dx}[f \cdot g] = f' \cdot g + f \cdot g'$ |
| 链式 | $\frac{d}{dx}[f(g(x))] = f'(g(x)) \cdot g'(x)$ |
| 幂(指数为常数 $n$) | $\frac{d}{dx}[f^n] = n \cdot f^{n-1} \cdot f'$ |
| 指数(底为常数 $a$) | $\frac{d}{dx}[a^g] = a^g \cdot \ln a \cdot g'$ |
| 一般幂 | $\frac{d}{dx}[f^g] = f^g \cdot (\ln f \cdot g' + g \cdot f'/f)$ |
初等函数导数表
| $f(u)$ | $f'(u)$ |
|---|---|
| $\sin u$ | $\cos u$ |
| $\cos u$ | $-\sin u$ |
| $\exp u$ | $\exp u$ |
| $\ln u$ | $u^{-1}$ |
| $\sqrt{u}$ | $(2\sqrt{u})^{-1}$ |
| $\tan u$ | $\sec^2 u$ |
| $\sec u$ | $\sec u \cdot \tan u$ |
| $\arctan u$ | $(1 + u^2)^{-1}$ |
注意:上表给出的是 $f'(u)$(关于 $u$ 的导数),实际 $\frac{d}{dx}[f(u(x))] = f'(u) \cdot u'(x)$,即还需乘以链式法则因子 $u'(x)$。
Taylor 级数
设 $f$ 在点 $a$ 的某邻域内无穷可微,则 $f$ 在 $a$ 处的 Taylor 级数为:
$$ f(x) = \sum_{n=0}^{\infty} \frac{f^{(n)}(a)}{n!} (x - a)^n $$
其中 $f^{(n)}(a)$ 是 $f$ 在 $a$ 处的 $n$ 阶导数。截断到 $N$ 阶得到 Taylor 多项式:
$$ T_N(x) = \sum_{n=0}^{N} \frac{f^{(n)}(a)}{n!} (x - a)^n $$
常见展开(在 $a = 0$ 处,即 Maclaurin 级数):
$$ e^x = 1 + x + \frac{x^2}{2!} + \frac{x^3}{3!} + \cdots $$
$$ \sin x = x - \frac{x^3}{3!} + \frac{x^5}{5!} - \cdots $$
$$ \cos x = 1 - \frac{x^2}{2!} + \frac{x^4}{4!} - \cdots $$
$$ \ln(1+x) = x - \frac{x^2}{2} + \frac{x^3}{3} - \cdots \quad (|x| < 1) $$
核心理论
表达式树上的递归微分
符号微分的核心算法是对表达式树进行结构递归(structural recursion)。对每个节点类型,应用对应的微分规则,递归地对子表达式求导。
算法 diff(expr, var):
- 常数
Num(c):返回Num(0)。 - 变量
Var(v):若 $v = \text{var}$,返回Num(1);否则返回Num(0)。 - 和
Add(a₁, a₂, …, aₙ):逐项求导,返回Add(diff(a₁), diff(a₂), …, diff(aₙ))。 - 积
Mul(a₁, a₂, …, aₙ):应用广义乘积法则。对 $n$ 个因子的乘积 $a_1 a_2 \cdots a_n$:
$$ \frac{d}{dx}\left[\prod_{i=1}^n a_i\right] = \sum_{i=1}^n \left(\frac{d a_i}{dx} \cdot \prod_{j \neq i} a_j\right) $$
即对每个位置 $i$,将第 $i$ 个因子替换为其导数,其余因子保持不变,然后将 $n$ 个结果相加。
-
幂
Pow(base, exp):分三种情况——- 指数为常数 $n$:$\frac{d}{dx}[b^n] = n \cdot b^{n-1} \cdot b'$
- 底为常数 $a$:$\frac{d}{dx}[a^u] = a^u \cdot \ln a \cdot u'$
- 一般情形(广义幂法则):$\frac{d}{dx}[b^e] = b^e \cdot (\ln b \cdot e' + e \cdot b'/b)$
-
函数调用
Fun(name, [u]):查表得到 $f'(u)$,再乘以 $u'$(链式法则)。 -
未知函数:返回未求值形式
Derivative(f(x), x)。
关键性质:由于 oCAS 的表达式使用 hash-consing(结构共享),递归微分产生的中间表达式自动共享相同子树。微分完成后,通过重写引擎化简(例如 $0 \cdot f \to 0$,$1 \cdot f \to f$),消除冗余项。
Taylor 展开的递推算法
Taylor 展开的直接实现可以利用逐次微分:
算法 taylor(expr, var, point, order):
- 令
current = expr,sum = 0。 - 对 $n = 0, 1, \dots, \text{order}$:
- 计算 $f^{(n)}(\text{point})$:将
current中的var替换为point(即求值)。 - 计算系数 $c_n = f^{(n)}(\text{point}) / n!$。
- 若 $n = 0$,项为 $c_0$;否则项为 $c_n \cdot (x - \text{point})^n$。
- 累加:$\text{sum} \mathrel{+}= \text{term}$。
- 若 $n < \text{order}$,更新
current = diff(current, var)。
- 计算 $f^{(n)}(\text{point})$:将
- 化简并返回
sum。
阶乘的处理:$1/n!$ 以幂的形式 $n!^{-1}$ 表示,避免引入浮点数。mul_by_factorial_inverse 函数计算 $n!$(使用 i64 乘法,对 $n \leq 20$ 安全),然后构造 $c \cdot (n!)^{-1}$。
复杂度:循环 $n = 0, \dots, N$ 中每一步执行一次替换(求值),但仅在 $n < N$ 时执行一次符号微分。符号微分本身是 $O(|\text{expr}|)$ 的($|\text{expr}|$ 为表达式树大小),但化简可能增加节点数。总体而言,$N$ 阶 Taylor 展开需要 $N$ 次符号微分和 $N+1$ 次替换。
替换的语义
substitute(expr, var, replacement) 将表达式中所有自由出现的变量 var 替换为 replacement。
实现策略:oCAS 使用自底向上变换(bottom-up transform)——通过 transform 函数遍历表达式树,对每个叶节点检查是否为要替换的变量。若是,返回 replacement;否则保持不变。
深拷贝 vs 引用:
- oCAS 的
Atom是 arena 上的 Copy 句柄(指针),而非拥有所有权的值。transform在 arena 上分配新节点来构造结果,但未修改的子树直接复用原句柄(零拷贝)。 - 这意味着
substitute的语义是纯函数式的:原表达式不变,结果是新分配的表达式树,其中被替换的路径指向新节点,未受影响的子树与原树共享。 - 与"破坏性修改"不同,这保证了引用透明性:对同一表达式多次替换不会互相干扰。
替换的局限:substitute 是语法替换而非语义替换。它替换的是字面上的变量名,而非"值"。例如,对 $\int_0^x f(t), dt$ 中的 $t$ 做替换 $t \to y$ 会错误地改变积分变量——这类语义替换需要更精细的绑定分析。
在 oCAS 中的实现
diff:符号微分
文件:ocas-calc/src/derivative.rs
#![allow(unused)] fn main() { pub fn diff<'a>(ctx: &'a AtomArena<'a>, expr: Atom<'a>, var: Symbol) -> Atom<'a> }
实现结构:
diff 是入口函数,它调用内部的 diff_raw 执行实际递归,然后用 simplify 化简结果,最后用 normalize 归一化。
#![allow(unused)] fn main() { pub fn diff<'a>(ctx: &'a AtomArena<'a>, expr: Atom<'a>, var: Symbol) -> Atom<'a> { let rules = calculus_rules(ctx, &crate::pattern_alloc::VecAlloc); let raw = diff_raw(ctx, expr, var); let simplified = simplify(ctx, raw, &rules, 20); normalize(ctx, simplified) } }
diff_raw 对 AtomNode 的每个变体进行模式匹配:
Num(_):常数,导数为 0。Var(v):若v == var则导数为 1,否则为 0。Add(args):逐项调用diff_raw,结果收集到新的Add节点。Mul(args):广义乘积法则——对 $n$ 个因子,生成 $n$ 个乘积项(每项将一个因子替换为其导数),然后相加。Pow(base, exp):三种情况(指数常数 / 底常数 / 一般),见上文。Fun(name, args):委托给diff_function。
diff_function 维护一张硬编码导数表,覆盖 sin、cos、exp、log、sqrt、tan、sec、atan 八个函数。对每个函数 $f(u)$,表中存储 $f'(u)$(关于 $u$ 的导数),然后乘以链式法则因子 $\frac{du}{dx}$。
对于表中没有的函数,返回未求值形式:
#![allow(unused)] fn main() { ctx.fun("Derivative", &[ctx.fun(name_str, args), ctx.var(var.as_str())]) }
即 Derivative(f(x), x),表示无法自动求导的导数。
化简管线:diff 产生的原始导数表达式可能包含冗余(如 0 * sin(x)、1 * cos(x))。simplify 使用 calculus_rules(微积分专用规则 + 默认化简规则)进行最多 20 轮不动点化简,然后 normalize 将结果归一化为规范形式。
taylor:Taylor 展开
文件:ocas-calc/src/series.rs
#![allow(unused)] fn main() { pub fn taylor<'a>( ctx: &'a AtomArena<'a>, expr: Atom<'a>, var: Symbol, point: Atom<'a>, order: usize, ) -> Atom<'a> }
实现细节:
- 预计算
(x - point)作为公共子表达式x_minus_p。 - 循环 $n = 0, \dots, \text{order}$:
substitute(ctx, current, var, point)求 $f^{(n)}(a)$。mul_by_factorial_inverse计算 $f^{(n)}(a) / n!$。- 构造项 $c_n \cdot (x - a)^n$($n = 0$ 时省略幂因子)。
- 累加到
sum。 - 若 $n < \text{order}$,
current = diff(ctx, current, var)计算下一阶导数。
- 化简 + 归一化。
mul_by_factorial_inverse:
#![allow(unused)] fn main() { fn mul_by_factorial_inverse<'a>(ctx: &'a AtomArena<'a>, expr: Atom<'a>, n: usize) -> Atom<'a> { if n == 0 { return expr; } let mut fact: i64 = 1; for i in 2..=n { fact = fact.checked_mul(i as i64).expect("factorial fits in i64"); } ctx.mul(&[expr, ctx.pow(ctx.num(fact), ctx.num(-1))]) } }
将 $1/n!$ 表示为 $(n!)^{-1}$,即 Pow(Num(n!), Num(-1)),保持精确有理数表示。checked_mul 确保 $n \leq 20$($20! = 2,432,902,008,176,640,000 < 2^{63}$)。
示例:
#![allow(unused)] fn main() { // e^x 在 x=0 处展开到 3 阶 let x = ctx.var("x"); let expr = ctx.fun("exp", &[x]); let result = taylor(&ctx, expr, Symbol::new("x"), ctx.num(0), 3); // result = "1 + x + ((2^-1)*(x^2)) + ((6^-1)*(x^3))" // 即 1 + x + x²/2 + x³/6 // sin(x) 在 x=0 处展开到 5 阶 let sin_x = ctx.fun("sin", &[x]); let result = taylor(&ctx, sin_x, Symbol::new("x"), ctx.num(0), 5); // result = "x + (-1*(6^-1)*(x^3)) + ((120^-1)*(x^5))" // 即 x - x³/6 + x⁵/120(偶数阶系数为零,自动消失) }
substitute:变量替换
文件:ocas-calc/src/series.rs
#![allow(unused)] fn main() { pub fn substitute<'a>( ctx: &'a AtomArena<'a>, expr: Atom<'a>, var: Symbol, replacement: Atom<'a>, ) -> Atom<'a> }
实现:
#![allow(unused)] fn main() { pub fn substitute<'a>( ctx: &'a AtomArena<'a>, expr: Atom<'a>, var: Symbol, replacement: Atom<'a>, ) -> Atom<'a> { transform(ctx, expr, |a| match a.node() { AtomNode::Var(v) if *v == var => Some(replacement), _ => None, }) } }
使用 transform(自底向上变换)遍历表达式树。闭包对每个节点检查:若为 Var(v) 且 v == var,返回 Some(replacement)(替换);否则返回 None(保持不变)。
关键性质:
- 纯函数式:不修改原表达式,返回新树。未修改的子树通过
Atom的 Copy 语义共享。 - 深替换:替换所有自由出现,包括嵌套在函数参数中的(如
sin(x)中的x)。 - 无捕获检查:不检查替换后是否产生变量捕获——这是语法层面的替换。
integrate:符号积分
文件:ocas-calc/src/integral/mod.rs
#![allow(unused)] fn main() { pub fn integrate<'a>(ctx: &'a AtomArena<'a>, expr: Atom<'a>, var: Symbol) -> Atom<'a> }
oCAS 的积分器采用分层管线架构(详见 符号积分算法 和 Risch 积分算法):
- 直接查表层(
integrate_raw):常数、变量、和式以及简单乘积/幂/函数的直接积分规则。 - 有理函数层(
rational.rs):部分分式分解 + Hermite 约化 + 对数部分(对数导数恒等式、配方、Rothstein–Trager)。 - Risch 算法层(
risch.rs):微分域塔 $\mathbb{Q}(x, t_1, \dots, t_n)$ 上的结构定理。 - 三角层(
trig.rs):Euler 公式重写为复指数($t = e^{ix}$)→ 对重写结果再次尝试 Risch →realify回转实数形式。 - 特殊函数层(
special.rs):erf、erfi、Ei、Si、Ci、Shi、Chi、Fresnel 等特殊函数表。 - 启发式层(
heuristic.rs):分部积分(LIATE 排序)、三角替换($\sqrt{a^2 - x^2} \to a\sin\theta$)、Weierstrass $t = \tan(x/2)$ 有理化、Euler 替换。 - 回退:返回未求值形式
Integral(expr, var)。
integrate_with_fuel 变体接受 Fuel 预算,防止化简阶段无限循环。积分遍历本身受 MAX_DEPTH = 8 的内部递归深度限制(Risch 层另有 MAX_RISCH_DEPTH = 16):
#![allow(unused)] fn main() { pub fn integrate_with_fuel<'a>( ctx: &'a AtomArena<'a>, expr: Atom<'a>, var: Symbol, fuel: &Fuel, ) -> Result<Atom<'a>> }
进阶话题
微分与化简的交互
diff 的正确性不仅取决于微分规则本身,还取决于化简质量。例如:
- $\frac{d}{dx}[x^2 + 3x] = 2x + 3$——需要化简 $1 \cdot 3$ 为 $3$。
- $\frac{d}{dx}[e^{\ln x}] = e^{\ln x} \cdot \frac{1}{x} = 1$——需要化简 $e^{\ln x} \to x$。
oCAS 通过 calculus_rules 和默认规则的多轮化简处理这类情况。但某些恒等式(如 $\sin^2 x + \cos^2 x = 1$)可能需要专门的三角化简规则。
未求值导数与积分
当 diff 遇到未知函数(不在导数表中的函数)时,返回 Derivative(f, x)。类似地,integrate 对无法处理的积分返回 Integral(f, x)。
这些未求值形式是合法的表达式节点——它们可以参与后续的符号运算。例如,$\frac{d}{dx}[\text{Derivative}(f, x)]$ 会返回 Derivative(Derivative(f, x), x),表示二阶导数。
与数值求值的衔接
符号微分产生的表达式可以传递给 ExpressionEvaluator(见 求值与 JIT)进行高效数值求值。典型工作流:
- 符号微分:
diff(&ctx, f, x)→ 导数表达式 $f'(x)$。 - 编译求值器:
ExpressionEvaluator::compile(f')。 - 数值求值:
evaluator.evaluate(&[x_val])→ $f'(x_{\text{val}})$。
这比数值差分(有限差分)精确得多,且避免了步长选择问题。
参考文献
- Geddes, K. O., Czapor, S. R. & Labahn, G. Algorithms for Computer Algebra. Kluwer Academic Publishers, 1992. — 第 11 章"Symbolic Differentiation"涵盖表达式树上的递归微分算法、化简策略和导数表设计。
- Bronstein, M. Symbolic Integration I: Transcendental Functions. Springer, 2005. — Risch 算法的权威参考,oCAS 积分器的设计依据。
- Cohen, H. A Course in Computational Algebraic Number Theory. Springer, 1993. — 第 1 章包含算法复杂度分析的一般方法论。
进阶:ODE 求解理论
前提知识
基础概念
常微分方程的定义
常微分方程(ordinary differential equation, ODE)是包含未知函数 $y(x)$ 及其导数的方程。一般形式为
$$F!\left(x,, y,, y',, y'',, \dots,, y^{(n)}\right) = 0$$
其中 $y^{(k)} = \dfrac{d^k y}{dx^k}$。
阶(order)
ODE 中出现的最高阶导数决定了方程的阶。例如:
| 方程 | 阶 |
|---|---|
| $y' + y = 0$ | 1 |
| $y'' + 2y' + y = \sin x$ | 2 |
| $y''' - y = 0$ | 3 |
线性与非线性
一个 $n$ 阶 ODE 是线性的,当且仅当它可以写成
$$a_n(x), y^{(n)} + a_{n-1}(x), y^{(n-1)} + \cdots + a_1(x), y' + a_0(x), y = g(x)$$
其中 $a_k(x)$ 和 $g(x)$ 仅依赖于自变量 $x$,不依赖于 $y$。若 $g(x) \equiv 0$,则称为齐次线性 ODE;否则称为非齐次。
含有 $y^2$、$\sin y$、$y,y'$ 等项的方程是非线性的。
初值条件与边值条件
- 初值问题(IVP):给定 $y(x_0) = y_0$(一阶),或 $y(x_0) = y_0,; y'(x_0) = y_1$(二阶),确定唯一解。
- 边值问题(BVP):在两个不同点指定条件,如 $y(0) = 0,; y(\pi) = 0$。
存在唯一性定理(Picard–Lindelöf)
定理(一阶情形). 设 $f(x, y)$ 在矩形区域 $R = {(x,y) : |x - x_0| \leq a,; |y - y_0| \leq b}$ 上连续,且关于 $y$ 满足 Lipschitz 条件
$$|f(x, y_1) - f(x, y_2)| \leq L, |y_1 - y_2|$$
则初值问题 $y' = f(x,y),; y(x_0) = y_0$ 在 $|x - x_0| \leq h$($h = \min(a,, b/M)$,$M = \max_R |f|$)上存在唯一解。
推广到 $n$ 阶. 将 $n$ 阶方程化为一阶方程组 $\mathbf{Y}' = \mathbf{F}(x, \mathbf{Y})$,对 $\mathbf{F}$ 施加同样的 Lipschitz 条件即可。
这个定理保证了:当右端函数足够"好"时,ODE 的解不仅存在,而且唯一。这是所有符号求解器的理论基础——如果符号方法找到了一个解,Picard–Lindelöf 保证了它就是唯一的解。
核心理论
一阶 ODE
可分离变量方程(Separable)
形式:
$$g(y), y' = f(x)$$
求解步骤:
- 将方程写为 $g(y), dy = f(x), dx$
- 两边积分:$\displaystyle\int g(y), dy = \int f(x), dx + C$
例子. 求解 $y' = xy$:
$$\frac{dy}{y} = x, dx \quad\Longrightarrow\quad \ln|y| = \frac{x^2}{2} + C \quad\Longrightarrow\quad y = C_1, e^{x^2/2}$$
oCAS 中的实现. 分类器 is_separable 通过检查方程是否能写成"所有含 $y$ 的项伴随 $y'$,其余项仅含 $x$"来判断可分离性。求解器 solve_separable 将方程拆为 $(y\text{-项},, x\text{-项})$,分别积分后构造隐式解 $\int g, dy - \int f, dx = C$。
一阶线性方程(Linear First-Order)
形式:
$$y' + p(x), y = q(x)$$
积分因子法. 乘以积分因子
$$\mu(x) = e^{\int p(x), dx}$$
方程化为
$$\frac{d}{dx}!\bigl[\mu(x), y\bigr] = \mu(x), q(x)$$
积分得
$$y = \frac{1}{\mu(x)} \int \mu(x), q(x), dx + \frac{C}{\mu(x)}$$
推导. 观察到 $\mu' = p,\mu$,因此
$$\mu, y' + p,\mu, y = \mu, y' + \mu', y = (\mu, y)'$$
例子. 求解 $y' - \dfrac{2}{x}, y = x^2$:
$$p(x) = -\frac{2}{x}, \quad \mu = e^{\int -2/x, dx} = e^{-2\ln x} = x^{-2}$$
$$\frac{d}{dx}!\bigl[x^{-2} y\bigr] = x^{-2} \cdot x^2 = 1$$
$$x^{-2}, y = x + C \quad\Longrightarrow\quad y = x^3 + Cx^2$$
oCAS 中的实现. solve_linear_first 提取 $p(x)$ 和 $q(x)$,计算 $\mu = \exp!\bigl(\int p, dx\bigr)$,然后返回
$$y = \mu^{-1}!\left(\int \mu, q, dx + C\right)$$
Bernoulli 方程
形式:
$$y' + p(x), y = q(x), y^n, \qquad n \neq 0,, 1$$
变换. 令 $v = y^{1-n}$,则 $v' = (1-n), y^{-n}, y'$。原方程两边乘以 $(1-n), y^{-n}$:
$$(1-n), y^{-n}, y' + (1-n), p(x), y^{1-n} = (1-n), q(x)$$
即
$$v' + (1-n), p(x), v = (1-n), q(x)$$
这是关于 $v$ 的一阶线性方程,用积分因子法求解后回代 $v = y^{1-n}$。
例子. 求解 $y' + \dfrac{1}{x}, y = x, y^2$($n = 2$):
令 $v = y^{-1}$,$v' = -y^{-2}, y'$,得
$$-v' + \frac{1}{x}, v = x \quad\Longrightarrow\quad v' - \frac{1}{x}, v = -x$$
积分因子 $\mu = x^{-1}$,解得 $v = -x^2 + Cx$,即 $y = \dfrac{1}{Cx - x^2}$。
oCAS 中的实现. solve_bernoulli 检测非线性幂 $n$,构造 $v = y^{1-n}$ 替换,调用 solve_linear_first 求解线性化后的方程,然后回代。
恰当方程(Exact Equations)
形式:
$$M(x,y) + N(x,y), y' = 0$$
若存在势函数 $F(x,y)$ 使得 $\dfrac{\partial F}{\partial x} = M$,$\dfrac{\partial F}{\partial y} = N$,则方程等价于 $\dfrac{d}{dx} F(x, y(x)) = 0$,解为 $F(x,y) = C$。
恰当性判据. 方程恰当当且仅当
$$\frac{\partial M}{\partial y} = \frac{\partial N}{\partial x}$$
求解步骤:
- 由 $\partial F/\partial x = M$ 对 $x$ 积分:$F = \int M, dx + h(y)$
- 由 $\partial F/\partial y = N$ 确定 $h(y)$:$h'(y) = N - \partial/\partial y \int M, dx$
- 代入得 $F(x,y) = C$
非恰当方程的积分因子. 若方程不恰当,可尝试寻找积分因子 $\mu$:
- 若 $\dfrac{M_y - N_x}{N}$ 仅依赖于 $x$,则 $\mu(x) = \exp!\left(\int \dfrac{M_y - N_x}{N}, dx\right)$
- 若 $\dfrac{N_x - M_y}{M}$ 仅依赖于 $y$,则 $\mu(y) = \exp!\left(\int \dfrac{N_x - M_y}{M}, dy\right)$
例子. 求解 $(2xy + 3) + (x^2 + 4y), y' = 0$:
$M_y = 2x = N_x$,方程恰当。
$$F = \int (2xy + 3), dx = x^2 y + 3x + h(y)$$
$$F_y = x^2 + h'(y) = x^2 + 4y \quad\Longrightarrow\quad h'(y) = 4y \quad\Longrightarrow\quad h = 2y^2$$
解:$x^2 y + 3x + 2y^2 = C$。
oCAS 中的实现. solve_exact 首先调用 partials_equal 验证恰当性。若不恰当,find_integrating_factor 尝试上述两种积分因子(依赖于 $x$ 或依赖于 $y$ 的情况)。找到势函数后返回隐式解。
齐次方程(Homogeneous)
形式:
$$y' = f!\left(\frac{y}{x}\right)$$
等价地,方程右端 $f(x,y)$ 是关于 $(x,y)$ 的零次齐次函数:$f(tx, ty) = f(x,y)$。
变换. 令 $v = y/x$,则 $y = vx$,$y' = v + xv'$。代入:
$$v + x, v' = f(v) \quad\Longrightarrow\quad x, v' = f(v) - v$$
这是关于 $v$ 和 $x$ 的可分离方程:
$$\frac{dv}{f(v) - v} = \frac{dx}{x}$$
积分后回代 $v = y/x$。
例子. 求解 $y' = \dfrac{y + x}{x} = \dfrac{y}{x} + 1$:
令 $v = y/x$,$v + xv' = v + 1$,$xv' = 1$,$v = \ln|x| + C$,$y = x\ln|x| + Cx$。
oCAS 中的实现. 分类器 is_homogeneous 要求方程是一阶线性的,且所有加法项关于 $(x, y)$ 具有相同总次数(零次齐次性),并存在不含 $y$ 的项。求解器 solve_homogeneous 执行 $v = y/x$ 替换,化为可分离方程后求解。
二阶线性 ODE
二阶线性 ODE 的一般形式为
$$a(x), y'' + b(x), y' + c(x), y = f(x)$$
叠加原理. 若 $y_1, y_2$ 是齐次方程的两个线性无关解,$y_p$ 是非齐次方程的一个特解,则通解为
$$y = C_1, y_1 + C_2, y_2 + y_p$$
常系数方程(Constant Coefficients)
形式:
$$a, y'' + b, y' + c, y = f(x), \qquad a, b, c \in \mathbb{R},; a \neq 0$$
特征方程. 齐次方程 $ay'' + by' + cy = 0$ 的特征方程为
$$a, r^2 + b, r + c = 0$$
判别式 $\Delta = b^2 - 4ac$ 决定了解的形式:
| 判别式 | 特征根 | 齐次通解 |
|---|---|---|
| $\Delta > 0$ | $r_1, r_2 \in \mathbb{R},; r_1 \neq r_2$ | $C_1, e^{r_1 x} + C_2, e^{r_2 x}$ |
| $\Delta = 0$ | $r_1 = r_2 = r$ | $(C_1 + C_2, x), e^{rx}$ |
| $\Delta < 0$ | $\alpha \pm \beta i$ | $e^{\alpha x}(C_1 \cos\beta x + C_2 \sin\beta x)$ |
待定系数法(Undetermined Coefficients). 当 $f(x)$ 是多项式、指数函数 $e^{kx}$、三角函数 $\sin\omega x$、$\cos\omega x$ 或其乘积/和时,可以猜测特解形式。
| $f(x)$ 的形式 | 猜测的 $y_p$ 形式 |
|---|---|
| $P_n(x)$($n$ 次多项式) | $Q_n(x) = a_0 + a_1 x + \cdots + a_n x^n$ |
| $F, e^{kx}$ | $A, e^{kx}$ |
| $f_c \cos\omega x + f_s \sin\omega x$ | $A\cos\omega x + B\sin\omega x$ |
| $F, e^{kx} \cos\omega x$ | $e^{kx}(A\cos\omega x + B\sin\omega x)$ |
共振(resonance). 当猜测形式与齐次解线性相关时,需乘以 $x^s$($s$ 为 $k$ 作为特征根的重数):
- 若 $k$ 是单特征根:$y_p = Ax, e^{kx}$
- 若 $k$ 是二重特征根:$y_p = Ax^2, e^{kx}$
对于多项式强迫,若 $r = 0$ 是 $s$ 重特征根,则猜测形式需乘以 $x^s$。
例子. 求解 $y'' - 3y' + 2y = e^{3x}$:
特征方程 $r^2 - 3r + 2 = 0$,$r_1 = 1, r_2 = 2$。
齐次通解:$C_1 e^x + C_2 e^{2x}$。
$k = 3$ 不是特征根,猜测 $y_p = Ae^{3x}$。代入:$9A - 9A + 2A = 1$,$A = 1/2$。
通解:$y = C_1 e^x + C_2 e^{2x} + \dfrac{1}{2} e^{3x}$。
oCAS 中的实现. solve_constant_coeff 提取系数 $(a, b, c)$ 和强迫项 $f(x)$。constant_coeff_basis 根据判别式构造齐次基解。particular_solution_undetermined 处理多项式、指数和三角强迫,包含完整的共振检测。
Cauchy–Euler 方程
形式:
$$a, x^2, y'' + b, x, y' + c, y = f(x)$$
系数中 $y^{(k)}$ 的系数恰好是 $x^k$ 的常数倍。
变换. 令 $x = e^t$(即 $t = \ln x$),利用
$$y' = \frac{1}{x}, \frac{dy}{dt}, \qquad y'' = \frac{1}{x^2}!\left(\frac{d^2 y}{dt^2} - \frac{dy}{dt}\right)$$
原方程化为常系数方程
$$a, \ddot{y} + (b - a), \dot{y} + c, y = f(e^t)$$
其中 $\dot{y} = dy/dt$。
指标方程. 对齐次方程,直接猜测 $y = x^r$,代入得指标方程
$$a, r(r-1) + b, r + c = 0 \quad\Longleftrightarrow\quad a, r^2 + (b - a), r + c = 0$$
解的结构与常系数方程类似(根据判别式分三种情况),但基本解为 $x^r$ 而非 $e^{rx}$:
| 判别式 | 基本解 |
|---|---|
| $\Delta > 0$ | $x^{r_1},; x^{r_2}$ |
| $\Delta = 0$ | $x^r,; x^r \ln x$ |
| $\Delta < 0$($\alpha \pm \beta i$) | $x^\alpha \cos(\beta\ln x),; x^\alpha \sin(\beta\ln x)$ |
例子. 求解 $x^2 y'' - 2xy' + 2y = 0$:
指标方程:$r(r-1) - 2r + 2 = r^2 - 3r + 2 = 0$,$r_1 = 1, r_2 = 2$。
通解:$y = C_1 x + C_2 x^2$。
oCAS 中的实现. 分类器 is_cauchy_euler 检查方程是否符合 $c_k x^k y^{(k)}$ 的模式。solve_cauchy_euler 提取 Cauchy–Euler 系数,解指标方程,然后根据判别式构造通解。cauchy_euler_basis 处理三种判别式情况。
降阶法(Reduction of Order)
当已知齐次方程的一个解 $y_1$ 时,第二个线性无关解可通过降阶获得。
方法. 设 $y_2 = v(x), y_1$,代入 $y'' + p, y' + q, y = 0$。利用 $y_1$ 满足齐次方程的条件,化简后得到关于 $v'$ 的一阶方程:
$$v'' y_1 + v'(2y_1' + p, y_1) = 0$$
令 $w = v'$,分离变量:
$$\frac{w'}{w} = -\frac{2y_1' + p, y_1}{y_1} = -2, \frac{y_1'}{y_1} - p$$
积分得
$$w = \frac{e^{-\int p, dx}}{y_1^2}$$
因此
$$y_2 = y_1 \int \frac{e^{-\int p, dx}}{y_1^2}, dx$$
例子. $x^2 y'' - 2xy' + 2y = 0$,已知 $y_1 = x$。
标准形:$y'' - \dfrac{2}{x}, y' + \dfrac{2}{x^2}, y = 0$,$p = -2/x$。
$$y_2 = x \int \frac{e^{\int 2/x, dx}}{x^2}, dx = x \int \frac{x^2}{x^2}, dx = x \cdot x = x^2$$
oCAS 中的实现. solve_reduction_of_order 首先尝试简单候选解($1, x, x^2, e^x, e^{-x}, e^{2x}$)代入齐次方程。找到 $y_1$ 后,用上述公式计算 $y_2$。对于非齐次方程,再用参数变易法求特解。
参数变易法(Variation of Parameters)
当待定系数法不适用(如 $f(x)$ 不是标准形式)时,参数变易法是通用方法。
方法. 已知齐次方程的两个线性无关解 $y_1, y_2$,设非齐次方程的特解为
$$y_p = u_1(x), y_1 + u_2(x), y_2$$
其中 $u_1, u_2$ 满足
$$\begin{cases} u_1', y_1 + u_2', y_2 = 0 \ u_1', y_1' + u_2', y_2' = g(x) \end{cases}$$
这里 $g(x) = f(x)/a$ 是标准形的右端。由 Cramer 法则:
$$u_1' = -\frac{y_2, g}{W}, \qquad u_2' = \frac{y_1, g}{W}$$
其中 $W = y_1, y_2' - y_1', y_2$ 是 Wronskian。
因此
$$y_p = -y_1 \int \frac{y_2, g}{W}, dx + y_2 \int \frac{y_1, g}{W}, dx$$
Wronskian 的意义. $y_1, y_2$ 线性无关当且仅当 $W \neq 0$。对于二阶齐次线性 ODE,Abel 公式给出 $W(x) = W(x_0), \exp!\bigl(-\int_{x_0}^x p, dt\bigr)$,因此 $W$ 要么恒零要么恒非零。
oCAS 中的实现. variation_of_parameters 接受两个基解 $y_1, y_2$ 和标准形右端 $g$,计算 Wronskian,然后用 oCAS 的符号积分器(integrate)计算 $u_1, u_2$。若积分无法化为闭形(返回未求值的 Integral(...) 形式),则放弃此方法。
级数解
当 ODE 的系数不是常数且无法用初等方法求解时,级数解法提供了构造解的系统方法。
幂级数解法(Ordinary Point)
适用条件. 考虑二阶线性 ODE
$$y'' + p(x), y' + q(x), y = 0$$
若 $p(x)$ 和 $q(x)$ 在 $x = x_0$ 处解析(即可以展开为收敛幂级数),则 $x_0$ 是常点(ordinary point),方程在 $x_0$ 附近有两个线性无关的解析解。
方法. 设
$$y = \sum_{n=0}^{\infty} a_n (x - x_0)^n$$
则
$$y' = \sum_{n=1}^{\infty} n, a_n (x - x_0)^{n-1}, \qquad y'' = \sum_{n=2}^{\infty} n(n-1), a_n (x - x_0)^{n-2}$$
将 $y, y', y''$ 和 $p(x), q(x)$ 的幂级数展开代入方程,合并同次幂,令各次幂系数为零,得到关于 $a_n$ 的递推关系。
对于 $k$ 阶 ODE,初始系数 $a_0, a_1, \dots, a_{k-1}$ 是自由参数(对应通解中的 $k$ 个任意常数),后续系数由递推确定。
例子. 求解 $y'' - xy = 0$(Airy 方程)在 $x = 0$ 附近的级数解:
$$\sum_{n=2}^{\infty} n(n-1)a_n x^{n-2} - x \sum_{n=0}^{\infty} a_n x^n = 0$$
$$\sum_{n=0}^{\infty} (n+2)(n+1)a_{n+2} x^n - \sum_{n=0}^{\infty} a_n x^{n+1} = 0$$
$x^0$ 项:$2 \cdot 1 \cdot a_2 = 0$,$a_2 = 0$。
$x^n$($n \geq 1$)项:$(n+2)(n+1), a_{n+2} - a_{n-1} = 0$,即
$$a_{n+2} = \frac{a_{n-1}}{(n+2)(n+1)}$$
取 $a_0 = 1, a_1 = 0$:$a_3 = a_0/(3 \cdot 2) = 1/6$,$a_6 = a_3/(6 \cdot 5) = 1/180$,……
取 $a_0 = 0, a_1 = 1$:$a_4 = a_1/(4 \cdot 3) = 1/12$,$a_7 = a_4/(7 \cdot 6) = 1/504$,……
oCAS 中的实现. solve_power_series 设置 $y = \sum a_n x^n$,构造 $y, y', y''$ 的级数三元组(build_series_triple),代入方程,按 $x$ 的幂次分组,解线性方程求各 $a_n$(solve_linear_coeff)。返回截断级数 $ODESolution::Series(expr, n_terms)$。
Frobenius 方法(Regular Singular Point)
适用条件. 若 $x = x_0$ 不是常点,但 $(x - x_0), p(x)$ 和 $(x - x_0)^2, q(x)$ 在 $x_0$ 处解析,则 $x_0$ 是正则奇点(regular singular point)。
方法. 设
$$y = x^r \sum_{n=0}^{\infty} a_n x^n = \sum_{n=0}^{\infty} a_n x^{n+r}, \qquad a_0 \neq 0$$
代入方程,最低次幂 $x^{r-2}$(对于 Cauchy–Euler 型的主部)的系数给出指标方程(indicial equation):
$$A, r(r-1) + B, r + C = 0$$
其中 $A, B, C$ 由方程系数在 $x_0$ 处的值确定。后续幂次给出 $a_n$ 的递推关系。
指标根的关系. 设指标方程的两个根为 $r_1, r_2$($r_1 \geq r_2$):
| 根的情况 | 解的形式 |
|---|---|
| $r_1 - r_2 \notin \mathbb{Z}$ | 两个独立级数解 $y_1 = x^{r_1}\sum a_n x^n$,$y_2 = x^{r_2}\sum b_n x^n$ |
| $r_1 = r_2$ | $y_1 = x^{r_1}\sum a_n x^n$,$y_2 = y_1 \ln x + x^{r_1}\sum c_n x^n$ |
| $r_1 - r_2 \in \mathbb{Z}^+$ | $y_1$ 如上,$y_2$ 可能含 $\ln x$ 项(对数情形) |
例子. 求解 $x^2 y'' + xy' + (x^2 - 1)y = 0$(Bessel 方程,$\nu = 1$)在 $x = 0$ 附近:
$p(x) = 1/x$,$q(x) = 1 - 1/x^2$。$x = 0$ 是正则奇点。
设 $y = \sum a_n x^{n+r}$,代入后 $x^r$ 的系数为
$$a_0[r(r-1) + r - 1] = a_0(r^2 - 1) = 0$$
指标方程:$r^2 - 1 = 0$,$r_1 = 1, r_2 = -1$。
$r_1 - r_2 = 2 \in \mathbb{Z}$,需检查对数情形。
oCAS 中的实现. solve_frobenius 设置 $y = x^r \sum a_n x^n$($r$ 为符号变量),代入方程,按 $x$ 的幂次分组。最低幂次组通过 indicial_coeffs 提取指标方程 $Ar^2 + Br + C = 0$ 的整数系数。解出 $r$ 后,后续组递推确定 $a_n$。目前仅处理指标方程有有理实根的情况,返回较大根对应的级数。
Laplace 变换
Laplace 变换是求解线性常系数 ODE 初值问题的强有力工具,尤其适用于分段连续或含有脉冲函数的强迫项。
定义
函数 $f(x)$($x \geq 0$)的 Laplace 变换定义为
$$\mathcal{L}{f}(s) = F(s) = \int_0^{\infty} e^{-sx}, f(x), dx$$
导数的变换
利用分部积分:
$$\mathcal{L}{y'}(s) = s, Y(s) - y(0)$$
$$\mathcal{L}{y''}(s) = s^2, Y(s) - s, y(0) - y'(0)$$
一般地
$$\mathcal{L}{y^{(n)}}(s) = s^n, Y(s) - \sum_{k=0}^{n-1} s^{n-1-k}, y^{(k)}(0)$$
常用变换对
| $f(x)$ | $F(s) = \mathcal{L}{f}(s)$ |
|---|---|
| $1$ | $\dfrac{1}{s}$ |
| $x^n$ | $\dfrac{n!}{s^{n+1}}$ |
| $e^{kx}$ | $\dfrac{1}{s - k}$ |
| $\cos\omega x$ | $\dfrac{s}{s^2 + \omega^2}$ |
| $\sin\omega x$ | $\dfrac{\omega}{s^2 + \omega^2}$ |
| $e^{kx}\cos\omega x$ | $\dfrac{s - k}{(s-k)^2 + \omega^2}$ |
| $e^{kx}\sin\omega x$ | $\dfrac{\omega}{(s-k)^2 + \omega^2}$ |
| $x, e^{kx}$ | $\dfrac{1}{(s-k)^2}$ |
IVP 求解步骤
以二阶方程 $ay'' + by' + cy = f(x)$,$y(0) = y_0$,$y'(0) = y_1$ 为例:
- 变换:对方程两边取 Laplace 变换
$$a[s^2 Y - sy_0 - y_1] + b[sY - y_0] + cY = F(s)$$
- 解代数方程:
$$Y(s) = \frac{F(s) + a(sy_0 + y_1) + by_0}{as^2 + bs + c}$$
-
部分分式分解:将 $Y(s)$ 分解为标准变换对的线性组合
-
逆变换:查表得到 $y(x) = \mathcal{L}^{-1}{Y(s)}(x)$
逆变换的关键技术. 对于分母为二次多项式的有理函数:
-
互异实根 $r_1 \neq r_2$:$\dfrac{n_1 s + n_0}{(s-r_1)(s-r_2)} = \dfrac{A}{s-r_1} + \dfrac{B}{s-r_2}$,$A = \dfrac{n_1 r_1 + n_0}{r_1 - r_2}$
-
重根 $r$:$\dfrac{n_1 s + n_0}{(s-r)^2} = \dfrac{n_1}{s-r} + \dfrac{n_1 r + n_0}{(s-r)^2}$
-
共轭复根 $k \pm i\omega$:$\dfrac{n_1 s + n_0}{(s-k)^2 + \omega^2} = e^{kx}!\left[n_1 \cos\omega x + \dfrac{n_0 + kn_1}{\omega}\sin\omega x\right]$
例子. 求解 $y'' - 3y' + 2y = 4$,$y(0) = 2$,$y'(0) = 3$:
$$s^2 Y - 2s - 3 - 3(sY - 2) + 2Y = \frac{4}{s}$$
$$(s^2 - 3s + 2)Y = \frac{4}{s} + 2s - 3 = \frac{2s^2 - 3s + 4}{s}$$
$$Y = \frac{2s^2 - 3s + 4}{s(s-1)(s-2)}$$
部分分式:$Y = \dfrac{2}{s} - \dfrac{3}{s-1} + \dfrac{3}{s-2}$。
逆变换:$y(x) = 2 - 3e^x + 3e^{2x}$。
验证:$y(0) = 2$,$y'(0) = -3 + 6 = 3$,且 $y'' - 3y' + 2y = 4$ ✓。
oCAS 中的实现. dsolve_ivp 是 IVP 求解的入口,内部调用 solve_laplace。
solve_laplace 的工作流程:
- 判断方程为一阶或二阶线性常系数方程
- 提取系数 $(a, b, c)$ 和强迫项 $f(x)$
laplace_transform计算 $\mathcal{L}{f}(s)$ — 支持多项式、指数、三角函数及其乘积- 构造 $Y(s) = \dfrac{F(s) + \text{初值项}}{as^2 + bs + c}$
inverse_laplace通过split_fraction分解有理函数,对每项调用inverse_distinct_roots、inverse_repeated_root或inverse_complex_roots- 返回 $ODESolution::Explicit(y(x))$(不含自由常数)
2×2 常系数线性方程组
考虑一阶常系数线性方程组
$$\mathbf{Y}' = A, \mathbf{Y}, \qquad \mathbf{Y} = \begin{pmatrix} y_1 \ y_2 \end{pmatrix}, \quad A = \begin{pmatrix} a_{11} & a_{12} \ a_{21} & a_{22} \end{pmatrix}$$
通解:矩阵指数
形式解为 $\mathbf{Y}(x) = e^{Ax}, \mathbf{Y}(0)$。实际求解依赖于 $A$ 的特征值。
特征值分解
$A$ 的特征方程为
$$\lambda^2 - (a_{11} + a_{22})\lambda + (a_{11}a_{22} - a_{12}a_{21}) = 0$$
设特征值为 $\lambda_1, \lambda_2$,对应特征向量为 $\mathbf{v}_1, \mathbf{v}_2$。
情形 1:互异实特征值($\lambda_1 \neq \lambda_2 \in \mathbb{R}$)
$$\mathbf{Y} = C_1, e^{\lambda_1 x}, \mathbf{v}_1 + C_2, e^{\lambda_2 x}, \mathbf{v}_2$$
情形 2:重特征值($\lambda_1 = \lambda_2 = \lambda$)
若 $A$ 不可对角化(只有一个线性无关特征向量 $\mathbf{v}$),需要广义特征向量 $\mathbf{w}$ 满足 $(A - \lambda I)\mathbf{w} = \mathbf{v}$:
$$\mathbf{Y} = C_1, e^{\lambda x}, \mathbf{v} + C_2, e^{\lambda x}(x, \mathbf{v} + \mathbf{w})$$
情形 3:共轭复特征值($\lambda = \alpha \pm \beta i$,$\beta \neq 0$)
特征向量也有实部和虚部:$\mathbf{v} = \mathbf{p} + i\mathbf{q}$。实值通解为
$$\mathbf{Y} = e^{\alpha x}\bigl[C_1(\mathbf{p}\cos\beta x - \mathbf{q}\sin\beta x) + C_2(\mathbf{p}\sin\beta x + \mathbf{q}\cos\beta x)\bigr]$$
例子. 求解谐振子系统 $y_1' = y_2,; y_2' = -y_1$:
$$A = \begin{pmatrix} 0 & 1 \ -1 & 0 \end{pmatrix}$$
特征方程:$\lambda^2 + 1 = 0$,$\lambda = \pm i$($\alpha = 0, \beta = 1$)。
$\lambda = i$ 的特征向量:$(A - iI)\mathbf{v} = 0$,$\mathbf{v} = (1, i)^T$,$\mathbf{p} = (1, 0)^T$,$\mathbf{q} = (0, 1)^T$。
$$\mathbf{Y} = C_1 \begin{pmatrix} \cos x \ -\sin x \end{pmatrix} + C_2 \begin{pmatrix} \sin x \ \cos x \end{pmatrix}$$
即 $y_1 = C_1\cos x + C_2\sin x$,$y_2 = -C_1\sin x + C_2\cos x$。
oCAS 中的实现. solve_linear_system 处理 $2 \times 2$ 常系数系统:
- 从方程组中提取矩阵 $A$ 的各元素(
extract_coeff) - 计算特征多项式,求特征值(整数或有理数范围内)
- 对互异实特征值:
eigenvector求整数特征向量 - 对重特征值:
generalized_eigenvector求广义特征向量 $(A - \lambda I)\mathbf{w} = \mathbf{v}$ - 对复特征值:
solve_complex_2x2构造实值基本解 - 返回 $ODESolution::System([y_1, y_2])$,其中含自由常数 $C_1, C_2$
在 oCAS 中的实现
分类器(classify.rs)
classify_ode 是 ODE 求解的入口决策器。它分析方程结构,返回按优先级排列的候选方法列表:
| 优先级 | ODEType | 检测条件 |
|---|---|---|
| 1 | LinearFirst | 一阶,$y$ 和 $y'$ 线性出现,无 $y^2, \sin y$ 等非线性项,且含 $y$ |
| 2 | Bernoulli | 一阶,含 $y^n$($n \geq 2$)非线性项 |
| 3 | Separable | 一阶,$y$ 的项可与 $y'$ 分组,其余项仅含 $x$ |
| 4 | Exact | 一阶,$M + Ny' = 0$,$\partial M/\partial y = \partial N/\partial x$ |
| 5 | Homogeneous | 一阶线性,所有加法项在 $(x,y)$ 上具有相同总次数(零次齐次),且存在不含 $y$ 的项 |
| 6 | LinearConstantCoeff | $\geq 2$ 阶,线性,各阶导数的系数不含自变量 |
| 7 | CauchyEuler | $\geq 2$ 阶,$y^{(k)}$ 的系数为 $c_k x^k$ |
| 8 | ReductionOfOrder | 二阶线性,非上述类型 |
| 9 | PowerSeries | 线性 ODE 的回退选项(先尝试常点幂级数,再尝试 Frobenius) |
dsolve 按此顺序依次尝试各方法,第一个成功的即返回结果。用户也可通过 hint 参数指定具体方法。
求解器架构
dsolve(ctx, ode, hint?)
├── normalize_ode() // 化简方程
├── classify_ode() // 返回候选方法列表
└── 依次尝试:
├── first_order::solve_linear_first()
├── first_order::solve_bernoulli()
├── first_order::solve_separable()
├── first_order::solve_exact()
├── first_order::solve_homogeneous()
├── second_order::solve_constant_coeff()
├── second_order::solve_cauchy_euler()
├── second_order::solve_reduction_of_order()
└── series::solve_power_series() 或 series::solve_frobenius()
dsolve_ivp(ctx, ode, y0, y1?)
└── laplace::solve_laplace() // Laplace 变换法
dsolve_system(ctx, equations, funcs, var)
└── systems::solve_linear_system() // 2×2 特征值分解
返回值类型
ODESolution 枚举涵盖所有可能的输出形式:
| 变体 | 含义 | 使用场景 |
|---|---|---|
Explicit(expr) | $y = \text{expr}$ | 大多数成功求解 |
Implicit(expr) | $F(x,y) = 0$ | 可分离方程(隐式解) |
Parametric(x_t, y_t) | $(x(t), y(t))$ | 参数形式解 |
Series(expr, n_terms) | 截断级数 | 幂级数 / Frobenius |
System([y1, y2]) | 各分量 | $2 \times 2$ 方程组 |
Unsolved(ode) | 原方程 | 所有方法均失败 |
与符号积分的集成
ODE 求解器深度依赖 oCAS 的符号积分能力:
- 一阶线性:积分因子 $\mu = \exp(\int p, dx)$ 和最终积分 $\int \mu, q, dx$
- Bernoulli:线性化后的积分因子
- 恰当方程:势函数的二重积分
- 齐次方程:分离后的积分
- 参数变易:$\int y_i, g / W, dx$
- 降阶法:$\int e^{-\int p, dx} / y_1^2, dx$
这些积分调用 oCAS 的分层积分管线(有理函数 → Risch → 三角 → 特殊函数 → 启发式),详见符号微积分和 Risch 积分算法。
局限性
当前实现的已知限制:
- 二阶以上:仅支持线性常系数和 Cauchy–Euler 型。一般的变系数高阶方程无专用求解器。
- 非线性 ODE:除 Bernoulli(可线性化)外,非线性方程通常返回
Unsolved。 - Laplace 变换:仅处理整数系数的常系数方程,且强迫项必须有表格变换。
- 级数解:默认展开 $x_0 = 0$ 处 8 项。Frobenius 方法仅处理指标方程有有理实根的情况。
- 2×2 系统:仅支持常系数,且特征值需为整数或有理数。
- 验证:
verify_solution将解代入原方程检查残差是否为零(仅用于测试)。
参考文献
-
Boyce, W. E. & DiPrima, R. C. Elementary Differential Equations and Boundary Value Problems, 11th ed., Wiley, 2017. — Chapters 2–7 cover first-order equations (Ch.2), second-order linear (Ch.3–4), series solutions (Ch.5), Laplace transform (Ch.6), and systems (Ch.7).
-
Coddington, E. A. & Levinson, N. Theory of Ordinary Differential Equations, McGraw-Hill, 1955. — Rigorous treatment of existence, uniqueness, and stability.
-
Ince, E. L. Ordinary Differential Equations, Dover, 1956. — Classical reference on special functions and series methods.
-
Zill, D. G. A First Course in Differential Equations with Modeling Applications, 12th ed., Cengage, 2021. — Accessible introduction with applications.
-
Tenenbaum, M. & Pollard, H. Ordinary Differential Equations, Dover, 1985. — Comprehensive classical methods including exact equations and integrating factors.
高阶:Risch 积分算法
前提知识
- 多项式代数 — 多项式环、因式分解、结式
- 有限域与模算术 — 模逆与扩展 Euclid 算法
- 线性代数 — 高斯消元与行列式
- 符号微积分 — 求导规则与链式法则
- 多项式 GCD 与因式分解 — 无平方分解与 Hensel 提升
基础概念
初等函数的精确定义
"初等函数"一词在微积分课本中往往凭直觉使用。Risch 算法需要严格定义:
定义 1(常数域). 令 $k$ 为一个特征零的微分域,导子记为 $D$。最基本的常数域是 $\mathbb{Q}$,其上 $D = 0$。
定义 2(初等扩张). 设 $k$ 为微分域,$K$ 为 $k$ 的扩域。称 $K/k$ 是一个初等扩张(elementary extension),如果 $K$ 可由以下三种类型的有限扩张逐层生成:
- 代数扩张:$K = k(\alpha)$,其中 $\alpha$ 在 $k$ 上代数(即 $\alpha$ 是某个 $k[x]$ 中多项式的根)。例如 $\mathbb{Q}(\sqrt{2})/\mathbb{Q}$。
- 对数扩张:$K = k(t)$,其中 $Dt = Du / u$ 对某个 $u \in k^*$,即 $t = \log(u)$。导子规则:$D(\log u) = Du/u$。
- 指数扩张:$K = k(t)$,其中 $Dt / t = Du$ 对某个 $u \in k$,即 $t = \exp(u)$。导子规则:$D(\exp u) = \exp(u) \cdot Du$。
定义 3(初等函数). 域 $k$ 上的初等函数是某个初等扩张塔 $k = k_0 \subset k_1 \subset \cdots \subset k_n$ 中的元素,其中每个 $k_{i+1}/k_i$ 是上述三种类型之一。
定理(Liouville, 1835). 设 $f$ 为 $k$ 上的初等函数。$f$ 在 $k$ 上有初等原函数,当且仅当它可以写成
$$f = Dv + \sum_{i=1}^{n} c_i \cdot \frac{Du_i}{u_i}$$
其中 $v, u_1, \dots, u_n \in k$,$c_1, \dots, c_n$ 为 $D$-常数(即 $Dc_i = 0$)。
这个定理的深刻意义在于:初等函数的原函数如果存在,其"新"的部分只可能是常数倍的对数。这是 Risch 算法的理论基础。
微分域
定义 4(微分域). 一个微分域(differential field)是一个域 $k$ 配备一个导子(derivation)$D: k \to k$,满足:
- 加法性:$D(a + b) = Da + Db$
- Leibniz 法则:$D(a \cdot b) = a \cdot Db + Da \cdot b$
导子的核 ${c \in k : Dc = 0}$ 称为常数域(field of constants),记为 $C_k$ 或 $k^D$。
在 Risch 算法的语境下:
- 基域 $k_0 = \mathbb{Q}(x)$,$D = d/dx$(关于积分变量 $x$ 的导数),常数域 $C_{k_0} = \mathbb{Q}$。
- 对数扩张 $k(t) = k(\log u)$,$Dt = Du/u$。
- 指数扩张 $k(t) = k(\exp u)$,$Dt = t \cdot Du$。
关键事实:在初等扩张塔中,常数域保持不变:$C_{k_n} = C_{k_0} = \mathbb{Q}$。这个性质在算法中被隐式使用——当我们说"有理常数"时,就是 $\mathbb{Q}$ 中的元素。
核心理论
微分域塔的构造
给定被积函数 $f$(一个初等函数表达式),Risch 算法的第一步是构造它所"生活"的微分域塔。
算法 build_tower(f, var):
- 收集函数符号:扫描 $f$ 的表达式树,收集所有函数应用(
log、exp)。 - 排序:按依赖关系排序——若 $t_2 = \exp(\log(x) + 1)$ 则 $t_1 = \log(x)$ 必须在 $t_2$ 之前。
- 代数依赖检测:对每个候选生成元 $t_i$,检查它是否在已有塔上代数相关。保守策略:
- $\log(c \cdot u)$ 与 $\log(u)$($c$ 为有理常数)→ 代数相关($\log(c \cdot u) = \log(u) + \log(c)$)
- $\exp(u + c)$ 与 $\exp(u)$($c$ 为有理常数)→ 代数相关($\exp(u + c) = e^c \cdot \exp(u)$)
- 0.28.0 起:不再拒绝,而是用精确域恒等式合并(
tower/merge.rs):exp(u+c) = e^c·exp(u)、exp(-u) = exp(u)^{-1}、log(cu) = log(u) + log(c)、log(u^k) = k·log(u)、log(exp(u)) = u、exp(log(u)) = u,必要时登记常量生成元(log(2)、exp(1),D t = 0);塔返回重写后的被积表达式,判定不了的关系仍然诚实拒绝
- 拒绝非整数幂:若 $f$ 含有 $\sqrt{x}$ 等非整数幂(即代数函数),拒绝(返回
None)。 - 计算导数:对每个生成元 $t_i$,计算 $Dt_i$(关于 $D = d/dx$):
- $t_i = \log(u)$:$Dt_i = Du / u$(递归计算 $Du$)
- $t_i = \exp(u)$:$Dt_i = t_i \cdot Du$
在 oCAS 中,build_tower 位于 ocas-calc/src/tower/build.rs。它返回一个 Tower 结构体,包含生成元列表 gens: Vec<GenInfo>(每个含 kind、原子引用、导数)和常数域信息。
域元素的表示:塔中的元素使用稀疏多元多项式(SparseMultivariatePolynomial<RationalDomain, Lex>)的分子/分母对表示:
KElem(k-元素):{ num: Sparse, den: Sparse },表示 $k_\ell$ 中的一个元素 $p/q$KPoly(k-多项式):关于顶层生成元 $t_\ell$ 的一元多项式,系数是KElemKRat(k-有理函数):{ num: KPoly, den: KPoly },表示 $k_\ell(t_\ell)$ 中的一个有理函数
例子. 对 $f = x \cdot \exp(x) + \log(x)$:
| 层级 | 域 | 生成元 | 导子 |
|---|---|---|---|
| $k_0$ | $\mathbb{Q}(x)$ | $x$ | $Dx = 1$ |
| $k_1$ | $k_0(t_1)$,$t_1 = \log(x)$ | $t_1$ | $Dt_1 = 1/x$ |
| $k_2$ | $k_1(t_2)$,$t_2 = \exp(x)$ | $t_2$ | $Dt_2 = t_2$ |
逐层积分:总框架
Risch 算法的核心是自顶向下逐层递归。在塔的每一层 $\ell$,被积函数 $f$ 被视为 $k_{\ell-1}(t_\ell)$ 中的一个有理函数。
算法 integrate_level(tower, level, f):
设 $f = a/d \in k_\ell(t_\ell)$,其中 $a, d \in k_\ell[t_\ell]$,$\gcd(a, d) = 1$。
- Hermite 约化(分离有理部分):将 $a/d$ 分解为
$$\frac{a}{d} = Dg + \frac{a_1}{d_1}$$
其中 $g \in k_\ell(t_\ell)$ 有显式原函数,$d_1$ 无平方。
-
积分多项式部分:若 $a/d$ 的多项式部分为 $p(t_\ell) \in k_\ell[t_\ell]$,则按生成元类型积分:
- 原始层($t_\ell = \log u$):待定系数法
- 超指数层($t_\ell = \exp u$):Risch 微分方程
-
积分对数部分:对 $a_1/d_1$($d_1$ 无平方),先检查对数导数恒等式(快路径),未命中则走 Rothstein–Trager 结式方法(0.29.0 起,仅限 $\mathbb{Q}$ 根;无理数根与非常量根诚实拒绝)。
-
基域 $\mathbb{Q}(x)$:委托给有理函数积分器。
结果组装为 LevelResult { elem, logs, extras },其中 elem 是域元素部分,logs 是 $\sum c_i \log(v_i)$ 形式的对数项,extras 是未能积分的部分。
0.29.0 新增(双曲指数层的 Laurent 拆分):在 $t_\ell = \exp u$ 层,$t$ 整除自身导数($Dt = Du \cdot t$),即 $t$ 是 special 多项式,含 $t$ 幂因子的分母不走 Hermite 约化($\gcd(v, Dv) = 1$ 的前提不成立,0.28.0 的塔内校验曾因此抓到一个负幂次系数缩放的潜伏错案)。现在先把分母拆为 $t^k \cdot d'$($\gcd(d', t) = 1$),$t^k$ 部分按负幂层逐层解 Risch 微分方程,仅 $d'$ 进 Hermite。
Hermite 约化
目标:将有理函数 $a/d$ 分解为"已积"部分加"更简单"的剩余(剩余部分的分母无平方)。
算法(Bronstein Ch. 2 的 Hermite 约化;oCAS 采用无平方分解递推形式):
设 $a/d$ 是一个真分式($\deg a < \deg d$),$d$ 为 monic 多项式。
- 计算 $d$ 的无平方分解 $d = \prod_j v_j^{m_j}$。若最大重数 $m \leq 1$,则 $d$ 已无平方,直接返回 $(0, a, d)$。
- 选取重数最大($= m \geq 2$)的因子 $v$,写 $d = u \cdot v^m$。
- 令 $B = u \cdot Dv$(塔导子下的导数;基域上即 $u \cdot v'$),用扩展 Euclid 算法求 $s, t$ 使得 $s \cdot B + t \cdot v = a$($s$ 取对 $v$ 的模余式)。
- 递推公式: $$\frac{a}{u, v^m} = D!\left(\frac{-s}{(m-1), v^{m-1}}\right) + \frac{t + u \cdot D\bigl(s/(m-1)\bigr)}{u, v^{m-1}}$$
- 对剩余项 $\dfrac{t + u \cdot D(s/(m-1))}{u, v^{m-1}}$ 递归执行,直到分母无平方。
其中 $D!\left(\frac{-s}{(m-1) v^{m-1}}\right)$ 有显式原函数 $\frac{-s}{(m-1) v^{m-1}}$ 本身,最终剩余 $a_1/d_1$ 的分母无平方。每次迭代将最大重数降低一次,故至多迭代 $m$ 次。
在 oCAS 中,Hermite 约化在两个地方实现:
ocas-calc/src/integral/rational.rs:基域 $\mathbb{Q}(x)$ 上的 Hermite 约化(hermite_reduce)ocas-calc/src/integral/risch.rs:任意塔层上的 Hermite 约化(hermite_tower)
例子. $\int \frac{1}{(x+1)^2}, dx$:
$d = (x+1)^2$,其无平方分解为 $(x+1)^2$,最大重数 $m = 2$,取 $v = x+1$,$u = 1$。$B = u \cdot Dv = 1$,解 $s \cdot B + t \cdot v = 1$ 得 $s = 1$(对 $v$ 取模),$t = 0$。递推公式给出:
$$\frac{1}{(x+1)^2} = D!\left(\frac{-1}{x+1}\right) + \frac{0}{x+1}$$
即 $\int = -1/(x+1)$。更一般地,$\int a/(x+1)^2$ 总能用 Hermite 约化处理。
对数导数恒等式
核心思想:形如 $c \cdot \frac{Du}{u}$ 的项有平凡原函数 $c \cdot \log(u)$。
定理(对数导数恒等式). 设 $f \in k_\ell(t_\ell)$,$d_1$ 无平方。若
$$\frac{a_1}{d_1} = c \cdot \frac{D d_1}{d_1}$$
对某个 $D$-常数 $c$,则
$$\int \frac{a_1}{d_1} = c \cdot \log(d_1)$$
更一般地,对无平方分母 $d_1$,对数部分的所有贡献形如 $\sum_i c_i \log(v_i)$,其中 $v_i$ 由 $d_1$ 的不可约因子给出。
判断方法:在 $\mathbb{Q}(x)$ 基域上,检查 $a_1$ 是否等于某个有理常数 $c$ 乘以 $d_1'$。在高阶塔上,检查 $a_1$ 是否是系数域中某个常数乘以 $d_1'$。
更精确的方法(Rothstein–Trager 结式):对一般无平方分母 $d(x)$,$\int a(x)/d(x),dx$ 的对数部分由 Rothstein–Trager 结式给出:
$$R(t) = \text{Res}_x!\bigl(d(x),; a(x) - t \cdot d'(x)\bigr)$$
设 $R(t)$ 在 $\mathbb{Q}$ 上的根为 $c_1, \dots, c_m$,则
$$\int \frac{a}{d},dx = \sum_{i=1}^{m} c_i \cdot \log\gcd(d,; a - c_i \cdot d') + C$$
在 oCAS 的实现中(rothstein_trager),$R(t)$ 通过插值计算——在 $t = 0, 1, 2, \dots$ 处求结式值,然后 Lagrange 插值恢复多项式。当 $R(t)$ 在 $\mathbb{Q}$ 上不完全分裂时,对应项以未求值形式 Integral(term, var) 返回。
二次分母的特殊情况:当 $d(x) = x^2 + bx + c$(不可约)时,配方(completing the square)给出:
$$\int \frac{Ax + B}{x^2 + bx + c},dx = \frac{A}{2}\log(x^2 + bx + c) + \frac{2B - Ab}{\sqrt{4c - b^2}}\arctan!\left(\frac{2x + b}{\sqrt{4c - b^2}}\right)$$
当 $4c - b^2 < 0$(实根情况),$\arctan$ 变为 $\text{artanh}$(反双曲正切),对应对数形式。
Risch 微分方程
Risch 算法中最核心的子问题是求解 Risch 微分方程(Risch Differential Equation, RDE)。
问题(0.29.0 起的完整片段). 给定微分域 $k_\ell$ 中的元素 $f, g$(允许含顶层变量 $t_\ell$),求 $q \in k_\ell(t_\ell)$(有理函数,不限于多项式)满足:
$$Dq + f \cdot q = g$$
其中 $D$ 是塔导子(对 $t_\ell$ 的全导数)。
求解管线(逐函数对照 SymPy 1.14 integrals/rde.py,即 Bronstein Ch. 6 的实现版):
- 弱规范化(Thm 6.1.1):把 $f$ 的每个"正整数留数"用对数导数吸收掉,得到 $f' = f - Dq_{wn}/q_{wn}$;原方程等价于 $Dz + f'z = q_{wn},g$($z = q_{wn},y$)。
- 法分母界(Thm 6.1.2):求 $h_n$ 使 $q_2 = z \cdot h_n$ 在所有 normal 不可约因子处为多项式,方程化为 $a \cdot Dq_2 + b \cdot q_2 = c$。
- 特殊分母界(Thm 6.2.1):在超指数层处理 $t$ 的幂($t$ 是 special 多项式):$r = q_2 \cdot t^\nu \in k[t]$,其中 $\nu$ 由 $b, c$ 在 $t$ 处的阶给出;当 $\nu_b = 0$ 时用参数对数导数(
parametric_log_deriv,0.29.0 在基域 $\mathbb{Q}(x)$ 上实现)精确改进 $\nu$。 - 次数界(§6.3):给出 $\deg r$ 的上界 $n$(未移植
limited_integrate改进——只会把界压低,错过即诚实拒绝)。 - SPDE(Rothstein 特殊多项式微分方程):把 $a \neq 1$ 的方程逐轮归约为 $a$ 常量的方程 $Dh + Bh = C$。
- 多项式 RDE 分派:
no_cancel_b_large(无消去时的逐层剥离);消去情形按生成元类型走cancel_exp/cancel_primitive,递归到下一层重新求解完整 RDE——下层的有理解由此被利用(例如 $q = 1/t$ 正是这样产生的)。
为什么 0.29.0 起不限于多项式解:有理函数解 $q = p/d$ 需要的分母界(denominator bound)分析已经实现——弱规范化(Thm 6.1.1)、法分母界(Thm 6.1.2)与特殊分母界(Thm 6.2.1,含基层 parametric_log_deriv 改进)逐层把未知量约束为多项式,再经次数界与 SPDE 归约到消去情形;limited_integrate 次数界改进与高层的 parametric_log_deriv 未实现,命中时返回 None 回退到其他积分方法(只可能错过解,不会给出错解)。
基域 $\mathbb{Q}(x)$ 上的 RDE
在基域 $k_0 = \mathbb{Q}(x)$ 上,RDE 化为常微分方程 $q'(x) + f(x) \cdot q(x) = g(x)$。
算法(base_rde):
- 度界:设 $\deg f = p$、$\deg g = r$。$f = 0$ 时直接积分,$\deg q = r + 1$;$f$ 为非零常数($p = 0$)时 $\deg q = r$;$p \geq 1$ 时 $\deg(f \cdot q) = p + \deg q$ 的首项主导 $\deg(q') \leq \deg q - 1$,故 $\deg q = r - p$(唯一确定)。
- 待定系数:设 $q = \sum_{i=0}^{n} a_i x^i$,代入方程。
- 从高次到低次消除:比较 $x$ 的各次幂系数,逐个确定 $a_i$。每个 $a_i$ 要么被唯一确定,要么矛盾(原函数不存在)。
例子. 求解 $q' + q = x$($f = 1, g = x$):
设 $q = a_1 x + a_0$。$q' = a_1$。代入:$a_1 + a_1 x + a_0 = x$。
比较系数:$x^1: a_1 = 1$;$x^0: a_1 + a_0 = 0 \Rightarrow a_0 = -1$。
解:$q = x - 1$。验证:$(x-1)' + (x-1) = 1 + x - 1 = x$ ✓
这给出 $\int x \cdot e^x,dx = (x - 1) e^x$(在超指数层中)。
原始层(Primitive Level)的 RDE
在 $t_\ell = \log(u)$ 的层上,导子满足 $Dt_\ell = Du/u \in k_{\ell-1}$(不含 $t_\ell$)。
性质:$D(a_0 + a_1 t + \cdots + a_m t^m) = Da_0 + Da_1 \cdot t + \cdots + Da_m \cdot t^m + (a_1 + 2a_2 t + \cdots + m a_m t^{m-1}) \cdot Dt$
由于 $Dt$ 不含 $t$(原始扩张的关键性质),$D$ 作用在 $k[t]$ 上时不改变关于 $t$ 的次数(除了 $\partial/\partial t$ 部分降低一次)。
算法:从 $q$ 的最高次系数 $a_m$ 开始,自顶向下消除:
- 由方程中 $t^m$ 的系数确定 $a_m$(一个关于 $a_m$ 的 RDE 在 $k_{\ell-1}$ 中)。
- 代入后降低次数,对 $a_{m-1}$ 重复。
- 最终 $a_0$ 需满足一个对数约束:$a_0$ 的常数部分必须使下层积分中出现的对数项一致。
递归结构:每步产生一个 $k_{\ell-1}$ 上的 RDE,递归调用 rde_solve 直到基域。
超指数层(Hyperexponential Level)的 RDE
在 $t_\ell = \exp(u)$ 的层上,导子满足 $Dt_\ell = t_\ell \cdot Du$。
关键性质:$Dt = t \cdot Du$ 含 $t$,因此 $D$ 作用在 $k[t]$ 上时混合次数:
$$D(a_i t^i) = (Da_i) t^i + a_i \cdot i \cdot t^i \cdot Du = (Da_i + i \cdot a_i \cdot Du) t^i$$
即 $D(a_i t^i) = (Da_i + i \cdot a_i \cdot Du) \cdot t^i$——每个 $t^i$ 层独立!
解耦性质:将 $q = \sum a_i t^i$ 和 $g = \sum b_j t^j$ 代入 $Dq + fq = g$,比较 $t^k$ 的系数:
$$Da_k + k \cdot a_k \cdot Du + f \cdot a_k = b_k$$
即
$$Da_k + (f + k \cdot Du) \cdot a_k = b_k$$
这是 $k_{\ell-1}$ 上的一个 RDE(未知数为 $a_k$),各 $k$ 独立!
算法:对 $k = 0, 1, \dots, \deg g$,分别解 RDE:
$$Da_k + (f + k \cdot Du) \cdot a_k = b_k$$
若某个 $a_k$ 无解,则整体无解。$\deg q$ 的上界由 $\deg g$ 给出。
例子. $\int x \cdot e^x,dx$:在 $t = e^x$($Dt = t$)的塔上,被积函数为 $x \cdot t$。
多项式部分 $p(t) = x \cdot t$($m = 1$)。比较 $t^1$:$Da_1 + a_1 \cdot 1 = x$,即 $a_1' + a_1 = x$。由基域 RDE 解得 $a_1 = x - 1$。$t^0$ 部分为 0。
结果:$\int = (x-1) \cdot t = (x-1) \cdot e^x$。
对数层的待定系数法
在原始(对数)层 $t = \log(u)$ 上积分多项式 $p(t) = \sum a_i t^i$ 时,使用待定系数法。
算法 integrate_kpoly_primitive:
设待积多项式为 $p(t) = \sum_{i=0}^{m} a_i t^i$,猜测原函数 $q(t) = \sum_{i=0}^{m'} b_i t^i$。
- 设 $m' = m + 1$(原函数的次数至多比被积函数高 1,因为 $D$ 的 $\partial/\partial t$ 部分会降次)。
- 计算 $Dq = \sum (Db_i) t^i + \sum i \cdot b_i \cdot (Du/u) \cdot t^{i-1}$。
- 比较 $p = Dq$ 的系数:
- $t^m$:$a_m = Db_m + (m+1) b_{m+1} \cdot Du/u$
- $\vdots$
- $t^0$:$a_0 = Db_0 + b_1 \cdot Du/u$
- 从 $t^{m+1}$ 开始向下求解。每个 $b_i$ 可能需要递归调用积分器(对 $Db_i$ 部分)。
- $b_0$ 的确定受对数约束限制:在最终结果中,所有对数项 $\sum c_j \log(v_j)$ 必须一致。若 $b_0$ 含有来自下层的对数项且不满足 Liouville 条件,则无解。
三角到复指数的重写
Risch 算法原生只处理 log 和 exp。三角函数需要先重写为复指数形式。
Euler 公式:
$$\sin(u) = \frac{e^{iu} - e^{-iu}}{2i}, \qquad \cos(u) = \frac{e^{iu} + e^{-iu}}{2}$$
$$\tan(u) = \frac{\sin u}{\cos u} = \frac{e^{iu} - e^{-iu}}{i(e^{iu} + e^{-iu})}$$
在 oCAS 中,trig_to_exp(ocas-calc/src/integral/trig.rs)遍历表达式树,将 sin、cos、tan、cot、sec、csc 逐个替换为上述等价形式。虚数单位 $I$ 作为常数生成元加入塔中($DI = 0$)。
重写后的积分:重写后的表达式在 $\mathbb{Q}(x, I, e^{Ix}, \dots)$ 上的微分域塔中,可由 Risch 算法处理。
回转为实数形式(realify):Risch 积分的结果可能含有虚数单位。realify 尝试将结果转换回实数形式:
- 共轭对数合并:若结果中含 $c \cdot \log(u + Iv) + c \cdot \log(u - Iv)$,合并为 $c \cdot \log(u^2 + v^2)$
- 共轭对数之差:若含 $c \cdot \log(u + Iv) - c \cdot \log(u - Iv)$,合并为 $2c \cdot \arctan(v/u)$
- 指数合并:$e^{Iu} \cdot e^{-Iu} = 1$,$e^{Iu} + e^{-Iu} = 2\cos(u)$ 等
这是"尽力而为"的过程。若无法匹配已知模式,保留复数形式(数学上仍然正确,因为求导验证仍然成立)。
当前限制:Risch 微分方程求解器在 $\mathbb{Q}[x]$ 上工作,因此当三角重写产生含 $I$ 的超指数方程(例如 $\sin(x)\cos(x)$ 或 $\cos^2(x)$ 积分时产生的方程)时,系数域扩展为 $\mathbb{Q}(I)$ 而非 $\mathbb{Q}$,求解器可能失败。这些被积函数返回未求值形式。
启发式层
在 Risch 算法之前,oCAS 尝试一组启发式积分技术(ocas-calc/src/integral/heuristic.rs)。这些技术快速且覆盖常见模式。
分部积分(LIATE 排序)
对乘积 $f \cdot g$,尝试分部积分 $\int u,dv = uv - \int v,du$。
LIATE 优先级(选 $u$ 的启发式规则):
| 优先级 | 类型 | 分数 | 例子 |
|---|---|---|---|
| 1 | Logarithmic | 0 | $\log x$, $\arctan x$ |
| 2 | Inverse trig | 1 | $\arcsin x$, $\text{arccosh}, x$ |
| 3 | Algebraic | 2 | $x^2$, $\sqrt{x}$ |
| 4 | Trigonometric | 3 | $\sin x$, $\cos x$ |
| 5 | Exponential | 4 | $e^x$, $2^x$ |
算法:对乘积中的每个因子,按 LIATE 分数排序。选择分数最低(优先级最高)的作为 $u$,其余的(经积分得 $v$)作为 $dv$。
深度限制:PARTS_MAX_DEPTH = 2,防止无限递归。
三角替换
匹配 $\sqrt{a^2 - x^2}$、$\sqrt{a^2 + x^2}$、$\sqrt{x^2 - a^2}$ 及其倒数,直接返回已知原函数:
| 被积函数 | 原函数 |
|---|---|
| $\frac{1}{\sqrt{a^2 - x^2}}$ | $\arcsin(x/a)$ |
| $\frac{1}{\sqrt{a^2 + x^2}}$ | $\text{arcsinh}(x/a)$ |
| $\frac{1}{\sqrt{x^2 - a^2}}$ | $\text{arccosh}(x/a)$ |
| $\sqrt{a^2 - x^2}$ | $\frac{x\sqrt{a^2-x^2} + a^2 \arcsin(x/a)}{2}$ |
| $\sqrt{a^2 + x^2}$ | $\frac{x\sqrt{a^2+x^2} + a^2 \text{arcsinh}(x/a)}{2}$ |
| $\sqrt{x^2 - a^2}$ | $\frac{x\sqrt{x^2-a^2} - a^2 \text{arccosh}(x/a)}{2}$ |
Weierstrass 替换
对 $\sin(u)$ 和 $\cos(u)$ 的有理函数($u$ 是 $x$ 的线性函数),使用万能替换 $t = \tan(u/2)$:
$$\sin u = \frac{2t}{1+t^2}, \qquad \cos u = \frac{1-t^2}{1+t^2}, \qquad du = \frac{2,dt}{1+t^2}$$
将三角有理积分化为 $t$ 的有理函数积分。
Euler 替换
对含 $\sqrt{ax^2 + bx + c}$ 的被积函数,根据 $a$ 的符号选择 Euler 替换:
- $a > 0$:$\sqrt{a},x + t = \sqrt{ax^2 + bx + c}$
- $a < 0$:$t(x - \alpha) = \sqrt{ax^2 + bx + c}$($\alpha$ 为根之一)
- $c > 0$:$\sqrt{c} + xt = \sqrt{ax^2 + bx + c}$
这些替换将根号有理积分化为有理函数积分。
特殊函数表
当 Risch 算法证明某个积分没有初等原函数时,许多常见情形仍有特殊函数的闭式。oCAS 在 ocas-calc/src/integral/special.rs 中直接编码这些标准反导数(定义与 SymPy 一致):
| 被积函数 | 原函数 | 特殊函数 |
|---|---|---|
| $e^{-x^2}$ | $\frac{\sqrt{\pi}}{2},\text{erf}(x)$ | 误差函数 |
| $e^{x^2}$ | $\frac{\sqrt{\pi}}{2},\text{erfi}(x)$ | 虚误差函数 |
| $e^{cx^2}$($c < 0$) | $\frac{\sqrt{\pi}}{2\sqrt{-c}},\text{erf}(\sqrt{-c},x)$ | 误差函数 |
| $e^x / x$ | $\text{Ei}(x)$ | 指数积分 |
| $\sin(x)/x$ | $\text{Si}(x)$ | 正弦积分 |
| $\cos(x)/x$ | $\text{Ci}(x)$ | 余弦积分 |
| $\sinh(x)/x$ | $\text{Shi}(x)$ | 双曲正弦积分 |
| $\cosh(x)/x$ | $\text{Chi}(x)$ | 双曲余弦积分 |
| $\sin(x^2)$ | $\sqrt{\frac{\pi}{2}},S!\left(\sqrt{\frac{2}{\pi}},x\right)$ | Fresnel $S$ |
| $\cos(x^2)$ | $\sqrt{\frac{\pi}{2}},C!\left(\sqrt{\frac{2}{\pi}},x\right)$ | Fresnel $C$ |
匹配逻辑:special_integrate 将被积函数分解为乘积因子,尝试匹配以下模式族:
- erf 族:$e^{c \cdot x^2}$($c < 0$),含 $e^{-x^2}$ 特例
- Ei 族:$e^x / x$,含 $e^{cx}/x$ 推广
- Si/Ci/Shi/Chi 族:三角或双曲函数除以 $x$
- Fresnel 族:$\sin(x^2)$、$\cos(x^2)$
注意:这些不是 Risch 算法的一部分。Risch 证明"不存在初等原函数"后,特殊函数表提供"次优"答案。
在 oCAS 中的实现
积分管线架构
integrate(expr, var)(ocas-calc/src/integral/mod.rs)按顺序尝试以下层,第一个产生答案的层获胜:
┌─────────────────────────────────────────────────┐
│ integrate(expr, var) │
├─────────────────────────────────────────────────┤
│ 1. 查找表(幂法则、线性参数函数) │
│ ↓ 失败 │
│ 2. 有理函数积分器(integrate_rational) │
│ ↓ 失败 │
│ 3. Risch 算法(risch_integrate) │
│ ↓ 失败 │
│ 4. 三角重写(trig_to_exp)→ 重试 Risch → realify │
│ ↓ 失败 │
│ 5. 特殊函数表(special_integrate) │
│ ↓ 失败 │
│ 6. 启发式层(heuristic_integrate) │
│ ↓ 失败 │
│ 7. 返回未求值形式 Integral(expr, var) │
└─────────────────────────────────────────────────┘
深度限制:MAX_DEPTH = 8(integrate_raw 的递归深度),MAX_RISCH_DEPTH = 16(risch_integrate 的递归深度,使用 thread_local! 计数器)。这些限制防止病态输入(如 sec(x) 触发 VOP 无限重试)导致栈溢出。
模块组织
ocas-calc/src/integral/
├── mod.rs ← integrate 入口 + 查找表 + 管线调度
├── rational.rs ← 基域 Q(x) 上的有理函数积分器
│ Hermite 约化 + 对数部分 + Rothstein–Trager
├── risch.rs ← Risch 算法主循环
│ 逐层 integrate_level + Hermite 层上约化
│ 多项式部分(原始/超指数)
├── rde.rs ← Risch 微分方程求解器
│ base_rde (Q[x]) + 递归塔
├── trig.rs ← 三角↔复指数重写(trig_to_exp / realify)
├── heuristic.rs ← 启发式:分部积分 / 三角替换 / Weierstrass / Euler
└── special.rs ← 特殊函数表(erf, Ei, Si, Ci, Fresnel 等)
Risch 算法的执行流程
以 $\int (x+1) e^x,dx$ 为例,追踪完整执行:
- 构建塔:
build_tower收集 $t_1 = \exp(x)$,塔为 $\mathbb{Q}(x) \subset \mathbb{Q}(x)(t_1)$。 risch_integrate:将被积函数转换为 $k_1(t_1)$ 中的有理函数:$(x+1) \cdot t_1$。integrate_level(tower, level=1, f):- 分母为 1,无 Hermite 约化。
- 多项式部分 $p(t_1) = (x+1) t_1$。
- 调用
integrate_kpoly_hyperexp(因为 $t_1 = \exp(x)$):- 比较 $t_1^1$ 系数:$Da_1 + a_1 \cdot 1 = x + 1$
- 递归调用
rde_solve:$a_1' + a_1 = x + 1$ - 基域
base_rde:设 $a_1 = ax + b$,$a_1' = a$,$a + ax + b = x + 1$ - 比较:$a = 1$,$a + b = 1 \Rightarrow b = 0$
- $a_1 = x$,验证:$x' + x = 1 + x = x + 1$ ✓
- $t_1^0$ 系数为 0,$b_0 = 0$。
- 结果组装:$q = x \cdot t_1$,转为原子:$x \cdot e^x$。
- 化简 + 归一化:返回 $x \cdot e^x$。
验证:$\frac{d}{dx}[x \cdot e^x] = e^x + x \cdot e^x = (x+1)e^x$ ✓
域元素的内部表示
在塔中,域元素使用平坦多元稀疏多项式表示:
#![allow(unused)] fn main() { // KElem: k = Q(x, t_1, ..., t_{n-1}) 中的元素 struct KElem { num: SparseMultivariatePolynomial<RationalDomain, Lex>, den: SparseMultivariatePolynomial<RationalDomain, Lex>, } }
变量索引分配:0 对应 $x$,1 对应 $t_1$,…,n-1 对应 $t_{n-1}$。
KPoly 是关于顶层变量的稠密一元多项式,系数为 KElem:
#![allow(unused)] fn main() { // KPoly: k[t_n] 中的多项式,系数在 k 中 struct KPoly { coeffs: Vec<KElem>, // 从低次到高次 top: usize, // 顶层变量的索引 n: usize, // 总变量数 } }
KRat 是有理函数 $p/q$:
#![allow(unused)] fn main() { struct KRat { num: KPoly, den: KPoly, } }
无 GCD 约简:KElem 不执行多元 GCD 约简(因为通用多元 GCD 代价高昂)。零检测仅通过分子为零来判断,交叉相乘相等是唯一可靠的相等性判断。
Fuel 受限积分
integrate_with_fuel(ctx, expr, var, &fuel) 将 Fuel 预算贯穿到两个积分后化简阶段。积分遍历本身使用 MAX_DEPTH / MAX_RISCH_DEPTH 限制;Fuel 仅约束化简阶段,防止病态结果(如产生极大表达式的积分)导致重写器无限循环。
#![allow(unused)] fn main() { use ocas_core::fuel::Fuel; let fuel = Fuel::new(500); let result = integrate_with_fuel(&ctx, expr, Symbol::new("x"), &fuel); // Ok(result) — 正常完成 // Err(_) — 化简中途 fuel 耗尽 }
范围限制与回退策略
Risch 算法的当前实现(0.29.0)有以下限制:
| 限制 | 原因 | 回退行为 |
|---|---|---|
limited_integrate 次数界改进与高层 parametric_log_deriv 未实现) | 改进只降次数界,错过即诚实拒绝 | 返回 None,调用者尝试其他层 |
塔层上的对数部分(0.29.0 起为 Rothstein–Trager;0.30.0 起接受代数常量根:基层二次/双二次四次因子,塔层复二次因子,共轭对实值化为 log(u²+w²)/atan 形;其余次数与非常量根仍拒绝) | 片段外因子(≥3 次、非双二次四次等)不在 0.30.0 范围 | 返回未求值形式 |
| 代数函数($\sqrt{x}$ 等)不支持 | 需要代数函数域扩展 | 由启发式三角替换覆盖常见模式 |
| 代数相关生成元的部分合并(0.28.0 精确合并六类 + 0.29.0 的 $\exp(ku)=\exp(u)^k$;分数倍 $\exp(u/k)$ 需重基,未实现) | 关系检测不完备 | 返回 None |
| 含 $I$ 的超指数 RDE | RDE 求解器仅在 $\mathbb{Q}[x]$ 上工作 | 三角被积函数返回未求值形式 |
当所有层都失败时,返回 Integral(expr, var)——这是有意的答案,表示"该积分在当前实现中没有闭式解",而非程序错误。
参考文献
- Bronstein, M. Symbolic Integration I: Transcendental Functions, 2nd ed., Springer, 2005. — Risch 算法的权威参考。第 2 章:有理函数积分(Hermite 约化、Rothstein–Trager);第 5 章:初等函数上的 Risch 算法;第 6 章:Risch 微分方程。oCAS 的实现严格遵循此书。
- Bronstein, M. Symbolic Integration II: Transcendental and Algebraic Functions, Springer, 2004. — 扩展到代数函数和特殊函数。oCAS 尚未覆盖此书的内容。
- Geddes, K. O., Czapor, S. R., & Labahn, G. Algorithms for Computer Algebra, Kluwer, 1992. — 第 12 章提供 Risch 算法的替代讲解。
- Liouville, J. "Sur les transcendantes elliptiques de première et de seconde espèce considérées comme fonctions de leur amplitude." Journal de l'École Polytechnique, 1835. — 初等函数原函数的 Liouville 定理原始出处。
- Risch, R. H. "The problem of integration in finite terms." Transactions of the AMS, 139:167–189, 1969. — Risch 算法的奠基论文。
- Rothstein, M. "A new algorithm for the integration of exponential and logarithmic functions." Proceedings of the 1977 MACSYMA Users Conference, 1977. — Rothstein–Trager 结式方法。
- Trager, B. M. "Algebraic factoring and rational function integration." Proceedings of SYMSAC '76, 1976. — Trager 算法(代数数域上的因式分解)。
- Lazard, D. & Rioboo, R. "Integration of rational functions: Rational computation of the logarithmic part." Journal of Symbolic Computation, 9(2):113–129, 1990. — 对数部分的高效算法。
高阶:FGLM 与消元理论
本章系统讲解零维理想的 Gröbner 基换序算法(FGLM)、消元定理,以及基于 Gröbner 基的理想运算(商、饱和、交集、准素分解、根式)。这些工具构成了 oCAS 多项式系统求解和代数几何计算的核心基础设施。
前提知识
阅读本章前,建议具备以下基础:
- Gröbner 基理论:Buchberger 算法、F4/F5 算法、S-多项式、约化 Gröbner 基——参见 Gröbner 基理论
- 多项式代数:多元多项式环 $\mathbb{F}[x_1, \ldots, x_n]$、单项式序(Lex / Grlex / Grevlex)——参见 多项式代数
- 线性代数:高斯消元、向量空间维数、线性相关性——参见 线性代数
- 多项式因式分解:无平方分解、一元多项式因式分解——参见 多项式 GCD 与因式分解
如需系统学习路径,请参考 数学基础总览。
基础概念
零维理想
定义。设 $I \subseteq R = \mathbb{F}[x_1, \ldots, x_n]$ 是一个理想。$I$ 称为零维的(zero-dimensional),如果商环 $R/I$ 作为 $\mathbb{F}$-向量空间是有限维的。
等价条件(以下任一成立即为零维):
- 代数几何:仿射簇 $V(I) \subseteq \overline{\mathbb{F}}^n$ 是有限集
- Gröbner 基刻画:对每个变量 $x_i$($1 \leq i \leq n$),Gröbner 基中存在某个多项式 $g$ 使得 $\text{lm}(g) = x_i^{N_i}$(纯幂)
- 阶梯有限性:标准单项式(不被任何首项单项式整除的单项式)集合有限
例。$I = \langle x^2 - 1, y - x \rangle \subseteq \mathbb{Q}[x, y]$。其 Lex Gröbner 基为 ${x^2 - 1, y - x}$,首项为 ${x^2, y}$。对 $x$ 有 $x^2$,对 $y$ 有 $y$——纯幂条件满足,故 $I$ 是零维的。实际上 $V(I) = {(1, 1), (-1, -1)}$,有限。
反例。$J = \langle x - y \rangle \subseteq \mathbb{Q}[x, y]$。首项为 ${x}$,对 $y$ 无纯幂首项。$V(J) = {(t, t) : t \in \overline{\mathbb{Q}}}$ 是无穷集,故 $J$ 是正维的。
阶梯与标准单项式
定义。设 $G = {g_1, \ldots, g_t}$ 是理想 $I$ 关于某个单项式序 $\succ$ 的 Gröbner 基。令 $\text{LT}(I) = \langle \text{lm}(g_1), \ldots, \text{lm}(g_t) \rangle$ 为首项理想。
标准单项式(standard monomials)集合定义为
$$\text{Std}(I) = {x^\alpha \in \mathbb{F}[x_1, \ldots, x_n] : x^\alpha \notin \text{LT}(I)}$$
即不被任何 $\text{lm}(g_i)$ 整除的单项式。标准单项式的集合也称为阶梯(staircase),因为其形状在二维时像一个阶梯。
核心性质。标准单项式构成 $R/I$ 的一组 $\mathbb{F}$-向量空间基。即每个陪集 $f + I \in R/I$ 都有唯一的表示
$$f + I = \sum_{x^\alpha \in \text{Std}(I)} c_\alpha \cdot x^\alpha + I$$
其中 $c_\alpha \in \mathbb{F}$。这等价于说 $f$ 对 Gröbner 基的正规形是标准单项式的唯一线性组合。
例。$I = \langle x^2, xy \rangle \subseteq \mathbb{F}[x, y]$(Grevlex 序)。首项为 ${x^2, xy}$。
- $1$:不被 $x^2$ 或 $xy$ 整除 → 标准单项式 ✓
- $y$:不被整除 → ✓
- $y^2$:不被整除 → ✓
- $y^k$($k \geq 0$):不被整除 → ✓
- $x$:不被整除 → ✓
- $x^2$:被 $x^2$ 整除 → ✗
- $xy$:被 $xy$ 整除 → ✗
阶梯为 ${1, x, y, y^2, y^3, \ldots}$——无穷集!这反映了 $I$ 是正维理想($V(I) = {(0, t) : t \in \overline{\mathbb{F}}}$)。
维数。对零维理想,阶梯的大小
$$D = \dim_{\mathbb{F}}(R/I) = |\text{Std}(I)|$$
是一个有限正整数,称为理想 $I$ 的向量空间维数或 Hilbert 函数的稳定值。这个维数 $D$ 在 FGLM 算法的复杂度分析中扮演核心角色。
正规形
定义。多项式 $f$ 关于 Gröbner 基 $G$ 的正规形(normal form)$\text{NF}(f, G)$ 是 $f$ 对 $G$ 做带余除法后的余数。它满足:
- $\text{NF}(f, G) \in f + I$(与 $f$ 同余模 $I$)
- $\text{NF}(f, G)$ 的每一项都是标准单项式
- 表示唯一(对约化 Gröbner 基)
对零维理想,正规形可以表示为长度为 $D$ 的系数向量——每个分量对应一个标准单项式。这正是 FGLM 算法将正规形计算转化为线性代数操作的关键。
核心理论
消元定理
消元理想。设 $I \subseteq \mathbb{F}[x_1, \ldots, x_n]$,固定 $1 \leq \ell \leq n$。第 $\ell$ 个消元理想为
$$I_\ell = I \cap \mathbb{F}[x_{\ell+1}, \ldots, x_n]$$
即 $I$ 中不涉及前 $\ell$ 个变量的多项式构成的理想。
消元定理(Elimination Theorem)。设 $G$ 是 $I$ 关于 Lex 序的 Gröbner 基($x_1 \succ x_2 \succ \cdots \succ x_n$)。则
$$G_\ell = G \cap \mathbb{F}[x_{\ell+1}, \ldots, x_n]$$
是 $I_\ell$ 的 Gröbner 基。
换言之,Lex Gröbner 基中不涉及 $x_1, \ldots, x_\ell$ 的那些多项式恰好生成了消元理想 $I_\ell$。
证明思路。Lex 序的关键性质是:若 $x^\alpha \succ x^\beta$ 且 $\alpha_1 = \cdots = \alpha_\ell = 0$,则 $\beta_1 = \cdots = \beta_\ell = 0$。因此首项理想 $\langle \text{lt}(g) : g \in G \rangle$ 与 $\mathbb{F}[x_{\ell+1}, \ldots, x_n]$ 的交集恰好由 $G_\ell$ 的首项生成。$\square$
应用。消元定理是以下所有构造的理论基础:
- 隐函数消元:从方程组中消去变量,得到仅含剩余变量的关系
- 理想商的 Rabinowitsch 技巧:引入辅助变量后消元
- 理想交集:引入辅助变量 $t$ 后消元
- 多项式系统求解:从 Lex GB 中逐变量提取约束
注意。Grevlex 序下的 Gröbner 基不具有消元性质。因此所有需要消元的操作都必须在 Lex 序下进行。这正是 FGLM 换序算法的核心价值:先用 Grevlex 快速计算基,再用 FGLM 转为 Lex 用于消元。
FGLM 算法
FGLM 算法(Faugère–Gianni–Lazard–Mora, 1993)对零维理想提供高效的 Gröbner 基换序。其核心思想是:利用源序下的正规形计算,按目标序遍历单项式,通过线性代数方法直接构造目标序下的 Gröbner 基——完全避免在目标序下重新运行 F4。
动机
假设我们已经用 Grevlex 序(最高效的计算序)得到了理想 $I$ 的 Gröbner 基 $G_{\text{grevlex}}$。为了做消元或求解,需要 Lex 序下的基 $G_{\text{lex}}$。
两种方法的对比:
| 方法 | 复杂度 | 说明 |
|---|---|---|
| 重跑 F4(Lex) | 指数级(最坏) | 完全重新计算,中间多项式可能膨胀 |
| FGLM | $O(n \cdot D^3)$ | $D = \dim(R/I)$,纯线性代数 |
对零维理想,FGLM 几乎总是更快——尤其是当 $D$ 适中但 Lex 基的中间多项式很庞大时。
算法描述
输入:零维理想 $I$ 在源序 $O_1$ 下的约化 Gröbner 基 $G_{\text{src}}$,目标序 $O_2$。
输出:$I$ 在目标序 $O_2$ 下的约化 Gröbner 基 $G_{\text{tgt}}$。
初始化:
- 从 $G_{\text{src}}$ 提取所有首项单项式 $\text{lm}(g_1), \ldots, \text{lm}(g_t)$
- 计算阶梯 $\text{Std}(I)$(BFS:从 $1$ 开始,逐变量扩展,跳过被首项整除者,直到不再有新标准单项式——零维保证终止)
- 令 $D = |\text{Std}(I)|$,将标准单项式编号为 $s_1, s_2, \ldots, s_D$
- 对每个变量 $x_i$,构造乘法矩阵 $M_i$:对每个标准单项式 $s_j$,计算 $x_i \cdot s_j$ 的正规形(在 $G_{\text{src}}$ 下),得到 $D \times D$ 矩阵
主循环:按目标序 $O_2$ 递增遍历单项式:
seen_nfs ← [] // 已见正规形(D 维向量)
seen_mons ← [] // 对应的单项式
boundary ← {1} // 边界集合
visited ← ∅ // 已访问单项式
GB_tgt ← ∅
while boundary ≠ ∅:
m ← boundary 中 O2-最小的单项式
visited ← visited ∪ {m}
// 计算 m 在源序基下的正规形(D 维坐标向量)
nf ← normal_form(m, G_src, staircase)
if nf ∈ span(seen_nfs): // 线性相关?
// 找到关系 nf = Σ c_i · seen_nfs[i]
coeffs ← Gaussian_solve(seen_nfs, nf)
// 构造新多项式:m - Σ c_i · seen_mons[i]
new_poly ← (m, 1) + Σ (-c_i, seen_mons[i])
GB_tgt ← GB_tgt ∪ {new_poly}
// m 的所有倍都在理想中,标记为已访问
mark_multiples(visited, m)
else:
// 线性无关:m 是目标序下的新标准单项式
seen_nfs.append(nf)
seen_mons.append(m)
// 将 m 的邻居加入边界
for i = 1 to n:
if m · x_i ∉ visited:
boundary ← boundary ∪ {m · x_i}
return minimize(GB_tgt).auto_reduce()
关键子程序
正规形计算。单项式 $m$ 在 $G_{\text{src}}$ 下的正规形是一个 $D$ 维向量。实现上,将 $m$ 视为系数为 $1$ 的单项式多项式,对 $G_{\text{src}}$ 做带余除法,然后将余数的每个项映射到阶梯的位置索引上。
线性关系检测。维护一个增广矩阵 $[\text{seen_nfs} \mid \text{nf}]$($D$ 行,$k+1$ 列,其中 $k$ 是已见正规形数)。每次加入新 nf 时做高斯消元:
- 若消元后增广列全零 → 线性相关,提取系数向量
- 若消元后增广列有非零元 → 线性无关,加入 seen_nfs
这可以用增量式高斯消元(每次只处理一列)高效实现,避免重复消元。
倍标记。当发现线性关系 $\text{nf}(m) = \sum c_i \cdot \text{nf}(m_i)$ 时,构造的多项式 $m - \sum c_i m_i \in I$ 的首项为 $m$(按目标序)。$m$ 的所有倍 $m \cdot x^\alpha$ 也必然在 $I$ 中,因此不需要再考虑——将它们从边界中移除。
复杂度分析
- 阶梯计算:$O(D)$ 个单项式,每个需要对基中每个首项做一次整除检查(每次检查 $O(n)$ 次比较)
- 乘法矩阵:$n$ 个 $D \times D$ 矩阵,每个元素需一次正规形计算 → $O(n \cdot D^2 \cdot T_{\text{NF}})$
- 主循环:至多发现 $D$ 个线性无关的标准单项式,每发现一个线性相关就输出一个目标基元素;总迭代数 $\le D + |G_{\text{tgt}}|$,实现中以 $D(D+1) + 4n$ 为安全上限
- 每次迭代:一次正规形计算 $O(T_{\text{NF}})$ + 一次高斯消元 $O(D^2)$
- 总复杂度:$O(n \cdot D^3)$ 次域运算
其中 $T_{\text{NF}}$ 是一次正规形计算的开销,对稀疏多项式通常与 $D$ 线性相关。
正确性
定理(Faugère–Gianni–Lazard–Mora 1993)。设 $I$ 是零维理想,$G_{\text{src}}$ 是 $I$ 在序 $O_1$ 下的约化 Gröbner 基。FGLM 算法输出 $G_{\text{tgt}}$ 是 $I$ 在序 $O_2$ 下的约化 Gröbner 基。
证明思路:
- 正规形计算与序无关——$G_{\text{src}}$ 的正规形映射与 $G_{\text{tgt}}$ 的正规形映射是同一个线性映射在不同基下的表示
- 主循环按目标序递增遍历单项式,恰好覆盖所有标准单项式和产生首项的单项式
- 线性关系检测保证:输出的多项式恰好生成首项理想
- minimize 和 auto_reduce 保证约化性
理想运算
Gröbner 基使得理想的基本算术运算可以算法化。本节介绍 oCAS 实现的核心理想运算,每种都依赖消元定理。
理想商(Ideal Quotient)
定义。两个理想 $I, J \subseteq R$ 的理想商为
$$I : J = {f \in R : f \cdot g \in I, , \forall g \in J}$$
对单生成元 $J = \langle g \rangle$,简记 $I : g$。
Rabinowitsch 技巧。计算 $I : g$ 的关键技巧是引入一个新变量 $w$。但要注意:辅助变量消元构造实际给出的是饱和理想:
$$I : g^\infty = \left( I \cdot R[w] + \langle 1 - wg \rangle \right) \cap R$$
即在扩展环 $R[w]$ 中,将 $I$ 的生成元提升、添加 $1 - wg$、计算 Gröbner 基,然后取不涉及 $w$ 的多项式(消元)。当 $I$ 关于 $g$ 已饱和(即 $I : g = I : g^\infty$,例如无平方、无嵌入分支的情形)时,它恰等于 $I : g$。oCAS 的 ideal_quotient 实现的正是这个消元构造(数学上即 $I : g^\infty$)。
正确性证明。局部化观点:$R[w]/\langle 1 - wg \rangle \cong R[1/g]$($w \mapsto 1/g$)。于是 $f \in (I \cdot R[w] + \langle 1-wg\rangle) \cap R$ 当且仅当 $f \in I \cdot R[1/g]$,即存在 $N$ 使得 $g^N f \in I$,亦即 $f \in I : g^N \subseteq I : g^\infty$。反之,若 $g^N f \in I$,则
$$f = f\bigl(1-(wg)^N\bigr) + w^N \cdot (g^N f)$$
第一项在 $\langle 1-wg\rangle$ 中($1-(wg)^N$ 被 $1-wg$ 整除),第二项在 $I \cdot R[w]$ 中。$\square$
多个生成元。对 $J = \langle g_1, \ldots, g_m \rangle$,有
$$I : J^\infty = \bigcap_{j=1}^{m} (I : g_j^\infty)$$
oCAS 通过逐个计算每个 $I : g_j^\infty$ 再求交集实现(这正是 ideal_quotient 的实际行为)。
例。$I = \langle x^2, xy \rangle$,$J = \langle x \rangle$。
$$I : x = \langle x^2, xy \rangle : \langle x \rangle$$
在 $\mathbb{Q}[x, y, w]$ 中,计算 $\text{GB}(x^2, xy, 1 - wx)$ 并消去 $w$:
- $1 - wx \implies w = 1/x$
- $x^2 \cdot w = x$,$xy \cdot w = y$(由 $x^2 w - x = -x(1-wx)$ 与 $xy w - y = -y(1-wx)$)
消元后得到 $\langle x, y \rangle$,故 $\langle x^2, xy \rangle : \langle x \rangle = \langle x, y \rangle$。
理想交集(Ideal Intersection)
定义。$I \cap J$ 是同时属于 $I$ 和 $J$ 的多项式构成的理想。
辅助变量法。引入新变量 $t$:
$$I \cap J = \langle t \cdot f_i, , (1-t) \cdot g_j \rangle_{i,j} \cap R$$
其中 ${f_i}$ 和 ${g_j}$ 分别是 $I$ 和 $J$ 的生成元。
直觉。$t = 1$ 时约束退化为 $f_i = 0$($I$ 的条件),$t = 0$ 时退化为 $g_j = 0$($J$ 的条件)。消去 $t$ 后,得到同时满足两组条件的多项式。
正确性。$h \in I \cap J$ ⟺ $h = t \cdot h + (1-t) \cdot h$,其中 $t \cdot h \in t \cdot I$ 且 $(1-t) \cdot h \in (1-t) \cdot J$。反之,消元理想中的多项式在 $t = 0$ 和 $t = 1$ 处都为零,故同时属于 $I$ 和 $J$。$\square$
例。$\langle x \rangle \cap \langle y \rangle = \langle xy \rangle$。
在 $\mathbb{Q}[x, y, t]$ 中,计算 $\text{GB}(tx, (1-t)y)$ 并消去 $t$:
- $tx = 0 \implies t = 0$ 或 $x = 0$
- $(1-t)y = 0 \implies t = 1$ 或 $y = 0$
消元后得到 $\langle xy \rangle$。$\square$
理想饱和(Ideal Saturation)
定义。$I$ 关于 $J$ 的饱和为
$$I : J^\infty = \bigcup_{k=1}^{\infty} (I : J^k)$$
等价地,$I : J^\infty$ 是 $I$ 在 $V(J)$ 之外的"限制"——在 $J$ 的零点集之外与 $I$ 一致。
迭代计算。由升链条件,存在 $k_0$ 使得 $I : J^{k_0} = I : J^{k_0+1} = \cdots = I : J^\infty$。算法反复计算 $I : J$,直到稳定:
I_old ← I
loop:
I_new ← I_old : J
if I_new == I_old: // 比较 Gröbner 基
return I_new
I_old ← I_new
应用。饱和在代数几何中极为重要:
- 根式计算:$\sqrt{I} = I : h^\infty$(正维情形,$h$ 与 Jacobian 相关)
- 准素分解:分离不同维度的分支
- 闭包计算:射影闭包、完备交
例。$\langle x^2 y, xy^2 \rangle : \langle x \rangle^\infty$。
按数学定义逐轮计算商:
第一轮:$\langle x^2 y, xy^2 \rangle : \langle x \rangle = \langle xy, y^2 \rangle$
第二轮:$\langle xy, y^2 \rangle : \langle x \rangle = \langle y \rangle$
第三轮:$\langle y \rangle : \langle x \rangle = \langle y \rangle$(稳定)
故 $\langle x^2 y, xy^2 \rangle : \langle x \rangle^\infty = \langle y \rangle$。$\square$
(注:oCAS 的 ideal_quotient 用消元构造单次计算 $I : g^\infty$——本例中一次调用即得到 $\langle y \rangle$,ideal_saturate 的迭代仅用于确认稳定。)
理想和、积与成员判定
这些是最基本的理想运算,实现相对简单:
| 运算 | 定义 | 实现 |
|---|---|---|
| $I + J$ | $\langle f_1, \ldots, f_m, g_1, \ldots, g_k \rangle$ | 合并生成元,计算 GB |
| $I \cdot J$ | $\langle f_i g_j \rangle_{i,j}$ | 所有生成元两两乘积,计算 GB |
| $f \in I$ ? | — | 计算 $\text{NF}(f, G_I)$,是否为零 |
准素分解
定义。理想 $Q \subseteq R$ 称为准素理想(primary ideal),如果 $fg \in Q$ 蕴含 $f \in Q$ 或 $g^n \in Q$(对某个 $n \geq 1$)。
准素分解定理(Lasker–Noether)。每个理想 $I \subseteq \mathbb{F}[x_1, \ldots, x_n]$($\mathbb{F}$ 特征零)都有准素分解
$$I = Q_1 \cap Q_2 \cap \cdots \cap Q_r$$
其中每个 $Q_i$ 是准素理想。对应的关联素理想为 $\mathfrak{p}_i = \sqrt{Q_i}$。
零维准素分解
对零维理想,准素分解可以通过 Lex Gröbner 基中一元多项式的因式分解来实现。
核心观察。零维理想的 Lex GB 包含一个仅含 $x_1$ 的多项式 $p_1(x_1)$、一个含 $x_1, x_2$ 的多项式 $p_2(x_1, x_2)$,以此类推。$p_1(x_1)$ 的因式分解对应了理想在 $x_1$ 方向上的不同分支。
算法(对应 oCAS 实现 primary_decomp_zero_dim):
- 计算 $I$ 的 Lex Gröbner 基 $G$
- 提取仅含 $x_1$ 的多项式 $p_1(x_1)$,计算其无平方部分 $\tilde{p}_1 = p_1 / \gcd(p_1, p_1')$
- 将 $\tilde{p}_1$ 在 $\mathbb{Q}$ 上分解为互异不可约因子 $\tilde{p}_1 = q_1 \cdot q_2 \cdots q_s$
- 对每个因子 $q_i$,用饱和分离分支:$I_i = I : \left(\prod_{j \neq i} q_j\right)^\infty$(源码逐个因子依次饱和)
- 每个分支 $I_i$ 的关联素理想取为 $\mathfrak{p}_i = \mathrm{GB}(I + \langle q_i \rangle)$
注意。若 $\tilde{p}_1$ 只有一个不可约因子(或 $G$ 中不存在仅含 $x_1$ 的多项式),算法返回单一分支 ${\text{primary} = I,; \text{prime} = \sqrt{I}}$。当前实现只处理第一个变量 $x_1$ 的因子分解,不递归处理后续变量。
例。$I = \langle x^2 - 1, y - x \rangle \subseteq \mathbb{Q}[x, y]$。
Lex GB:${x^2 - 1, y - x}$。$p_1(x) = x^2 - 1$ 已无平方,分解为 $q_1 = x - 1$、$q_2 = x + 1$。
- 分支 1:$I_1 = I : \langle x + 1 \rangle^\infty = \langle x - 1, y - 1 \rangle$(关联素理想 $\langle x - 1, y - 1 \rangle$)
- 分支 2:$I_2 = I : \langle x - 1 \rangle^\infty = \langle x + 1, y + 1 \rangle$(关联素理想 $\langle x + 1, y + 1 \rangle$)
检查:$\langle x^2 - 1, y - x \rangle = \langle x - 1, y - 1 \rangle \cap \langle x + 1, y + 1 \rangle$,两个分支都是素理想。
作为对照,$I = \langle x^2, xy \rangle$ 的 $p_1(x) = x^2$ 无平方部分只有 $x$ 一个因子,故算法返回单一分支 ${\text{primary} = \langle x^2, xy \rangle,; \text{prime} = \sqrt{I} = \langle x \rangle}$。不过该理想仍有更细的分解 $\langle x^2, xy \rangle = \langle x \rangle \cap \langle x^2, y \rangle$,其中 $\langle x \rangle$ 是素理想,$\langle x^2, y \rangle$ 是关联素理想为 $\langle x, y \rangle$ 的准素理想(嵌入分支)。
正维准素分解
正维理想的准素分解更加复杂,oCAS 目前标记为待实现(TODO)。完整实现需要:
- Gianni–Trager–Zacharias 算法
- 或 Eisenbud–Huneke–Vasconcelos 的特征方法
根式(Radical)
定义。理想 $I$ 的根式为
$$\sqrt{I} = {f \in R : f^n \in I \text{ 对某个 } n \geq 1}$$
等价地,$\sqrt{I}$ 是包含 $I$ 的最大理想使得 $V(\sqrt{I}) = V(I)$(相同的零点集)。
零维根式
对零维理想,根式计算利用 Lex GB 中一元多项式的无平方分解。
算法:
- 计算 Lex GB $G$
- 对 $G$ 中每个仅含 $x_i$ 的多项式 $p_i(x_i)$,计算其无平方部分 $\tilde{p}_i = p_i / \gcd(p_i, p_i')$
- 用 $\tilde{p}_i$ 替换 $p_i$,得到 $\sqrt{I}$ 的生成元
正确性。零维理想的根式对应于将每个关联素理想的幂次降为 $1$。无平方分解恰好实现了这一点——$p_i^{e_i}$ 的无平方部分是 $p_i$。
例。$\sqrt{\langle x^2, xy \rangle}$。
Lex GB:${x^2, xy}$。$x^2$ 的无平方部分:$x^2 / \gcd(x^2, 2x) = x^2 / x = x$。
根式:$\langle x, xy \rangle = \langle x \rangle$(因为 $x$ 已经整除 $xy$)。$\square$
正维根式:Jacobian 饱和
对正维理想,oCAS 使用基于 Jacobian 的饱和方法(简化版 Kemper 算法):
$$\sqrt{I} = I : h^\infty$$
其中 $h$ 与 Jacobian 行列式相关。
算法(特征零,对应实现 radical_via_jacobian):
- 对 $I$ 的每个生成元 $f_i$ 和每个变量 $x_j$,计算偏导数 $\partial f_i / \partial x_j$(跳过常值/零偏导数)
- 启发式选取 $h$:源码并非计算真正的 $\gcd$,而是对非平凡偏导数做
reduce折叠、保留总次数最小的那个。这是对经典 Jacobian 饱和公式($\sqrt{I} = I : h^\infty$,其中 $h$ 需取合适的 Jacobian 相关元素)的启发式近似——$h$ 只是某个偏导数(Jacobian 矩阵的一个元素),既不保证整除 Jacobian 行列式,也不保证饱和结果恰好等于 $\sqrt{I}$ - 计算 $I : h^\infty$(迭代饱和)
理论基础。在正则点处,Jacobian 矩阵的秩等于簇的维数(Jacobian 判据);奇异点正是秩不足(小于 $n - \dim$)的点——在这些点处幂次结构非平凡。饱和 $I : h^\infty$ 的作用是去除这些幂次。
限制。当所有偏导数都是常数/零,或 $h$ 是常值多项式(总次数为 0)时,算法回退到返回原始 GB。由于 $h$ 是启发式选取而非真正的 Jacobian 相关元素,饱和结果只是 $\sqrt{I}$ 的近似(可能偏大,也可能偏小、漏掉分量)——对正维理想,完整根式计算需要更精细的算法(如 Gianni–Trager–Zacharias 或 Eisenbud–Huneke–Vasconcelos)。
素性与准素性判定
零维素性判定。零维理想 $I$ 是素理想的充要条件是:Lex GB 中每个仅含 $x_i$ 的多项式都是不可约的。
oCAS 实现(is_prime_zero_dim):
- 计算 Lex GB
- 提取每个一元多项式 $p_i(x_i)$
- 对次数 $\le 3$ 的多项式用有理根定理检查可约性($\mathbb{Q}$ 上次 $d \le 3$ 的多项式不可约 ⟺ 无有理根);次数 $> 3$ 的多项式不做完全分解检验
注意。正维理想目前保守返回 false——素的正维理想会被误判为非素(假阴性)。而零维情形下,次数 $> 3$ 的一元多项式若可约但无有理根(如 $(x^2-2)(x^2-3)$),会被误判为不可约,从而可能产生假阳性(把非素理想报为素)。
准素性判定。理想 $I$ 是准素理想当且仅当它恰好有一个关联素理想。实现上通过准素分解检查分量数。
在 oCAS 中的实现
FGLM 实现
FGLM 算法实现在 ocas-poly/src/groebner/fglm.rs 中。
入口函数
#![allow(unused)] fn main() { pub fn fglm<D: Domain, O2: MonomialOrder>( gb: &GroebnerBasis<D, impl MonomialOrder>, ) -> Option<GroebnerBasis<D, O2>> }
泛型参数:
D:系数域(实现Domaintrait)O2:目标单项式序- 输入基的源序由
gb的类型参数impl MonomialOrder确定
返回 None 当理想不是零维的(阶梯计算发现无穷多个标准单项式)。
阶梯计算
#![allow(unused)] fn main() { fn compute_staircase(lms: &[Vec<usize>], n_vars: usize) -> Option<Vec<Vec<usize>>> }
BFS 算法:
- 从全零指数向量 $1 = x_1^0 \cdots x_n^0$ 开始
- 对队列中每个单项式 $m$,检查是否被某个 $\text{lm}(g_i)$ 整除
- 若不被整除 → 加入阶梯,将其 $n$ 个邻居($m$ 的每个变量指数 $+1$)加入队列
- 安全阈值 100,000:若 BFS 访问超过此数,判定为正维
整除检查使用逐分量比较:
#![allow(unused)] fn main() { fn monomial_divides_big(lm: &[usize], big: &[usize]) -> bool { lm.iter().zip(big.iter()).all(|(a, b)| a <= b) } }
正规形计算
#![allow(unused)] fn main() { fn normal_form_monomial<D: Domain>( m: &[usize], gb: &GroebnerBasis<D, impl MonomialOrder>, staircase: &[Vec<usize>], domain: &D, ) -> Vec<D::Element> }
将单项式 $m$ 构造为单项式多项式 $1 \cdot x^m$,对 GB 做带余除法,然后将余数的每个项映射到阶梯的位置索引上。返回长度为 $D$ 的坐标向量。
线性关系检测
#![allow(unused)] fn main() { fn find_relation<D: Domain>( seen: &[Vec<D::Element>], nf: &[D::Element], domain: &D, ) -> Option<Vec<D::Element>> }
构造增广矩阵 $[\text{seen}^T \mid \text{nf}]$($D$ 行,$k+1$ 列),执行前向消元 + 回代:
- 逐列找主元(非零元素),交换行
- 归一化主元行,消去其余行的该列
- 检查一致性:若某行左侧全零但右侧非零 → 无解(线性无关)
- 回代提取系数向量
返回 Some(coeffs) 当线性相关,None 当线性无关。
倍标记
#![allow(unused)] fn main() { fn mark_multiples( visited: &mut HashMap<Vec<usize>, bool>, m: &[usize], n_vars: usize, max_deg: usize, ) }
BFS 标记 $m$ 的所有倍(总次数不超过 max_deg)为已访问。max_deg 设为 $2D$——足够覆盖所有可能产生新首项的单项式。
主循环细节
主循环维护:
boundary:候选单项式的集合(初始为 ${1}$,即全零指数向量)seen_nfs:已见正规形向量的列表seen_mons:对应的单项式列表new_basis:输出的 Gröbner 基
每步取 boundary 中目标序最小的单项式,计算正规形,检测线性关系。若相关 → 输出新多项式并标记倍;若无关 → 加入 seen 并扩展邻居。
循环终止条件:boundary 为空或步数超过 $D(D+1) + 4n$(安全上限)。
最终对 new_basis 执行 minimize()(移除冗余)和 auto_reduce()(互相约化),得到约化 Gröbner 基。
理想运算实现
理想运算实现在 ocas-poly/src/ideal.rs 中,统一使用 Lex 序以保证消元性质。
成员判定
#![allow(unused)] fn main() { pub fn ideal_contains<D: Domain + 'static>( generators: &[SparseMultivariatePolynomial<D, Lex>], f: &SparseMultivariatePolynomial<D, Lex>, algo: Algorithm, ) -> bool }
计算 GB 后对 $f$ 求正规形,检查是否为零。
理想商
#![allow(unused)] fn main() { pub fn ideal_quotient<D: Domain + 'static>( generators_i: &[SparseMultivariatePolynomial<D, Lex>], generators_j: &[SparseMultivariatePolynomial<D, Lex>], ) -> GroebnerBasis<D, Lex> }
内部流程:
- 对 $J$ 的每个生成元 $g_j$,调用
quotient_single_generator(Rabinowitsch 技巧) quotient_single_generator实现:- 将 $I$ 的生成元提升到 $R[w]$
- 添加 $1 - wg$
- 计算 Lex GB
- 消去 $w$(取不涉及 $w$ 的多项式)
- 对所有 $I : g_j^\infty$ 的结果取交集(
intersect_generators)
理想交集
#![allow(unused)] fn main() { pub fn ideal_intersection<D: Domain + 'static>( generators_a: &[SparseMultivariatePolynomial<D, Lex>], generators_b: &[SparseMultivariatePolynomial<D, Lex>], ) -> GroebnerBasis<D, Lex> }
内部调用 intersect_generators:
- 引入辅助变量 $t$(index 0)
- 构造生成元 ${t \cdot f_i} \cup {(1-t) \cdot g_j}$
- 在扩展环 $R[t]$ 中计算 Lex GB
- 消去 $t$,得到 $I \cap J$ 的生成元
理想饱和
#![allow(unused)] fn main() { pub fn ideal_saturate<D: Domain + 'static>( generators_i: &[SparseMultivariatePolynomial<D, Lex>], generators_j: &[SparseMultivariatePolynomial<D, Lex>], ) -> GroebnerBasis<D, Lex> }
迭代调用 ideal_quotient 直到 GB 稳定(双向包含判定:两边基元素互相约化为零),上限 20 轮;超限后返回当前结果。
准素分解
#![allow(unused)] fn main() { pub fn primary_decomposition( generators: &[SparseMultivariatePolynomial<RationalDomain, Lex>], ) -> Vec<PrimaryComponent> }
目前仅实现零维情况(primary_decomp_zero_dim),且只处理第一个变量:
- 计算 Lex GB
- 提取仅含 $x_1$ 的一元多项式 $p_1(x_1)$,计算其无平方部分
- 对无平方部分做 $\mathbb{Q}$ 上因式分解
- 用饱和分离各分支(对每个其他因子依次
ideal_saturate) - 分支的关联素理想取 $\mathrm{GB}(I + \langle q_i \rangle)$;只有一个因子时返回单一分支($\text{prime} = \sqrt{I}$)
PrimaryComponent 结构体包含:
#![allow(unused)] fn main() { pub struct PrimaryComponent { pub primary: GroebnerBasis<RationalDomain, Lex>, // 准素理想 pub prime: GroebnerBasis<RationalDomain, Lex>, // 关联素理想 } }
根式
#![allow(unused)] fn main() { pub fn ideal_radical( generators: &[SparseMultivariatePolynomial<RationalDomain, Lex>], ) -> GroebnerBasis<RationalDomain, Lex> }
分两种情况:
- 零维(
radical_zero_dim):对 Lex GB 中的每个一元多项式做无平方分解,替换后重新生成 GB - 正维(
radical_via_jacobian):计算所有生成元对所有变量的非平凡偏导数,取 $h$ 为其中总次数最小者(启发式,非真正的 $\gcd$),计算 $I : h^\infty$
其他判定
#![allow(unused)] fn main() { pub fn is_zero_dimensional(gb: &GroebnerBasis<RationalDomain, Lex>) -> bool pub fn is_prime_ideal(generators: &[...]) -> bool pub fn is_primary_ideal(generators: &[...]) -> bool }
is_zero_dimensional:检查每个变量是否有纯幂首项is_prime_ideal:零维检查不可约性,正维保守返回falseis_primary_ideal:检查是否恰好有一个关联素理想
零维系统求解
#![allow(unused)] fn main() { pub fn solve_polynomial_system( equations: &[SparseMultivariatePolynomial<RationalDomain, Lex>], algo: Algorithm, ) -> PolynomialSystemSolution }
算法流程:
- 计算 Lex GB(若输入不是 GB)
- 检查零维性
- 三角分解求解(
solve_triangular)——从 Lex 序中最小的变量($x_n$)开始向前回代:- 提取仅含 $x_n$ 的多项式 → 实根隔离 + 数值求根
- 对每个根,代入涉及 $x_{n-1}$ 的多项式 → 求 $x_{n-1}$ 的根
- 递归直到所有变量确定(结果按 $x_1, \ldots, x_n$ 顺序返回)
返回类型:
#![allow(unused)] fn main() { pub enum PolynomialSystemSolution { ZeroDimensional(ZeroDimSolutions), // 有限个解 PositiveDimensional(GroebnerBasis), // 正维(无穷多解) Empty, // 无解 } }
进阶话题
FGLM 与 F4 的选择
| 场景 | 推荐方法 | 原因 |
|---|---|---|
| 零维 + 需要 Lex GB | Grevlex F4 → FGLM | $O(n \cdot D^3)$ 远快于 Lex F4 |
| 零维 + 需要 Grevlex GB | 直接 F4 | 无需换序 |
| 正维 + 需要 Lex GB | Lex F4 或 reorder | FGLM 不适用 |
| 消元 + 零维 | FGLM → 消元 | 消元定理保证正确性 |
| 消元 + 正维 | Lex F4 | FGLM 不适用 |
倍域上的 FGLM
oCAS 的 FGLM 实现对任意域 $D$ 泛型工作,正规形计算完全经由 Domain trait 的运算(有限域元素以 $[0, p)$ 的规范代表存储)。注意:F4/F5 中针对 $\mathbb{F}_p$ 的 FpPoly i64 快速路径并不用于 FGLM。
局限性
- 仅限零维:正维理想的阶梯无穷,FGLM 不适用
- $D^3$ 瓶颈:当向量空间维数 $D$ 很大时(如 $D > 10^4$),高斯消元成为瓶颈
- 内存:需要存储 $D \times D$ 乘法矩阵和增广矩阵
与文献的关系
oCAS 的实现直接基于原始论文:
- FGLM:Faugère, Gianni, Lazard, Mora (1993), Efficient Computation of Zero-dimensional Gröbner Bases by Change of Ordering, JSC
- 理想运算:Cox, Little, O'Shea, Ideals, Varieties, and Algorithms, Chapters 4, 8
- 准素分解:Gianni, Trager, Zacharias (1988), Gröbner Bases and Primary Decomposition of Polynomial Ideals
- 根式计算:Kemper (2002), A Course in Commutative Algebra
参考文献
- J.-C. Faugère, P. Gianni, D. Lazard, T. Mora. "Efficient Computation of Zero-dimensional Gröbner Bases by Change of Ordering." Journal of Symbolic Computation, 16(4):329–344, 1993.
- D. Cox, J. Little, D. O'Shea. Ideals, Varieties, and Algorithms. 4th ed., Springer, 2015. Chapters 3 (消元), 4 (理想商与饱和), 8 (准素分解).
- W. W. Adams, P. Loustaunau. An Introduction to Gröbner Bases. AMS, 1994.
- P. Gianni, B. Trager, G. Zacharias. "Gröbner Bases and Primary Decomposition of Polynomial Ideals." Journal of Symbolic Computation, 6(2–3):149–167, 1988.
- G. Kemper. A Course in Commutative Algebra. Springer, 2011.
- T. Becker, V. Weispfenning. Gröbner Bases: A Computational Approach to Commutative Algebra. Springer, 1993.
参见:
- Rust API:Gröbner 基与理想 — 函数签名与完整示例
- Gröbner 基理论 — Buchberger/F4/F5 算法的理论基础
- 多项式代数 — 单项式序与多元带余除法
- 多项式 GCD 与因式分解 — 无平方分解与因式分解算法
- 求解器 — 多项式系统求解与 ODE
高阶:代数数域
代数数域是计算机代数中连接多项式代数与数论的核心结构。在 oCAS 中,代数数域 $\mathbb{Q}(\alpha)$ 不仅是系数域——允许在扩域上做多项式运算和因式分解——还是 Galois 域 $\mathrm{GF}(p^d)$ 的统一抽象。本章从极小多项式和域扩张的基本概念出发,系统讲解代数数域上的算术、范数算法和模方法,并展示这些理论如何映射到 oCAS 的实现中。
前提知识
阅读本章前,建议先学习:
- 基础:多项式代数 — 多项式环、次数、带余除法、不可约性
- 基础:有限域与模算术 — $\mathbb{F}_p$ 构造、模逆、CRT
- 基础:线性代数 — 矩阵运算、行列式、结式
- 进阶:多项式 GCD 与因式分解 — 无平方分解、Hensel 提升、模 GCD
极小多项式
设 $K$ 是 $\mathbb{Q}$ 的扩域,$\alpha \in K$。若存在非零多项式 $f \in \mathbb{Q}[x]$ 使得 $f(\alpha) = 0$,则称 $\alpha$ 为代数元(algebraic element)。满足此条件的首一最低次多项式 $m(x)$ 称为 $\alpha$ 的极小多项式(minimal polynomial)。
性质:
- 极小多项式在 $\mathbb{Q}[x]$ 中不可约。
- 任何满足 $f(\alpha) = 0$ 的多项式 $f$ 都有 $m \mid f$。
- 极小多项式在同构意义下唯一。
例:$\sqrt{2}$ 的极小多项式是 $x^2 - 2$,因为 $(\sqrt{2})^2 - 2 = 0$ 且 $x^2 - 2$ 在 $\mathbb{Q}$ 上不可约(Eisenstein 判据,$p = 2$)。
域扩张与扩张次数
给定域 $F \subseteq K$,称 $K$ 是 $F$ 的一个域扩张(field extension),记为 $K/F$。$K$ 作为 $F$-向量空间的维数称为扩张次数:
$$[K : F] = \dim_F K$$
当 $K = F(\alpha)$(由单个代数元生成)时,称为单扩张(simple extension)。关键定理:
$$[F(\alpha) : F] = \deg(m_\alpha)$$
其中 $m_\alpha$ 是 $\alpha$ 在 $F$ 上的极小多项式。因此 ${1, \alpha, \alpha^2, \ldots, \alpha^{d-1}}$($d = [K:F]$)构成 $K$ 作为 $F$-向量空间的一组基。
域塔与传递公式
若有域塔 $F \subseteq K \subseteq L$,则扩张次数满足传递公式:
$$[L : F] = [L : K] \cdot [K : F]$$
例:设 $\alpha = \sqrt[3]{2}$,$\omega = e^{2\pi i/3}$。则 $\mathbb{Q}(\alpha, \omega) / \mathbb{Q}(\alpha)$ 的次数为 2($\omega$ 满足 $x^2 + x + 1$),$\mathbb{Q}(\alpha) / \mathbb{Q}$ 的次数为 3,因此 $[\mathbb{Q}(\alpha, \omega) : \mathbb{Q}] = 6$。
基础概念
$\mathbb{Q}(\alpha)$ 的元素表示
设 $m(x) = x^d + a_{d-1}x^{d-1} + \cdots + a_0$ 为 $\alpha$ 的极小多项式,$d = [K:\mathbb{Q}]$。由 $m(\alpha) = 0$ 可得
$$\alpha^d = -(a_{d-1}\alpha^{d-1} + \cdots + a_0)$$
因此 $\mathbb{Q}(\alpha)$ 中的每个元素都可以唯一表示为
$$c_0 + c_1 \alpha + c_2 \alpha^2 + \cdots + c_{d-1} \alpha^{d-1}, \quad c_i \in \mathbb{Q}$$
即 $\alpha$ 的次数 $< d$ 的多项式。这个表示是唯一的,因为两个不同的次数 $< d$ 的多项式之差是 $m(x)$ 的一个非零倍数(次数 $< d$ 的多项式不可能是 $m$ 的倍数,除非为零)。
加法和数乘按系数逐项运算。乘法先做多项式乘法(得到次数 $\le 2(d-1)$ 的多项式),再对 $m(\alpha)$ 取模——即反复用 $\alpha^d = -\sum_{i=0}^{d-1} a_i \alpha^i$ 替换,直到次数 $< d$。
例:$\mathbb{Q}(\sqrt{2})$,$m(x) = x^2 - 2$。元素为 $a + b\sqrt{2}$($a, b \in \mathbb{Q}$)。乘法:
$$(1 + \sqrt{2})(3 - \sqrt{2}) = 3 + 3\sqrt{2} - \sqrt{2} - 2 = 1 + 2\sqrt{2}$$
求逆
元素 $a(\alpha) \in \mathbb{Q}(\alpha)$ 可逆当且仅当 $a(\alpha) \ne 0$。求逆的关键工具是扩展 Euclid 算法:对 $a(x)$ 和 $m(x)$ 在 $\mathbb{Q}[x]$ 上做扩展 Euclid,得到
$$s(x) \cdot a(x) + t(x) \cdot m(x) = g(x)$$
其中 $g = \gcd(a, m)$。由于 $m$ 不可约且 $a \not\equiv 0 \pmod{m}$,必有 $\deg(g) = 0$,即 $g$ 是非零常数。归一化后 $g = 1$,故
$$a(\alpha)^{-1} = s(\alpha) \bmod m(\alpha)$$
例:在 $\mathbb{Q}(\sqrt{2})$ 中求 $(1 + \sqrt{2})^{-1}$。
用扩展 Euclid 求 $s(x)(1+x) + t(x)(x^2-2) = 1$:
- $x^2 - 2 = (x - 1)(1 + x) + (-1)$
- 因此 $(-1) = (x^2 - 2) - (x-1)(1+x)$
- 归一化:$1 = (x-1)(1+x) - (x^2-2)$
所以 $s(x) = x - 1$,验证:$(1+\sqrt{2})(\sqrt{2}-1) = \sqrt{2}^2 - 1 = 1$。✓
核心理论
$\mathrm{GF}(p^d)$ 的构造
代数数域的模 $p$ "投影"需要有限域 $\mathrm{GF}(p^d)$。构造方法与 $\mathbb{Q}(\alpha)$ 完全平行:
- 取 $\mathbb{F}_p[x]$ 中一个 $d$ 次不可约多项式 $\bar{m}(x)$(通常取极小多项式 $m(x)$ 模 $p$ 的像)。
- $\mathrm{GF}(p^d) = \mathbb{F}_p[x]/(\bar{m}(x))$。
- 元素为 $c_0 + c_1\alpha + \cdots + c_{d-1}\alpha^{d-1}$,$c_i \in \mathbb{F}_p$。
- 运算与 $\mathbb{Q}(\alpha)$ 相同,只是系数在 $\mathbb{F}_p$ 中取模。
关键条件:$m(x) \bmod p$ 必须仍是 $d$ 次不可约多项式。若 $p$ 整除 $m$ 的某个分母或使 $m \bmod p$ 退化(降次或可约),则此素数不可用于模方法。
例:$m(x) = x^2 + 1$($\mathbb{Q}(i)$ 的极小多项式)。在 $\mathbb{F}_3$ 上 $x^2 + 1$ 不可约(因为 $0^2+1=1$,$1^2+1=2$,$2^2+1=2$,均非零),所以 $\mathrm{GF}(3^2) = \mathbb{F}_3[x]/(x^2+1)$,乘法群阶为 8。
Trager 范数算法
设 $K = \mathbb{Q}(\alpha)$,$f \in K[x]$ 是无平方多项式。Trager 算法通过将 $f$ 的因式分解问题从 $K[x]$ "降到" $\mathbb{Q}[x]$ 来求解。核心工具是范数映射。
范数的定义
对 $f \in K[x]$,其范数(norm)$N(f) \in \mathbb{Q}[x]$ 定义为:
$$N(f)(x) = \operatorname{Res}_\alpha\bigl(m(\alpha),, f(x, \alpha)\bigr)$$
即视 $f(x, \alpha)$ 为 $\alpha$ 的多项式(系数在 $\mathbb{Q}[x]$ 中),取它与 $m(\alpha)$ 的结式(消去 $\alpha$)。
关键性质:若 $f$ 在 $K[x]$ 中不可约,则 $N(f)$ 在 $\mathbb{Q}[x]$ 中是不可约多项式的幂。更精确地,若 $f = g_1 g_2 \cdots g_r$ 是 $K[x]$ 中的不可约分解,则
$$N(f) = N(g_1) \cdot N(g_2) \cdots N(g_r)$$
且每个 $N(g_i)$ 在 $\mathbb{Q}[x]$ 中不可约。
求值—插值计算范数
直接计算结式 $\operatorname{Res}_\alpha(m, f(x,\alpha))$ 对高次多项式代价昂贵。oCAS 使用求值—插值法:
- 范数 $N(f)$ 作为 $x$ 的多项式,次数为 $\deg_x(f) \cdot [K:\mathbb{Q}]$。
- 取 $n = \deg_x(f) \cdot d + 1$ 个有理点 $x_0, x_1, \ldots, x_{n-1}$(使用对称序列 $0, -1, 1, -2, 2, \ldots$ 保持算术小)。
- 在每个 $x_j$ 处,计算 $f(x_j, \alpha) \in K$(Horner 求值),再计算标量结式 $\operatorname{Res}_\alpha(m, f(x_j, \alpha))$。
- 用 Newton 差商插值恢复 $N(f)$。
这是精确的(有理算术无舍入),且避免了高次结式的直接计算。
Trager 平移
即使 $f$ 无平方,其范数 $N(f)$ 也可能有平方因子(例如 $f = x - \alpha$ 在 $\mathbb{Q}(\sqrt{2})$ 上的范数是 $x^2 - 2$,已无平方;但 $f = x^2 - 2$ 的范数是 $(x^2-2)^2$,有平方)。
Trager 平移:对 $f(x)$ 做替换 $x \mapsto x - s\alpha$($s \ge 0$),得到 $g(x) = f(x - s\alpha)$。由于坏的 $s$ 值(使 $N(g)$ 有平方的 $s$)只有有限多个,总存在 $s$ 使 $N(g)$ 无平方。
算法:
for s = 0, 1, 2, ...:
g(x) ← f(x − s·α) // Horner 复合
R(x) ← N(g)(x) // 求值—插值范数
if R(x) 无平方(模素数验证):
return (s, g, R)
oCAS 尝试最多 MAX_TRAGER_SHIFTS = 100 个平移值。无平方性用模方法验证:取若干素数 $p$,若 $R \bmod p$ 在 $\mathbb{F}_p[x]$ 中无平方,则 $R$ 在 $\mathbb{Q}[x]$ 中无平方(充分条件,非必要——但这恰好是我们需要的"接受"判据)。
因式分解流程
给定无平方 $f \in K[x]$:
- 找平移:$(s, g, R) \leftarrow \text{norm_with_shift}(f)$。
- 分解范数:$R = r_1 \cdot r_2 \cdots r_k$(在 $\mathbb{Q}[x]$ 中不可约分解)。
- 回拉到 $K$:对每个 $r_i$,计算 $\gcd_K(g, r_i)$(嵌入 $r_i$ 到 $K[x]$),得到 $f$ 在 $K[x]$ 中的因子。
- 撤消平移:$x \mapsto x + s\alpha$,恢复原变量。
正确性保证:范数的因子 $r_i$ 对应 $g$ 的因子,而 GCD 在 $K[x]$ 中精确提取这些因子。
数域上的模 GCD(Encarnación 方法)
第 3 步中的 $\gcd_K$(两个 $K[x]$ 多项式的 GCD)是计算瓶颈。直接在 $K[x]$ 上做 Euclidean GCD 会导致系数($\mathbb{Q}(\alpha)$ 的元素,即有理数的 $\alpha$-多项式)剧烈膨胀。
模方法(Encarnación)将问题投影到 $\mathrm{GF}(p^d)$:
输入:a, b ∈ K[x](K = ℚ(α))
输出:monic gcd(a, b) ∈ K[x]
1. CRT 状态:residues = None, modulus = 1
2. for p in primes_from(2):
3. if m(x) mod p 可约或降次: 跳过 // m 在 F_p 上退化,该素数不可用
4. GF ← GF(p^d) = F_p[x]/(m mod p)
5. if p | lc(a) 或 p | lc(b): 跳过
6. g_p ← monic(gcd(a mod GF, b mod GF)) // GF(p^d)[x] 上的 GCD
7. if deg(g_p) > best_deg: 跳过 // 不幸素数
8. if deg(g_p) < best_deg:
9. best_deg ← deg(g_p)
10. residues ← g_p 的系数表 // [x-次数][α-次数] 的 F_p 值
11. modulus ← p
12. else:
13. residues ← CRT(residues, g_p) // 逐系数合并
14. modulus ← modulus × p
15.
16. // 有理重构 + 试除验证
17. candidate ← rational_reconstruct(residues, modulus)
18. if candidate | a 且 candidate | b:
19. return candidate
20.
21. return dense_euclidean_gcd(a, b) // 安全回退
素数筛选
不是所有素数都适用于模方法。需要满足:
- $m(x) \bmod p$ 保持不可约且次数不变:否则 $\mathbb{F}_p[\alpha]/(m) \ne \mathrm{GF}(p^d)$,GCD 语义错误。
- $p$ 不整除首项系数:否则映射后多项式降次。
- $p$ 不整除任何系数的分母:有理数到 $\mathbb{F}_p$ 的映射失败。
不幸素数检测
若素数 $p$ 上的模 GCD 次数高于当前最优值,该素数是"不幸的"——$p$ 整除了某个真因子的系数。直接丢弃此结果。
若模 GCD 次数低于当前最优值,说明之前的素数都是不幸的,丢弃之前的积累,以新结果重新开始。
有理重构
CRT 合并后的残差是大整数(模大合数)。对每个系数($\alpha$-多项式的每个分量),用有理重构从整数对中恢复有理数:
给定整数 $a$ 和模数 $m$,找有理数 $n/d$ 使得 $a \equiv n/d \pmod{m}$,且 $|n|, |d| \le \sqrt{m/2}$。
Wang/扩展 Euclid 方法追踪 $(r_i, t_i)$ 序列直到 $|r_1|, |t_1| \le \sqrt{m/2}$,验证 $a \cdot d \equiv n \pmod{m}$。
试除验证
有理重构的候选 GCD 必须试除验证:检查它是否同时整除原始输入 $a$ 和 $b$。只有通过验证的结果才返回。
oCAS 最多尝试 MAX_ANF_GCD_PRIMES = 1000 个素数。极少需要超过数十个。极端情况下回退到 $K[x] = \mathbb{Q}(\alpha)[x]$ 上的稠密 Euclidean GCD(虽然慢但保证正确)。
分圆域 $\mathbb{Q}(\zeta_n)$
$n$ 次本原单位根 $\zeta_n = e^{2\pi i/n}$ 生成的数域 $\mathbb{Q}(\zeta_n)$ 称为分圆域(cyclotomic field)。
极小多项式
$\zeta_n$ 的极小多项式是分圆多项式(cyclotomic polynomial):
$$\Phi_n(x) = \prod_{\substack{1 \le k \le n \ \gcd(k,n)=1}} \left(x - e^{2\pi i k/n}\right)$$
其次数为 $\varphi(n)$(Euler 函数)。$\Phi_n$ 在 $\mathbb{Q}[x]$ 中不可约。
例:
- $\Phi_1(x) = x - 1$
- $\Phi_2(x) = x + 1$
- $\Phi_3(x) = x^2 + x + 1$
- $\Phi_4(x) = x^2 + 1$
- $\Phi_6(x) = x^2 - x + 1$
- $\Phi_p(x) = x^{p-1} + x^{p-2} + \cdots + 1$($p$ 为素数)
分圆多项式满足 $x^n - 1 = \prod_{d \mid n} \Phi_d(x)$,可用 Möbius 反演逐因子计算。
域的性质
- $[\mathbb{Q}(\zeta_n) : \mathbb{Q}] = \varphi(n)$。
- $\mathbb{Q}(\zeta_n)$ 是 $\mathbb{Q}$ 的Abel 扩张(Galois 群为 $(\mathbb{Z}/n\mathbb{Z})^*$)。
- 整数环为 $\mathbb{Z}[\zeta_n]$,判别式与 $n$ 的素因子有关。
例:$\mathbb{Q}(\zeta_5)$ 的扩张次数为 $\varphi(5) = 4$,极小多项式 $\Phi_5(x) = x^4 + x^3 + x^2 + x + 1$。元素表示为 $a_0 + a_1\zeta + a_2\zeta^2 + a_3\zeta^3$($a_i \in \mathbb{Q}$)。
在 oCAS 中构造分圆域只需指定分圆多项式:
#![allow(unused)] fn main() { use ocas_domain::{AlgebraicNumberField, Domain, Rational, RationalDomain}; // Q(ζ_5): 极小多项式 x^4 + x^3 + x^2 + x + 1 // 升幂排列:[1, 1, 1, 1, 1] let field = AlgebraicNumberField::new( RationalDomain, vec![ Rational::new(1, 1), Rational::new(1, 1), Rational::new(1, 1), Rational::new(1, 1), Rational::new(1, 1), ], ); let zeta = field.alpha(); // ζ_5 // ζ_5^5 = 1(由极小多项式保证) let z5 = field.pow(&zeta, 5); assert_eq!(z5, field.one()); }
在 oCAS 中的实现
核心结构体
代数数域的实现在 ocas-domain/src/algebraic.rs 中。
AlgebraicElement<E>
域元素——极小多项式的剩余类,以唯一的次数 $< d$ 的多项式代表存储:
#![allow(unused)] fn main() { pub struct AlgebraicElement<E> { coeffs: Vec<E>, // 升幂排列,尾部零已裁剪 } }
- 空向量表示零元素。
PartialEq直接比较系数向量(语义相等 = 结构相等,因为代表唯一)。Display从高次到低次输出:(c_d)·α^d + … + (c_1)·α + c_0(常数项不带括号;例如1 + 2α显示为(2)·α + 1)。
#![allow(unused)] fn main() { // 访问系数 let e = field.element(vec![r(1, 1), r(2, 1)]); // 1 + 2α assert_eq!(e.coeffs(), &[r(1, 1), r(2, 1)]); }
AlgebraicExtension<D>
域扩张 $D[\alpha]/(m(\alpha))$:
#![allow(unused)] fn main() { pub struct AlgebraicExtension<D: Domain> { base: D, // 基域(RationalDomain 或 FiniteField) min_poly: Vec<D::Element>, // 极小多项式(升幂,首一) } }
构造:
#![allow(unused)] fn main() { // Q(√2): m(x) = x² − 2,升幂排列 [−2, 0, 1] let field = AlgebraicNumberField::new( RationalDomain, vec![Rational::new(-2, 1), Rational::new(0, 1), Rational::new(1, 1)], ); }
不变量(debug_assert 检查):
- 极小多项式次数 $\ge 1$(至少 2 个系数)。
- 极小多项式首一(首项系数 = 1)。
- 不验证不可约性:可约模上的环有零因子,
inv对非单位返回None。
关键方法:
| 方法 | 说明 |
|---|---|
extension_degree() | $[K:\mathbb{Q}] = \deg(m)$ |
min_poly() | 极小多项式系数(升幂) |
base_domain() | 基域 $D$ |
alpha() | 生成元 $\alpha$ |
from_base(c) | 基域元素嵌入 |
element(coeffs) | 从系数构造(自动对 $m$ 取模) |
AlgebraicNumberField
类型别名:
#![allow(unused)] fn main() { pub type AlgebraicNumberField = AlgebraicExtension<RationalDomain>; }
算术运算
AlgebraicExtension<D> 实现了 Domain 和 EuclideanDomain trait。
加法/减法:多项式逐系数加减。
乘法:多项式乘法后对极小多项式取模(reduce)。取模过程:
while v.len() > d:
c ← v.pop() // 最高次系数
offset ← v.len() − d
for i in 0..d:
v[offset + i] −= c · m[i] // 减去 c·α^d 的展开
这利用了 $m$ 的首一性(首项系数 = 1),不需要除法。
求逆:扩展 Euclid 算法。计算 $s(x) \cdot a(x) + t(x) \cdot m(x) = 1$,返回 $s(\alpha) \bmod m(\alpha)$。若 $\gcd(a, m) \ne 1$($a$ 是零因子),返回 None。
除法:div(a, b) = a * inv(b)。inv(b) 为 None 时除法失败。
EuclideanDomain:域上的带余除法退化为精确除法(余数恒为零)。GCD 退化为:两者均为零时返回零,否则返回一。
数域上的因式分解(Trager 算法)
因式分解的实现在 ocas-poly/src/factor/algebraic.rs 中。
整体流程
输入:f ∈ K[x](K = ℚ(α))
输出:[(g₁, e₁), (g₂, e₂), …] 使得 f = lc(f) · ∏ gᵢ^eᵢ
1. if f 的系数全是 ℚ 常数:
// 快速路径:先在 ℚ 上分解,再逐因子在 K 上分裂
f_Q ← 去掉 α 分量(实际先去分母并取本原部分)
for (h, e) in factor_primitive(f_Q):
for g in factor_square_free_anf(K, embed(h)):
output (g, e)
return
2. for (g, e) in square_free_anf(K, f): // Yun 无平方分解
3. for h in factor_square_free_anf(K, g): // Trager 因式分解
4. output (h, e)
无平方分解(square_free_anf)
在 $K[x]$ 上实现 Yun 算法,但使用 gcd_anf(模 GCD)代替通用的伪余式 GCD。这是因为 $\mathbb{Q}(\alpha)$ 元素的系数是有理数,伪余式 GCD 的系数膨胀在任意精度有理数上更加严重。
Trager 因式分解(factor_square_free_anf)
#![allow(unused)] fn main() { fn factor_square_free_anf(field, f): if deg(f) <= 1: return [monic(f)] (s, g, R) ← norm_with_shift(field, f) // 找平移使范数无平方 rational_factors ← factor_square_free_rationals(R) // ℚ 上分解范数 if len(rational_factors) <= 1: return [monic(f)] // 范数不可约 → f 不可约 remaining ← g out ← [] back_shift ← s·α // 平移的逆 for r_i in rational_factors: r_i_K ← embed(r_i) // 嵌入 K[x] h ← gcd_anf(field, r_i_K, remaining) // 模 GCD remaining ← remaining / h out.append(monic(compose_linear(h, back_shift))) // 撤消平移 return out }
模 GCD(gcd_anf)
Encarnación 方法的完整实现。CRT 状态跟踪每个系数的 $\alpha$-分量:
#![allow(unused)] fn main() { // CRT 状态:residues[x-次数][α-次数] 是整数残差 let mut residues: Option<Vec<Vec<Integer>>> = None; let mut modulus = Integer::from(1); }
每步迭代:
- 取下一个素数 $p$,检查 $m \bmod p$ 是否不可约(
min_poly_irreducible_mod:映射系数到 $\mathbb{F}_p$,验证次数和不可约性)。 - 构造 $\mathrm{GF}(p^d) = \mathbb{F}_p[\alpha]/(m \bmod p)$。
- 映射 $a, b$ 到 $\mathrm{GF}(p^d)[x]$,计算 monic GCD。
- 合并到 CRT 状态。
- 尝试有理重构 + 试除验证。
不幸素数处理:比较模 GCD 次数与历史最优值。高次 → 丢弃;低次 → 重置。
回退:超过 MAX_ANF_GCD_PRIMES = 1000 个素数后,使用 $K[x]$ 上的稠密 Euclidean GCD。
从 $\mathbb{Q}$ 到 $\mathrm{GF}(p^d)$ 的映射
将有理数映射到 $\mathbb{F}_p$:
#![allow(unused)] fn main() { fn map_rational_fp(c: &Rational, fp: &FiniteField) -> Option<FiniteFieldElement> { let num = fp.element(c.numer().to_bigint()); // 分子 mod p let den = fp.element(c.denom().to_bigint()); // 分母 mod p fp.div(&num, &den) // num · den^{-1} mod p,分母为零时返回 None } }
将 $\mathbb{Q}(\alpha)$ 元素映射到 $\mathrm{GF}(p^d)$:
#![allow(unused)] fn main() { fn map_element_gf(e: &AlgebraicElement<Rational>, gf: &GaloisField) -> Option<...> { e.coeffs().iter().map(|c| map_rational_fp(c, gf.base_domain())).collect() } }
映射 $\alpha$-多项式到 $\mathrm{GF}(p^d)[x]$:逐系数映射。
平移与复合
compose_linear(f, shift) 用 Horner 方法计算 $f(x + \text{shift})$:
#![allow(unused)] fn main() { fn compose_linear<D: Domain>(f: &UP<D>, shift: &D::Element) -> UP<D> { let domain = f.domain().clone(); // linear = x + shift let linear = UP::from_coeffs(domain.clone(), vec![shift.clone(), domain.one()]); let mut acc = UP::from_coeffs(domain.clone(), Vec::new()); // 零多项式 for c in f.coeffs().iter().rev() { // acc ← acc · (x + shift) + c(多项式 Horner 复合) acc = acc .mul(&linear) .add(&UP::from_coeffs(domain.clone(), vec![c.clone()])); } acc } }
在 Trager 算法中,平移为 $x \mapsto x - s\alpha$,逆平移为 $x \mapsto x + s\alpha$。
范数计算
norm_eval_interp 实现求值—插值范数:
#![allow(unused)] fn main() { fn norm_eval_interp(field: &AlgebraicNumberField, g: &UP<AlgebraicNumberField>) -> UP<RationalDomain> { let deg_x = g.degree().unwrap_or(0); let d = field.extension_degree(); let n_points = d * deg_x + 1; // 插值需要的点数 // 对称点 0, −1, 1, −2, 2, ... for j in 0..n_points { let x = Rational::new(((j+1)/2) * sign, 1); // Horner 求值 g(x) ∈ K let val = horner_eval(g, field.from_base(x)); // 标量结式 Res_α(m, val(α)) let norm_val = m_poly.resultant(&val_as_poly); xs.push(x); ys.push(norm_val); } interpolate_rational(&xs, &ys) // Newton 差商插值 } }
Galois 域的统一抽象
AlgebraicExtension 的泛型设计使得 $\mathbb{Q}(\alpha)$ 和 $\mathrm{GF}(p^d)$ 共享同一份代码:
| 类型 | 基域 $D$ | 极小多项式 | 用途 |
|---|---|---|---|
AlgebraicExtension<RationalDomain> | $\mathbb{Q}$ | $\mathbb{Q}[x]$ 中不可约 | 精确算术 |
AlgebraicExtension<FiniteField> | $\mathbb{F}_p$ | $\mathbb{F}_p[x]$ 中不可约 | 模方法的"工作域" |
在 gcd_anf 中,同一素数 $p$ 上的 GCD 计算在 $\mathrm{GF}(p^d)[x]$ 中完成,然后 CRT 合并回 $\mathbb{Q}(\alpha)[x]$。
进阶话题
可约模与零因子
当 $m(x) \bmod p$ 在 $\mathbb{F}_p$ 上可约时,$\mathbb{F}_p[x]/(m)$ 不是域——它有零因子。oCAS 不验证极小多项式的不可约性,允许在可约模上构造环。此时:
- 非零零因子的
inv返回None(扩展 Euclid 的 $\gcd \ne 1$)。 gcd_anf跳过此类素数(min_poly_irreducible_mod检查)。
例:$m(x) = x^2 - 1 = (x-1)(x+1)$ 在 $\mathbb{Q}$ 上可约。$\mathbb{Q}[x]/(x^2-1)$ 有零因子 $\alpha - 1$(因为 $(\alpha-1)(\alpha+1) = \alpha^2-1 = 0$)。
#![allow(unused)] fn main() { let field = AlgebraicNumberField::new( RationalDomain, vec![Rational::new(-1, 1), Rational::new(0, 1), Rational::new(1, 1)], ); let alpha = field.alpha(); let a = field.sub(&alpha, &field.one()); assert!(field.inv(&a).is_none()); // α − 1 是零因子 }
模 GCD 的素数筛选效率
在实践中,大多数小素数都满足 $m \bmod p$ 不可约的条件。对于度 $d$ 的极小多项式,$\mathbb{F}_p$ 上随机 $d$ 次多项式不可约的概率约为 $1/d$(由不可约多项式计数公式)。因此平均只需尝试约 $d$ 个素数就能找到一个可用的。
oCAS 从 $p = 2$ 开始遍历素数(使用 primes_from 迭代器)。对于较大的 $d$(如 $d \ge 6$),可能需要跳过较多素数,但 1000 个素数的预算几乎总是足够的。
系数膨胀与有理重构的权衡
模方法的核心优势是避免了系数膨胀:$\mathrm{GF}(p^d)$ 上的运算是模运算,系数有界。但 CRT 合并后的残差增长为 $O(\prod p_i)$,有理重构需要 $\sqrt{m/2}$ 的界。
实践中,若真 GCD 的系数较小(典型情况),通常 2–5 个素数就够了。若系数很大(如高次扩张上的复杂 GCD),可能需要更多素数来积累足够的 CRT 模数。
与多变量因式分解的关系
Trager 范数算法也可以用于多变量多项式在代数数域上的因式分解(Wang EEZ + Zassenhaus 重组),但这在 oCAS 中尚未实现。当前的多元因式分解(ocas-poly/src/factor/multivariate.rs)仅支持 $\mathbb{Z}$ 和 $\mathbb{F}_p$ 系数。
计算复杂度
对 $f \in K[x]$($K = \mathbb{Q}(\alpha)$,$[K:\mathbb{Q}] = d$,$\deg f = n$):
| 步骤 | 复杂度 | 说明 |
|---|---|---|
| 范数计算 | $O(dn \cdot (n + d^3))$ 次系数运算 | 每个求值点:Horner 求值 $O(n)$ + 结式 $O(d^3)$;共 $O(dn)$ 个点 |
| 无平方分解 | $O(n^2)$ 次 GCD 调用 | Yun 算法,瓶颈在 GCD |
| 模 GCD | $O(d \cdot p \cdot \log p)$ 每个素数 | $\mathrm{GF}(p^d)$ 上的 Euclidean GCD |
| CRT 合并 | $O(d \cdot n \cdot \log^2 M)$ | $M$ 为 CRT 模数,$n$ 个系数 × $d$ 个 $\alpha$-分量 |
总体瓶颈通常是范数计算中的结式求值($d$ 较大时)或模 GCD 的素数筛选(不幸素数较多时)。
参考文献
- [Cohen] H. Cohen, A Course in Computational Algebraic Number Theory, Springer GTM 138, 1993. — 第 2 章(数域的表示与算术)、第 3 章(基本数论函数)、第 4 章(理想与类群)。
- [Trager] B. M. Trager, "Algebraic factoring and rational function integration", Proc. SYMSAC 1976, pp. 196–208. — Trager 范数算法的原始论文。
- [Encarnación] M. J. Encarnación, "Computing GCDs of polynomials over algebraic number fields", J. Symbolic Computation 20:3, 1995, pp. 299–313. — 数域上模 GCD 方法。
- [Gathen–Gerhard] J. von zur Gathen & J. Gerhard, Modern Computer Algebra, 3rd ed., Cambridge, 2013. — 第 6 章(域扩张)、第 14–15 章(因式分解)。
- [Cox–Little–O'Shea] D. Cox, J. Little & D. O'Shea, Ideals, Varieties, and Algorithms, 4th ed., Springer UTM, 2015. — 第 4 章(多项式环上的 Groebner 基与消元)。
- [Lang] S. Lang, Algebra, 3rd ed., Springer GTM 211, 2002. — 第 5 章(域扩张的基本理论)。
参见:系数域 API — AlgebraicExtension、AlgebraicNumberField、AlgebraicElement 完整 API;因式分解 API — DenseUnivariatePolynomial::factor() 在代数数域上的使用;多项式 GCD 与因式分解 — 模 GCD 和 Hensel 提升的通用理论;有限域与模算术 — $\mathbb{F}_p$ 和 $\mathrm{GF}(p^d)$ 的构造。
高阶:张量代数与规范化
本章讨论 oCAS 中张量表达式的代数结构与规范化的数学基础。核心问题:给定一个包含缩并和对称性的张量乘积表达式,如何判定两个看起来不同的写法(例如 $T_{\mu\nu} U^{\nu\rho}$ 与 $U^{\rho\nu} T_{\nu\mu}$)是否代表同一个数学对象?oCAS 的方案是将张量表达式编码为有色有向图,然后通过图同构算法计算其规范形式。
前提知识
张量积与缩并
一个 $(r, s)$-型张量 $T$ 有 $r$ 个逆变指标(上标)和 $s$ 个协变指标(下标)。两个张量的张量积 $T \otimes U$ 将指标简单拼接。
缩并(contraction)是将一个逆变指标和一个协变指标配对并求和的操作。在 Einstein 求和约定中:
$$ T_{\mu\nu} U^{\nu\rho} = \sum_{\nu} T_{\mu\nu} U^{\nu\rho} $$
这里 $\nu$ 出现了两次——一次在下标、一次在上标——因此被求和掉,结果是一个 $(1,1)$-型张量。被求和掉的指标称为哑指标(dummy index),保留的指标称为自由指标(free index)。
Einstein 求和 vs. oCAS 显式匹配:Einstein 约定隐式地对重复指标求和,依赖位置(上/下)判断配对。oCAS 采用显式匹配——每个指标槽是一个带位置标记的参数,缩并通过两个槽共享同一标签来表达。这消除了对"上/下位置"的隐式依赖,使系统更加明确。
指标升降
在有度规 $g_{\mu\nu}$ 的空间中,可以通过缩并度规来升降指标:
$$ T^{\mu} = g^{\mu\nu} T_{\nu}, \qquad T_{\mu} = g_{\mu\nu} T^{\nu} $$
在 oCAS 的张量系统中,指标升降由 IndexPosition 枚举(Upper / Lower)标记在每个指标槽上,不自动引入度规张量。
基础概念
指标匹配
一个张量函数 $T(i, j, k)$ 的每个参数称为一个指标槽(index slot)。每个槽携带:
- 标签(label):一个
Atom,如i、mu、d0 - 位置(variance):
Upper(逆变)或Lower(协变)
两个槽之间的缩并由标签匹配来表达:当两个槽共享同一标签且一个为 Upper、另一个为 Lower 时,它们形成一个缩并对。
哑指标管理
在一个张量乘积 $T(i, j) \cdot U(j, k)$ 中,标签 $j$ 出现恰好两次,因此是哑指标。oCAS 的哑指标管理遵循以下规则:
- 识别:遍历表达式中所有
Fun节点的参数,统计每个标签的出现次数。出现恰好 2 次的标签是哑指标。 - 验证:规范化(
canon.rs)中,标签出现超过 2 次报错TensorCanonError::ContractedMoreThanOnce;dummy.rs声明了OverContracted与BadContraction(两次出现方差相同)错误变体,但refresh_dummies本身不执行方差校验。 - 重命名:规范化后,哑指标被替换为按组编号的新名称
d0,d1,d2, …。不同指标组(如时空指标和内部指标)使用不同的命名空间d{group}_{n}。
#![allow(unused)] fn main() { // 哑指标重命名示例 // 输入:T(i, j) * U(j, k) * V(i, l) // 输出:T(d0, d1) * U(d1, k) * V(d0, l) // i → d0(组 0,第 0 个),j → d1(组 0,第 1 个) }
这一重命名由 dummy.rs 中的 refresh_dummies 函数实现。它通过 TensorRegistry::index_group 查询标签所属的指标组,确保不同维度的哑指标使用不同的命名空间。
核心理论
图编码
张量规范化的第一步是将张量表达式编码为一个有色有向图 $G = (V, E)$。编码规则如下(实现于 canon.rs 的 tensor_to_graph):
顶点类型
| 类型 | 顶点数据 TgNode | 含义 |
|---|---|---|
| 头顶点 | Head(hash) | 一个张量函数头 $T$,hash 为函数名的哈希值 |
| 槽顶点 | Slot(hash) | 一个指标槽,hash 为指标标签的哈希值(对称槽统一为 Slot(0)) |
| 标量顶点 | Scalar(hash) | 乘积中的标量因子 |
边类型
| 类型 | 边数据 TgEdge | 方向 | 含义 |
|---|---|---|---|
| 头→槽 | HeadToSlot(pos, flag) | 有向 | 张量头到其第 pos 个槽;flag=1 可见,flag=0 隐藏(对称) |
| 缩并 | Contraction(group) | 无向 | 两个缩并的槽顶点之间的边;group 为指标组标识 |
对称性处理
当张量的某些槽是对称的(SymmetrySpec::symmetric_subsets)时,这些槽在图中被标记为隐藏:
- 对称槽的顶点数据统一为
Slot(0),使得图同构引擎可以自由交换它们。 - 对称槽的头→槽边标记为
HeadToSlot(pos, 0)(flag=0),并使用排序后的位置而非原始位置,使 $T(a, b)$ 和 $T(b, a)$ 生成相同的编码(flag 本身仍参与证书比较)。 - 输入时,对称槽按标签字母序预排序,确保 $T(a, b)$ 和 $T(b, a)$ 生成相同的图。
关键不变量:两个张量表达式数学上等价 $\Leftrightarrow$ 它们的图同构。
McKay 精炼-个体化算法
图同构判定和规范标号计算使用 McKay 精炼-个体化算法(nauty 系列),实现在 graph.rs(约 1100 行的独立实现)。
算法概览
给定一个图 $G$,算法输出:
- 规范标号 $\lambda: V \to {0, 1, \dots, n-1}$——一个双射,使得对任意同构图 $G'$,$\lambda$ 和 $\lambda'$ 产生的证书(certificate)完全相同。
- 自同构轨道——顶点在自同构群作用下的等价类。
- 自同构群阶 $|\text{Aut}(G)|$。
第 1 步:初始着色
将顶点按其 data 值分组:相同 data 的顶点属于同一单元(cell)。例如,所有 Head(42) 顶点在同一单元,所有 Slot(7) 在另一单元。
这定义了一个有序划分 $\pi_0 = (C_1, C_2, \dots, C_k)$,其中每个 $C_i$ 是一个顶点集合。
第 2 步:1-WL 颜色精炼
精炼过程迭代地将每个非平凡单元($|C| > 1$)按其对其他单元的邻居签名(neighbour signature)进行分裂:
对于顶点 $v \in C_i$ 相对于目标单元 $C_j$ 的签名:
$$ \sigma(v, C_j) = \text{sorted}\bigl[,(\text{edge_data}, \text{direction}) ;\big|; \text{edge } v \to w,; w \in C_j,\bigr] $$
其中 direction 编码为:0 = 有向出边,1 = 有向入边,2 = 无向边。
如果同一单元内的顶点有不同的签名,则将该单元按签名分裂为多个子单元。重复此过程直到划分是公平的(equitable)——即对于任意两个在同一单元的顶点 $u, v$ 和任意单元 $C_j$,$\sigma(u, C_j) = \sigma(v, C_j)$。
复杂度:每轮精炼最多产生 $O(n)$ 个新单元,每步需要 $O(n^2)$ 时间检查所有 (单元, 目标) 对。实际中,精炼通常在 $O(n \log n)$ 步内收敛。
第 3 步:个体化-精炼搜索(DFS)
如果精炼后划分仍然非离散(存在 $|C| > 1$ 的单元),算法进入搜索阶段:
- 选择最小非平凡单元 $C_{\min}$。
- 个体化(individualize)$C_{\min}$ 中的每个顶点 $v$:将 $v$ 从 $C_{\min}$ 移出,放入一个新的单元素单元(位于 $C_{\min}$ 之前)。这打破了 $v$ 与其他顶点的对称性。
- 对个体化后的划分重新精炼。
- 递归直到划分离散(每个单元恰好一个顶点),产生一个离散标号。
搜索使用栈式 DFS(非递归),通过 SearchFrame 记录每层的划分、路径不变量和是否在最左路径。
第 4 步:路径不变量与自同构剪枝
路径不变量
每个搜索节点记录一个路径不变量 $\mathcal{I}$——从根到当前节点的划分单元长度序列:
$$ \mathcal{I} = \bigl[,|C_1^{(0)}|, \dots, |C_k^{(0)}|,\bigr] ;\to; \bigl[,|C_1^{(1)}|, \dots, |C_{k'}^{(1)}|,\bigr] ;\to; \cdots $$
如果两个搜索节点的路径不变量不同,它们不可能产生同构的离散标号,因此可以安全剪枝。(当前实现计算并随 SearchFrame 记录该不变量,但尚未执行基于不变量的剪枝——实际生效的剪枝只有自同构轨道剪枝。)
自同构轨道剪枝
当算法发现两个不同的离散标号产生相同的证书时,它们之间的映射构成一个自同构。这个自同构被记录为轨道生成器(orbit generator)。
在后续搜索中,如果待个体化的顶点 $v$ 与已处理的顶点 $w$ 在自同构群作用下处于同一轨道(即存在自同构将 $v$ 映射到 $w$),则跳过 $v$——因为个体化 $v$ 必然产生与个体化 $w$ 同构的子树。
轨道的计算通过 BFS 闭包实现:从种子顶点出发,反复应用生成器(及其逆元),直到闭合。
第 5 步:规范形式
在搜索过程中,算法维护当前找到的字典序最大证书。每当到达一个离散节点,计算其证书:
$$ \text{cert}(\lambda) = \text{sorted}\bigl[,(i, j, \text{edge_data}, \text{dir}) ;\big|; \text{edge } \lambda^{-1}(i) \to \lambda^{-1}(j),\bigr] $$
如果新证书更大,则更新最佳标号;如果相等,则记录自同构生成器。
最终输出:
- 规范图:按最佳标号重新标号的图。
- 自同构群阶:通过 BFS 闭包枚举生成器产生的所有置换来计算(上限 $n!$,实际中远小于此)。
- 轨道划分:由
compute_orbits从生成器集合计算。
完整示例
考虑表达式 $T_{\mu\nu} U^{\nu\rho}$,其中 $T$ 和 $U$ 无对称性:
图编码:
顶点: H_T (Head), S_mu (Slot), S_nu_T (Slot), H_U (Head), S_nu_U (Slot), S_rho (Slot)
边:
H_T → S_mu (HeadToSlot(0, 1))
H_T → S_nu_T (HeadToSlot(1, 1))
H_U → S_nu_U (HeadToSlot(0, 1))
H_U → S_rho (HeadToSlot(1, 1))
S_nu_T — S_nu_U (Contraction(0)) // 无向
初始着色:顶点按 data 分组——{H_T} 与 {H_U} 是两个独立的 Head 单元(函数名哈希不同);槽顶点按标签哈希分组:{S_mu}、{S_nu_T, S_nu_U}(标签同为 nu)、{S_rho}。
精炼:非平凡单元 {S_nu_T, S_nu_U} 对目标单元 {H_T} 的邻居签名不同($S_{\nu_T}$ 有来自 $H_T$ 的入边——头→槽边从 $H_T$ 指向 $S_{\nu_T}$——而 $S_{\nu_U}$ 没有),因此被分裂。
结果:经过精炼后到达离散划分,无需搜索。规范形式确定了张量因子的顺序和哑指标的命名。
Young 表与对称化投影
Young 表是表示对称群不可约表示的经典工具,在张量代数中用于构造具有特定对称性的张量分量。
Young 表的定义
一个 Young 表(Young tableau)由一个整数分拆 $\lambda = (\lambda_1 \geq \lambda_2 \geq \cdots \geq \lambda_k > 0)$ 定义,其中 $\lambda_i$ 是第 $i$ 行的方格数。总方格数 $|\lambda| = \sum \lambda_i$ 等于张量的秩。
例如,$\lambda = (2, 1)$ 对应的 Young 图:
□ □
□
Young 对称化子
给定 Young 表 $\lambda$,其 Young 对称化子 $e_\lambda$ 作用于张量 $T$ 产生具有 $\lambda$ 指定对称性的分量。定义如下:
- 行对称化:对每一行内的指标进行完全对称化。
- 列反对称化:对每一列内的指标进行完全反对称化。
- 组合:先列反对称化,再行对称化。
形式化地,设 $R$ 为行对称群的直积,$C$ 为列反对称群的直积,则:
$$ e_\lambda = \left(\sum_{\sigma \in R} \sigma\right) \cdot \left(\sum_{\tau \in C} \operatorname{sgn}(\tau) \cdot \tau\right) $$
对张量 $T$ 的投影为:
$$ (e_\lambda \cdot T){i_1 \dots i_n} = \sum{\sigma \in R} \sum_{\tau \in C} \operatorname{sgn}(\tau) \cdot T_{i_{\sigma(\tau(1))} \dots i_{\sigma(\tau(n))}} $$
置换的符号计算
对于给定的排列 $\pi$,Young 投影的符号计算分两步:
-
行约束检查:对每个位置 $p$,检查 $\pi(p)$ 是否与 $p$ 在同一行。如果不是(且该行长度 > 1),则 $\operatorname{sgn} = 0$(该排列不保持表的形状)。
-
列奇偶性:对每一列 $c$,提取 $\pi$ 限制在该列位置上的子置换,计算其奇偶性(通过圈分解:长度为偶数的圈贡献 $-1$ 因子)。所有列的奇偶性相乘得到总符号。
$$ \operatorname{sgn}\lambda(\pi) = \begin{cases} \displaystyle\prod{c} (-1)^{#\text{even cycles in column } c} & \text{if } \pi \text{ preserves rows} \ 0 & \text{otherwise} \end{cases} $$
oCAS 实现:显式展开
young.rs 中的实现采用显式置换展开(非 BSGS 群论方法):
- 将行组 $R$(每行内部置换)与列组 $C$(每列内部置换)分别展开为完整位置映射,共 $\prod r_i! \cdot \prod c_j!$ 个组合。
- 对每个组合 $(\sigma, \tau)$ 构造复合置换 $\tau \circ \sigma$,项系数为列置换奇偶 $\operatorname{sgn}(\tau)$。
- 每项贡献 $\operatorname{sgn}(\tau) \cdot T(i_{\tau\sigma(1)}, \dots, i_{\tau\sigma(n)})$。
- 所有项求和——这是经典 Young 对称子 $c_\lambda = a_\lambda b_\lambda$,不做 Hook 长度归一化,每项系数仅为 $\pm 1$;因此结果是模标量 $\kappa = (\prod r_i! \cdot \prod c_j!) / \dim(\lambda)$ 的投影算子($c_\lambda^2 = \kappa \cdot c_\lambda$)。
完全反对称表 $\lambda = (1, 1, \dots, 1)$:行组平凡,列组为整个 $S_n$,产生标准交错和。
完全对称表 $\lambda = (n)$:每列一个元素,列反对称化退化为恒等,行对称化产生所有排列的和。
对称性规格
SymmetrySpec 结构体声明一个张量函数头的槽位对称性,是图编码和 Young 投影之间的桥梁。
三种对称类型
| 类型 | 字段 | 图编码行为 | Young 投影行为 |
|---|---|---|---|
| 对称子集 | symmetric_subsets | 槽标记为隐藏(is_slot_hidden = true),图同构可自由置换 | 保持对应行对称 |
| 反对称子集 | antisymmetric_subsets | 槽可见,编码与普通槽相同(无特殊标记;反对称符号由 Young 投影在表达式层面处理) | 保持对应列反对称 |
| 循环置换 | cyclic | 槽标记为隐藏 | 循环置换的对称化 |
is_slot_hidden 判定
一个槽位 $p$ 被标记为隐藏当且仅当:
$$ \text{is_hidden}(p) \iff \exists S \in \text{symmetric_subsets}: p \in S ;\lor; \exists C = \text{cyclic}: p \in C $$
隐藏槽在图中使用统一的颜色 Slot(0),使图同构引擎可以自由交换它们,从而保证 $T(a, b) = T(b, a)$ 对对称张量成立。
TensorRegistry
TensorRegistry 管理所有张量的规格和指标组:
register(name, spec):注册张量函数头及其对称性规格。set_index_group(label, group):设置指标标签所属的组(如时空指标组 1,内部指标组 2)。index_group(label):查询指标组(0 为默认/未分组)。
指标组的作用是在哑指标重命名时防止跨维度的冲突。例如,时空哑指标 $\mu$ 和内部哑指标 $i$ 使用不同的命名空间 d1_0, d1_1, … 和 d2_0, d2_1, …。
在 oCAS 中的实现
模块结构
ocas-atom/src/tensor/
├── canon.rs # 图编码 + 规范化入口
├── graph.rs # McKay 精炼-个体化引擎(约 1100 行)
├── young.rs # Young 表投影
├── spec.rs # SymmetrySpec + TensorRegistry
└── dummy.rs # 哑指标管理
canon.rs:图编码与规范化
入口函数 canonicalize_tensors(ctx, expr, registry):
- 如果表达式是加法(
Add),逐项规范化并验证自由指标一致性。 - 单项式进入
canonicalize_single_term:- 快速路径:如果张量有对称子集、无反对称子集、且全部槽都是隐藏的(
is_slot_hidden全部为真),直接按标签字母序排序参数,无需图同构。 - 通用路径:调用
tensor_to_graph编码为图,调用g.canonize()计算规范形式,调用reconstruct重建表达式。
- 快速路径:如果张量有对称子集、无反对称子集、且全部槽都是隐藏的(
tensor_to_graph 的编码逻辑:
- 对乘积中的每个因子
Fun(name, args):- 创建头顶点
Head(hash(name))。 - 对称槽按标签字母序预排序,使 $T(a,b)$ 和 $T(b,a)$ 生成相同的图。
- 对每个参数创建槽顶点,隐藏槽使用
Slot(0),可见槽使用Slot(hash(label))。 - 添加有向边
Head → Slot。
- 创建头顶点
- 对乘积中标签出现恰好 2 次的槽对,添加无向缩并边。
reconstruct 的重建逻辑:
- 遍历规范图中的头顶点,按规范顺序收集张量因子。
- 对每个头的出边(按
orig_pos或标签序排序),确定槽的标签:- 缩并槽:分配规范哑指标名
d0,d1, … - 自由槽:保留原始标签。
- 缩并槽:分配规范哑指标名
- 将因子组装为乘积或加法表达式。
graph.rs:McKay 算法
这是 oCAS 中最大、最独立的算法模块(约 1100 行),实现了完整的 nauty 风格图同构引擎。
数据结构:
Graph<N, H, E>:三参数泛型图。N= 顶点可见数据(参与比较),H= 顶点隐藏数据(不参与比较),E= 边数据(参与比较)。Partition:有序划分,cells[i]为第 $i$ 个单元的顶点列表,cell_of[v]为顶点 $v$ 所在单元的索引。SearchFrame:DFS 搜索栈帧,包含划分、路径不变量和是否在最左路径。CanonicalForm<N, H, E>:输出结果,包含vertex_map(原→规范映射)、orbits(自同构轨道)、automorphism_group_size(群阶)和graph(规范图)。
关键实现细节:
- 精炼(
Partition::refine):使用stable_below指针优化——已稳定的单元不再参与分裂检查。当某单元被分裂后,stable_below回退到该单元的索引,确保后续精炼覆盖所有受影响的单元。 - 邻居签名(
cell_signatures/vertex_signature):对每个顶点 $v$,收集它到目标单元 $C_j$ 中所有邻居的(edge_data, direction)对,排序后作为签名。direction区分有向出边(0)、有向入边(1)和无向边(2)。 - 证书(
Graph::certificate):将图的所有边按规范标号转换为(i, j, data, dir)四元组列表,排序后作为字典序比较的键。 - 自同构群阶(
group_size):通过 BFS 闭包枚举生成器产生的所有置换(全枚举,无截断;上限为 $n!$,实际远小于此)。 - 轨道计算(
compute_orbits):BFS 从每个未访问顶点出发,沿生成器及其逆元扩展,直到闭合。
young.rs:Young 投影
YoungTableau 结构体:
row_lengths: Vec<usize>:各行的方格数,如[2, 1]表示 $\lambda = (2,1)$。total_boxes():总方格数。sign_of_permutation(perm):计算排列在给定表下的符号($-1, 0, +1$)。
young_project(ctx, tensor_expr, tableau):
- 仅处理
Fun(name, args)形式的表达式。 - 使用 Heap 算法生成全排列($O(n!)$ 时间,常数空间)。
- 对每个排列计算符号,非零则构造带符号的项。
- 返回所有项的加法(或单一项、零)。
注意:这是显式展开,适用于低秩张量($n \leq 8$ 左右)。对于高秩张量,应考虑使用 BSGS(基本 Schreier–Sims)方法,但 oCAS 当前未实现。
已知限制:sign_of_permutation 的列奇偶性代码对保持行但跨列移动的置换会触发 unwrap() panic(在另一列的列位置查找失败)。完全对称表 $(n)$ 与完全反对称表 $(1, \ldots, 1)$ 不会触发;一般形状(如 $(2,1)$)的显式展开可能 panic,使用时需注意。
spec.rs:对称性规格
SymmetrySpec 的三种构造器:
none():无对称性。fully_symmetric(rank):所有槽完全对称。fully_antisymmetric(rank):所有槽完全反对称。
is_slot_hidden(pos):检查槽是否属于对称子集或循环子集。隐藏槽不参与图规范化的比较,允许图同构引擎自由置换它们。
dummy.rs:哑指标管理
refresh_dummies(ctx, expr, registry):
- 收集表达式中所有
Fun节点参数的出现次数。 - 识别哑指标(出现恰好 2 次)。
- 对每个哑指标,查询其指标组(
registry.index_group),分配组内编号。 - 重命名:组 0 的哑指标命名为
d0,d1, …;组 $g$ 的命名为d{g}_0,d{g}_1, …。
错误变体(dummy.rs 声明,但 refresh_dummies 当前不触发):
OverContracted(Symbol):指标出现超过 2 次。BadContraction(Symbol):两次出现的方差相同(非上/下配对)。
(规范化路径中,超过 2 次的标签由 canon.rs 报 TensorCanonError::ContractedMoreThanOnce。)
进阶话题
隐藏槽与快速路径
对于全对称张量(如度规 $g_{\mu\nu}$),所有槽都被标记为隐藏。此时图中所有槽顶点共享同一颜色 Slot(0),McKay 算法的自同构群将非常大($|S_n| = n!$)。
为避免这种开销,canonicalize_single_term 检测到全对称张量时走快速路径:直接按标签字母序排序参数,无需构建图。这将规范化复杂度从 $O(n!)$ 降低到 $O(n \log n)$。
自同构群信息的用途
McKay 算法不仅输出规范形式,还输出自同构群的轨道和阶。这些信息在张量代数中有重要应用:
- 轨道:标识哪些槽在对称性下等价,可用于化简求和。
- 群阶:$|\text{Aut}(G)|$ 是表达式内在对称性的度量,可用于估计对称化展开的规模。
与 nauty 的关系
graph.rs 是一个独立的 nauty 风格实现,不依赖外部库。它实现了 McKay (1981/2014) 算法的核心组件:
- 1-WL 颜色精炼(等价于 nauty 的
refine) - 个体化-精炼 DFS(等价于
search) - 路径不变量记录(等价于 nauty 的
compare,但基于它的剪枝当前未启用) - 自同构轨道剪枝(等价于 nauty 的
orbits)
与原始 nauty 的主要差异:
- 使用邻接表而非位矩阵,适合稀疏图。
- 证书使用排序的边列表而非规范邻接矩阵。
- 隐藏数据(
H参数)允许存储不参与比较的元数据。
扩展到高阶对称性
对于更复杂的对称性(如黎曼曲率张量 $R_{abcd}$ 的代数 Bianchi 恒等式),当前系统通过组合 symmetric_subsets 和 antisymmetric_subsets 来近似。完整的 Young 对称化通过 young_project 函数实现,但它产生的是展开后的和,而非保持压缩表示。
参考文献
- Cvitanović, P. Group Theory: Birdtracks, Lie's, and Exceptional Groups. Princeton University Press, 2008. — 张量不变量与对称群表示的经典参考。
- McKay, B. D. Practical Graph Isomorphism. Congressus Numerantium, 30:45–87, 1981 (updated 2014). — nauty 算法的原始论文。
- Faugère, J.-C. A new efficient algorithm for computing Gröbner bases (F4). Journal of Pure and Applied Algebra, 139(1–3):61–88, 1999. — F4 算法,与张量缩并的矩阵方法相关。
- Cox, D., Little, J., O'Shea, D. Ideals, Varieties, and Algorithms. Springer, 4th ed., 2015. — 多项式代数基础,Gröbner 基理论参考。
- Hamermesh, M. Group Theory and Its Application to Physical Problems. Dover, 1989. — Young 表与张量对称性的物理应用。
高阶:蒙特卡洛积分
前提知识
- 线性代数 — 矩阵运算(用于理解高维采样)
- 基础概率论 — 随机变量、期望、方差
- 基础微积分 — 定积分的定义与变量替换
建议具备以下概率论基础:
| 概念 | 说明 |
|---|---|
| 期望 $\mathbb{E}[X]$ | 随机变量的加权平均值 |
| 方差 $\text{Var}(X) = \mathbb{E}[(X - \mu)^2]$ | 随机变量偏离均值的度量 |
| 独立同分布 (i.i.d.) | 多个随机变量相互独立且服从同一分布 |
| 大数定律 | 样本均值随样本量增大收敛于期望 |
| 中心极限定理 | 样本均值近似服从正态分布 |
基础概念
从定积分到期望
考虑定积分
$$I = \int_\Omega f(\mathbf{x}), d\mathbf{x}$$
其中 $\Omega \subseteq \mathbb{R}^d$ 是积分域。蒙特卡洛方法的核心思想是将积分重写为期望。
令 $\mathbf{X}$ 为在 $\Omega$ 上服从均匀分布的随机变量,其概率密度函数为
$$p(\mathbf{x}) = \frac{1}{V}, \qquad V = \text{vol}(\Omega)$$
则积分变为
$$I = V \cdot \mathbb{E}[f(\mathbf{X})]$$
基本估计量
蒙特卡洛基本估计量:从 $p(\mathbf{x})$ 中抽取 $N$ 个独立样本 $\mathbf{x}_1, \dots, \mathbf{x}_N$,定义
$$\hat{I} = \frac{V}{N} \sum_{i=1}^{N} f(\mathbf{x}_i)$$
性质:
- 无偏性:$\mathbb{E}[\hat{I}] = I$(精确等于真值的期望)。
- 方差:$\text{Var}(\hat{I}) = \frac{V^2}{N} \text{Var}(f(\mathbf{X}))$。
- 收敛速率:$\hat{I}$ 的标准误差为 $\sigma_{\hat{I}} \propto 1/\sqrt{N}$,与维度 $d$ 无关。
最后一条性质是蒙特卡洛方法的核心优势:传统数值积分(梯形法则、Simpson 法则)的误差随维度指数增长("维度灾难"),而蒙特卡洛方法的收敛速率始终为 $O(N^{-1/2})$。
大数定律与中心极限定理
大数定律(Kolmogorov 强大数定律). 若 $X_1, X_2, \dots$ 是独立同分布的随机变量,$\mathbb{E}[|X_1|] < \infty$,则
$$\bar{X}n = \frac{1}{n}\sum{i=1}^{n} X_i ;\xrightarrow{\text{a.s.}}; \mathbb{E}[X_1]$$
应用到蒙特卡洛:$\hat{I}/V \to \mathbb{E}[f(\mathbf{X})]$ 几乎必然成立,因此 $\hat{I} \to I$。
中心极限定理(CLT). 在上述条件下,若 $\text{Var}(X_1) = \sigma^2 < \infty$,则
$$\frac{\bar{X}_n - \mu}{\sigma / \sqrt{n}} ;\xrightarrow{d}; \mathcal{N}(0, 1)$$
应用到蒙特卡洛:对足够大的 $N$,$\hat{I}$ 近似服从正态分布:
$$\hat{I} ;\sim; \mathcal{N}!\left(I,; \frac{V^2 \sigma_f^2}{N}\right)$$
其中 $\sigma_f^2 = \text{Var}(f(\mathbf{X}))$。这给出了置信区间的基础。
方差估计与置信区间
实际中 $\sigma_f^2$ 未知,用样本方差估计:
$$\hat{\sigma}f^2 = \frac{1}{N-1} \sum{i=1}^{N} \bigl(f(\mathbf{x}i) - \bar{f}\bigr)^2, \qquad \bar{f} = \frac{1}{N}\sum{i=1}^{N} f(\mathbf{x}_i)$$
$\hat{I}$ 的标准误差估计为
$$\hat{\sigma}_{\hat{I}} = \frac{V}{\sqrt{N}} \hat{\sigma}_f$$
$95%$ 置信区间为 $\hat{I} \pm 1.96, \hat{\sigma}_{\hat{I}}$。
加权采样的推广:当采样密度 $p(\mathbf{x})$ 不是均匀分布时(重要性采样),每个样本 $i$ 带有权重 $w_i = 1/p(\mathbf{x}_i)$,则
$$\hat{I} = \frac{1}{N} \sum_{i=1}^{N} w_i \cdot f(\mathbf{x}_i)$$
方差估计变为
$$\hat{\sigma}^2 = \frac{1}{N} \sum_{i=1}^{N} w_i^2 f(\mathbf{x}i)^2 - \left(\frac{1}{N}\sum{i=1}^{N} w_i f(\mathbf{x}_i)\right)^2$$
oCAS 的 StatisticsAccumulator 使用与之类似的加权方差形式(见下文『统计累加器』一节)。
核心理论
重要性采样
蒙特卡洛估计量的方差取决于被积函数在采样分布下的"波动"程度。重要性采样(importance sampling)通过选择一个非均匀的采样分布 $p(\mathbf{x})$ 来降低方差。
原理:将积分重写为
$$I = \int_\Omega \frac{f(\mathbf{x})}{p(\mathbf{x})} \cdot p(\mathbf{x}), d\mathbf{x} = \mathbb{E}_p!\left[\frac{f(\mathbf{X})}{p(\mathbf{X})}\right]$$
估计量为
$$\hat{I}p = \frac{1}{N} \sum{i=1}^{N} \frac{f(\mathbf{x}_i)}{p(\mathbf{x}_i)}, \qquad \mathbf{x}_i \sim p$$
最优采样分布:方差最小化的 $p^*$ 为
$$p^*(\mathbf{x}) = \frac{|f(\mathbf{x})|}{\int_\Omega |f(\mathbf{y})|, d\mathbf{y}}$$
当 $f$ 不变号时,$w \cdot f = \int |f| \cdot \mathrm{sgn}(f)$ 是常数,此时方差为零;$f$ 变号时方差也达到最小。但 $p^$ 本身需要知道积分值——这是一个"先有鸡还是先有蛋"的问题。实际中,我们用自适应网格来近似 $p^$。
关键约束:$p(\mathbf{x})$ 必须在 $f(\mathbf{x}) \neq 0$ 的所有点上严格为正。若 $p$ 在 $f$ 非零处为零,估计量无界,方差无穷大。
Vegas 自适应网格
Vegas 算法(Lepage, 1978)是最广泛使用的自适应蒙特卡洛积分方法。其核心思想是用一个分段常数的采样密度来近似最优分布 $p^*$。
乘积网格结构
Vegas 在 $d$ 维单位超立方体 $[0,1]^d$ 上工作(物理域通过线性变量替换映射到超立方体)。采样密度采用乘积形式:
$$p(\mathbf{x}) = \prod_{k=1}^{d} p_k(x_k)$$
每个 $p_k$ 是第 $k$ 维上的独立一维分段常数密度。这意味着每维的网格独立维护——$d$ 维问题被分解为 $d$ 个一维问题。
第 $k$ 维的网格:将 $[0,1]$ 分为 $M$ 个 bin,边界为
$$0 = b_0 < b_1 < \cdots < b_M = 1$$
第 $j$ 个 bin 的宽度为 $\Delta_j = b_{j+1} - b_j$。在该 bin 上 $p_k(x) = 1/(M \cdot \Delta_j)$(常数),因为每个 bin 被等概率选中。
采样过程
对每个样本点 $\mathbf{x} = (x_1, \dots, x_d)$:
- 选 bin:对第 $k$ 维,从 ${0, 1, \dots, M-1}$ 中均匀随机选取 bin 编号 $j_k$。
- 选点:在 $[b_{j_k}, b_{j_k+1}]$ 内均匀随机选取 $x_k$。
- 计算 Jacobian(权重):该点的概率密度为 $p_k(x_k) = 1/(M \cdot \Delta_{j_k})$,因此
$$w_k = \frac{1}{p_k(x_k)} = M \cdot \Delta_{j_k}$$
总权重为
$$w = \prod_{k=1}^{d} w_k = \prod_{k=1}^{d} (M \cdot \Delta_{j_k})$$
- 累加:将 $w \cdot f(\mathbf{x})$ 加入统计累加器。
权重的物理意义:$w = 1/p(\mathbf{x})$ 是采样密度的倒数——稀疏区域($\Delta_j$ 大)的样本获得更大权重,密集区域($\Delta_j$ 小)的样本获得更小权重,从而补偿了非均匀采样。
网格自适应:训练信号
每次迭代中,Vegas 记录每个 bin 的"重要性"。对第 $k$ 维的第 $j$ 个 bin,训练信号为
$$d_j = \sum_{\text{samples in bin } j} w \cdot f^2(\mathbf{x})$$
直觉:$f^2$ 度量该 bin 区域内被积函数的"能量"(方差贡献),$w$ 补偿非均匀采样。高 $d_j$ 的 bin 对积分误差贡献大,需要更细的网格。
网格更新:平滑累积弧长重分布
有了训练信号 ${d_0, d_1, \dots, d_{M-1}}$,Vegas 通过以下步骤更新网格边界:
步骤 1:3-bin 平滑。对每个 bin $j$,计算平滑值:
$$\tilde{d}j = \frac{d{j-1} + d_j + d_{j+1}}{3}$$
边界 bin($j = 0$ 或 $j = M-1$)缺失的邻居用 0 代替。平滑的目的是抑制单 bin 的噪声,使网格变化更稳定。
步骤 2:均值归一化。计算平均值 $\bar{d} = \frac{1}{M}\sum_j \tilde{d}_j$,令
$$\hat{d}_j = \frac{\tilde{d}_j}{\bar{d}}$$
归一化后 $\hat{d}_j = 1$ 表示"平均重要性",$\hat{d}_j > 1$ 表示该 bin 需要更细的网格。
步骤 3:阻尼。引入学习率 $\alpha > 0$(oCAS 默认 $\alpha = 1.5$),对归一化值进行幂变换:
$$\hat{d}_j ;\leftarrow; \hat{d}_j^{1/\alpha}$$
当 $\alpha = 1$ 时无阻尼($\hat{d}_j^{1/1} = \hat{d}_j$);$\alpha > 1$ 时幂次 $< 1$,压缩极端值,使网格更新更保守。这防止了网格对单次迭代的噪声过度反应。
步骤 4:累积弧长与等距重分布。计算累积和:
$$S_j = \sum_{i=0}^{j-1} \hat{d}_i, \qquad j = 0, 1, \dots, M$$
其中 $S_0 = 0$,$S_M = \sum_{i=0}^{M-1} \hat{d}_i$。将 $S$ 视为一条"弧长"曲线,新的 bin 边界在弧长上等距分布:
$$b'_j = \text{在 } [0,1] \text{ 上对应弧长 } S = j \cdot S_M / M \text{ 的位置}$$
具体地,对 $j = 1, \dots, M-1$,找到 $S$ 中满足 $S_i \leq j \cdot S_M / M < S_{i+1}$ 的位置 $i$,然后线性插值:
$$b'j = \frac{i + \frac{j \cdot S_M / M - S_i}{S{i+1} - S_i}}{M}$$
固定 $b'_0 = 0$,$b'_M = 1$。
步骤 5:单调性钳制。由于浮点精度和数值噪声,$b'_j$ 可能不严格递增。对结果进行单调性修复:
$$b'_j ;\leftarrow; \max(b'j,; b'{j-1})$$
直觉:弧长重分布的几何意义是——如果我们在 $(x, S(x))$ 平面上画出累积重要性曲线,"等弧长分点"恰好使得每个新 bin 在曲线上的弧长相等,即每个新 bin 承载近似相等的积分贡献。
分层采样
分层采样(stratified sampling)是另一种方差缩减技术。其思想是将积分域划分为不相交的子域("层"),在每层内独立采样:
$$I = \sum_{j=1}^{M} I_j, \qquad I_j = \int_{\Omega_j} f(\mathbf{x}), d\mathbf{x}$$
每层的估计量为 $\hat{I}_j = V_j \cdot \bar{f}_j$($V_j$ 是层体积,$\bar{f}_j$ 是层内样本均值),总估计量为 $\hat{I} = \sum_j \hat{I}_j$。
方差分析:
$$\text{Var}(\hat{I}) = \sum_{j=1}^{M} \frac{V_j^2}{N_j} \sigma_j^2$$
其中 $N_j$ 是第 $j$ 层的样本数,$\sigma_j^2$ 是第 $j$ 层内 $f$ 的方差。若各层方差不等,最优分配(Neyman 分配)为
$$N_j \propto V_j \sigma_j$$
即高方差层分配更多样本。
Vegas 与分层采样的关系:Vegas 的乘积网格本质上是一种自适应分层采样——bin 边界将积分域分层,每层内均匀采样。区别在于 Vegas 的层边界是迭代自适应的,而非事先固定的。
多轮迭代的逆方差加权组合
Vegas 运行多轮迭代(每轮 $N$ 个样本),每轮给出一个估计 $\hat{I}_r$ 及其标准误差 $\hat{\sigma}_r$。如何组合多轮结果?
逆方差加权(inverse-variance weighting):令 $w_r = 1/\hat{\sigma}_r^2$,组合估计为
$$\hat{I}_{\text{combined}} = \frac{\sum_r w_r \hat{I}_r}{\sum_r w_r}$$
组合误差为
$$\hat{\sigma}_{\text{combined}} = \frac{1}{\sqrt{\sum_r w_r}}$$
最优性:当各轮估计相互独立时,逆方差加权是方差最小的无偏组合方式(Gauss–Markov 定理的直接推论)。
在线逆方差加权累加:为避免存储所有轮次的估计值,StatisticsAccumulator 使用在线更新(加权均值 + 增量 $\chi^2$,并非 Welford 算法本身)。维护状态变量:
integral:当前组合估计error:当前组合标准误差chi_square:跨轮次 $\chi^2$ 统计量
当第 $r$ 轮完成(估计 $\mu_r$,误差 $\sigma_r$)时,更新规则为:
$$w_{\text{prev}} = 1/\sigma_{\text{prev}}^2, \quad w_r = 1/\sigma_r^2, \quad w_{\text{new}} = w_{\text{prev}} + w_r$$
$$\hat{I}{\text{new}} = \frac{w{\text{prev}} \cdot \hat{I}{\text{prev}} + w_r \cdot \mu_r}{w{\text{new}}}$$
$$\sigma_{\text{new}} = 1/\sqrt{w_{\text{new}}}$$
实现细节:为防止零方差轮次导致权重溢出,oCAS 将误差钳制在 $\sigma_r \geq 10^{-150}$($10^{-300}$ 仍可表示为 f64)。
$\chi^2$ 诊断
目的:检验各轮次的估计是否一致——若各轮的 $\hat{I}_r$ 差异远超其误差条,说明方差估计偏低或积分器未收敛。
定义:
$$\chi^2 = \sum_{r=1}^{R} w_r (\hat{I}r - \hat{I}{\text{combined}})^2$$
其中 $w_r = 1/\hat{\sigma}_r^2$。
解释:若各轮独立且方差估计准确,$\chi^2$ 服从自由度为 $R - 1$ 的卡方分布。经验准则:
| $\chi^2 / (R-1)$ | 含义 |
|---|---|
| $\approx 1$ | 正常——各轮一致 |
| $\gg 1$ | 方差被低估,或积分器未收敛 |
| $\ll 1$ | 方差被高估(罕见) |
oCAS 的 StatisticsAccumulator 在每轮结束时在线更新 $\chi^2$:
$$\chi^2_{\text{new}} = \chi^2_{\text{prev}} + w_{\text{prev}} (\hat{I}{\text{prev}} - \hat{I}{\text{new}})^2 + w_r (\mu_r - \hat{I}_{\text{new}})^2$$
注意这是增量式更新——无需存储历史轮次的估计值。
在 oCAS 中的实现
oCAS 的 Vegas 实现位于 ocas-eval/src/numeric/vegas.rs,配套的统计累加器在 ocas-eval/src/numeric/statistics.rs。
数据结构
Vegas
├── opts: VegasOptions // 调参选项
├── axes: Vec<GridAxis> // 每维独立的网格
│ ├── boundaries: Vec<f64> // M+1 个 bin 边界 ∈ [0,1]
│ └── bin_accum: Vec<f64> // M 个 bin 的训练信号累加器
└── accumulator: StatisticsAccumulator // 跨轮次逆方差加权
├── sum_w, sum_wf, sum_wf2 // 当前轮次的加权累加
├── integral, error // 组合估计与误差
├── chi_square // χ² 诊断
└── iterations // 已完成轮次数
默认参数
| 参数 | 默认值 | 含义 |
|---|---|---|
n_bins | 64 | 每维 bin 数 $M$ |
n_samples | 10,000 | 每轮采样数 $N$ |
iterations | 10 | 自适应轮次数 $R$ |
learning_rate | 1.5 | 网格更新阻尼参数 $\alpha$ |
seed | 0x0C45 | 确定性 RNG 种子 |
采样过程的实现
每轮迭代的伪代码:
#![allow(unused)] fn main() { for _ in 0..n_samples { // 1. 对每维独立采样 let mut x = Vec::with_capacity(n_dims); let mut jac = 1.0; for axis in axes.iter_mut() { // 均匀选 bin let b = rng.random_range(0..n_bins); let lo = boundaries[b]; let hi = boundaries[b + 1]; // bin 内均匀选点 let u = rng.random::<f64>(); let xi = lo + (hi - lo) * u; // Jacobian = M × bin_width = 1/pdf let wi = (hi - lo) * n_bins as f64; x.push(xi); jac *= wi; } // 2. 求值并累加 let fx = f(&x); accumulator.add_sample(jac, fx); // 3. 记录训练信号 for (i, xi) in x.iter().enumerate() { axes[i].add_training(*xi, jac, fx * fx); } } }
关键实现细节:
- RNG 选择:使用
Xoshiro256PlusPlus(而非thread_rng),通过固定种子保证结果可重现。 - 乘积 Jacobian:总权重
jac = ∏ w_k——各维独立采样,总密度为各维密度之积。 - 训练信号:
add_training使用二分查找定位 bin($O(\log M)$),将weight × f²累加到对应 bin。
网格更新的实现
GridAxis::update(learning_rate) 的实现步骤:
#![allow(unused)] fn main() { fn update(&mut self, learning_rate: f64) { let n = self.bin_accum.len(); let total: f64 = self.bin_accum.iter().sum(); if total <= 0.0 { return; } // 1. 3-bin 平滑 let avg = total / n as f64; let mut d = vec![0.0; n]; for i in 0..n { let prev = if i > 0 { bin_accum[i-1] } else { 0.0 }; let next = if i+1 < n { bin_accum[i+1] } else { 0.0 }; d[i] = (prev + bin_accum[i] + next) / 3.0 / avg; } // 2. 阻尼:d[i] = d[i]^(1/learning_rate) if learning_rate != 1.0 { for v in d.iter_mut() { *v = v.max(1e-30).powf(1.0 / learning_rate); } } // 3. 累积弧长 let mut cum = vec![0.0; n+1]; for i in 0..n { cum[i+1] = cum[i] + d[i]; } // 4. 等距重分布 let mut new_boundaries = vec![0.0; n+1]; new_boundaries[0] = 0.0; new_boundaries[n] = 1.0; let mut j = 0; for i in 1..n { let target = i as f64 / n as f64 * cum[n]; while j < n && cum[j+1] < target { j += 1; } let frac = (target - cum[j]) / (cum[j+1] - cum[j]); new_boundaries[i] = (j as f64 + frac) / n as f64; } // 5. 单调性钳制 for i in 1..=n { if new_boundaries[i] < new_boundaries[i-1] { new_boundaries[i] = new_boundaries[i-1]; } } new_boundaries[n] = 1.0; self.boundaries = new_boundaries; self.bin_accum.fill(0.0); // 重置训练信号 } }
实现中的数值保护:
- 归一化后的值在幂变换前先取
max(1e-30),保证 $d_i > 0$,使弧长累积严格递增(防止后续插值除零/退化)。 - 当
learning_rate = 1.0时幂变换(连同 1e-30 钳制)被跳过,此时cum[j+1] - cum[j]仍可能为零(某 bin 完全平坦),frac取 0。 - 最终钳制保证边界严格非递减,首尾固定为 0 和 1。
统计累加器
StatisticsAccumulator 在每轮中用三个变量在线累加:
$$S_w = \sum_i w_i, \qquad S_{wf} = \sum_i w_i f_i, \qquad S_{wf^2} = \sum_i w_i f_i^2$$
轮次结束时,估计量为
$$\mu = \frac{S_{wf}}{S_w}, \qquad \sigma^2 = \frac{S_{wf^2}}{S_w} - \mu^2$$
注意这不是标准的样本方差公式——这是加权方差公式,适用于每个样本有不同权重的情况(Vegas 的 Jacobian 权重)。
一维便捷函数
integrate_1d(f, a, b, opts) 内部做线性变量替换 $x = a + u \cdot (b-a)$:
#![allow(unused)] fn main() { let width = b - a; let wrapped = |u: &[f64]| f(a + u[0] * width) * width; let mut vegas = Vegas::new(1, opts); vegas.integrate(&wrapped) }
Jacobian 因子 $(b-a)$ 直接乘入被积函数,用户传入的 $f(x)$ 接收物理坐标而非超立方体坐标。
进阶话题
何时使用 Vegas vs 符号积分
| 场景 | 推荐方法 |
|---|---|
| 被积函数有初等原函数 | 符号积分(integrate)——精确结果 |
| 被积函数含特殊函数(Bessel、误差函数等) | 数值积分(Vegas) |
| 高维积分($d > 3$) | 蒙特卡洛(Vegas 或其他) |
| 被积函数有奇点或边界层 | Vegas + 增加 n_bins 和 iterations |
| 需要可微分的积分值 | 自动微分(见 自动微分) |
Vegas 的局限性
-
乘积网格假设:Vegas 假设最优采样密度可分离为 $\prod_k p_k(x_k)$。当被积函数有强变量间相关性(如 $f(x,y) = \delta(x - y)$)时,乘积网格无法有效近似,收敛变慢。
-
分段常数近似:网格密度是分段常数的——在每个 bin 内采样均匀。若被积函数在某个 bin 内有剧烈变化(如窄峰),该 bin 的方差估计可能不准确。
-
自适应偏差:早期迭代使用尚未收敛的网格,其样本不完全独立。Vegas 通过逆方差加权缓解此问题——收敛后的迭代权重更大。
-
确定性种子:oCAS 使用固定 RNG 种子(默认
0x0C45)保证可重现性。这在测试和基准中有用,但生产环境中应考虑使用随机种子以避免系统性偏差。
改进方向
- Vegas+(Lepage, 2020):在网格更新中引入"反射"策略,处理对称被积函数。
- Suave(Hahn, 2005):结合 Vegas 自适应网格与分层采样的优势。
- Divonne(Friedman & Harris, 1996):使用分区树和启发式细分策略。
- Cuhre:纯确定性求积(非蒙特卡洛),适用于低维光滑被积函数。
这些方法可通过实现 Integrator trait 扩展到 oCAS 中。
参考文献
-
Lepage, G. P. "A new algorithm for adaptive multidimensional integration." Journal of Computational Physics, 27(2):192–203, 1978. — Vegas 算法的原始论文。
-
James, F. "Monte Carlo theory and practice." Reports on Progress in Physics, 43(9):1145–1189, 1980. — 蒙特卡洛方法的综述,包含 Vegas 的详细描述。
-
Lepage, G. P. "Vegas: An adaptive multi-dimensional integration program." Cornell preprint CLNS 80-447, 1980. — Vegas 的实现细节和使用指南。
-
Hahn, T. "CUBA—a library for multidimensional numerical integration." Computer Physics Communications, 168(2):78–95, 2005. — Vegas、Suave、Divonne、Cuhre 的比较。
-
Neal, R. M. "Annealed importance sampling." Statistics and Computing, 11(2):125–139, 2001. — 重要性采样的理论基础。
-
Kahn, H. & Marshall, A. W. "Methods of reducing sample size in Monte Carlo computations." Journal of the Operations Research Society of America, 1(5):263–278, 1953. — 分层采样和重要性采样的早期工作。
Gröbner 基实现
本章详述 oCAS 中 Gröbner 基计算的实现细节——算法选择、数据结构、关键优化 和内部流水线。数学理论见 Gröbner 基理论,API 签名见 Rust API 参考。
架构总览
flowchart TD
A["groebner_basis(ideal, algo)"] --> B{算法选择}
B -->|Auto| C["F4(默认)"]
B -->|Buchberger| D["buchberger()"]
B -->|F4| C
B -->|F5| E["f5()"]
C --> F{系数域?}
F -->|FiniteField| G["f4_fp(): ℤ_p 原生 i64 快速路径"]
F -->|其他域| H["f4_generic(): 泛型路径"]
E --> I{系数域?}
I -->|FiniteField| J["f5_fp(): ℤ_p 原生快速路径"]
I -->|其他域| K["f5_generic(): 泛型路径"]
G --> L["输出: GroebnerBasis"]
H --> L
J --> L
K --> L
D --> L
L --> M{"需要换序?"}
M -->|"零维 + 不同序"| N["fglm(): O(n·D³)"]
M -->|"一般理想"| O["reorder(): 重跑 F4"]
统一入口 groebner_basis 接受一个 Algorithm 枚举:
| 变体 | 行为 |
|---|---|
Auto | 当前路由到 F4(未来将基于 cyclic-n 基准校准 F5 切换点) |
Buchberger | 经典 S-多项式迭代 |
F4 | 矩阵批量约化 |
F5 | 签名判据 |
所有算法输出约化 Gröbner 基(GroebnerBasis::minimize().auto_reduce())。
Buchberger 算法
GroebnerBasis::buchberger 实现经典 Buchberger 算法:
- 初始化基为输入多项式集合
- 构造所有临界对(S-多项式)
- 对每对计算 S-多项式并对当前基做多步除法
- 若余式非零,加入基并更新临界对
- 重复直到无新多项式加入
- 最后调用
minimize()(去冗余)和auto_reduce()(互相约化)
#![allow(unused)] fn main() { pub fn buchberger<D: Domain, O: MonomialOrder>( ideal: &[SparseMultivariatePolynomial<D, O>], ) -> GroebnerBasis<D, O> { GroebnerBasis::buchberger(ideal).minimize().auto_reduce() } }
Buchberger 适用于小理想和教学场景。生产环境应使用 F4。
F4 算法
F4(Faugère 1999)将逐对 S-多项式约化替换为稀疏矩阵行阶梯批处理。 这是 oCAS 的默认 Gröbner 基算法。
主循环
f4 的主循环结构:
初始化 basis ← 输入多项式
初始化 pairs ← 空临界对集合
对每个初始多项式: update_pairs(basis, pairs, simplifications, poly)
while pairs 非空:
(1) 选出所有 lcm 总次数最小的临界对(一批)
(2) 符号预处理:构造约化矩阵
(3) 行阶梯化:Gaussian 消元
(4) 提取幸存行 → 新基元素
(5) 对每个新基元素: update_pairs(...)
临界对与 Gebauer-Moeller 筛选
CriticalPair 存储两个基元素的索引和预计算的 lcm:
#![allow(unused)] fn main() { struct CriticalPair { idx1: usize, // 第一个基元素索引 idx2: usize, // 第二个基元素索引 lcm: SmallVec<[usize; 4]>, // LM(idx1) 和 LM(idx2) 的 lcm degree: usize, // lcm 的总次数 } }
update_pairs 实现 Gebauer-Moeller 临界对管理,遵循 Becker-Weispfenning
《A Computational Approach to Commutative Algebra》中的描述。其核心是三个判据:
第一判据(Chain Criterion):对已有对 $(f_i, f_j)$,若存在 $f_k$ 使得 $\text{lcm}(f_i, f_k)$ 整除 $\text{lcm}(f_i, f_j)$ 且 $\text{lcm}(f_j, f_k)$ 整除 $\text{lcm}(f_i, f_j)$,则 $(f_i, f_j)$ 冗余。
第二判据(Update Criterion):新多项式加入时,遍历已有对,若新多项式的 LM 与对中某元素的 lcm 严格整除对的 lcm,则该对可移除。
冗余对清理:移除那些首项已被基中其他元素约化的对。
符号预处理(Symbolic Preprocessing)
对于选中的临界对,F4 构造一个稀疏矩阵,其中:
- 每行对应一个多项式(S-多项式或基元素的倍式)
- 每列对应矩阵中出现的唯一单项式
- 单项式按当前序降序排列(列 0 = 最大单项式)
预处理的工作列表(worklist)算法:
- 将 S-多项式对的两个分量分别加入矩阵
- 对矩阵中每个单项式 $\mathbf{x}^\alpha$,若存在基元素 $f_i$ 使得 $\text{LT}(f_i) \mid \mathbf{x}^\alpha$,则将 $f_i \cdot (\mathbf{x}^\alpha / \text{LT}(f_i))$ 加入矩阵
- 重复直到工作列表为空
DivisorIndex:快速除子查询
朴素实现需要 $O(\text{单项式数} \times \text{基大小})$ 的线性扫描来查找
约化器。oCAS 引入 DivisorIndex 替代之:
#![allow(unused)] fn main() { struct DivisorIndex { buckets: HashMap<u64, Vec<usize>>, } }
原理:每个单项式的 support(出现正指数的变量集合)用 64 位掩码表示。 基元素按其首项单项式的 support 掩码分桶。查询时,$\mathbf{x}^\alpha$ 的约化器 的 support 必须是 $\text{support}(\alpha)$ 的子掩码——枚举子掩码(位运算)并 在对应桶中做精确整除检查。
选择约化器时,优先选项数最少的基元素(ties 按基索引),这与 Buchberger 的线性扫描行为一致。
SimpCache:倍式缓存
#![allow(unused)] fn main() { type SimpCache<P> = Vec<(SmallVec<[usize; 4]>, P)>; }
get_simplified 在缓存中查找给定指数差 diff 对应的已计算倍式
basis_poly * x^diff。命中时直接返回,避免重复乘法。缓存跨轮次保持,
因此同一 S-多次项只构造一次。
行阶梯化
矩阵构造完成后,执行稀疏 Gaussian 消元:
ℤ_p 快速路径(echelonize_fp):
#![allow(unused)] fn main() { fn echelonize_fp( matrix: &mut Vec<Vec<(i64, usize)>>, ncols: usize, prime: i64, pivots: &mut Vec<Option<usize>>, ) }
- 行按首列升序排序
- 逐列扫描:找到当前列的第一个非零行作为主元
- 主元行 monic 化(乘以首系数的模逆)
- 用主元行消去其余行的当前列
sub_scaled_fp执行稀疏行减法:双指针合并两个按列升序排列的行, 跳过首列(主元是 monic,首列自动消去),结果系数归一化到 $[0, p)$, 零系数丢弃
泛型路径(echelonize_generic):
结构与 ℤ_p 路径相同,但使用域的通用 sub/mul/div 运算。
sub_scaled_generic 同样是双指针合并,零系数丢弃。
ℤ_p 原生快速路径(FpPoly)
当系数域是 FiniteField 时,F4 自动切换到 f4_fp,其中所有多项式操作
使用 FpPoly——一个纯 i64 模算术的多项式表示:
#![allow(unused)] fn main() { struct FpPoly { terms: Vec<(SmallVec<[usize; 4]>, i64)>, // 按序降序,系数 ∈ [0, p) n_vars: usize, } }
约束:$p < 2^{31}$,确保两个残差的乘积仍在 i64 范围内。
register_row_fp 实现跨轮次的行缓存:用 (basis_idx, diff) 作键,
避免重复构造同一 S-多项式或约化器倍式。
monic_fp 通过扩展 Euclid 算法(mod_inv)计算模逆,使首项系数归一化。
性能影响:ℤ_p 路径完全避免 BigInt 分配,行阶梯步骤使用惰性模算术,
使得有限域计时接近有理数计时。
基后处理
F4 循环结束后:
minimize():移除首项被其他基元素首项整除的冗余元素auto_reduce():对每个基元素,用其余基元素约化其尾项(非首项部分)
F5 算法
F5(Faugère 2002)是基于签名的 Gröbner 基算法。其核心思想:为每个 多项式附加一个"签名",利用 syzygy 判据在矩阵构造之前拒绝零约化器, 对困难理想(如 cyclic-n)可实现数量级加速。
自 0.19.0 版本起,F5 已是生产级实现。
签名与 pot 序
#![allow(unused)] fn main() { struct Signature { module_pos: usize, // 输入生成元的索引(0-based) monomial: SmallVec<[usize; 4]>, // 乘子单项式 } }
签名记录多项式的"历史":module_pos 是它源自第几个输入生成元,
monomial 是施加在该生成元的模基向量 $\mathbf{e}_{\text{module_pos}}$
上的单项式倍数。
签名按 pot(position-over-term)序比较:先比较 module_pos(更小的
更优先),再按单项式序 $O$ 比较 monomial。
LabeledPoly:带签名的多项式
#![allow(unused)] fn main() { struct LabeledPoly<D: Domain, O: MonomialOrder> { poly: SparseMultivariatePolynomial<D, O>, sig: Signature, } }
F5 基中每个多项式都携带签名。LabeledPoly 实现了 BasisPoly trait,
因此可以复用 F4 的临界对管理(update_pairs)和简化缓存。
SyzygySet:零约化跟踪
#![allow(unused)] fn main() { struct SyzygySet { lms: HashMap<usize, Vec<SmallVec<[usize; 4]>>>, } }
当矩阵行约化为零时,其签名是一个 syzygy。F5 的 syzygy 判据: 若未来某行的签名是已知 syzygy 签名的单项式倍数,则该行也将约化为零, 可立即跳过。
内部按模块位置(module_pos)分组存储已知 syzygy 的首项单项式。
查询 (k, t) 时,检查位置 k 下是否有 LM 整除 t。
F5 主循环
f5 的结构与 F4 类似但有关键差异:
对每个输入生成元 g_i (i = 0, 1, ...):
给 g_i 附加签名 (i, 1)(单位单项式)
将 (i, 1) 加入基
update_pairs(...)
while pairs 非空:
选中一组临界对
对每个对:
构造 S-多项式并计算其签名 sig
if syzygies.is_syzygy(sig): ← 关键优化:跳过
continue
build_and_reduce(selected, basis, syzygies):
(1) 构造带签名的矩阵行
(2) 行阶梯化
(3) 约化为零的行 → 将其签名加入 syzygies
(4) 幸存行 → 新基元素(保留签名)
对每个新基元素: update_pairs(...)
F5 的 ℤ_p 快速路径
与 F4 类似,F5 有 f5_fp 变体使用 LabeledFpPoly(FpPoly + 签名):
#![allow(unused)] fn main() { struct LabeledFpPoly { poly: FpPoly, sig: Signature, } }
所有多项式操作在 i64 模算术上执行,BigInt 转换仅发生在输入读取和
结果输出边界。
FGLM 换序算法
FGLM(Faugère–Gianni–Lazard–Mora 1993)将零维理想的 Gröbner 基从 一种单项式序转换为另一种,代价为 $O(n \cdot D^3)$ 次域运算($D$ 为阶梯 维数),远低于对一般理想重跑 F4 的代价。
核心思路
零维理想的阶梯(不被任何首项单项式整除的单项式集合)是有限的。 FGLM 按目标序递增遍历阶梯中的单项式,计算它们对当前基的正规形, 并检测线性相关:
compute_staircase(lms, n_vars):
BFS 从单位单项式出发
单项式 m ∈ 阶梯 ⟺ 无 LM 整除 m
若 BFS 不终止 → 正维,返回 None
fglm(gb, TargetOrder):
staircase ← compute_staircase(gb.lms)
seen ← [] // 已遍历单项式的正规形(阶梯坐标)
new_basis ← []
按 TargetOrder 递增遍历阶梯中的 m:
nf ← normal_form_monomial(m, gb, staircase)
coeffs ← find_relation(seen, nf)
if coeffs 存在: // nf = Σ c_i · seen_i → 线性相关
new_poly ← m - Σ c_i · m_i // 目标序下的新基元素
new_basis.push(new_poly)
mark_multiples(visited, m) // 跳过 m 的倍数
else:
seen.push(nf)
return GroebnerBasis::new(new_basis)
正规形计算
normal_form_monomial 将单项式 $m$ 对源基 GB 约化,结果表示为阶梯
坐标的线性组合——即长度为 $\dim(R/I)$ 的向量,每个分量对应阶梯中的
一个基单项式。
线性相关检测
find_relation 用 Gaussian 消元在域上检测:是否存在系数 $c_i$ 使得
$\text{nf} = \sum c_i \cdot \text{seen}_i$。若存在,这些系数确定了
新基多项式。
返回 None 的情况
当理想为正维(阶梯无限)时,fglm 返回 None。此时应改用
reorder(在新序下重跑 F4)。
Hilbert 级数计算
hilbert 模块从 Gröbner 基计算 Hilbert 级数和相关不变量。
Hilbert 分子(容斥原理)
对单项式理想 $\langle m_1, \dots, m_s \rangle$,Hilbert 分子通过 容斥原理计算:
$$N(t) = \sum_{k=0}^{s} (-1)^k \sum_{|S|=k} t^{\deg \text{lcm}(S)}$$
hilbert_numerator 遍历生成元的所有子集,计算 lcm 的次数,按容斥符号
累加。实际适用于约 20 个生成元以内。
正则性界
regularity_bound 返回 Hilbert 分子中非零系数的最高次数。这是 F4 的
提前终止提示(Bayer–Stillman):F4 可以停止选择次数超过正则性界的
临界对——因为所有剩余 S-多项式将约化为零。当前该界是建议性的,
不改变计算结果。
完整 Hilbert 级数
hilbert_series 使用 Macaulay 定理:$R/I$ 的 Hilbert 级数等于
$R/\text{LT}(I)$ 的 Hilbert 级数(其中 $\text{LT}(I)$ 是首项理想)。
#![allow(unused)] fn main() { struct HilbertSeries { numerator: Vec<i64>, // N(t) 的系数(从常数项起) denominator_power: usize, // 分母 (1-t)^n 的幂(= 变量数) } }
提供四个方法:
hilbert_function(d):$\dim_k (R/I)_d$ 在次数 $d$ 的值dimension():$R/I$ 的 Krull 维数degree():射影簇的次数hilbert_polynomial():Lagrange 插值计算完整多项式系数
阶梯维数 staircase_dimension 由容斥分子在 $t = 1$ 处的取值给出(容斥
枚举随生成元数指数增长,实际约 20 个以内可用);当理想为正维(分子
系数和为 0)时返回 None。
理想运算
ocas_poly::ideal 模块基于 Gröbner 基实现完整的理想算术。所有运算
使用 Lex 序保持一致性。
成员判定
ideal_contains(gens, f, algo):
- 计算生成元的 Gröbner 基 GB
- 将 $f$ 对 GB 约化
- 余式为零 $\iff f \in I$
和与积
理想和 $I + J = \langle f_1, \dots, f_m, g_1, \dots, g_n \rangle$: 合并两组生成元后计算 GB。
理想积 $I \cdot J = \langle f_i \cdot g_j \rangle$: 取所有对的乘积后计算 GB。
商(Rabinowitsch 技巧)
$I : J = {f : f \cdot g \in I, \forall g \in J}$
对单个生成元 $g$,quotient_single_generator 执行:
- 引入新变量 $w$,在扩展环 $k[x_1, \dots, x_n, w]$ 中
- 计算 $\text{GB}(I' \cup {1 - wg'})$
- 消去 $w$(取 Lex GB 中不包含 $w$ 的元素)
- 结果为 $I : g \subset k[x_1, \dots, x_n]$
对多个生成元 $J = \langle g_1, \dots, g_k \rangle$,分别计算 $I : g_j$ 后取交集。
饱和
$I : J^\infty = \bigcup_k (I : J^k)$
ideal_saturate 迭代计算 $I : J$,$(I : J) : J$,…,直到稳定
(连续两次结果相同)。
交集(辅助变量法)
$I \cap J = \langle t \cdot f_i, (1-t) \cdot g_j \rangle \cap k[x_1, \dots, x_n]$
intersect_generators:
- 引入辅助变量 $t$(索引 0)
- 构造 $t \cdot f_i$ 和 $(1-t) \cdot g_j$
- 计算 Lex GB
- 消去 $t$(取不含 $t$ 的元素)
消元
eliminate(gens, elim_vars, algo):
利用 Lex 序的自然消元性质:在 Lex 序下,约化 GB 自动包含消元理想的
生成元。取 GB 中不涉及前 elim_vars 个变量的多项式。
零维求解
solve_polynomial_system 对方程组分类并求实数解。
维数检测
is_zero_dimensional 检查:对每个变量 $x_i$,GB 中是否存在纯幂
$x_i^N$ 作为某个首项单项式。等价地,阶梯(标准单项式)是有限的。
三角分解求解
对零维系统,solve_triangular 执行:
- 取 Lex GB(自然形成三角形式)
- 从最后一个变量(Lex 中最小)开始反向代入:
- 提取仅含单变量 $x_n$ 的多项式 → 一元方程 → 求实根
- 将 $x_n$ 的每个根代入含 $x_{n-1}, x_n$ 的多项式
- 递归求解 $x_{n-1}$,…
- 实根用 Sturm 根隔离法计算(
f64精度)
compute_vector_space_dim 返回 $k[x_1, \dots, x_n]/I$ 的向量空间维数
(各变量一元多项式次数的乘积)。
解的分类
#![allow(unused)] fn main() { enum PolynomialSystemSolution { ZeroDimensional(ZeroDimSolutions), // 有限个实数解 PositiveDimensional(GroebnerBasis), // 无限解集;返回 Lex GB Empty, // 无解(理想 = ⟨1⟩) } }
准素分解与根式
零维根式
radical_zero_dim:取 Lex GB 中每个一元多项式的无平方分解,
用无平方因子替换原多项式后重新计算 GB。
正维根式
radical_via_jacobian(简化 Kemper 算法,特征 0):
- 对所有生成元和变量计算偏导数 $\partial f_i / \partial x_j$
- 取所有非零偏导数的 GCD 为 $h$
- $\sqrt{I} = I : h^\infty$(用饱和实现)
若 Jacobian 平凡(所有导数为零或 $h = 1$),回退到返回原 GB。
零维准素分解
primary_decomp_zero_dim:
- 取 Lex GB 中一元多项式的不可约因式分解
- 每个不可约因子 $p_i^{e_i}$ 对应一个准素分量
- 用饱和分离各分量:$Q_i = I : \prod_{j \neq i} p_j^\infty$
- 关联素理想 $\mathfrak{p}_i = \langle p_i \rangle$
返回 Vec<PrimaryComponent>,每个包含:
primary:准素理想的 GBprime:关联素理想的 GB
素性与准素性检测
is_prime_ideal:零维时检查一元多项式是否不可约;正维保守返回falseis_primary_ideal:检查是否恰有一个关联素理想
单项式序系统
oCAS 支持丰富的单项式序配置(0.19.1+):
| 序 | 说明 |
|---|---|
Lex | 字典序:$x_1 > x_2 > \cdots$,适合消元 |
Grlex | 分次字典序:先总次数,再字典序 |
Grevlex | 分次反字典序:先总次数,反向字典序 |
WeightOrder | 加权序:按 $\sum w_i e_i$ 降序 |
BlockOrder | 分块消元:变量分组,各组独立子序 |
MatrixOrder | 通用权重矩阵序(0.23.0+) |
MatrixOrder::elimination_order(elim_vars, n_vars) 生成消元序,
先消去前 elim_vars 个变量,剩余按 Grevlex 比较。
换序策略
| 场景 | 推荐策略 |
|---|---|
| 零维 + 需要 Lex | 先 Grevlex 计算,再 fglm 换序到 Lex |
| 一般理想 + 需要不同序 | reorder(在新序下重跑 F4) |
| 消元 | 直接用 Lex 或 MatrixOrder::elimination_order |
基准数据
Criterion 计时(cyclic 方程组,本机):
| 方程组 | Buchberger | F4 | 加速比 |
|---|---|---|---|
| cyclic-3 ℚ | 308 µs | 147 µs | 2.1× |
| cyclic-4 ℚ | 3.99 ms | 2.13 ms | 1.9× |
| cyclic-3 ℤ₁₃ | 582 µs | 276 µs | 2.1× |
| cyclic-4 ℤ₁₃ | 6.19 ms | 2.80 ms | 2.2× |
ℤ_p 原生 i64 快速路径使有限域计时接近有理数计时。
参见
- 数学理论:Gröbner 基理论 — Buchberger/F4/F5 算法的数学基础、Hilbert 函数理论
- FGLM 与消元:FGLM 与消元理论 — 换序算法 的数学原理、理想运算的 Gröbner 基实现
- API 参考:Rust Gröbner 基与理想 — 函数签名、 参数、返回值、完整示例
- 多项式:多项式 —
SparseMultivariatePolynomial的表示与单项式序 trait - 系数域:系数域 —
Domaintrait 与各域实现
多项式因式分解
oCAS 实现了整数和素有限域上一元与二元多项式的因式分解。本章介绍相关算法、适用范围以及公共 API。
适用范围
当前支持的因式分解包括:
| 域 | 多项式 | 算法 |
|---|---|---|
| $\mathbb{Z}[x]$ | 一元 | 无平方分解、Berlekamp–Zassenhaus、Hensel 提升 |
| $\mathbb{F}_p[x]$ | 一元 | 无平方分解、Berlekamp |
| $\mathbb{Z}[x,y]$ | 二元(常数 LC 快速路径;非常数 LC 自动转 EEZ) | Wang Hensel 提升 |
| $\mathbb{F}_p[x,y]$ | 二元(关于 $x$ 首一) | 有限域上的 Hensel 提升 |
| $\mathbb{Z}[x_1,\dots,x_n]$ | 任意多元 | Wang EEZ Hensel 提升 + 首项系数预处理 + Zassenhaus 重组 |
| $\mathbb{F}_p[x_1,\dots,x_n]$ | 任意多元 | EEZ Hensel 提升(含特征 $p$ 的 $p$ 次幂处理;非常数 LC 走域版 Wang 预处理) |
| $\mathbb{Q}(\alpha)[x]$ | 一元 | Trager 算法:范数 + $\mathbb{Q}$ 上分解 + $\mathrm{GF}(p^d)$ 模 GCD |
自 0.16.0 起支持多于两个变量的任意多元因式分解。非首一的一元多项式经首项系数变换后同样可分解。自 0.16.1 起,$\mathbb{Z}$ 上的多元路径通过 p-adic 系数 Hensel 提升(骨架插值 Diophantine)完整支持非常数首项系数强加;二元非常数 LC 输入自动转入 EEZ 路径。自 0.17.0 起支持代数数域上的一元因式分解(Trager 算法)。
有限域上的一元因式分解
对于素域 $\mathbb{F}_p$,oCAS 使用 Berlekamp 算法。首先将多项式化为无平方形式,然后构造 Frobenius 矩阵 $Q$($Q[i][j]$ = $x^{ip} \bmod f$ 中 $x^j$ 的系数),计算 $Q^T - I$ 的核空间。每个非平凡核向量 $v$ 满足 $v^p \equiv v \pmod f$,通过 $\gcd(f, v - a)$($a \in \mathbb{F}_p$)给出因子分解。小素数($p \le 1000$)走 Berlekamp 矩阵路径;大素数使用 Cantor–Zassenhaus。
#![allow(unused)] fn main() { use ocas_domain::{FiniteField, Integer}; use ocas_poly::DenseUnivariatePolynomial; let p = FiniteField::new(Integer::from(101)); let f = DenseUnivariatePolynomial::<FiniteField>::from_coeffs(p.clone(), vec![ p.element(1), // 常数项 p.element(0), p.element(1), // x^2 ]); let factors = f.factor(); }
返回结果是 (因子, 重数) 的列表。经过无平方分解后,有限域上的重数总是 1。
整数上的一元因式分解
对于 $\mathbb{Z}[x]$,oCAS 将无平方分解与 Berlekamp–Zassenhaus 型 Hensel 提升结合使用:
- 计算内容并约化为本原多项式。
- 计算无平方分解。
- 选取一个小素数 $p$,使得模 $p$ 约化后仍无平方且次数与原多项式相同。
- 使用 Cantor–Zassenhaus(小素数 $p \le 1000$ 内部采用 Berlekamp 矩阵法) 在模 $p$ 下分解。
- 通过 Hensel 提升将模 $p$ 因子提升为 $\mathbb{Z}[x]$ 上的因子。
实际调用链为:factor() → factor_primitive(Yun 无平方分解,因子按
重数升序输出,$k = 1$ 的因子在前)→ 对每个无平方分量 factor_square_free
(首项系数变换:对首项系数 $a$、次数 $d$,构造首一多项式
$g(x) = a^{d-1} f(x/a)$;$|a| = 1$ 时直接进入 monic 路径)→
factor_square_free_monic(Mignotte 系数界 $B$、模 $p$ 分解、Hensel
提升至 $p^k > 2B$、Zassenhaus 子集重组)。变换路径的每个首一因子 $G$
经本原部分 $\operatorname{pp}(G(a x))$ 映射回 $\mathbb{Z}[x]$。
#![allow(unused)] fn main() { use ocas_domain::IntegerDomain; use ocas_poly::DenseUnivariatePolynomial; let f = DenseUnivariatePolynomial::<IntegerDomain>::from_coeffs( IntegerDomain, vec![1.into(), 0.into(), 1.into()], // x^2 + 1 ); let factors = f.factor(); }
整数上的二元因式分解
ocas-poly 使用 Wang Hensel 提升 对 $\mathbb{Z}[x,y]$ 上的二元多项式进行因式分解,假设多项式关于 $x$ 首一。
算法流程:
- 选取赋值点 $y = \alpha$,使得一元像 $f(x, \alpha)$ 无平方且不可约因子数最少。
- 在 $\mathbb{Z}[x]$ 上分解该一元像。
- 通过修正 $f$ 在 $y = \alpha$ 处的 Taylor 系数,将一元因子提升回二元因子。
修正步骤使用一元因子的有理 Bézout 系数,然后通过整数除法重建整系数修正项。如果重建失败(余式非整)或提升后的乘积与原多项式不符,实现会尝试下一个候选赋值点,最终回退为将该多项式视为不可约。
#![allow(unused)] fn main() { use ocas_domain::IntegerDomain; use ocas_poly::SparseMultivariatePolynomial; use ocas_poly::sparse::Lex; type MPoly = SparseMultivariatePolynomial<IntegerDomain, Lex>; let domain = IntegerDomain; let f = MPoly::from_terms(domain, 2, vec![ (vec![3, 0], 1.into()), // x^3 (vec![2, 1], 1.into()), // x^2*y (vec![2, 0], 2.into()), // 2*x^2 (vec![1, 1], 1.into()), // x*y (vec![1, 0], 1.into()), // x (vec![0, 2], 1.into()), // y^2 (vec![0, 1], 3.into()), // 3*y (vec![0, 0], 2.into()), // 2 ]); let factors = f.factor(); // factors 包含 (x^2 + y + 1, 1) 和 (x + y + 2, 1) }
有限域上的二元因式分解
在 $\mathbb{F}_p[x,y]$ 上,使用相同的 Hensel 提升框架,但所有运算直接在有限域中进行。Bézout 系数通过有限域上的 gcd 计算,所有修正项都保持在域中,因此无需整系数重建。
#![allow(unused)] fn main() { use ocas_domain::FiniteField; use ocas_poly::SparseMultivariatePolynomial; use ocas_poly::sparse::Lex; let domain = FiniteField::new(101.into()); type FpPoly = SparseMultivariatePolynomial<FiniteField, Lex>; let f = FpPoly::from_terms(domain.clone(), 2, vec![ (vec![2, 0], 1.into()), // x^2 (vec![0, 1], 1.into()), // y (vec![0, 0], 1.into()), // 1 ]); let factors = f.factor(); }
C/C++ 多项式 API
C 绑定为二元整数和有限域多项式提供不透明句柄。多项式可通过 ASCII 字符串创建、因式分解、打印和释放。
#include <ocas.h>
#include <stdio.h>
int main(void) {
int err = 0;
OcasPolyZ *f = ocas_poly_z_create("x^2 + y + 1", &err);
if (f == NULL) {
fprintf(stderr, "parse error: %s\n", ocas_error_last_message());
return 1;
}
OcasPolyFactorArray factors = {0};
int rc = ocas_poly_z_factor(f, &factors, &err);
if (rc != OCAS_OK) {
fprintf(stderr, "factor error: %s\n", ocas_error_last_message());
ocas_poly_z_free(f);
return 1;
}
printf("factors: %zu\n", factors.len);
for (size_t i = 0; i < factors.len; ++i) {
OcasPolyZ *factor = (OcasPolyZ *)factors.factors[i].poly;
char *s = ocas_poly_z_to_string(factor, &err);
printf(" %s^%zu\n", s, factors.factors[i].multiplicity);
ocas_string_free(s);
ocas_poly_z_free(factor);
}
ocas_poly_factor_array_free(&factors);
ocas_poly_z_free(f);
return 0;
}
有限域版本使用 OcasPolyFp、ocas_poly_fp_create 和 ocas_poly_fp_factor。因子数组中存储的是 void* 多项式句柄,调用者必须先将其转换为正确的具体类型,再进行打印或释放。
任意多元因式分解(Wang EEZ)
自 0.16.0 起,oCAS 支持 $\mathbb{Z}$ 与 $\mathbb{F}_p$ 上任意变量数的多项式因式分解,核心是 Wang 的 EEZ(Evaluation and EZ-lifting)算法:
- 无平方分解(Yun):对主变量 $x_1$ 求偏导,用 $n$ 元 GCD(稠密递归求值–插值)剥离重因子;在特征 $p$ 下处理 $p$ 次幂(收缩/展开)。
- 求值点采样:把副变量 $x_2,\dots,x_n$ 代入样本点,使一元像次数不变且无平方。
- Wang 首项系数预处理:分解首项系数 $\ell(x_2,\dots)$,用其在样本点的两两互素整数像把各因子分配到各一元因子,重建真实首项系数 $\ell_i$。
- 逐变量 Hensel 提升:通过多元 Diophantine 方程把一元因子沿理想 $(x_k - a_k)$ 逐变量提升回多元。
- Zassenhaus 重组:当提升的因子多于真实因子时,枚举子集组合并试除,得到不可约因子。
#![allow(unused)] fn main() { use ocas_domain::{Integer, IntegerDomain}; use ocas_poly::sparse::{Lex, SparseMultivariatePolynomial}; // (x + y + z)(x - y + 2z) let f = SparseMultivariatePolynomial::<IntegerDomain, Lex>::from_terms( IntegerDomain, 3, vec![ (vec![2, 0, 0], Integer::from(1)), (vec![1, 0, 1], Integer::from(3)), (vec![0, 1, 1], Integer::from(1)), (vec![0, 2, 0], Integer::from(-1)), (vec![0, 0, 2], Integer::from(2)), ], ); let factors = f.factor(); }
返回 (因子, 重数) 列表,因子按本原且首项系数为正规范化。
代数数域上的因式分解(Trager)
自 0.17.0 起,oCAS 支持代数数域 $K = \mathbb{Q}(\alpha)$ 上一元多项式的因式分解,其中 $\alpha$ 由 $\mathbb{Q}$ 上的首一极小多项式给出。域类型位于 ocas-domain:AlgebraicExtension<D>($D = \mathbb{Q}$ 时为 AlgebraicNumberField);同一类型在 $D = \mathbb{F}_p$ 时表示 $\mathrm{GF}(p^d)$。
分解器实现 Trager 算法:
- 无平方分解(Yun):在 $K$ 上进行,GCD 使用下述模方法而非伪余式序列。
- 范数与平移:对 $s = 0, 1, 2, \dots$ 计算 $g(x) = f(x - s\alpha)$ 的范数——通过求值–插值计算标量结式 $\operatorname{Res}_\alpha(m, g)$——直到范数在 $\mathbb{Q}$ 上无平方(用小素数模检验;接受条件是精确的)。
- 有理数域分解:无平方范数在 $\mathbb{Z}$ 上分解(Hensel 路径)。
- 数域上的模 GCD:把每个有理因子映到 $K[x]$,用模方法求其与 $g$ 的 GCD——在 $m \bmod p$ 不可约的素数上映到 $\mathrm{GF}(p^d)$,CRT 合并首一模 GCD,有理重构系数,试除验证。
- 回代 $x \mapsto x + s\alpha$ 并首一化。
当 $f$ 的所有系数都是有理常数时走有理快速通道:先在 $\mathbb{Q}$ 上分解,仅把 $\mathbb{Q}$ 不可约因子送入 Trager 范数流程。
#![allow(unused)] fn main() { use ocas_domain::{AlgebraicNumberField, Domain, Rational, RationalDomain}; use ocas_poly::DenseUnivariatePolynomial; // ℚ(√2):极小多项式 α² − 2。 let field = AlgebraicNumberField::new( RationalDomain, vec![Rational::new(-2, 1), Rational::new(0, 1), Rational::new(1, 1)], ); // x² − 2 = (x − √2)(x + √2) 在 ℚ(√2) 上分裂。 let f = DenseUnivariatePolynomial::from_coeffs( field.clone(), vec![field.from_base(Rational::new(-2, 1)), field.zero(), field.one()], ); let factors = f.factor(); assert_eq!(factors.len(), 2); }
基准(criterion 组 poly_factor_anf):$\mathbb{Q}(\sqrt2)$、$\mathbb{Q}(\sqrt[3]{2})$ 与分圆域 $\mathbb{Q}(\zeta_5)$ 上次数 ≤ 12 的多项式分解均远低于 100 ms。
限制与未来工作
- $\mathbb{Z}$ 上的多元路径自 0.16.1 起已完整支持非常数首项系数强加(p-adic 系数 Hensel 提升 + 骨架插值 Diophantine)。
- 二元 Wang Hensel 实现的原有"首项系数须为常数"限制已由 0.16.0 的任意多元路径覆盖(走 EEZ);二元非常数 LC 输入现自动转入 EEZ 路径。
- 自 0.16.2 起 $\mathbb{F}_p$ 多元路径同样支持非常数首项系数(域版 Wang LC 预处理
wang_reconstruct_lcoeffs_fp+eez_lift_imposed)。 - 代数数域因式分解(0.17.0)目前仅限一元;扩域上的多元因式分解与解析器的
RootOf/根式语法留待后续版本。极小多项式的不可约性由调用方保证(模可约时环中存在零因子,求逆会失败)。 - 自适应赋值点搜索已扩大(ℤ:7 → 25,$\mathbb{F}_p$:8 → 32);对于非常稀疏或高度特殊的多项式,可能仍需进一步扩大搜索范围或额外的 Diophantine 插值遍次。
这些限制已在项目路线图中跟踪,并会随着代数内核的成熟逐步解除。
数论 / Number Theory
ocas_domain::number_theory 模块提供计算数论栈:多模中国剩余定理、BPSW
素性判定、整数分解(试除、Pollard rho/p−1、Williams p+1、ECM)、离散对数
与经典积性函数。Python 与 C 绑定暴露同样的功能。
The ocas_domain::number_theory module provides the computational
number-theory stack: multi-modulus Chinese remaindering, BPSW primality,
integer factorization (trial division, Pollard rho/p−1, Williams p+1, ECM),
discrete logarithms, and the classical multiplicative functions. Python and C
bindings expose the same functionality.
素性判定 / Primality
is_prime(n):在 $n < 3.3\cdot10^{24}$ 内确定性成立。primes::is_prime_bpsw(n):base-2 强 Miller–Rabin + 强 Lucas 测试 (Selfridge 参数),目前无已知合数能通过。primes::is_prime_u64(n):对整个u64范围确定性成立。
is_prime(n) is deterministic for $n < 3.3\cdot10^{24}$; is_prime_bpsw
combines a base-2 strong Miller–Rabin round with a strong Lucas test
(Selfridge parameters) — no composite is known to pass it; is_prime_u64
is deterministic over the whole u64 range.
整数分解 / Integer Factorization
#![allow(unused)] fn main() { use ocas_domain::Integer; use ocas_domain::number_theory::factor::factor_integer; let f = factor_integer(&Integer::from(360)); // [(2, 3), (3, 2), (5, 1)] }
factor_integer 先试除小因子,然后对每个合数余因子按递增光滑界升级
方法:先做一次快速 Pollard-rho 尝试,再逐轮执行 Pollard p−1 /
Williams p+1 / ECM(Suyama 参数化 + Montgomery 曲线 stage-1)。30 位
半素数在 release 模式下约一秒分解。
factor_integer peels small factors by trial division, then splits composite
cofactors with an escalating strategy: one quick Pollard-rho attempt, then
rounds of Pollard p−1 / Williams p+1 / ECM (Suyama parametrization,
Montgomery curves, stage 1) with growing smoothness bounds. A 30-digit
semiprime factors in about a second in release mode.
中国剩余定理 / Chinese Remainder Theorem
crt::crt_many(&[(r1, m1), (r2, m2), ...]) 将任意同余式列表合并为单一
同余 x ≡ R (mod M);模数无需两两互素,不一致的系统返回 None。
crt::crt_many merges any list of congruences into x ≡ R (mod M); moduli
need not be coprime, and inconsistent systems return None.
离散对数 / Discrete Logarithms
dlog::dlog_bsgs(base, target, modulus):小步大步法,适用于小阶群。dlog::dlog_pohlig_hellman(base, target, p):分解base在素数p下的阶,对各素幂子群分别求解后经 CRT 合并。
dlog_bsgs is baby-step giant-step, practical for small groups;
dlog_pohlig_hellman factors the order of base modulo the prime p and
combines subgroup logarithms via CRT.
数论函数 / Number-Theoretic Functions
functions::euler_phi、moebius_mu、divisor_tau、divisor_sigma(n, k)、
liouville_lambda——均由 |n| 的素数分解式计算。二次剩余工具
(legendre、jacobi、Tonelli–Shanks 模平方根)位于父模块中。
All functions are computed from the prime factorization of |n|.
Quadratic-residue tools (legendre, jacobi, Tonelli–Shanks mod_sqrt)
live in the parent module.
Python 绑定 / Python Bindings
注意两个函数的参数顺序:discrete_log(p, base, target) 模数在前
(求 base^x ≡ target (mod p) 的 x);crt(moduli, residues) 模数
列表在前、余数列表在后。
import ocas
ocas.factorint(360) # [("2", 3), ("3", 2), ("5", 1)]
ocas.isprime(2**61 - 1) # True
ocas.nextprime(10**6) # 1000003
ocas.discrete_log(101, 2, 66) # 83
ocas.crt([3, 5, 7], [2, 3, 2]) # (23, 105)
ocas.jacobi_symbol(2, 7) # 1
ocas.totient(36) # 12
ocas.mobius(30) # -1
ocas.divisor_count(12) # 6
ocas.divisor_sigma(12, 2) # 210
ocas.liouville_lambda(12) # -1
C 绑定 / C Bindings
C API(ocas_ntheory_*,声明于 include/ocas.h)以十进制字符串传递
整数;返回的堆字符串用 ocas_string_free 释放。RAII 包装位于
ocas::ntheory(include/ocas.hpp)。
The C API (ocas_ntheory_*, declared in include/ocas.h) passes integers
as decimal strings; results are heap strings released with
ocas_string_free. RAII wrappers live in ocas::ntheory
(include/ocas.hpp).
模多项式 GCD / Modular Polynomial GCD
ocas_poly::gcd::modular::gcd_modular_z 用 Brown 模算法计算整系数稠密
单变量多项式的本原 GCD:对多个素数取 monic GCD 像,经 CRT(对称代表)
重构后用精确试除验证。它替代了朴素伪余式 GCD——后者在次数 ≳ 16 时
系数爆炸——并可处理 100 位系数的 50 次多项式。二元 gcd_modular
采用同样策略,另加主变量内容分离、monic 插值像与有理重构。
gcd_modular_z computes the primitive GCD of two dense univariate integer
polynomials with Brown's modular algorithm: monic GCD images modulo several
primes are combined with CRT (symmetric representatives) and confirmed by
exact trial division. It replaces the naive pseudo-remainder GCD, whose
coefficients explode for degrees ≳ 16, and handles degree-50 inputs with
100-digit coefficients. The bivariate gcd_modular applies the same strategy
with content separation, monic interpolation images, and rational
reconstruction.
符号积分
oCAS 通过分层管线进行积分:快速启发式表、有理函数积分器、初等塔上
的 Risch 算法、三角到指数的重写,最后是特殊函数表。第一个产生答案
的层获胜。本章解释每一层以及何时返回未求值形式 Integral(expr, var)。
管线
integrate(expr, var) 按顺序尝试:
- 快速表 —— 结构内联的幂法则($x^n$、$(ax+b)^n$,含分数指数)、
线性参数
sin/cos/exp的替换、以及log(x)等直接表项。快速且 总是最先尝试。 - 有理函数积分器 —— Hermite 约化加对数部分(对数导数恒等式、
配方、Rothstein–Trager)。处理
x的任意有理函数。 - 符号常数有理积分器(0.27)—— 系数在 ℚ(symbols) 中的有理函数: Yun 无平方分解 + Hermite + 部分分式(线性留数、二次因子的 log/atan/atanh)。
- Risch 算法 —— 由
log和exp构建的初等超越塔(塔深受MAX_RISCH_DEPTH = 16上限约束)。 - 三角重写 ——
sin/cos/tan/… 重写为exp(I·x)后由 Risch 积分,再尽力转换回实数形式。 - 特殊函数表 —— 具有
erf、Ei、Si、Ci、FresnelS/C等 闭式的非初等积分。 - 规则表引擎(0.27)—— 自研标准微积分规则(A–H 族:幂/二项式、
指数/对数、三角、双曲、反三角/反双曲、有理拦截、根式、特殊形态),
模板可含残项
Integral(g, x)归约。可用IntegrateOptions { rules: false }关闭。 - 三角积化和差/降幂(0.27.x)—— 线性变元
sin/cos乘积与幂化为 多倍角和式后逐项积分。 - 多根式基归约(0.30.0)—— 含两个及以上不同根式基的被积函数在原子层
有理化(MR-L 双仿射基 Möbius 有理化、带门同根合并、共轭/倒数/嵌套去根
代换、完全平方片层因子)。该阶段在链中位于
binomial之后、exp_log之前,单基引擎因此保持对单基形状的先占权;片段之外诚实拒绝。 - 反函数代换(0.30.0)——
inverse_trig分派序末的 M4 机制:u = F(w)覆盖 11 个头(asin/acos/atan/asinh/acosh/atanh以及acsc/asec/acoth/asech/acsch)× 仿射变元 × 整数核幂 × 多项式权,伴随根式吸收为片层因子。 - 启发式技巧 —— 分部积分、三角替换、Weierstrass $t = \tan(x/2)$、 Euler 替换。
- 有界展开重试(0.27.x)—— 分配展开乘积为和式(项数预算 64) 后逐项积分。
- 未求值形式 ——
Integral(expr, var)。
有理函数
积分变量的任意有理函数都被精确积分。多项式部分逐项积分;真分式由
Hermite 约化分解为有理部分加分母无平方的剩余;剩余部分产生对数
(经恒等式 c·f'/f → c·log(f))、反正切(二次分母配方)或
Rothstein–Trager 对数。
#![allow(unused)] fn main() { use ocas::prelude::*; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let expr = parse(&ctx, "(2*x + 3)/(x^2 + 3*x + 5)").unwrap(); let result = integrate(&ctx, expr, Symbol::new("x")); // log(x^2 + 3*x + 5) }
Risch 算法
初等超越被积函数通过构建微分域塔 ℚ(x, t₁, …, tₙ) 处理,其中
每个 tᵢ 是下层域上的 log 或 exp,然后递归积分(Bronstein
《Symbolic Integration I》第 5 章):
- 每层由 Hermite 约化分出有理部分(超指数层的
t幂分母先按 Laurent 多项式拆出,0.29.0); - 对数部分先试对数导数恒等式(快路径),未命中则走
Rothstein–Trager 结式方法:0.29.0 只判定 ℚ 根,0.30.0 增加代数
常量根(基层实/复二次因子与双二次四次因子
Az⁴+Bz²+C,塔层复二次 因子,均在扩域内自证);非常量根与更高次因子仍然诚实拒绝; - 多项式部分在
log层用待定系数、在exp层用 Risch 微分方程Dq + f·q = g积分——0.29.0 起在完整有理片段上求解 (弱规范化、分母界、次数界、SPDE、抵消递归); - 基域
ℚ(x)委托给有理函数积分器。
塔递归深度受 MAX_RISCH_DEPTH = 16 上限约束:超过该深度时 Risch 层
直接放弃,把被积函数交给下一层(防止病态被积函数造成无限递归)。
#![allow(unused)] fn main() { use ocas::prelude::*; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); // ∫ x·exp(x) dx = (x - 1)·exp(x) let result = integrate(&ctx, parse(&ctx, "x*exp(x)").unwrap(), Symbol::new("x")); }
范围限制
0.29.0 起 Risch 微分方程在完整有理片段上求解(含分母界),
对数部分用 Rothstein–Trager 的 ℚ 根判定;0.30.0 再由其代数常量根
片段(二次因子与双二次四次因子,扩域内自证)扩展该对数部分。片段之外
——非常量根、次数 ≥ 3 或非双二次四次的因子、混合扩域,以及未移植的
limited_integrate / 高层 parametric_log_deriv 改进——一律诚实回退。
因此:
∫ exp(x)/x dx没有初等原函数 —— 由特殊函数表回答为Ei(x)。- 某些需要自由选择常数使下层可积的
log塔情形(如log(x+1)) 尚未判定,走回退。
当所有层都失败时,结果是未求值形式 Integral(expr, var)——这是
有意的答案,而非错误。
三角被积函数
sin、cos、tan、cot、sec、csc 经 t = exp(I·x) 重写为复
指数后由 Risch 积分。虚数单位作为常数塔生成元携带(D I = 0)。结果
在可能时转换回实数形式:共轭对数对合并为实 log/atan 项。
Risch 微分方程求解器目前在 ℚ[x] 上工作,因此系数含 I 的超指数
方程(如 sin(x)·cos(x) 或 cos(x)² 产生的方程)尚不能求解;这些被积
函数返回未求值形式。线性参数的简单 sin/cos 由启发式表覆盖。
特殊函数
没有初等原函数但有标准闭式的积分直接回答(定义与 SymPy 一致):
| 被积函数 | 结果 |
|---|---|
exp(-x²) | (√π/2)·erf(x) |
exp(x²) | (√π/2)·erfi(x) |
exp(c·x²),c < 0 | √π/(2√(-c))·erf(√(-c)·x) |
exp(x)/x | Ei(x) |
sin(x)/x | Si(x) |
cos(x)/x | Ci(x) |
sinh(x)/x | Shi(x) |
cosh(x)/x | Chi(x) |
sin(x²) | √(π/2)·fresnels(√(2/π)·x) |
cos(x²) | √(π/2)·fresnelc(√(2/π)·x) |
#![allow(unused)] fn main() { use ocas::prelude::*; use ocas_core::arena::Arena; let arena = Arena::new(); let ctx = AtomArena::new(&arena); // ∫ exp(-x^2) dx = (√π/2)·erf(x) let result = integrate(&ctx, parse(&ctx, "exp(-x^2)").unwrap(), Symbol::new("x")); }
Fuel 受限积分
integrate_with_fuel 包装同一管线,但将 [Fuel] 预算贯穿到两个积分后
化简阶段。病态结果(否则会导致重写器无限循环)可被确定性地截断。
#![allow(unused)] fn main() { use ocas_core::arena::Arena; use ocas_core::fuel::Fuel; use ocas_atom::{AtomArena, Symbol}; use ocas_calc::integral::integrate_with_fuel; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let expr = ctx.var("x"); let fuel = Fuel::new(500); let result = integrate_with_fuel(&ctx, expr, Symbol::new("x"), &fuel); match result { Ok(r) => println!("{}", r), Err(_) => println!("fuel exhausted during simplification"), } }
仅在化简中途 fuel 耗尽时返回 Err。积分遍历本身使用内部深度限制;
fuel 仅约束化简后的传递。
绑定
同一管线支撑 Python 与 C API:
- Python:
Expression.integrate(var) - C:
ocas_expr_integrate(...)
找不到闭式时两者都返回未求值形式 Integral(...),与 Rust API 一致。
两者都不认证结果;需要可机检证书时用 Expression.integrate_outcome
(Python)或 ocas_expr_integrate_outcome(C)。
0.28.0:证书、循环检测与残项解析
符号证书与三值输出
integrate_outcome(Rust/Python/C 均已暴露)返回三值之一:
| 取值 | 含义 |
|---|---|
Found { value, certificate } | 原函数 + 可机检的符号证书(精确检查器中 D(F) − f ≡ 0) |
ProvedNonElementary { witness } | 已证明非初等(0.28.0 无生产者,为后续非初等层预留) |
Unknown { residue, uncertified } | 诚实未知:residue 是未求值 Integral(f, x);uncertified 是管线候选(不是答案,仅供诊断) |
证书引擎(ocas-calc/src/integral/certify.rs)分层判定:结构零(normalize + 同类项收集)、
初等域零(三角→指数、I² = −1 约化、依赖 exp/log 原子合并后嵌入有理函数域)、根式层
(0.30.0 已落地:CertMethod::RadicalField——把 sqrt(S) 与 S^(p/q) 统一为生成元 r 与
关系 r^q = S,以三角列余式约化判零;确定性预算 q ≤ 8、根式 ≤ 8、项 ≤ 8192、节点 ≤ 2000、
入口项 ≤ 2048)。各层都是可靠的:把原子当作独立生成元只会漏判(假阴性),不会把错误答案判成
正确。域运算有确定性预算,超预算即诚实拒绝。
ocas-calc/src/integral/risch.rs 还会在塔自身的域里校验每个 Risch 结果,因此引擎不会发射
被精确检查器证伪的答案——0.28.0 正是靠这条护栏抓到并兜住一个潜伏错案(指数层有理部分对负幂次
系数的错误缩放)。
表达式级循环检测与残项解析
链入口(integral/chain.rs)按「表达式地址 + rule_depth + parts_depth」判环:预算递减的
同形状重入是流水线合法的递归下降,必须放行;只有预算相同的重复才是真循环。绝对条目兜底保持
0.27.3 的 256,残项解析的嵌套重入走独立预算,不与主链争用。
若干阶段会返回部分结果(分式分解已完成、剩下的 Integral(...) 本身可解)。顶层入口在链条
结束后会做一次预算化的残项解析:只处理「有理形状、≤2 个符号、≤64 节点」的残项,并且只在残项数
严格减少时接受解析结果。纯回退 Integral(f, x) 永远不解析(否则会重跑整条链,并在
rules = false 时违反调用方意图)。
依赖生成元合并(正则塔)
ocas-calc/src/tower/merge.rs 用精确域恒等式合并代数相关生成元:exp(u)/exp(u+c)、
exp(u)/exp(−u)、log(u)/log(cu)、log(u)/log(u^k)、log(exp(u))、exp(log(u)),
必要时登记常量生成元(log(2)、exp(1),D t = 0)。塔现在返回重写后的被积表达式,
risch 在其中工作;判定不了的关系仍然诚实拒绝。
有理函数、结式与部分分式
oCAS 提供完整的有理函数运算、结式计算和部分分式分解栈,支持任何欧几里得域。
这些功能在 0.12.0 版本中添加,弥补了与 Symbolica 的 rational_polynomial.rs、
resultant.rs 和 partial_fraction.rs 的差距。
有理多项式
RationalPolynomial<D, O> 表示多项式环分式域中的元素——即 $\frac{p}{q}$,
其中 $p$ 和 $q$ 是域 D 上的多元多项式。
#![allow(unused)] fn main() { use ocas_domain::{IntegerDomain, Integer}; use ocas_poly::{RationalPolynomial, SparseMultivariatePolynomial, Lex}; // 创建多项式:x + 1 和 x - 1 let x_plus_1 = SparseMultivariatePolynomial::<_, Lex>::from_terms( IntegerDomain, 1, vec![(vec![0], Integer::from(1)), (vec![1], Integer::from(1))], ); let x_minus_1 = SparseMultivariatePolynomial::<_, Lex>::from_terms( IntegerDomain, 1, vec![(vec![0], Integer::from(-1)), (vec![1], Integer::from(1))], ); // (x+1) / (x-1) let rat = RationalPolynomial::from_num_den(x_plus_1, x_minus_1); }
规范化
通过 from_num_den 构造时,分数自动约简为规范形式:
- 分子分母的 GCD 被约去。
- 分母的首项系数被归一化(有序域为正,有限域为 1)。
算术运算
支持所有标准运算:
| 运算 | 方法 | 策略 |
|---|---|---|
| 加法 | a.add(&b) | 交叉相乘,然后规范化 |
| 减法 | a.sub(&b) | 通过取反 + 加法 |
| 乘法 | a.mul(&b) | 交叉消去 GCD,然后相乘 |
| 除法 | a.div(&b) | 委托 b.inv() 后相乘;b 的分子为零时返回 None |
| 取反 | a.neg() | 取反分子 |
| 逆元 | a.inv() | 交换分子分母;分子为零时返回 None |
| 幂 | a.pow(n) | 快速幂 |
结式
两个多项式 $a$ 和 $b$ 的结式是一个标量,当且仅当 $a$ 和 $b$ 有公共根 (或等价地,有非平凡 GCD)时为零。
#![allow(unused)] fn main() { use ocas_domain::{IntegerDomain, Integer}; use ocas_poly::DenseUnivariatePolynomial; let d = IntegerDomain; // Res(x^2 + 1, (x+1)^2) = 4 let a = DenseUnivariatePolynomial::from_coeffs(d, vec![ Integer::from(1), Integer::from(0), Integer::from(1), ]); let b = DenseUnivariatePolynomial::from_coeffs(d, vec![ Integer::from(1), Integer::from(2), Integer::from(1), ]); assert_eq!(a.resultant(&b), Integer::from(4)); }
算法:Brown PRS
oCAS 使用 Brown 多项式余式序列算法,避免构造完整的 Sylvester 矩阵。 该算法跟踪首项系数和次数差,用基本定理公式从 PRS 计算结式。
对于两个 15 次多项式,结式在 20ms 内完成。
部分分式
给定真分式 $\frac{p(x)}{q(x)}$(其中 $\deg(p) < \deg(q)$), 部分分式分解将其表示为更简单分式的和。
#![allow(unused)] fn main() { use ocas_domain::{RationalDomain, Rational}; use ocas_poly::DenseUnivariatePolynomial; use ocas_calc::partial_fraction::apart; let d = RationalDomain; let num = DenseUnivariatePolynomial::from_coeffs(d, vec![Rational::new(1, 1)]); let den = DenseUnivariatePolynomial::from_coeffs(d, vec![ Rational::new(1, 1), Rational::new(0, 1), Rational::new(-1, 1), ]); let (poly_part, terms) = apart(&num, &den); // poly_part 为 None(真分式) // x^2 - 1 是无平方的 → terms 恰好一个项(1/(x^2 - 1),exp = 1) }
算法
分解步骤:
- 多项式除法:若 $\deg(p) \geq \deg(q)$,通过
div_rem提取整式部分。 - 无平方分解:将分母分解为无平方因子 $f_i^{e_i}$。
- Diophantine CRT:对多个因子,求解多项式中国剩余定理以拆分分式。
- p-adic 展开:对重因子($e_i > 1$),对分子做 p-adic 展开得到各项。
together(poly_part, &terms) 函数执行逆操作,返回合并后的
(分子, 分母) 二元组。
辅助方法
为支持这些算法,DenseUnivariatePolynomial 新增了以下方法:
| 方法 | 说明 |
|---|---|
extended_gcd_poly(&self, other) | 扩展 GCD:返回 (g, s, t) 使得 $s \cdot a + t \cdot b = g$ |
diophantine(polys, b) | 多项式 CRT 求解器 |
p_adic_expansion(&self, p) | 重复除法展开 |
pow(n) | 多项式幂(快速幂) |
lcoeff() | 首项系数(便捷) |
constant() | 常数项(便捷) |
mul_coeff(c) / div_coeff(c) | 标量乘/除所有系数 |
content() | 系数的 GCD(现为 public) |
SparseMultivariatePolynomial 新增:
| 方法 | 说明 |
|---|---|
div_exact(&self, divisor) | 精确多项式除法(无余数) |
degree_in(var_index) | 指定变量的次数 |
有理重构
rational_reconstruction 函数从模表示恢复有理数 $\frac{n}{d}$:
#![allow(unused)] fn main() { use ocas_domain::{Integer, IntegerDomain}; use ocas_poly::rational_reconstruction::rational_reconstruction; // 3/7 mod 101: 7^{-1} mod 101 = 29, 所以 a = 3 * 29 mod 101 = 87 let m = Integer::from(101); let a = Integer::from(87); let result = rational_reconstruction(&a, &m); assert!(result.is_some()); }
使用扩展 Euclidean 算法(连分数方法),适用于需要将结果提升回 $\mathbb{Q}$ 的模方法算法。
Karatsuba 乘法
稠密多项式乘法现在对 32 个或更多系数的多项式使用 Karatsuba 算法。 这将乘法复杂度从 $O(n^2)$ 降低到 $O(n^{1.585})$。
阈值(32)通过经验选择——低于此大小时,Karatsuba 额外加减法的开销超过 减少乘法次数的收益。
对于两个 500 次 $\mathbb{Z}$ 上的多项式,Karatsuba 比之前的 schoolbook 实现有显著加速。
SymPy 迁移
| SymPy | oCAS |
|---|---|
sp.apart(expr, x) | apart(&num, &den) |
sp.together(expr) | together(poly_part, &terms) |
sp.resultant(a, b, x) | a.resultant(&b) |
sp.Rational(n, d) | Rational::new(n, d) |
限制
- 部分分式分解目前使用无平方分解,适用于任何
EuclideanDomain。 完全分解为不可约因子需要IntegerDomain或FiniteField。 - 多元部分分式(
apart_multivariate)推迟到未来版本。 - FFT/NTT 乘法自 0.12.1 起通过
ntt特性提供。
ODE 求解算法
oCAS 的 ODE 模块(ocas-calc/src/ode/)实现了常微分方程的符号求解引擎。本章详述
每种 ODE 类型的分类条件、求解算法的实现细节和设计选择。数学理论推导参见
ODE 求解理论。
总体架构
ODE 求解的核心入口是 dsolve,它执行三步:
- 规范化(
normalize_ode):对输入方程做代数化简和标准化 - 分类(
classify_ode):按优先级返回所有适用的求解方法 - 分派:依次尝试各方法,返回第一个成功的结果
输入方程 ─→ normalize ─→ classify ─→ dispatch ─→ ODESolution
│
┌───────────────────┼───────────────────┐
│ │ │
一阶 ODE 二阶线性 ODE 级数解(回退)
┌────┴────┐ ┌────┴────┐
可分离 线性 Bernoulli 常系数 Cauchy-Euler
恰当 齐次 精确 降阶 参数变易
求解结果类型
ODESolution 枚举表示求解结果:
| 变体 | 含义 | 典型来源 |
|---|---|---|
Explicit(expr) | 显式解 $y = f(x)$ | 线性、常系数、Laplace |
Implicit(expr) | 隐式解 $F(x, y) = C$ | 可分离、恰当、齐次 |
Parametric(x(t), y(t)) | 参数化解 $(x(t), y(t))$ | 枚举预留(当前无求解器产生) |
Series(expr, n) | 截断级数解 | 幂级数、Frobenius |
System(&[Atom]) | 系统解分量 $(y_1, y_2)$ | $2\times2$ 系统 |
Unsolved(ode) | 无法解析求解 | 所有方法失败时 |
分类引擎
分类器(classify.rs)对 ODE 做结构分析,返回按优先级排列的候选方法列表。
不涉及求解——仅做模式识别。
检测流程
一阶 ODE(order == 1):
- 检查
is_first_order_linear:$y$ 和 $y'$ 仅以一次方出现 →LinearFirst - 检查
is_bernoulli:存在 $y^n$($n \geq 2$)+ 线性 $y$ 项 →Bernoulli - 检查
is_separable:有不含 $y$ 的加法项 + 有含 $y$ 的加法项 →Separable - 检查
is_exact:$M + Ny' = 0$ 中 $\partial M/\partial y = \partial N/\partial x$ →Exact - 检查
is_homogeneous:所有项的 $(x, y)$ 总次数相同 →Homogeneous
二阶及以上线性 ODE:
is_constant_coeff_linear:$y, y', y''$ 的系数均不含 $x$ →LinearConstantCoeffis_cauchy_euler:各项为 $c_k x^k y^{(k)}$ 形式 →CauchyEuler- 二阶线性 →
ReductionOfOrder(尝试简单候选解)
全局回退:
- 线性 ODE →
PowerSeries(幂级数或 Frobenius)
精确方程的检测
精确方程判定 is_exact 实现如下:
- 用
split_mn将方程拆分为 $M(x, y) + N(x, y),y' = 0$ - 将 $y(x)$ 替换为纯符号 $y$,使 $M, N$ 成为二元表达式
- 计算 $\partial M/\partial y$ 和 $\partial N/\partial x$
- 先比较规范化字符串(
normalize后的to_string()) - 若不同,计算差值并用规则化简器检查是否为零
#![allow(unused)] fn main() { let dm_norm = normalize(ctx, dm_dy); let dn_norm = normalize(ctx, dn_dx); if dm_norm.to_string() == dn_norm.to_string() { return true; } // 回退:化简差值 let difference = simplify(ctx, ctx.add(&[dm_dy, ctx.mul(&[ctx.num(-1), dn_dx])]), &rules, 20); matches!(normalize(ctx, difference).node(), AtomNode::Num(0)) }
这种双重检查策略弥补了化简器不合并同类项的局限。
齐次方程的检测
is_homogeneous 检查方程是否关于 $(x, y)$ 齐次(零次齐次性)。实现上,
all_terms_homogeneous_degree 遍历所有加法项,计算每项在 $x$ 和 func 上的总
次数(term_degree),断言所有项次数相同且为正。
对 $\text{Derivative}(y(x), x)$,按 $y$ 的次数 1 计算(不含 $x$ 的额外幂次)。
一阶 ODE 求解器
一阶求解器在 first_order.rs 中实现,共五种方法。
可分离方程
判定:方程可写为 $g(y),y' = f(x)$。
算法:
- 用
separate_by_func将方程拆为含 $y$ 的项($y$-terms)和不含 $y$ 的项($x$-terms) - 将 $y$-terms 中的 $x$ 替换为 $y$(
substitute_var) - 对两侧分别积分:$\int g(y),dy$ 和 $\int f(x),dx$
- 返回隐式解 $\int g(y),dy - \int f(x),dx = C$
设计选择:返回 Implicit 而非 Explicit,因为一般情形下需要反解 $y$,这
在符号计算中不可判定。积分调用 crate::integral::integrate,它启动完整的
积分管线(快速表→有理函数→Risch→三角重写→特殊函数→启发式技巧→未求值形式)。
一阶线性方程
判定:$y' + p(x),y = q(x)$,其中 $y$ 和 $y'$ 仅以一次方出现。
算法(积分因子法):
- 用
extract_linear_coeffs提取 $p(x)$ 和 $q(x)$ - 计算积分因子 $\mu(x) = e^{\int p,dx}$
- 通解: $$y = e^{-\int p,dx}\left(\int e^{\int p,dx},q(x),dx + C_1\right)$$
#![allow(unused)] fn main() { let p_integral = integrate(ctx, p, var); let mu = ctx.fun("exp", &[p_integral]); let mu_q = ctx.mul(&[mu, q]); let int_mu_q = integrate(ctx, mu_q, var); let neg_p_integral = ctx.mul(&[ctx.num(-1), p_integral]); let exp_neg_p = ctx.fun("exp", &[neg_p_integral]); let particular = ctx.mul(&[exp_neg_p, int_mu_q]); let c1 = ctx.var("C1"); let homogeneous = ctx.mul(&[c1, exp_neg_p]); }
extract_linear_coeffs 将方程分解为加法项,分类为 $y'$-项、$y$-项和自由项。
对 $y$-项,通过过滤掉 func 因子提取 $p(x)$ 系数。
Bernoulli 方程
判定:$y' + p(x),y = q(x),y^n$($n \neq 0, 1$)。
算法:
- 用
find_bernoulli_power递归查找 $y^n$ 中的 $n$ - 代换 $v = y^{1-n}$,方程化为 $v' + (1-n),p(x),v = (1-n),q(x)$
- 用线性方程方法求解 $v$
- 反代换 $y = v^{1/(1-n)}$
find_power_inner 递归遍历 AST,当遇到 Pow(base, Num(n)) 且 base 含有
func 时返回 $n$。
边界情况:$n = 0$ 或 $n = 1$ 时退化为线性方程,返回 None 让 LinearFirst
处理。
恰当方程(Exact)
判定:$M(x,y) + N(x,y),y' = 0$ 且 $\partial M/\partial y = \partial N/\partial x$。
算法:
- 用
split_mn拆分 $M$ 和 $N$($y'$ 必须线性出现,否则返回None) - 检查恰当性(
partials_equal) - 若不恰当,尝试积分因子(
find_integrating_factor) - 对 $M$ 关于 $x$ 积分得 $F$ 的部分结果
- 计算修正项 $g(y) = N - \partial F/\partial y$
- 若 $g(y)$ 不含 $x$,对 $y$ 积分得完整解 $F + g = C$
积分因子策略:
- 候选 1:$(M_y - N_x)/N$ 仅含 $x$ → $\mu(x) = \exp!\int!(M_y - N_x)/N,dx$
- 候选 2:$(N_x - M_y)/M$ 仅含 $y$ → $\mu(y) = \exp!\int!(N_x - M_y)/M,dy$
exp_simplify 辅助化简 $\exp(k \cdot \log u) = u^k$,避免积分因子保持为
未化简的指数形式。
齐次方程
判定:$y' = f(y/x)$(所有加法项的 $(x,y)$ 总次数相同)。
算法:
- 代换 $v = y/x$(即 $y = vx$),得 $y' = v + xv'$
- 方程化为 $v + xv' = f(v)$,即 $xv' = f(v) - v$(关于 $v$ 和 $x$ 可分离)
- 用
separate_by_var拆分为含 $v$ 和含 $x$ 的项 - 两侧积分,再反代换 $v = y/x$
返回 Implicit 解。
二阶线性 ODE 求解器
二阶求解器在 second_order.rs 中实现。
常系数方程
形式:$a,y'' + b,y' + c,y = f(x)$,其中 $a, b, c$ 为常数。
算法:
- 用
extract_second_order_coeffs提取 $a, b, c$ 和 $f(x)$ - 验证 $a, b, c$ 不含 $x$(
contains_x) - 求特征方程 $ar^2 + br + c = 0$ 的判别式 $\Delta = b^2 - 4ac$
- 构造齐次解 $y_c$
- 构造特解 $y_p$(待定系数或参数变易)
- 返回 $y = y_c + y_p$
齐次解的三种情况
constant_coeff_basis 根据判别式构造基本解组:
| 判别式 | 基本解组 |
|---|---|
| $\Delta > 0$(两实根 $r_1, r_2$) | $e^{r_1 x},; e^{r_2 x}$ |
| $\Delta = 0$(重根 $r$) | $e^{rx},; x,e^{rx}$ |
| $\Delta < 0$(复根 $\alpha \pm \beta i$) | $e^{\alpha x}\cos\beta x,; e^{\alpha x}\sin\beta x$ |
实现中,判别式被折叠(collect_terms)为 Num(d) 时直接分支。对复根情况,
$\beta = \sqrt{-\Delta}/(2a)$:当 $-\Delta$ 是完全平方数时精确计算有理 $\beta$;
否则保留符号根式。
#![allow(unused)] fn main() { let sn = isqrt(num); if sn * sn == num { // beta = sn/den — 精确有理数 ctx.mul(&[ctx.num(sn), ctx.pow(ctx.num(den), ctx.num(-1))]) } else { // beta = sqrt(num)/den — 符号形式 ctx.mul![ ctx.pow(ctx.num(num), ctx.pow(ctx.num(2), ctx.num(-1))), ctx.pow(ctx.num(den), ctx.num(-1)), ] } }
待定系数法
particular_solution_undetermined 按激励类型分派:
| 激励类型 | 检测方式 | 求解策略 |
|---|---|---|
| 多项式 $f(x)$ | is_polynomial_in | 设 $y_p = \sum A_k x^{k+s}$,回代求系数 |
| 指数 $F e^{kx}$ | extract_exp_forcing | 设 $y_p = A x^s e^{kx}$ |
| 三角 $f_c\cos\omega x + f_s\sin\omega x$ | extract_trig_forcing | 设 $y_p = x^s(A\cos\omega x + B\sin\omega x)$ |
$s$ 是共振移位:当 $k$(或 $0$)是特征方程的 $s$ 重根时,试探解乘以 $x^s$。
多项式激励的回代算法:
对 $y_p = \sum_{k=0}^{d} A_k x^{k+s}$,代入 $ay'' + by' + cy = f(x)$ 后, $x$ 的同次幂系数给出递推关系。从最高次 $k = d$ 开始反向求解:
- $s = 0$(无共振):$A_k = \frac{f_k - b(k+1)A_{k+1} - a(k+2)(k+1)A_{k+2}}{c}$
- $s = 1$(一重共振):$A_k = \frac{f_k - a(k+2)(k+1)A_{k+1}}{b(k+1)}$
- $s = 2$(二重共振):$A_k = \frac{f_k}{a(k+2)(k+1)}$
叠加原理:对多项式激励的加法项逐项求解再求和。
参数变易法
当待定系数法不适用时,回退到参数变易(variation_of_parameters):
给定齐次解 $y_1, y_2$ 和标准形方程 $y'' + py' + qy = g$:
- 计算 Wronskian $W = y_1 y_2' - y_1' y_2$
- $u_1' = -y_2 g / W$,$u_2' = y_1 g / W$
- 积分得 $u_1, u_2$
- $y_p = y_1 u_1 + y_2 u_2$
若积分返回未求值的 Integral(...) 形式,该方法失败(is_integral_fallback)。
Cauchy–Euler 方程
形式:$a x^2 y'' + b x y' + c y = f(x)$。
算法:
- 用
extract_cauchy_euler_coeffs提取系数 - 约化为常数系数(除以 $x$ 的幂次)
- 求指标方程 $ar(r-1) + br + c = 0$,即 $ar^2 + (b-a)r + c = 0$
- 根据判别式构造齐次解:
| 判别式 | 基本解组 |
|---|---|
| $\Delta > 0$(两实根 $r_1, r_2$) | $x^{r_1},; x^{r_2}$ |
| $\Delta = 0$(重根 $r$) | $x^r,; x^r \ln x$ |
| $\Delta < 0$(复根 $\alpha \pm \beta i$) | $x^\alpha\cos(\beta\ln x),; x^\alpha\sin(\beta\ln x)$ |
- 非齐次项用参数变易求特解(除以 $ax^2$ 化为标准形)
降阶法
适用:任何二阶线性 ODE(不要求常系数)。
算法:
- 尝试候选解 $y_1 \in {1, x, x^2, e^x, e^{-x}, e^{2x}}$
- 验证 $a y_1'' + b y_1' + c y_1 = 0$(
satisfies_extracted) - 若 $y_1$ 满足齐次方程,第二解为: $$y_2 = y_1 \int \frac{e^{-\int p,dx}}{y_1^2},dx$$ 其中 $p = b/a$
- 特解通过参数变易获得
- 返回 $y = C_1 y_1 + C_2 y_2 + y_p$
设计选择:候选解集 ${1, x, x^2, e^x, e^{-x}, e^{2x}}$ 覆盖了教学和工程中最
常见的简单解形式。对更一般的方程,回退到 PowerSeries。
Laplace 变换法
laplace.rs 实现初值问题的 Laplace 变换求解,通过 dsolve_ivp 入口调用。
适用范围
- 一阶:$a y' + b y = f(x)$,$y(0) = y_0$
- 二阶:$a y'' + b y' + c y = f(x)$,$y(0) = y_0$,$y'(0) = y_1$
- 系数 $a, b, c$ 必须为整数常数
- 激励 $f(x)$ 必须在变换表中
正变换
laplace_kernel 对 $x$-依赖的核函数做变换,支持的核:
| 核 | Laplace 变换 |
|---|---|
| $1$(常数) | $1/s$ |
| $x$ | $1/s^2$ |
| $x^n$($n \leq 12$) | $n!/s^{n+1}$ |
| $e^{kx}$ | $1/(s-k)$ |
| $\sin\omega x$ | $\omega/(s^2 + \omega^2)$ |
| $\cos\omega x$ | $s/(s^2 + \omega^2)$ |
| $e^{kx}\sin\omega x$ | $\omega/((s-k)^2 + \omega^2)$ |
| $e^{kx}\cos\omega x$ | $(s-k)/((s-k)^2 + \omega^2)$ |
split_const_factors 将项拆分为 $x$-无关常数和 $x$-依赖核,仅变换核部分。
linear_coeff_of 从 $kx$ 中提取 $k$($x$ 本身返回 $1$)。
代数方程构造
变换后的代数方程:
一阶:$a(sY - y_0) + bY = F(s)$ $$Y(s) = \frac{F(s) + a,y_0}{as + b}$$
二阶:$a(s^2Y - sy_0 - y_1) + b(sY - y_0) + cY = F(s)$ $$Y(s) = \frac{F(s) + a(sy_0 + y_1) + by_0}{as^2 + bs + c}$$
逆变换
inverse_laplace_term 对有理分式 $Y(s) = \frac{n_1 s + n_0}{as^2 + bs + c}$ 做
部分分式分解和查表反变换。
先计算判别式 $\Delta = b^2 - 4ac$,按根的类型分三种情况:
情况 1:两不同实根 $r_1 \neq r_2$
$$\frac{n_1 s + n_0}{(s - r_1)(s - r_2)} = \frac{A}{s - r_1} + \frac{B}{s - r_2}$$
其中 $A = \frac{n_1 r_1 + n_0}{r_1 - r_2}$,$B = \frac{n_1 r_2 + n_0}{r_2 - r_1}$。
逆变换:$A,e^{r_1 x} + B,e^{r_2 x}$。
情况 2:重根 $r$($\Delta = 0$)
$$\frac{n_1 s + n_0}{(s - r)^2} = \frac{n_1}{s - r} + \frac{n_1 r + n_0}{(s - r)^2}$$
逆变换:$n_1,e^{rx} + (n_1 r + n_0),x,e^{rx}$。
情况 3:复根 $k \pm i\omega$($\Delta < 0$)
$$\frac{n_1 s + n_0}{(s - k)^2 + \omega^2} = e^{kx}!\left[n_1\cos\omega x + \frac{n_0 + kn_1}{\omega}\sin\omega x\right]$$
$k = 0$ 时去掉 $e^{kx}$ 因子。要求 $(n_0 + kn_1) \bmod \omega = 0$(整数算术)。
整数算术约束
Laplace 模块全程使用 i64 整数运算(通过 const_i64、quadratic_coeffs_i64、
linear_coeffs_i64)。这限制了适用范围——系数含符号常数或无理数的方程会
回退到其他方法。设计理由:整数运算完全精确,不引入浮点误差。
幂级数与 Frobenius 方法
series.rs 实现两种级数解法,作为所有其他方法失败时的回退。
幂级数法
适用:线性 ODE 在常点 $x_0$ 附近的解。
算法(solve_power_series):
- 构造符号系数 $a_0, a_1, \ldots, a_{N-1}$
- 手动构造级数 $S = \sum a_n (x - x_0)^n$ 及其一阶、二阶导数 $S', S''$
(
build_series_triple—— 不使用diff,避免对 $a_n$ 微分) - 代入 ODE:$R(x) = a,S'' + b,S' + c,S - f = 0$
- 逐阶满足条件:对 $k = 0, 1, \ldots$,计算 $R^{(k)}(x_0) = 0$
- 每个条件线性地确定一个新系数 $a_{k+\text{order}}$
- 已求解的系数立即回代
关键实现细节:
substitute_series递归替换方程中所有 $y(x)$、$y'(x)$、$y''(x)$ 为级数形式solve_linear_coeff从条件方程 $\text{coeff} \cdot a_n + \text{rest} = 0$ 解出 $a_n$- 自由参数 $a_0, \ldots, a_{\text{order}-1}$ 保持为符号(初值条件)
- 若某条件不含待解系数但非零,说明 $x_0$ 不是常点 → 返回
None,交给 Frobenius
限制:默认 $N = 8$ 项。系数是初值的有理表达式(非数值)。
Frobenius 方法
适用:二阶线性 ODE 在正则奇点 $x_0 = 0$ 附近的解。
算法(solve_frobenius):
- 提取系数 $a(x), b(x), c(x)$ 并验证 $f = 0$(仅齐次方程)
- 设 $y = x^r \sum a_n x^n$,构造 $y, y', y''$
- 代入 ODE,按 $x$ 的幂次分组残差
- 最低幂次组给出指标方程 $Ar^2 + Br + C = 0$
- 求指标方程的有理根(
indicial_coeffs→isqrt+ 判别式) - 取较大根 $r_1$,代入后续组递推求 $a_n$
$y$ 的构造:
$$y = u \cdot S, \quad y' = r x^{-1} u S + u S', \quad y'' = r(r-1)x^{-2}uS + 2rx^{-1}uS' + uS''$$
其中 $u = x^r$ 是不透明占位符,$S = \sum a_n x^n$。strip_x_and_u 从残差项中
剥离 $x$ 的幂次和 $u$ 因子,用于分组。
指标方程的提取(indicial_coeffs):
最低幂次组的项应为 $a_0$ 乘以 $r$ 的二次式。提取 $A, B, C$ 使得该组等于
$a_0(Ar^2 + Br + C)$。expand_indicial 处理含 $(r-1)$ 因子的展开。
限制:
- 仅支持 $x_0 = 0$
- 仅齐次方程
- 仅实有理根
- 仅取较大根的级数(较小根可能给出线性无关解)
$2\times2$ 线性 ODE 系统
systems.rs 实现常系数线性系统的求解,通过 dsolve_system 入口调用。
输入格式
系统 $\mathbf{Y}' = A\mathbf{Y}$ 以两个方程表示: $$\text{Derivative}(y_i, x) - (a_{i1}y_1 + a_{i2}y_2) = 0$$
extract_coeff 从每个方程提取 $2\times2$ 系数矩阵 $A$。
特征多项式
$$\lambda^2 - \text{tr}(A)\lambda + \det(A) = 0$$
其中 $\text{tr} = a_{11} + a_{22}$,$\det = a_{11}a_{22} - a_{12}a_{21}$。
判别式 $\Delta = \text{tr}^2 - 4\det$。
三种情况的求解
不同实特征值
$\lambda_1 \neq \lambda_2$,均为整数。
eigenvector求 $(A - \lambda I)\mathbf{v} = 0$:若 $a_{12} \neq 0$,取 $\mathbf{v} = (a_{12},; \lambda - a_{11})$- 通解: $$\mathbf{Y} = C_1 \mathbf{v}_1 e^{\lambda_1 x} + C_2 \mathbf{v}_2 e^{\lambda_2 x}$$
重特征值
$\lambda_1 = \lambda_2 = \lambda$。
子情况 A:$A = \lambda I$(完全矩阵): $$\mathbf{Y} = C_1 \mathbf{e}_1 e^{\lambda x} + C_2 \mathbf{e}_2 e^{\lambda x}$$
子情况 B:$A$ 亏损(defective):
- 求特征向量 $\mathbf{v}$(
eigenvector) - 求广义特征向量 $\mathbf{w}$ 满足 $(A - \lambda I)\mathbf{w} = \mathbf{v}$
(
generalized_eigenvector) - 通解: $$\mathbf{Y} = e^{\lambda x}!\left[C_1 \mathbf{v} + C_2(x\mathbf{v} + \mathbf{w})\right]$$
generalized_eigenvector 通过解线性方程组 $(a_{11}-\lambda)w_1 + a_{12}w_2 = v_1$
寻找整数解,尝试 $w_1 = 0$ 和 $w_1 = 1$。
复特征值
$\lambda = \alpha \pm \beta i$(要求 $\text{tr}$ 为偶数且 $-\Delta$ 为完全平方)。
- $\alpha = \text{tr}/2$,$\beta = \sqrt{-\Delta}/2$
- 特征向量 $(v + iw)$ 的实部 $p$ 和虚部 $q$:取 $v_1 = a_{12}$, $v_2 = (\alpha - a_{11}) + i\beta$,故 $p = (a_{12},; \alpha - a_{11})$, $q = (0,; \beta)$
- 实基本解: $$\mathbf{Y}_1 = e^{\alpha x}(p\cos\beta x - q\sin\beta x)$$ $$\mathbf{Y}_2 = e^{\alpha x}(p\sin\beta x + q\cos\beta x)$$
$\alpha = 0$ 时去掉 $e^{\alpha x}$ 因子,避免冗余的 exp(0)。
辅助工具
util.rs 提供 ODE 模块共用的基础设施。
阶数检测
ode_order 递归扫描 AST,查找 Derivative(func, var, var, ...) 节点,
返回最高微分阶数。
线性性检查
is_linear_in 检查表达式中 func 及其导数是否仅以一次方出现:
is_func_first_degree统计func/Derivative(func, ...)在乘法因子中的出现次数- 若某因子含
Pow(func, Num(n))且 $n \geq 2$,则非线性
同类项收集
collect_terms 解决化简器不合并同类项的问题:
- 将加法项分解为有理系数 + 底数-指数因子对
- 按底数-指数签名分组
- 对每组求和有理系数
- 系数为零的组丢弃
分解支持有理指数:$x \cdot x^{-1/2} = x^{1/2}$。
指数化简
exp_simplify 处理 $\exp(k \cdot \log u) = u^k$:
- 识别
Mul(k, Fun("log", [u]))形式的指数 - 数值常数因子从 $\log$ 参数中丢弃(对 ODE 积分因子无影响)
- 回退到字面
exp形式
解的替换与验证
substitute_solution 将候选解 $y = \text{sol}$ 代入方程:
- 替换
func→sol - 替换
Derivative(func, var)→diff(sol, var) - 替换
Derivative(func, var, var)→diff(diff(sol, var), var)
verify_solution(仅测试)代入后检查残差是否化简为零。
设计选择与权衡
化简策略
ODE 模块重度依赖 ocas_rewrite::simplify(规则化简,最多 20 轮迭代)
和 ocas_atom::normalize(结构规范化)。两者分工:
normalize:快速的结构性等价判定(字符串比较)simplify:基于规则的代数化简(合并、展开、消除)
在恰当方程检测和解的构造中,两者交替使用以处理化简器无法自动发现的等价性。
积分依赖
ODE 求解器调用 crate::integral::integrate 来计算不定积分。这启动了完整的
积分管线(快速表→有理函数→Risch→三角重写→特殊函数→启发式技巧→未求值形式)。
当积分无法闭合求值时,返回 Integral(...) 形式——此时参数变易和降阶法标记为失败。
整数限制
Laplace 变换模块严格使用 i64 整数算术,拒绝符号系数。这是因为部分分式
分解需要精确的有理算术,而整数是 i64 上最安全的选择。对需要符号系数的
初值问题,应回退到通用 dsolve + 初始条件代入。
回退链
分类器返回的优先级顺序确保最具体的方法先尝试:
LinearFirst → Bernoulli → Separable → Exact → Homogeneous
→ LinearConstantCoeff → CauchyEuler → ReductionOfOrder
→ PowerSeries
PowerSeries 作为最后手段总是在列表末尾;其求解器内部先尝试幂级数法,
失败时再尝试 Frobenius 法。dsolve 依次尝试直到第一个成功。
源码位置
| 文件 | 职责 |
|---|---|
ocas-calc/src/ode/mod.rs | 入口:dsolve、dsolve_ivp、dsolve_system、ODE、ODESolution |
ocas-calc/src/ode/classify.rs | 分类器:classify_ode、ODEType、各项检测函数 |
ocas-calc/src/ode/first_order.rs | 一阶求解器:可分离、线性、Bernoulli、恰当、齐次 |
ocas-calc/src/ode/second_order.rs | 二阶求解器:常系数、Cauchy-Euler、降阶、参数变易 |
ocas-calc/src/ode/laplace.rs | Laplace 变换:正变换表、逆变换(三种根类型)、IVP 求解 |
ocas-calc/src/ode/series.rs | 幂级数和 Frobenius 方法 |
ocas-calc/src/ode/systems.rs | $2\times2$ 系统:特征值分解、实/复/重根情况 |
ocas-calc/src/ode/util.rs | 工具:阶数、线性性、同类项收集、指数化简、解替换 |
参见
- ODE 求解理论 — 各方法的数学推导和证明
- 符号微积分 — 微分和 Taylor 展开的理论基础
- Rust API:求解器 —
dsolve、dsolve_ivp、dsolve_system的 API 参考 - Rust API:微积分 —
diff、integrate的 API 参考
求解器
oCAS 提供线性方程组、丢番图方程与多项式系统的求解器。本章介绍所有可用求解器及其用法。
ℚ 上的线性方程组
solve_linear_rational 在有理数域上求解 $n \times n$ 系统 $Ax = b$。
输入系数为 i64 值;解以 (分子, 分母) 对返回。
#![allow(unused)] fn main() { let a = vec![vec![2, 1], vec![1, -1]]; let b = vec![5, 1]; let x = solve_linear_rational(&a, &b).unwrap(); // x = [(2, 1), (1, 1)] → 2, 1 }
错误:EmptySystem、NonSquare、Inconsistent、Underdetermined { rank }。
Python:
print(ocas.solve_linear_rational([[2, 1], [1, -1]], [5, 1]))
# [(2, 1), (1, 1)]
ℤ 上的线性方程组
solve_linear_integer 求 $Ax = b$ 的整数解。若无整数解则返回错误。
#![allow(unused)] fn main() { // 2x + y = 3 let a = vec![vec![2, 1]]; let b = vec![3]; let x = solve_linear_integer(&a, &b).unwrap(); // x = [1, 1] (2·1 + 1·1 = 3) }
当解含分数时返回 ResultNotInDomain 错误。
丢番图方程
solve_diophantine 求解线性丢番图方程 $a \cdot x + b \cdot y = c$ 的整数解 $x, y$。
#![allow(unused)] fn main() { let sol = solve_diophantine(3, 5, 1).unwrap(); // sol = DiophantineSolution { x0: 2, y0: -1, x_step: 5, y_step: -3 } }
结果给出特解 $(x_0, y_0)$ 和步长值。通解为:
$$ \begin{aligned} x &= x_0 + x_{step} \cdot t \ y &= y_0 + y_{step} \cdot t \end{aligned} $$
其中 $t$ 为任意整数。
多项式系统(基于 Gröbner 基)
solve_polynomial_system 求解多项式方程组:内部先计算 Gröbner 基
(默认 F4,可通过 Algorithm 选择 Buchberger/F4/F5),对零维理想再做
Sturm 实根隔离与回代。
#![allow(unused)] fn main() { use ocas_domain::{Rational, RationalDomain}; use ocas_poly::groebner::Algorithm; use ocas_poly::ideal::solve_polynomial_system; use ocas_poly::sparse::{Lex, SparseMultivariatePolynomial}; let d = RationalDomain; // x + y = 0, x*y - 1 = 0 let eq1 = SparseMultivariatePolynomial::<_, Lex>::from_terms(d, 2, vec![ (vec![1, 0], Rational::new(1, 1)), (vec![0, 1], Rational::new(1, 1)), ]); let eq2 = SparseMultivariatePolynomial::<_, Lex>::from_terms(d, 2, vec![ (vec![1, 1], Rational::new(1, 1)), (vec![0, 0], Rational::new(-1, 1)), ]); let sol = solve_polynomial_system(&[eq1, eq2], Algorithm::Auto); }
结果为 PolynomialSystemSolution:零维时包含实数解列表
(ZeroDimSolutions),正维时返回 Gröbner 基(PositiveDimensional),
无解时为 Empty。
理想运算(0.23.0 起)
理想运算由 ocas_poly::ideal 与 ocas_poly::groebner 模块提供:
| 运算 | 函数 | 描述 |
|---|---|---|
| 成员判定 | ideal_contains(gens, f, algo) | 测试 $f \in I$ |
| 和 | ideal_sum(I, J) | $I + J$ |
| 积 | ideal_product(I, J) | $I \cdot J$ |
| 商 | ideal_quotient(I, J) | $I : J$ |
| 饱和 | ideal_saturate(I, J) | $I : J^\infty$ |
| 交集 | ideal_intersection(I, J) | $I \cap J$ |
| 消元 | eliminate(gens, elim_vars, algo)(ocas_poly::groebner) | 消去变量 |
| 根式 | ideal_radical(gens) | $\sqrt{I}$ |
| 准素分解 | primary_decomposition(gens) | 分解为准素分量 |
| Hilbert 级数 | hilbert_series(&gb)(ocas_poly::groebner::hilbert) | 计算 Hilbert 级数 |
#![allow(unused)] fn main() { use ocas_domain::{Rational, RationalDomain}; use ocas_poly::groebner::Algorithm; use ocas_poly::ideal::*; use ocas_poly::sparse::{Lex, SparseMultivariatePolynomial}; let d = RationalDomain; let circle = SparseMultivariatePolynomial::<_, Lex>::from_terms(d, 2, vec![ (vec![2, 0], Rational::new(1, 1)), (vec![0, 2], Rational::new(1, 1)), (vec![0, 0], Rational::new(-1, 1)), ]); let line = SparseMultivariatePolynomial::<_, Lex>::from_terms(d, 2, vec![ (vec![1, 0], Rational::new(1, 1)), (vec![0, 1], Rational::new(-1, 1)), ]); // 求解圆 ∩ 直线 let sol = solve_polynomial_system(&[circle, line], Algorithm::Auto); }
常微分方程
dsolve 解析求解常微分方程。方程以等于零的表达式、未知函数
(如 y(x))与自变量(如 x)给出。
#![allow(unused)] fn main() { let arena = Arena::new(); let ctx = AtomArena::new(&arena); let x = ctx.var("x"); let y = ctx.fun("y", &[x]); let dy = ctx.fun("Derivative", &[y, x]); // y' - y = 0 let ode = ODE { equation: ctx.add(&[dy, ctx.mul(&[ctx.num(-1), y])]), func: y, var: Symbol::new("x"), }; let sol = dsolve(&ctx, ode, None); // ODESolution::Explicit(C1*exp(x)) }
classify_ode 只返回可用的求解方法而不求解:
#![allow(unused)] fn main() { let types = classify_ode(&ctx, ode); // [LinearFirst, Separable, Homogeneous, PowerSeries] }
支持的 ODE 类型
| 类型 | 形式 | 方法 |
|---|---|---|
| 可分离 | $f(x)dx = g(y)dy$ | 直接积分 |
| 一阶线性 | $y' + p(x)y = q(x)$ | 积分因子 $\mu = e^{\int p}$ |
| Bernoulli | $y' + p(x)y = q(x)y^n$ | 替换 $v = y^{1-n}$ |
| 恰当 | $M dx + N dy = 0$,$\partial M/\partial y = \partial N/\partial x$ | 势函数 |
| 齐次 | $y' = f(y/x)$ | 替换 $v = y/x$ |
| 积分因子 | 非恰当一阶 | $\mu(x)$ 或 $\mu(y)$ 检测 |
| 常系数 | $ay'' + by' + cy = f(x)$ | 特征方程 |
| Cauchy-Euler | $ax^2y'' + bxy' + cy = f(x)$ | 指标方程 |
| 降阶法 | 二阶线性 | $y_2 = y_1\int e^{-\int p}/y_1^2$ |
| 常数变易法 | 二阶非齐次 | Wronskian 公式 |
| 待定系数法 | 多项式/指数/三角 forcing | 系数匹配 + 共振 |
| 幂级数 | 常点 | 系数递推 |
| Frobenius | 正则奇点 | 指标方程 + 递推 |
| Laplace IVP | 一阶/二阶线性初值问题 | dsolve_ivp |
| 2×2 系统 | $\mathbf{Y}' = A\mathbf{Y}$ | dsolve_system(特征分解) |
无法解析求解的 ODE 返回未求值的 ODE(equation, func) 形式。
初值问题
dsolve_ivp 通过 Laplace 变换求解线性常系数初值问题:
#![allow(unused)] fn main() { // y' - y = 0, y(0) = 1 => y = exp(x) let sol = dsolve_ivp(&ctx, ode, ctx.num(1), None); }
系统
dsolve_system 求解 2×2 常系数系统 $\mathbf{Y}' = A\mathbf{Y}$:
#![allow(unused)] fn main() { // y1' = y2, y2' = -y1(谐振子) let sol = dsolve_system(&ctx, &[eq1, eq2], &[y1, y2], Symbol::new("x")); // ODESolution::System([C1*sin(x) + C2*cos(x), C1*cos(x) - C2*sin(x)]) }
Python
import ocas
e = ocas.Expression("Derivative(y(x), x) - y(x)")
print(ocas.classify_ode(e, "y", "x")) # ['LinearFirst', 'Separable', ...]
print(ocas.dsolve(e, "y", "x")) # y = C1*exp(x)
print(ocas.dsolve_ivp(e, "y", "x", "1")) # y = exp(x)
C
int err = 0;
char *types = ocas_ode_classify("Derivative(y(x), x) - y(x)", "y", "x", &err);
char *sol = ocas_ode_dsolve("Derivative(y(x), x) - y(x)", "y", "x", NULL, &err);
char *ivp = ocas_ode_dsolve_ivp("Derivative(y(x), x) - y(x)", "y", "x", "1", NULL, &err);
ocas_string_free(types);
ocas_string_free(sol);
ocas_string_free(ivp);
错误
线性求解器返回 Result<T, SolveError>;solve_diophantine 返回
Option<DiophantineSolution>(无解时为 None);solve_polynomial_system
返回 PolynomialSystemSolution。常见错误变体:
| 错误 | 含义 |
|---|---|
EmptySystem | 未提供方程 |
NonLinear | 系统对请求变量非线性 |
NonSquare | 方程数与未知数个数不匹配 |
Inconsistent | 无解 |
Underdetermined { rank } | 无穷多解 |
ResultNotInDomain | 解含分数但要求整数 |
参见
重写与化简
oCAS 提供模式匹配引擎和基于规则的表达简化器。本章介绍核心概念与 API。
模式
Pattern 用通配符描述表达式结构。通配符名称后缀决定匹配级别:
| 级别 | 名称后缀 | 匹配 |
|---|---|---|
WildcardLevel::Single | _(如 x_) | 任意单个子表达式 |
WildcardLevel::Sequence | __(如 __x) | 有序列表中的一个或多个操作数 |
WildcardLevel::NullSequence | ___(如 ___x) | 有序列表中的零个或多个操作数 |
#![allow(unused)] fn main() { let arena = Arena::new(); let ctx = AtomArena::new(&arena); // 模式:x + y_ 其中 y_ 匹配任意单个子表达式 let x = ctx.var("x"); let pat = Pattern::Add(vec![ Pattern::Literal(x), Pattern::Wildcard(Symbol::new("y_"), WildcardLevel::Single), ]); // 匹配:x + 5 → 将 y_ 绑定到 5 let e = parse(&ctx, "x + 5").unwrap(); let bindings = match_pattern(pat, e).unwrap(); match bindings.get(Symbol::new("y_")).unwrap() { MatchValue::Single(v) => assert_eq!(v.to_string(), "5"), _ => {} } }
在 Add 和 Mul 内部,匹配是结合可交换的:参数被排序并按规范顺序匹配。
匹配与绑定
match_pattern 返回 Result<Bindings, MatchError>。Bindings 将通配符 Symbol 名称映射到匹配值。
#![allow(unused)] fn main() { // 模式:a_ + b_ + ___rest — 捕获两项及剩余部分 let pat = Pattern::Add(vec![ Pattern::Wildcard(Symbol::new("a_"), WildcardLevel::Single), Pattern::Wildcard(Symbol::new("b_"), WildcardLevel::Single), Pattern::Wildcard(Symbol::new("___rest"), WildcardLevel::NullSequence), ]); let e = parse(&ctx, "x + y + z + 5").unwrap(); let bindings = match_pattern(pat, e).unwrap(); // 绑定使用 MatchValue::Single(atom) 或 MatchValue::Sequence(slice) use ocas_rewrite::MatchValue; match bindings.get(Symbol::new("a_")).unwrap() { MatchValue::Single(a) => println!("a = {}", a), // 例如 "x" 或 "5" _ => {} } }
MatchError 变体:
NoMatch— 模式不匹配InconsistentBinding— 同一通配符名称绑定到不同值BudgetExhausted— 回溯预算耗尽
规则
Rule 将模式与替换闭包配对,闭包接收匹配绑定和 arena 上下文。
#![allow(unused)] fn main() { use ocas_rewrite::rules::default_rules; // 内置规则集 let rules = default_rules(&ctx, &()); // 自定义规则:x_ + 0 → x_ let custom_pat = Pattern::Add(vec![ Pattern::Wildcard(Symbol::new("x_"), WildcardLevel::Single), Pattern::Literal(ctx.num(0)), ]); let custom_rule = Rule::new(custom_pat, |bindings, _ctx| { match bindings.get(Symbol::new("x_")).unwrap() { MatchValue::Single(x) => *x, _ => unreachable!(), } }); }
化简
simplify() 反复应用规则集,直到不动点(或达到最大迭代次数)。
#![allow(unused)] fn main() { let e = parse(&ctx, "x + 0 + y*0 + z*1").unwrap(); let rules = default_rules(&ctx, &()); let simplified = simplify(&ctx, e, &rules, 20); println!("{}", simplified); // x + z }
默认规则集处理:
- 恒等元移除:
x + 0 → x、x * 1 → x、x * 0 → 0 - 常量折叠:
2 + 3 → 5、2 * 3 → 6 - 幂化简:
x^0 → 1、x^1 → x、0^x → 0、1^x → 1 - 数字运算:
2*3 + 4*5 → 26
自底向上变换
transform() 自底向上遍历表达式树,对每个节点应用函数。适用于不适合模式匹配模型的自定义遍历。
#![allow(unused)] fn main() { // 将每个变量 "x" 替换为 "t" let replacer = |_ctx: &AtomArena, atom: Atom| { if let AtomNode::Var(sym) = _ctx.get(atom) { if sym.as_str() == "x" { return _ctx.var("t"); } } atom }; let e = parse(&ctx, "x^2 + x + 1").unwrap(); let result = transform(&ctx, e, &replacer); println!("{}", result); // t^2 + t + 1 }
E-graph 化简(egg feature)
启用 egg feature 后,oCAS 可使用等式饱和进行更强大的化简,这是纯规则重写无法实现的。
cargo build -p ocas --features egg
#![allow(unused)] fn main() { // 需要 `egg` feature #[cfg(feature = "egg")] { use ocas_rewrite::egraph::egg_simplify; let e = parse(&ctx, "sin(x)^2 + cos(x)^2").unwrap(); let result = egg_simplify(&ctx, e).unwrap(); println!("{}", result); // 1 } }
E-graph 方法同时探索多种等价形式,通过同余闭包组合重写。可处理需要特定多步 重写顺序的三角恒等式和代数恒等式。
Fuel 受限化简
simplify_with_fuel 是 simplify 的变体,每次自底向上遍历消耗一个
fuel 单元,预算耗尽时提前停止。适用于处理不可信或病态表达式,防止重写
器无限循环。
#![allow(unused)] fn main() { use ocas_core::fuel::Fuel; use ocas_rewrite::simplify::simplify_with_fuel; let fuel = Fuel::new(100); let result = simplify_with_fuel(&ctx, e, &rules, 20, &fuel); match result { Ok(expr) => println!("simplified: {}", expr), Err(_) => println!("fuel exhausted before fixpoint"), } }
simplify_with_fuel 仅在 fuel 耗尽未达不动点时返回 Err。旧的
simplify API 保持可用,语义不变(无 fuel 限制)。
局限性
默认基于规则的化简器有意保持可预测性:
- 不处理
sin(x)^2 + cos(x)^2 → 1(需eggfeature) - 默认不执行完全多项式展开
- 不应用三角恒等式或对数恒等式
如需高级化简,请启用 egg feature 或编写自定义规则。
参见
回溯 AC 匹配(0.22.0)
匹配器现在对 Add/Mul 上下文使用全回溯搜索,取代之前的贪心
算法。这带来:
- Add/Mul 内序列通配符:
x_ + __rest匹配任意和,至少捕获一项。 - AC 内多通配符:
x_ + y_ + z_对a + b + c + d正确绑定。 - 回溯预算:防止病态模式的组合爆炸(默认 10,000 次尝试)。
#![allow(unused)] fn main() { use ocas_rewrite::matcher::match_pattern_with_budget; let bindings = match_pattern_with_budget(pattern, atom, 50_000)?; }
多模式替换(0.22.0)
ocas_rewrite::replace 提供带条件守卫和遍历设置的受控替换。
replace_once / replace_all
#![allow(unused)] fn main() { use ocas_rewrite::replace::{replace_once, replace_all}; // 替换树中第一个 x 出现: let result = replace_once(&ctx, expr, Pattern::Literal(x), |_, ctx| ctx.num(42)); // 替换所有 x: let result = replace_all(&ctx, expr, Pattern::Literal(x), |_, ctx| ctx.num(42)); }
replace_all_multiple — 首匹配获胜
#![allow(unused)] fn main() { use ocas_rewrite::replace::{replace_all_multiple, Replacement}; let replacements = vec![ Replacement { pattern: pat1, replacement: rhs1, condition: None }, Replacement { pattern: pat2, replacement: rhs2, condition: Some(cond) }, ]; let result = replace_all_multiple(&ctx, expr, &replacements); }
条件
Condition 是对 Bindings 的谓词:
#![allow(unused)] fn main() { use ocas_rewrite::replace::Condition; let cond = Condition::new(|bindings| { match bindings.get(Symbol::new("x_")) { Some(MatchValue::Single(a)) => matches!(a.node(), AtomNode::Num(_)), _ => false, } }); }
Transformer::Partition(0.22.0)
将 arg(a, b, c, …) 表达式按指定容量的命名桶分拆,返回所有合法
分配方式的求和。
#![allow(unused)] fn main() { use ocas_rewrite::transformer::partition_expr; use ocas_atom::Symbol; let result = partition_expr(&ctx, expr, &[(Symbol::new("f"), 2), (Symbol::new("g"), 2), (Symbol::new("f"), 1)], false, false); }
三种模式:
- exact:桶总容量必须等于元素数量。
- fill_last:剩余元素吸收到最后一个桶。
- repeat:桶模式重复直到所有元素消耗完毕。
求值与 JIT
oCAS 为符号表达式的数值求值提供三条路径:解释器(栈 VM)、Cranelift JIT 编译器和 SIMD 向量化批量求值器。 本章解释每条路径及何时使用。
栈 VM 解释器
默认求值路径。ExpressionEvaluator 将 Atom 表达式树编译为栈机指令序列,然后在扁平操作数栈上执行。
#![allow(unused)] fn main() { let arena = Arena::new(); let ctx = AtomArena::new(&arena); // 解析并编译 let e = parse(&ctx, "x^2 + y").unwrap(); let ev = ExpressionEvaluator::<f64>::compile(e).unwrap(); // 用参数值求值 let result = ev.evaluate(&[3.0, 1.0]).unwrap(); // [10.0] let result = ev.evaluate(&[2.0, 0.0]).unwrap(); // [4.0] }
编译器自动检测自由变量,按排序顺序分配参数槽,并优化指令序列(常量折叠、拷贝链消除)。
Python:
ev = ocas.ExpressionEvaluator("x^2 + y", ["x", "y"])
print(ev.evaluate([3.0, 1.0])) # [10.0]
print(ev.evaluate([2.0, 0.0])) # [4.0]
Cranelift JIT
启用 jit feature 后,oCAS 通过 Cranelift 将表达式编译为本机机器码。
这适用于使用不同输入重复求值同一表达式 —— 将编译开销分摊到多次调用。
cargo build -p ocas --features jit
#![allow(unused)] fn main() { #[cfg(feature = "jit")] { use ocas::prelude::*; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let e = parse(&ctx, "sin(x) * cos(y)").unwrap(); let ev: ExpressionEvaluator<f64> = ExpressionEvaluator::compile(e).unwrap(); let compiled = ev.compile_jit().unwrap(); let result = compiled.call(&[0.5, 1.0]); // ~0.2590 } }
JIT 路径将解释器使用的同一 IR 翻译为本机 x86-64 或 aarch64 代码。对于需要数千次求值的表达式, 最高可比解释器快 97 倍(见基准)。
多输出 JIT
compile_multi 将多个表达式编译为一个求值器,跨输出共享公共子表达式;call_into 将结果写入
调用方提供的缓冲(每次调用零分配)。
#![allow(unused)] fn main() { #[cfg(feature = "jit")] { use ocas::prelude::*; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let e1 = parse(&ctx, "sin(x) + 1").unwrap(); let e2 = parse(&ctx, "sin(x) * 2").unwrap(); let ev: ExpressionEvaluator<f64> = ExpressionEvaluator::compile_multi(&[e1, e2]).unwrap(); let compiled = ev.compile_jit().unwrap(); let mut out = [0.0f64; 2]; compiled.call_into(&[1.0], &mut out); } }
f32 混合精度
compile_jit_f32 生成单精度代码(libm *f 符号);compile_vector_evaluator_f32 在同一硬件上将
SIMD 通道数翻倍。当 f32 精度足够时使用。
SIMD 批量求值
simd feature 使用 pulp 启用向量化求值,通过运行时检测的 SIMD 宽度(SSE2/AVX2/AVX-512)同时
计算多个输入。
cargo build -p ocas --features simd
#![allow(unused)] fn main() { #[cfg(feature = "simd")] { use ocas::prelude::*; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let e = parse(&ctx, "x^2 + y").unwrap(); let ev: ExpressionEvaluator<f64> = ExpressionEvaluator::compile(e).unwrap(); let vec = ev.compile_vector_evaluator().unwrap(); // 每个 vec 是一个参数在所有输入行上的取值 let results = vec.evaluate(&[ vec![1.0, 2.0, 3.0, 0.0], // x:1² + 0 = 1, 2² + 1 = 5, … vec![0.0, 1.0, 2.0, 5.0], // y ]).unwrap(); // results[0] = [1.0, 5.0, 11.0, 5.0](每个输出一行;单输出时仅一行) } }
SIMD 求值并行处理多行输入(批量通道数由运行时检测的 SIMD 宽度决定),适用于参数扫描、绘图和 Monte Carlo 工作负载。
对比
| 路径 | Feature | 延迟 | 吞吐 | 最适合 |
|---|---|---|---|---|
| 解释器 | (默认) | 低(无编译开销) | 中 | 单次求值、交互使用 |
| JIT | jit | 高(编译)+ 低(运行) | 高 | 同一表达式的重复求值 |
| SIMD | simd | 低–中 | 极高(4×) | 批量求值、参数扫描 |
使用建议
- 解释器:交互式 REPL 会话、一次性求值、Python API。
- JIT:重复数值求值(求根迭代、优化循环)。
- SIMD:大量输入的批量处理(绘图、灵敏度分析、ML 管道)。
用户自定义函数
FunctionMap 允许注册自定义 Rust 函数,使其可在求值表达式中调用。
#![allow(unused)] fn main() { let mut fns = FunctionMap::<f64>::new(); fns.insert("f", |args: &[f64]| args[0].sin() * args[1].cos()); let e = parse(&ctx, "f(x, y)").unwrap(); let ev = ExpressionEvaluator::<f64>::compile_with(e, fns).unwrap(); let result = ev.evaluate(&[0.5, 1.0]).unwrap(); }
参见
数值积分
oCAS 内置自适应蒙特卡洛积分器(Vegas),用于数值计算定积分。当符号反 导数不存在或计算代价过高时,数值积分是实用的替代方案。
Vegas 算法
Vegas(Lepage 1978)是一种重要性采样蒙特卡洛方法,通过迭代细化分段常数 近似来集中采样到被积函数变化最剧烈的区域。oCAS 实现了标准自适应网格变 体,可配置迭代次数、采样预算和学习率。
| 入口 | 说明 |
|---|---|
integrate_1d(f, a, b, opts) | 一维积分便捷函数 |
Vegas::new(n_dims, opts) | 多维积分器 |
Integrator::integrate(&mut self, f) | 运行积分 |
返回 IntegrateResult { integral, error }。
快速上手:一维积分
#![allow(unused)] fn main() { use ocas_eval::numeric::{integrate_1d, VegasOptions}; // 计算 sin(x) 从 0 到 π 的积分 let opts = VegasOptions { n_bins: 50, n_samples: 10_000, iterations: 5, learning_rate: 1.5, seed: 42, }; let result = integrate_1d(|x| x.sin(), 0.0, std::f64::consts::PI, opts); println!("integral = {:.6} ± {:.6}", result.integral, result.error); // ≈ 2.000000 }
多维积分
对多变量积分,直接使用 Vegas:
#![allow(unused)] fn main() { use ocas_eval::numeric::{Vegas, VegasOptions, Integrator}; let opts = VegasOptions { n_bins: 50, n_samples: 10_000, iterations: 10, learning_rate: 1.5, seed: 0, }; let mut vegas = Vegas::new(2, opts); // 计算 x*y 在 [0,1]×[0,1] 上的积分 → 精确值 = 0.25 let result = vegas.integrate(&|coords| coords[0] * coords[1]); println!("integral = {:.6} ± {:.6}", result.integral, result.error); }
闭包接收一个 &[f64] 切片,每个元素对应一个维度,已映射到单位超立方
体 [0, 1]ⁿ。
调优参数
| 字段 | 默认值 | 作用 |
|---|---|---|
n_bins | 64 | 每个维度的网格分箱数 |
n_samples | 10,000 | 每次迭代的采样数 |
iterations | 10 | 自适应迭代次数 |
learning_rate | 1.5 | 网格自适应速度(1.0–2.0) |
seed | 0x0C45(u64) | 随机数种子,用于可重现性 |
增加迭代和采样次数可降低方差估计但增加运行时间。学习率控制网格对被积 函数结构的自适应强度。
统计累加器
StatisticsAccumulator 是 Vegas 内部使用的逆方差加权累加器。它跟踪每
次迭代的积分估计、卡方诊断和最终组合结果。
#![allow(unused)] fn main() { use ocas_eval::numeric::StatisticsAccumulator; let mut acc = StatisticsAccumulator::new(); acc.add_sample(1.0, 1.5); acc.add_sample(1.0, 1.8); acc.add_sample(1.0, 2.1); acc.finalize_iteration(); println!("integral = {:.6}", acc.integral()); println!("error = {:.6}", acc.error()); println!("chi² = {:.2}", acc.chi_square()); }
Python 与 C 用法
Python
import ocas
# 一维便捷函数
result = ocas.integrate_1d(lambda x: x**2, 0, 1, n_samples=10000, iterations=10)
print(result.integral, result.error)
# 多维积分
vegas = ocas.Vegas(n_dims=2, n_samples=10000, iterations=10)
result = vegas.integrate(lambda coords: coords[0] * coords[1])
print(result.integral)
C
#include <ocas.h>
/* 一维便捷函数(opts 传 NULL 使用库默认参数) */
int err = 0;
struct ocas_OcasIntegrateResult result =
ocas_integrate_1d(my_fn, NULL, 0.0, 1.0, NULL, &err);
printf("integral = %f ± %f\n", result.integral, result.error);
完整的绑定文档见 Python API 和 C/C++ API 章节。
限制
- Vegas 使用蒙特卡洛采样,结果是带误差条的统计估计,非精确符号值。
- 对高维且变量间强相关的积分,收敛较慢。
- 被积函数必须是纯
f64 → f64函数;符号表达式需先编译为数值求值器 (见 求值与 JIT)。
自动微分
oCAS 通过超对偶数提供前向自动微分。这与符号微分(diff)不同:自
动微分在求函数值的同时数值计算导数,无需构建或化简表达式树。
超对偶数
超对偶数将标量域扩展出无穷小分量 ε₁, ε₂, …,满足 εᵢ² = 0。对这些 数的算术运算会精确传播一阶(以及可选的更高阶)偏导数。
oCAS 用 HyperDual<T> 表示超对偶数,其中 T 是实现 DualCoeff trait
的系数类型。实践中 T 通常是 Rational(标准构建中唯一完整实现
DualCoeff 的类型)。
| 类型 | 作用 |
|---|---|
DualShape | 布局描述:变量分组、乘法表 |
HyperDual<T> | 具体的对偶数,含值和导数槽 |
DualCoeff | trait:zero、one、T 的算术运算 |
new_first_order(n) | 便捷函数:含 n 个一阶分量的形状 |
快速上手
#![allow(unused)] fn main() { use ocas_domain::Rational; use ocas_domain::dual::{new_first_order, HyperDual}; // 2 个变量的一阶对偶数 let shape = new_first_order::<Rational>(2); // x = 1 + ε₁(变量 0,单位系数) let x = HyperDual::variable(&shape, 0, Rational::new(1, 1)); // y = 2 + ε₂(变量 1,单位系数) let y = HyperDual::variable(&shape, 1, Rational::new(2, 1)); // f = x * y → 值 = 2,∂f/∂x = 2,∂f/∂y = 1 let f = x * y; println!("f = {}", f.value()); // 2 println!("df/dx = {}", f.deriv(0).unwrap()); // 2 println!("df/dy = {}", f.deriv(1).unwrap()); // 1 }
支持的运算
HyperDual<T> 支持标准算术 trait(Add、Sub、Mul、Div、Neg)。
每个运算通过 DualShape 中预计算的乘法表传播导数。
| 运算 | 支持? |
|---|---|
+、-、*、/ | 是 |
inv()(乘法逆元) | 是 |
pow / exp / log / 三角函数 | 否(超越函数需要尚未实现的 DualCoeff 扩展) |
对超越函数,可回退到符号微分(diff)或将表达式编译为带自动微分支
持的数值求值器(计划在 1.0 之后实现)。
高阶导数
DualShape 支持多指标分组以计算高阶导数(如二阶 εᵢεⱼ 分量)。可使
用 DualShape::new 传入嵌套索引向量来自定义形状,或使用一阶便捷函数
处理常见场景。
Python 与 C 用法
Python
from ocas import DualShape, HyperDual
shape = DualShape.first_order(2)
x = HyperDual.variable(shape, 0, 1)
y = HyperDual.variable(shape, 1, 2)
f = x * y
print(f.value()) # 2
print(f.deriv(0)) # 2 (∂f/∂x)
print(f.deriv(1)) # 1 (∂f/∂y)
# dunder 方法:__add__、__sub__、__mul__、__truediv__、__neg__
g = x + y * x
print(g.deriv(0)) # 3 (1 + y = 1 + 2)
C
#include <ocas.h>
int err = 0;
struct ocas_OcasDualShape *shape = ocas_dual_shape_new(2, &err);
struct ocas_OcasHyperDual *x = ocas_dual_variable(shape, 0, "1", &err);
struct ocas_OcasHyperDual *y = ocas_dual_variable(shape, 1, "2", &err);
struct ocas_OcasHyperDual *f = ocas_dual_mul(x, y, &err);
char *val = ocas_dual_value(f, &err); /* "2" */
char *dx = ocas_dual_deriv(f, 0, &err); /* "2" */
ocas_string_free(val);
ocas_string_free(dx);
ocas_hyperdual_free(f);
ocas_hyperdual_free(y);
ocas_hyperdual_free(x);
ocas_dual_shape_free(shape);
完整的绑定文档见 Python API 和 C/C++ API 章节。
限制
- 标准构建仅支持
Rational系数(尚不支持浮点或有限域对偶数)。 - 超越函数(
sin、cos、exp、log、pow)尚不可用于HyperDual。 - 对偶表达式的 JIT 编译尚未集成。
张量
oCAS 提供带显式指标管理的基础张量代数。Tensor 携带命名指标槽
(上标/下标)、可选的对称性元数据,并可转换为 Atom 表达式进行符号处理。
指标槽
每个张量指标具有:
| 属性 | 类型 | 含义 |
|---|---|---|
label | Atom | 指标名(通常为希腊字母或 i、j 等) |
position | IndexPosition | Upper(逆变)或 Lower(协变) |
#![allow(unused)] fn main() { use ocas_core::arena::Arena; use ocas_atom::AtomArena; use ocas_atom::tensor::{IndexSlot, IndexPosition}; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let mu = IndexSlot::new(ctx.var("mu"), IndexPosition::Upper); let nu = IndexSlot::new(ctx.var("nu"), IndexPosition::Lower); }
创建张量
Tensor 由名称(Symbol)和指标槽向量定义:
#![allow(unused)] fn main() { use ocas_core::arena::Arena; use ocas_atom::{AtomArena, Symbol}; use ocas_atom::tensor::{Tensor, IndexSlot, IndexPosition, Symmetry}; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let slots = vec![ IndexSlot::new(ctx.var("mu"), IndexPosition::Upper), IndexSlot::new(ctx.var("nu"), IndexPosition::Lower), ]; let t = Tensor::new(Symbol::new("g"), slots); println!("rank = {}", t.rank()); // 2 println!("name = {}", t.name()); // g }
可选地附加对称性元数据:
#![allow(unused)] fn main() { let symmetric = t.clone().with_symmetry(Symmetry::Symmetric); let antisymmetric = t.clone().with_symmetry(Symmetry::Antisymmetric); }
对称性元数据对下游消费者是建议性的;张量代数运算不会自动对称化。
缩并
contract 接受两个张量,返回标量(当所有指标都缩并时)或含剩余自由
指标的 TensorProduct:
#![allow(unused)] fn main() { use ocas_core::arena::Arena; use ocas_atom::{AtomArena, Symbol}; use ocas_atom::tensor::{contract, Contracted, IndexSlot, IndexPosition, Tensor}; let arena = Arena::new(); let ctx = AtomArena::new(&arena); // 缩并两个 1 阶张量:A^μ B_μ → 标量 let a = Tensor::new(Symbol::new("A"), vec![ IndexSlot::new(ctx.var("mu"), IndexPosition::Upper), ]); let b = Tensor::new(Symbol::new("B"), vec![ IndexSlot::new(ctx.var("mu"), IndexPosition::Lower), ]); match contract(&ctx, &a, &b) { Contracted::Scalar(expr) => println!("scalar: {}", expr), Contracted::Product(tp) => { for f in &tp.factors { println!("free factor: {}", f.name()); } } } }
缩并按标签和相反位置匹配指标。单次调用可缩并多对匹配指标。
对称化
symmetrise_sign 返回张量反对称化的符号:对哑指标(已缩并标签)的
偶排列返回 +1,奇排列返回 -1。这在计算类行列式表达式时很有用。
#![allow(unused)] fn main() { use ocas_atom::tensor::symmetrise_sign; let sign = symmetrise_sign(&tensor); }
转换为 Atom
张量可通过 to_atom 降级为标准 Atom 表达式:
#![allow(unused)] fn main() { let atom = tensor.to_atom(&ctx); println!("{}", atom); // 例如 g(mu, nu) }
这允许张量参与通用符号处理流水线(重写、化简、微分)。
Python 与 C 用法
Python
from ocas import Tensor, contract_tensors, tensor_symmetrise_sign
# 创建 2 阶张量
g = Tensor("g", [("mu", "upper"), ("nu", "lower")])
print(g.rank()) # 2
print(g.name()) # g
# 缩并两个张量
A = Tensor("A", [("mu", "upper")])
B = Tensor("B", [("mu", "lower")])
result = contract_tensors(A, B)
print(result) # 标量表达式
# 反对称化符号
sign = tensor_symmetrise_sign(g)
C
#include <ocas.h>
int err = 0;
/* 创建张量 A^μ(slots 为 "label,position;..." 字符串,symmetry 可传 NULL) */
struct ocas_OcasTensor *A = ocas_tensor_create("A", "mu,upper", NULL, &err);
/* 查询阶数 */
size_t rank = ocas_tensor_rank(A); /* 1 */
ocas_tensor_free(A);
完整的绑定文档见 Python API 和 C/C++ API 章节。
张量规范化(0.22.0)
基于图同构的规范化为张量表达式提供与指标命名无关的唯一规范形。 仅由指标重命名不同的两个张量乘积会得到相同的规范形。
工作原理
- 将张量表达式编码为图:张量头成为头顶点,每个指标槽成为槽顶点, 缩并成为无向边,对称性编码为隐藏的位置数据。
- 对图运行 McKay 细化-个体化搜索(1-WL 颜色细化 + 带自同构轨道 剪枝的个体化 DFS)。
- 根据规范标号重建表达式,哑指标从按维度组的池中重命名(
d0、d1、…)。
#![allow(unused)] fn main() { use ocas_core::arena::Arena; use ocas_atom::{AtomArena, Symbol}; use ocas_atom::tensor::canon::canonicalize_tensors; use ocas_atom::tensor::spec::{SymmetrySpec, TensorRegistry}; let arena = Arena::new(); let ctx = AtomArena::new(&arena); let mut reg = TensorRegistry::new(); reg.register(Symbol::new("T"), SymmetrySpec::none()); reg.register(Symbol::new("U"), SymmetrySpec::none()); let i = ctx.var("i"); let j = ctx.var("j"); let k = ctx.var("k"); let t = ctx.fun("T", &[i, j]); let u = ctx.fun("U", &[j, k]); let prod = ctx.mul(&[t, u]); let ct = canonicalize_tensors(&ctx, prod, ®).unwrap(); println!("规范形: {}", ct.canonical_form); // T(i,d0) * U(d0,k) }
对称性规格
| 规格 | 含义 |
|---|---|
SymmetrySpec::none() | 所有槽位独立 |
SymmetrySpec::fully_symmetric(n) | 槽位可互换(隐藏位置) |
SymmetrySpec::fully_antisymmetric(n) | 反对称(Ricci 恒等式验证) |
| 自定义子集 | symmetric_subsets / antisymmetric_subsets / cyclic 字段 |
哑指标管理
dummy::refresh_dummies 将恰好出现两次的标签重命名为规范名称
(d0、d1、…),按维度组分配:
#![allow(unused)] fn main() { use ocas_atom::tensor::dummy::refresh_dummies; let result = refresh_dummies(&ctx, expr, ®)?; // "T(i,j)*U(j,i)" → "T(d0,d1)*U(d1,d0)" }
Young 投影子(显式展开)
young::young_project 按 Young 盘将张量展开为置换和,列反对称:
#![allow(unused)] fn main() { use ocas_atom::tensor::young::{YoungTableau, young_project}; // 全反对称 [1,1]: f(a,b) → f(a,b) - f(b,a) let result = young_project(&ctx, f_ab, &YoungTableau::new(vec![1, 1])); // 全对称 [2]: g(a,b) → g(a,b) + g(b,a) let result = young_project(&ctx, g_ab, &YoungTableau::new(vec![2])); }
Python API
import ocas
# 规范化
result = ocas.canonicalize_tensors("T(i,j)*U(j,k)", {"T": "none", "U": "none"})
# 对称规范化(g(b,a) == g(a,b))
result = ocas.canonicalize_tensors("g(b,a)", {"g": "symmetric"})
# Young 投影
result = ocas.young_project("f(a,b)", [1, 1])
# 刷新哑指标
result = ocas.refresh_dummies("T(i,j)*U(j,i)", {"T": "none", "U": "none"})
限制
- 张量代数是显式的:指标按标签匹配,不使用爱因斯坦求和约定。必须手
动调用
contract。 Tensor的Symmetry元数据仅支持Symmetric与Antisymmetric; 更细粒度的对称性(子集对称symmetric_subsets、循环对称cyclic) 通过 0.22.0 的SymmetrySpec/TensorRegistry在图规范化中表达。- 图规范化需要先用
TensorRegistry注册各张量的对称性规格;未注册的 张量按无对称处理。 - 基于图的规范化不执行自动对称性强制的 Wick 缩并等物理语义操作。
基准与性能对比
oCAS 在 ocas-tests/benches/ 中提供基于 criterion 的基准套件,以及面向 SymPy、SageMath、Symbolica 的跨语言对比工具。本章说明如何运行各项基准及其测量内容。
运行 oCAS 基准
# 所有基准
cargo bench --workspace
# 单项基准
cargo bench --bench poly_gcd
cargo bench --bench poly_factor
cargo bench --bench groebner
cargo bench --bench integrate
cargo bench --bench ntt_poly_mul
# 更快但精度较低的运行
cargo bench --bench poly_gcd -- --warm-up-time 0.5 --measurement-time 1
| 基准 | 覆盖 |
|---|---|
arena | Arena 分配吞吐 |
parse / normalize | 表达式解析与规范化 |
poly_dense / poly_sparse | 多项式运算 |
poly_gcd | 一元与二元多项式 GCD |
poly_factor | 无平方与完全因式分解(ℤ/𝔽_p/多元/代数数域) |
groebner | Gröbner 基(cyclic-n 理想) |
calculus / rewrite | 微分、Taylor、规则化简 |
integrate | 符号积分(有理/Risch/特殊函数) |
eval_interpreter / eval_jit / eval_simd | 数值求值路径 |
eval_streaming | 流式求值(恒定内存) |
eval_estrin / eval_pulp | 快速多项式求值(Estrin / pulp SIMD) |
ntt_poly_mul | NTT 多项式乘法 |
gmp_integer | GMP 整数运算 |
sympy_comparison | 与 SymPy 直接对比 |
SymPy 对比(自动化)
sympy_comparison 基准通过 uv 管理的 Python 子进程(scripts/compare_sympy.py)驱动 SymPy,用 iter_custom 将耗时(纳秒)接入 criterion,使 oCAS 与 SymPy 在同一报告中并列展示。
# 需要 PATH 中有 `uv`;Python 环境会自动准备
cargo bench --bench sympy_comparison
支持的任务:parse、diff、expand、factor、gcd、series。
SageMath 对比(本地手动)
SageMath 安装体积过大,不适合进 CI,因此对比在本地通过 sage 解释器运行。该工具与 bench_sympy.py 的输出契约一致。
# 在 ocas-tests 目录下运行(需已安装 SageMath)
sage scripts/bench_sage.py factor "x^30 - 1" 100
任务:parse、diff、expand、factor、gcd。注意 SageMath 用 ^
表示乘方(与 oCAS 相同),无需语法转换。
JIT 与求值
eval_jit 基准对比 Cranelift JIT 与栈式解释器在单输出与多输出工作负载上的表现(各 1000 次调用,criterion iter_custom 计时):
| 工作负载 | 解释器 | JIT | 加速比 |
|---|---|---|---|
| 多项式(单输出) | 221 µs | 2.27 µs | 97× |
| 三角函数 3 输出 | 479 µs | 22.4 µs | 21× |
表中数字来自 0.15.0 的测量(criterion
iter_custom,各 1000 次调用), 随版本演进可能略有变化。
cargo bench --bench eval_jit --features jit
多输出用例通过 compile_multi 将三个表达式(sin(x)、cos(x)、sin(x)/cos(x))编译为一个求值器,跨输出共享 sin(x) 子表达式;call_into 将结果写入栈分配缓冲,每次调用零堆分配。
流式求值
StreamingEvaluator 跨行复用内部缓冲,处理百万行数据集时内存恒定:
| 工作负载 | 逐行 evaluate | StreamingEvaluator | 加速比 |
|---|---|---|---|
| 100k 行,多项式 | 23.4 ms | 16.8 ms | 28% |
cargo bench --bench eval_streaming
f32 混合精度
compile_jit_f32 / compile_vector_evaluator_f32 生成单精度代码。同一硬件上 SIMD 求值器通道数翻倍(AVX-512 上 f64 为 8、f32 为 16)。当 f32 精度足够时使用。
Symbolica 对比(本地手动)
Symbolica 是 oCAS 的主要性能参考。由于 Symbolica 采用 AGPL 类许可证且作为独立 Cargo workspace 发布,不会链接进 oCAS 构建。请从源码检出运行 Symbolica 自带的示例二进制,手动对比耗时。
推荐对比矩阵(Symbolica 示例 → oCAS 基准):
| Symbolica 示例 | oCAS 基准 | 工作负载 |
|---|---|---|
polynomial_gcd | poly_gcd | 整数与有理多项式 GCD |
factorization | poly_factor | x^n - 1 无平方/完整因式分解 |
groebner_basis | groebner | cyclic-4 理想 |
derivative | calculus | 符号微分 |
series | calculus | Taylor 展开 |
# 运行 Symbolica 示例(在 symbolica 源码根目录)
cd ../symbolica
cargo run --release --example polynomial_gcd
# 再运行对应的 oCAS 基准
cd ../ocas
cargo bench --bench poly_gcd -- --warm-up-time 0.5 --measurement-time 1
正确性对比
除性能外,oCAS 还在 ocas-tests/tests/correctness/ 中提供了正确性交叉验证框架。
它运行 201 项自动化测试,覆盖 19 个数学模块(截至 0.24.0),将 oCAS 结果与 SymPy、
SageMath、Symbolica 进行对比。详见正确性章节。
报告结果
criterion 将 HTML 报告写入 target/criterion/。在浏览器打开
target/criterion/poly_gcd/index.html 可查看分布、回归与并列对比。
正确性
oCAS 包含自动化正确性交叉验证框架,将结果与三个参考系统进行对比:SymPy、SageMath 和 Symbolica。 本章介绍该框架、如何运行及其当前已知限制。
框架概述
正确性套件位于 ocas-tests/tests/correctness/,包含 201 项 #[test] 测试,覆盖 19 个数学模块(截至 0.24.0;其中 35 项标记 #[ignore] 记录已知差距)。每项测试:
- 生成输入(表达式、多项式、方程组等)
- 使用 oCAS 计算结果
- 使用参考系统计算等价结果
- 断言两者语义相等
模块覆盖 oCAS 的全部功能:
| 模块 | 测试数 | 覆盖 |
|---|---|---|
calculus | 16 | 微分、Taylor、积分(SymPy 对比) |
evaluation | 6 | 数值求值 |
finite_field | 5 | 有限域运算 |
groebner | 24 | Gröbner 基计算 |
integral_risch | 15 | Risch 符号积分 |
integral_rules | 1 | 规则表 100 例抽样(SymPy 对比) |
integral_verify | 3 | 数值错案护栏(独立 oracle;0.27.1 的 C14/D7b 与 √(p/q) 两类,以及 0.27.3 的精确平方折叠/特殊函数族/半幂仿射变元机制) |
linear_solve | 5 | 线性求解器 |
matrix | 5 | 矩阵运算 |
normalize | 8 | 表达式规范化 |
ntheory | 11 | 数论(与 SymPy ntheory 交叉验证) |
ode | 34 | ODE 求解 |
parse | 12 | 表达式解析与输出(含一元负号/省略空格减法) |
partial_fraction | 8 | 部分分式分解 |
poly_arithmetic | 6 | 稠密/稀疏多项式运算 |
poly_factor | 12 | 无平方与完全因式分解 |
poly_factor_anf | 21 | 代数数域因式分解 |
poly_gcd | 5 | 多项式 GCD |
resultant | 8 | 结式 |
rewrite | 8 | 重写与化简 |
root_isolation | 4 | 实根隔离 |
正确性套件共 217 项测试(另有 ocas-calc、ocas-parse、ocas-atom、
ocas-poly 等 crate 的单元测试)。
忽略的测试(已知差距)
部分测试标记 #[ignore](当前 34 项),用于记录已知差距——它们预期失败,
仅在需要复现/推进时手动运行(cargo test -p ocas-tests --test correctness -- --ignored)。Wilkinson n=10 实根隔离差距已闭合:精确二进分数 Sturm
求值(0.27.x)找齐全部 10 个根。
自 0.27.2 起,积分正确性套件增加了一条独立判据:integral_verify 用
ocas-tests/src/integral_eval.rs 的数值 oracle(在确定性哑参数下的采样点上
做 5 点中心差分)复核积分结果,因此「形式像但实际错误」的原函数会使套件失败。
同一 oracle 也驱动 Rubi 1892 题覆盖率 harness 的 verified_solved /
verify_mismatches 字段。
运行测试
# 运行全部正确性测试
cargo test -p ocas-tests --test correctness
# 运行特定模块(如 ODE 求解)
cargo test -p ocas-tests --test correctness ode
# 运行被忽略的测试(已知差距)
cargo test -p ocas-tests --test correctness -- --ignored
# 详细输出以检查失败
cargo test -p ocas-tests --test correctness -- --nocapture
测试无需外部依赖 —— 所有参考计算通过 uv 管理的 Python 子进程使用 SymPy,子进程自动自举。
对比工具
独立脚本提供针对 SageMath 和 Symbolica 的手动交叉检查,用于深入调查:
# SageMath(需本地安装 `sage`)
cd ocas-tests
sage scripts/bench_sage.py factor "x^30 - 1" 100
# Symbolica(需 Symbolica 源码检出)
cd ../symbolica
cargo run --release --example factorization
这些工具在开发期间用于验证自动化测试套件,并维护用于手动回归测试。
审计报告
ocas-tests/scripts/generate_audit_report.py 会运行全部测试(含 --ignored
部分)并生成审计报告:
cd ocas-tests
python scripts/generate_audit_report.py
报告写入 docs/planning/correctness/audit-<日期>.md,包含:
- 普通测试与被忽略测试的通过/失败/忽略计数摘要
- 失败测试列表
- 与 Symbolica 的因式分解计时对比
已知限制
| 问题 | 模块 | 状态 |
|---|---|---|
sin(x)^2 + cos(x)^2 → 1 需要 egg feature | rewrite | 启用 egg feature 后可化简 |
| Bernoulli forcing y^n 使线性系数提取混淆 | ode | 已知限制(#[ignore]) |
| 积分器缺 tan/sec 表项 | ode | 计划中(#[ignore]) |
参见
数值后端
oCAS 默认提供纯 Rust 构建(使用 num-bigint/num-rational),并通过 feature flag 提供可选的高性能后端。默认构建仅使用 LGPL 兼容依赖;GPL 专属代码隔离在可选的 ocas-gpl crate 中。
功能开关
| Feature | Crate | 后端 | 许可证 |
|---|---|---|---|
gmp | ocas-core、ocas-domain、ocas-poly | 经 rug 的 GMP 整数/有理数 | LGPL |
mpfr | ocas-domain | 经 rug 的 MPFR 实数球 | LGPL |
flint | ocas-poly | FLINT 3(仅 Linux/WSL) | LGPL |
ntt | ocas-poly | 数论变换多项式乘法(oCAS 自研实现) | LGPL |
sprs | ocas-poly | 稀疏矩阵后端(sprs crate) | MIT |
fast-poly | ocas-eval | 快速多项式求值(SIMD 友好) | MIT |
jit | ocas-eval | Cranelift JIT 编译 | Apache-2.0 |
simd | ocas-eval | SIMD 向量化求值 | MIT |
egg | ocas-rewrite | E-graph 等式饱和 | MIT |
启用后端
# GMP + MPFR 后端(Windows 上推荐通过 MSYS2 MINGW64)
cargo build -p ocas --features gmp,mpfr
# FLINT 后端(仅 Linux/WSL;需要系统 libflint)
cargo build -p ocas --features gmp,mpfr,flint
# JIT + SIMD 求值
cargo build -p ocas --features jit,simd
Windows 上通过 MSYS2 安装 GMP/MPFR 的步骤见 docs/build-windows.md。
GPL 后端
ocas-gpl crate(GPL-3.0-or-later 许可)为可选,从不包含在默认构建中。它承载无法在 LGPL 下发布的 GPL 专属算法。默认 oCAS 构建保持 LGPL 纯净。
在 Windows 上构建
Windows 推荐工具链为 MSYS2 MINGW64(GNU 目标),可为可选数值后端提供 GMP/MPFR。MSVC 构建适用于纯 Rust 默认(无后端)配置。
MSYS2 MINGW64 配置
# 在 MSYS2 MINGW64 shell 中
pacman -S mingw-w64-x86_64-gcc mingw-w64-x86_64-gmp mingw-w64-x86_64-mpfr
然后带后端构建:
cargo build -p ocas --features gmp,mpfr
MSVC(无后端)
cargo build --target x86_64-pc-windows-msvc --no-default-features
由于上游
flint3-sys的限制(仅支持 POSIX 类型),flintfeature 在 Windows 上不可用。
包含 PATH 配置与问题排查的完整分步指南,见仓库中的 docs/build-windows.md。
贡献
欢迎贡献!oCAS 在 main 主干上进行开发,使用短生命周期特性分支与 Pull Request。
开发环境
- Rust 1.97+(Edition 2024)
- C 编译器(MSVC、GCC 或 Clang)
- 可选:用于后端特性的 GMP/MPFR/FLINT 系统库
质量门禁
分为两级:每次改动都运行 fast(快速) 门禁,提交 Pull Request 前运行 full(完整) 门禁(与 CI 一致)。
Fast(默认反馈回路):
cargo fmt --all -- --check
cargo clippy --workspace --all-targets --features egg --exclude ocas-py -- -D warnings
cargo test --workspace --exclude ocas-py --exclude ocas-tests --features egg
Full(与 CI 一致——额外包含 ocas-tests 中的跨系统正确性测试套件):
cargo test --workspace --exclude ocas-py --features egg
cargo deny check
标记为 #[ignore] 的测试(慢速属性测试、性能验收测试、进行中的修复)刻意不在
两级门禁内,由定时长测任务(.github/workflows/full-tests.yml,每周)执行,
也可以显式运行:
cargo test --workspace --exclude ocas-py --features egg -- --ignored
cargo test --release -p ocas-domain --lib -- --ignored # 性能验收
编码规范
- 遵循 Rust API Guidelines。
- 提交前运行
cargo fmt --all。 - 表达式树优先使用 arena 分配;避免逐节点
Box/Rc。 - 错误类型使用
thiserror;用?传播。 - 后端代码隐藏在 feature flag 后。
ocas-gpl是用于未来 GPL-3.0-or-later 后端的 独立占位 crate;顶层ocasfacade 不再暴露gplfeature,因此 GPL 后端必须 通过直接依赖ocas-gpl来显式启用。 - 稳定版本的公共 API 必须包含 rustdoc 示例。
完整约定与分层依赖规则见 docs/planning/ARCHITECTURE_CN.md。
Python 绑定
cd ocas-py
uv venv .venv
uv pip install pytest maturin
uv run maturin develop
uv run python -m pytest ../ocas-tests/tests/python/ -v
发布流程
发布已自动化且有 CI 门禁;顶层 vX.Y.Z 标签就是发布开关。变更日志只有根
CHANGELOG.md 一份(release-plz.toml 设 changelog_update = false,不生成
per-crate CHANGELOG,也不创建发版 PR)。
-
版本 bump 与打标签。 自行在
main上提升workspace.package.version并更新CHANGELOG.md(12 个可发布 crate;ocas-tests为publish = false),然后 push。release-plzworkflow 先等待该提交的 CI 变绿,再按依赖序发布各 crate,最后创建 顶层vX.Y.Z标签。打标签需要RELEASE_PLZ_TOKENsecret(具备Contents: Read and write权限的 fine-grained PAT):用默认GITHUB_TOKEN推送的标签不会触发下游 workflow。版本未变化时 release-plz 自然 no-op。 -
标签触发的下游流程。
release.yml校验标签与工作区版本一致,对每个 crate 做cargo publish试运行,并把 C 绑定产物附加到项目级 GitHub Release。wheels.yml用 maturin 构建 Python wheel 与 sdist 并发布到 PyPI(预发布 标签发到 TestPyPI);pypienvironment 会要求 owner 批准发布 job。docs.yml重新构建双语 mdBook,并把/latest/{en,zh}/与冻结的/v<version>/{en,zh}/版本快照部署到 GitHub Pages。
-
手动兜底。 若自动化失败,请对已在
main上且 CI 通过的提交打标签:git tag -a vX.Y.Z -m "oCAS vX.Y.Z" git push origin vX.Y.Z不要在本地执行发布步骤;发布由 CI 完成。
v*受保护:可创建,不可删除/强推。 -
版本一致性。 保持
Cargo.toml、CHANGELOG.md、两个 README、docs/book/*/src/introduction.md中的版本表以及AGENTS.md的Current version行同步。consistency.yml会在每次 push 与 Pull Request 上通过ocas-tests/scripts/check_release_consistency.py强制执行该检查。请在到期前轮换CARGO_REGISTRY_TOKEN/RELEASE_PLZ_TOKEN。
外部参考
- Rust API 文档(docs.rs):公共 Rust API 的自动生成
参考文档,由已发布 crate 以可移植特性构建(不含
gmp/mpfr/flint等需要系统 C 库的后端 feature);如需完整后端 API 文档,可本地运行cargo doc -p ocas --features gmp,mpfr,flint --no-deps。 - crates.io 注册页:已发布版本、feature 标签 与依赖图。
- GitHub 仓库:源代码、Issue 跟踪与贡献 指南。
- 文档站点:本书,由 GitHub Pages
提供。每个发布 tag 下
/oCAS/v<版本>/en/与/oCAS/v<版本>/zh/存有对应的 版本快照。