#prime #finite-fields #number-theory #arithmetic-operations #integer-arithmetic

nightly feanor-math

一个数论库,提供各种环中的算术运算和在这些环上工作的算法的实现

42 个稳定版本

2.2.0 2024 年 8 月 14 日
2.1.10 2024 年 7 月 28 日
1.7.5 2024 年 6 月 11 日
1.5.3 2024 年 3 月 11 日
1.3.0 2023 年 10 月 29 日

数学 中排名第 29

Download history 4/week @ 2024-04-29 15/week @ 2024-05-20 150/week @ 2024-05-27 357/week @ 2024-06-03 179/week @ 2024-06-10 177/week @ 2024-06-17 21/week @ 2024-06-24 64/week @ 2024-07-01 512/week @ 2024-07-08 529/week @ 2024-07-15 676/week @ 2024-07-22 771/week @ 2024-07-29 265/week @ 2024-08-05 125/week @ 2024-08-12

每月下载量 1,837
用于 he-ring

MIT 许可证

57MB
32K SLoC

Rust 26K SLoC // 0.0% comments Python 5.5K SLoC

feanor-math

这是一个完全用 Rust 编写的数论库。目的是提供一个比 NTL 或 FLINT 等项目更现代的替代品,然而由于这些项目范围广泛,当前的实现还远远达不到这个目标。更具体地说,我们使用现代语言特性——特别是特质系统——来提供环的通用框架,这使得嵌套环或创建自定义环变得容易,同时仍然保持高性能。在 NTL 中这是不可能的,虽然 FLINT 提供了这个框架,但它相当复杂,并且由于 FLINT 是用 C 编写的,依赖于运行时多态性——这阻止了彻底的类型检查。从用户的角度来看,我们希望这个库在某种程度上更接近像 sagemath 这样的高级计算机代数系统,但同时也具有强大的静态类型系统和非常高的性能。

当前状态

当前状态与这一愿景还有很大差距,因为只有一些最重要的算法已经实现。此外,可能存在错误,并且许多实现尚未特别优化。尽管如此,我认为这个库已经很有用,我经常使用它来处理各种应用,包括密码学。请注意,除非它们带有 #[stability::unstable] 标记,否则我会努力保持接口稳定。

这个库使用 nightly Rust,甚至包括像 const-generics 和 specialization 这样的不稳定功能——这造成了太多的麻烦,所以我再次移除了这些使用。

简要介绍

该库的设计考虑了环及其算术作为最基本的概念。这导致了两个特质的产生:RingBaseRingStore。这两个特质反映了在此crate中的一般设计策略,即提供面向实现者(例如RingBase)和面向用户(例如RingStore)的特质。前者试图使实现具有自定义算术的新环变得容易,后者使编写在环内执行算术的代码变得容易。更多关于这种分离的原因将在本页的下方进行解释。

功能

以下提供了以下环

  • 整数环Z,作为一个特质crate::integer::IntegerRing,对于所有原始整数(从i8i128)都有实现,一个任意精度的实现crate::rings::rust_bigint::RustBigintRing,以及一个使用绑定到高度优化的库mpir的可选实现(通过features=mpir启用)。
  • 商环Z/nZ,作为一个特质crate::rings::zn::ZnRing,有四种实现。其中模数较小且在编译时已知crate::rings::zn::zn_static::Zn,小于64位的模数优化的Barett约简实现crate::rings::zn::zn_64::Zn,任何模数和任何整数环(包括任意精度环)的Barett约简的泛型实现crate::rings::zn::zn_big::Zn,以及高度复合模数的剩余数系统实现crate::rings::zn::zn_rns::Zn
  • 任何基环上的多项式环 R[X],作为一个具有两个实现(稠密多项式 crate::rings::poly::dense_poly::DensePolyRing 和稀疏多项式 crate::rings::poly::sparse_poly::SparsePolyRing)的特质 crate::rings::poly::PolyRing
  • 有限秩的简单自由环扩张,作为一个基于多项式除法的特质 crate::rings::extension::FreeAlgebra,具体包括有限/伽罗瓦域和数域。
  • 任何基环上的多元多项式环 R[X1, ..., XN],作为特质 crate::rings::multivariate::MultivariatePolyRing 和一个基于有序向量的稀疏表示的实现 crate::rings::multivariate::ordered::MultivariatePolyRingImpl
  • 结合以上内容,您可以获得伽罗瓦域(使用 [crate::rings::extension::galois_field::GF()] 容易获得)或任意数域。

以下算法已实现

  • 快速傅里叶变换,包括对二的幂次情况的Cooley-Tuckey算法的优化实现,对任意长度的Bluestein算法的实现,以及基于Cooley-Tuckey算法的因子FFT实现。傅里叶变换适用于所有具有合适单位根的环,特别是复数C和合适的有限环Fq
  • 用于快速卷积的Karatsuba算法的优化变体。
  • 用于有限域上分解多项式的Cantor-Zassenhaus算法的实现。
  • 有理数/整数上的多项式分解(使用Hensel提升)和数域上的多项式分解。
  • Lenstra的椭圆曲线算法分解整数(目前非常慢)。
  • 用于格减小的LLL算法。
  • 主理想环上的基本线性代数。
  • 用于检查整数素性的Miller-Rabin测试。
  • 基于婴儿步-巨人步和分解算法计算任意离散对数。
  • Faugere的F4算法计算Gröbner基。

遗憾的是,目前对无限环(整数、有理数、数域)上的多项式操作非常慢,因为有效的实现需要大量小心以防止系数膨胀,而我没有时间和需求去投资。

最重要的缺失功能

  • 矩阵和线性代数的全面处理。目前只有矩阵和线性代数的一个非常简约的抽象crate::matrix,主要供内部使用。
  • 对无限环上的多项式进行仔细处理,主要使用专门实现以防止系数膨胀。
  • 格减小和LLL算法。这也可能是上述点所需的内容。现在已实现!
  • 更仔细设计的内存分配抽象(最好是使用新的crate memory-provider 或类似)。现在使用Rust的allocator-api以及feanor-mempool
  • 更多的数论算法,例如计算Galois群。我还没有确定在哪里划线,因为我认为高级数论算法(椭圆曲线、类群、...)不在这个项目的范围之内。技术上我会把整数分解包括在内,但它对其他算法来说太重要了。

语义版本

在版本 1..x.x 中,库使用了不一致的版本方案。现在已修复,并且从版本 2..x.x 开始使用语义版本,如Cargo手册中所述。请注意,所有标记为#[stability::unstable]的rust库中的项目都免于语义版本。换句话说,这些结构/特质/函数接口的破坏性更改只会增加次要版本号。请注意,除非激活了功能 unstable-enable,否则这些更改对其他crate不可见。

类似项目

最近我了解到一个名为 Symbolica 的项目,它与 Rust 中的符号计算方法类似。与主要用于数论计算的 feanor-math 不同,Symbolica 主要关注多变量多项式(包括浮点数)的计算,在这个领域比 feanor-math 更优化。如果它更适合您的用例,不妨去看看!我个人认为这是一个非常棒的项目。

示例

使用环

以下是一个如何使用库的简单示例,我们在这里实现了费马素性检验

use feanor_math::homomorphism::*;
use feanor_math::assert_el_eq;
use feanor_math::ring::*;
use feanor_math::primitive_int::*;
use feanor_math::rings::zn::zn_big::*;
use feanor_math::rings::zn::*;
use feanor_math::rings::finite::*;
use feanor_math::algorithms;
use oorandom;

fn fermat_is_prime(n: i64) -> bool {
    // the Fermat primality test is based on the observation that a^n == a mod n if n
    // is a prime; On the other hand, if n is not prime, we hope that there are many
    // a such that this is not the case. 
    // Note that this is not always the case, and so more advanced primality tests should 
    // be used in practice. This is just a proof of concept.

    let ZZ = StaticRing::<i64>::RING;
    let Zn = Zn::new(ZZ, n); // the ring Z/nZ

    // check for 6 random a whether a^n == a mod n
    let mut rng = oorandom::Rand64::new(0);
    for _ in 0..6 {
        let a = Zn.random_element(|| rng.rand_u64());
        let a_n = Zn.pow(Zn.clone_el(&a), n as usize);
        if !Zn.eq_el(&a, &a_n) {
            return false;
        }
    }
    return true;
}

assert!(algorithms::miller_rabin::is_prime(StaticRing::<i64>::RING, &91, 6) == fermat_is_prime(91));

如果我们想支持任意整数环 - 例如 BigIntRing::RING,它是对任意精度整数的简单实现 - 我们可以将函数泛型化

use feanor_math::homomorphism::*;
use feanor_math::ring::*;
use feanor_math::integer::*;
use feanor_math::integer::*;
use feanor_math::rings::zn::zn_big::*;
use feanor_math::rings::zn::*;
use feanor_math::rings::finite::*;
use feanor_math::algorithms;

use oorandom;

fn fermat_is_prime<R>(ZZ: R, n: El<R>) -> bool 
    where R: RingStore, R::Type: IntegerRing
{
    // the Fermat primality test is based on the observation that a^n == a mod n if n
    // is a prime; On the other hand, if n is not prime, we hope that there are many
    // a such that this is not the case. 
    // Note that this is not always the case, and so more advanced primality tests should 
    // be used in practice. This is just a proof of concept.

    // ZZ is not guaranteed to be Copy anymore, so use reference instead
    let Zn = Zn::new(&ZZ, ZZ.clone_el(&n)); // the ring Z/nZ

    // check for 6 random a whether a^n == a mod n
    let mut rng = oorandom::Rand64::new(0);
    for _ in 0..6 {
        let a = Zn.random_element(|| rng.rand_u64());
        // use a generic square-and-multiply powering function that works with any implementation
        // of integers
        let a_n = Zn.pow_gen(Zn.clone_el(&a), &n, &ZZ);
        if !Zn.eq_el(&a, &a_n) {
            return false;
        }
    }
    return true;
}

// the miller-rabin primality test is implemented in feanor_math::algorithms, so we can
// check our implementation
let n = BigIntRing::RING.int_hom().map(91);
assert!(algorithms::miller_rabin::is_prime(BigIntRing::RING, &n, 6) == fermat_is_prime(BigIntRing::RING, n));

这个函数现在可以与任何实现了 IntegerRing 的环一起工作,它是 RingBase 的子特质。

实现环

要实现自定义环,只需创建一个结构体并添加一个 impl RingBase 和一个 impl CanIsoFromTo<Self> - 就这么简单!假设我们想提供自己实现的有限二元域 F2,可以这样做。

use feanor_math::homomorphism::*;
use feanor_math::integer::*;
use feanor_math::assert_el_eq;
use feanor_math::ring::*;

#[derive(PartialEq)]
struct F2Base;

impl RingBase for F2Base {
   
    type Element = u8;

    fn clone_el(&self, val: &Self::Element) -> Self::Element {
        *val
    }

    fn add_assign(&self, lhs: &mut Self::Element, rhs: Self::Element) {
        *lhs = (*lhs + rhs) % 2;
    }
    
    fn negate_inplace(&self, lhs: &mut Self::Element) {
        *lhs = (2 - *lhs) % 2;
    }

    fn mul_assign(&self, lhs: &mut Self::Element, rhs: Self::Element) {
        *lhs = (*lhs * rhs) % 2;
    }
    
    fn from_int(&self, value: i32) -> Self::Element {
        // make sure that we handle negative numbers correctly
        (((value % 2) + 2) % 2) as u8
    }

    fn eq_el(&self, lhs: &Self::Element, rhs: &Self::Element) -> bool {
        // elements are always represented by 0 or 1
        *lhs == *rhs
    }

    fn characteristic<I>(&self, ZZ: &I) -> Option<El<I>>
        where I: IntegerRingStore, I::Type: IntegerRing
    {
        Some(ZZ.int_hom().map(2))
    }

    fn is_commutative(&self) -> bool { true }
    fn is_noetherian(&self) -> bool { true }

    fn dbg<'a>(&self, value: &Self::Element, out: &mut std::fmt::Formatter<'a>) -> std::fmt::Result {
        write!(out, "{}", *value)
    }
}

pub const F2: RingValue<F2Base> = RingValue::from(F2Base);

assert_el_eq!(F2, F2.int_hom().map(1), F2.add(F2.one(), F2.zero()));

一起使用

这个特质的其中一个主要目标是为了使嵌套环变得容易,因此在环的范畴上实现了一个函子。经典的例子是对于任何环 R 都存在的多项式环 R[X]。由于在这种情况下我们既在使用也在实现环,我们应该使用框架的两面。例如,一个简单的多项式环实现可能看起来像这样。

use feanor_math::assert_el_eq;
use feanor_math::ring::*;
use feanor_math::integer::*;
use feanor_math::homomorphism::*;
use feanor_math::rings::zn::*;
use std::cmp::{min, max};

pub struct MyPolyRing<R: RingStore> {
    base_ring: R
}

impl<R: RingStore> PartialEq for MyPolyRing<R> {

    fn eq(&self, other: &Self) -> bool {
        self.base_ring.get_ring() == other.base_ring.get_ring()
    }
}

impl<R: RingStore> RingBase for MyPolyRing<R> {

    // in a real implementation, we might want to wrap this in a newtype, to avoid
    // exposing the vector interface (although exposing that interface might be intended - 
    // the crate does not judge whether this is a good idea)
    type Element = Vec<El<R>>;

    fn clone_el(&self, el: &Self::Element) -> Self::Element {
        el.iter().map(|x| self.base_ring.clone_el(x)).collect()
    }

    fn add_assign(&self, lhs: &mut Self::Element, rhs: Self::Element) {
        for i in 0..min(lhs.len(), rhs.len()) {
            self.base_ring.add_assign_ref(&mut lhs[i], &rhs[i]);
        }
        for i in lhs.len()..rhs.len() {
            lhs.push(self.base_ring.clone_el(&rhs[i]))
        }
    }

    fn negate_inplace(&self, val: &mut Self::Element) {
        for x in val {
            self.base_ring.negate_inplace(x);
        }
    }

    fn mul_ref(&self, lhs: &Self::Element, rhs: &Self::Element) -> Self::Element {
        // this is just for demonstration purposes - note that the length of the vectors would slowly increase,
        // even if the degree of the polynomials doesn't
        let mut result = (0..(lhs.len() + rhs.len())).map(|_| self.base_ring.zero()).collect::<Vec<_>>();
        for i in 0..lhs.len() {
            for j in 0..rhs.len() {
                self.base_ring.add_assign(&mut result[i + j], self.base_ring.mul_ref(&lhs[i], &rhs[j]));
            }
        }
        return result;
    }

    fn mul_assign(&self, lhs: &mut Self::Element, rhs: Self::Element) {
        *lhs = self.mul_ref(lhs, &rhs);
    }

    fn from_int(&self, x: i32) -> Self::Element {
        vec![self.base_ring.int_hom().map(x)]
    }

    fn eq_el(&self, lhs: &Self::Element, rhs: &Self::Element) -> bool {
        let zero = self.base_ring.zero();
        for i in 0..max(lhs.len(), rhs.len()) {
            if !self.base_ring.eq_el(lhs.get(i).unwrap_or(&zero), rhs.get(i).unwrap_or(&zero)) {
                return false;
            }
        }
        return true;
    }

    fn is_commutative(&self) -> bool {
        self.base_ring.is_commutative()
    }

    fn is_noetherian(&self) -> bool {
        // by Hilbert's basis theorem
        self.base_ring.is_noetherian()
    }

    fn dbg(&self, val: &Self::Element, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
        // this is just for demonstration purposes - note that this prints zero coefficients
        for i in 0..(val.len() - 1) {
            write!(f, "{} * X^{} + ", self.base_ring.format(&val[i]), i)?;
        }
        write!(f, "{} * X^{}", self.base_ring.format(val.last().unwrap()), val.len() - 1)
    }

    fn characteristic<I>(&self, ZZ: &I) -> Option<El<I>>
        where I: IntegerRingStore, I::Type: IntegerRing
    {
        self.base_ring.get_ring().characteristic(ZZ)
    }
}

// in a real implementation, we definitely should implement also `feanor_math::rings::poly::PolyRing`, and
// possibly other traits (`CanHomFrom<other polynomial rings>`, `RingExtension`, `DivisibilityRing`, `EuclideanRing`)

let base = zn_static::F17;
// we do not use the `RingBase`-implementor directly, but wrap it in a `RingStore`;
// note that here, this is not strictly necessary, but still recommended
let ring = RingValue::from(MyPolyRing { base_ring: base });
let x = vec![0, 1];
let f = ring.add_ref(&x, &ring.int_hom().map(8));
let g = ring.add_ref(&x, &ring.int_hom().map(7));
let h = ring.add(ring.mul_ref(&x, &x), ring.add_ref(&ring.mul_ref(&x, &ring.int_hom().map(-2)), &ring.int_hom().map(5)));
assert_el_eq!(ring, h, ring.mul(f, g));

RingBaseRingStore

特质 RingBase 的设计是为了提供一个简单的方式来定义环结构。因此,它提供了一个具有所有环操作(如加法、乘法和等式测试)的基本接口。在许多情况下,定义了基本操作的变体以利用移动语义和内存重用。然而,它们通常具有默认实现,以简化创建新的环。

另一方面,RingStore 是任何可以访问底层 RingBase 对象的对象。这个特质提供了所有环操作的默认实现,这些实现只是将调用委托给底层 RingBase 对象。在正常情况下,这个特质不应为自定义类型实现。

当我们开始嵌套环时,这种分离的主要优势变得明显。比如说,我们有一个基于底层环类型的环类型,例如在基环 R 上的多项式环 PolyRing<R>。在这种情况下,PolyRing 实现 RingBase,但底层环 R 被限制为必须是 RingStore。因此,类型如 PolyRing<R>PolyRing<&&R>PolyRing<Box<R>> 都可以等价使用,这提供了很大的灵活性,但仍然可以与昂贵的克隆环和零大小环一起工作。

约定和最佳实践

  • 许多以环为参数的函数目前使用通用类型 RingStoreR 类型作为 &R。然而,这并不是最佳选择,相反,应该选择通过值传递环,即 R。如果需要在函数内部复制环,则完全允许要求 R: Copy + RingStore。因为对于 R: RingStore 同样有 &R: RingStore,这使得接口更加通用。
  • 封装基本环的环(如 MyRing<BaseRing: RingStore>)不应该实现 Copy,除非 BaseRing: CopyEl<BaseRing>: Copy 都实现。在某些情况下,我已经添加了 #[derive(Copy)](例如 feanor_math::rings::rational::RationalFieldBase),现在这可以阻止将基本环元素的成员添加到环中。特别是,这样做意味着如果只有基本环而没有其元素实现 Copy,则无法实现 Copy,从而破坏向后兼容性。在下一个重大版本更新中,这将进行更改。

性能

总的来说,我希望在这个库中将性能作为一个高优先级。然而,到目前为止,我没有时间彻底优化许多算法。

实现最佳性能的建议

  • 在项目的 Cargo.toml 中使用 lto = "fat"。这对于在库边界之间启用内联至关重要,如果广泛使用具有“简单”基本算术的环(如 zn_64::Znprimitive_int::StaticRing),则可以产生巨大影响。
  • 这个库的不同部分处于不同的优化阶段。虽然我在有限域和FFT算法上花了一些时间,但例如整数分解目前相对较慢。
  • 如果您广泛使用需要动态内存分配的环元素,请务必使用自定义分配器,例如来自 feanor-mempool 的一个。
  • 默认的任意精度整数算术目前较慢。如果您大量使用任意精度整数,请使用功能“mpir”以及 mpir 库的安装。

设计决策

在这里,我记录了一些更重要设计决策——主要为了我自己。

RingStore

已经讨论过这个问题了。主要,这是我在第一个版本的这个crate中因为尝试将 RingWrapper<Zn<IntRing>> 的元素映射到 RingWrapper<Zn<&IntRing>> 或类似结构时,产生的头痛问题的结果。

引用环的元素

看起来这是一个相当常见的需求,即环的元素可能包含对环的引用。例如,如果元素使用由环的内存池管理的内存,那么这种情况就会发生。换句话说,我们会定义 RingBase

trait RingBase {

    type Element<'a> where Self: 'a;

    ...
}

然而,这与另一个设计决策相冲突:我们希望能够嵌套环,并允许嵌套的环拥有所有权(而不仅仅是借用)。如果我们允许环引用元素,这现在将阻止我们定义存储嵌套环及其一些元素的环。例如,Z/qZ 的实现可能看起来像

struct Zn<I: IntegerRingStore> {
    integer_ring: I,
    modulus: El<I>
}

如果 El<I> 可能包含对 I 的引用,那么这个结构就是自引用的,会引起很多麻烦。

现在看来,禁止拥有嵌套环而不是环引用元素似乎更自然,但这将严重限制可以从函数返回的环。例如,我们可能希望一个函数能够生成具有基于内存池的大整数实现如 Fq 的函数

fn galois_field(q: i64, exponent: usize) -> RingExtension<ZnBarett<MempoolBigIntRing>> {
    ...
}

这将是不可能的,以及许多其他用例。另一方面,如果我们想要在静态生命周期分析的情况下执行运行时检查,那么在环引用元素的情况下,这会更简单。这可能稍微有些不自然,但非常实用。实际上,如果元素需要引用环,那么它们不会很小,并且算术成本将远远超过运行时管理成本。

依赖关系

~0.4–1.5MB
~31K SLoC