#testing #fundamental #general-purpose #decoupling

no-std math_adapter

数学适配器集合,使您的应用程序与数学库的实现解耦,并提供库间兼容性和低成本的可交换性

14 个版本

0.3.8 2022 年 7 月 19 日
0.3.6 2022 年 7 月 18 日
0.3.3 2022 年 6 月 13 日
0.3.1 2022 年 5 月 3 日
0.1.1 2022 年 4 月 29 日

#2484 in 算法

Download history 3382/week @ 2024-03-13 2559/week @ 2024-03-20 377/week @ 2024-03-27 577/week @ 2024-04-03 372/week @ 2024-04-10 233/week @ 2024-04-17 470/week @ 2024-04-24 646/week @ 2024-05-01 1174/week @ 2024-05-08 714/week @ 2024-05-15 315/week @ 2024-05-22 1031/week @ 2024-05-29 294/week @ 2024-06-05 184/week @ 2024-06-12 56/week @ 2024-06-19 79/week @ 2024-06-26

695 每月下载量
3 个包中使用(通过 wmath

MIT 许可证

125KB
3.5K SLoC

模块 :: math_adapter 实验性 rust-status docs.rs Open in Gitpod discord

数学适配器集合,使您的应用程序与数学库的实现解耦,并提供库间兼容性和低成本的可交换性。

从具体数学库的实现中解耦是有用的。这使得它们可以互换,并利用它们之间的互操作性。

选择后端

目前,您可以选择将 cgmathnalgebra 作为后端,并且这两个数学库都集成在包中。尽管如此,默认情况下,math_adapter 不包含任何一个,也不会通过不必要的依赖增加可执行文件的大小。要选择后端,请使用功能

  • cgmath - 使用 cgmath 作为后端
  • cgmath_ops - 使用 cgmath 作为后端并制作适配器,以便引用其函数和运算符
  • nalgebra - 使用 nalgebra 作为后端
  • nalgebra_ops - 使用 nalgebra 作为后端,并制作适配器以引用其函数和运算符

可以使用多个数学库,但应隐式使用其中的一个库的函数和运算符。即使没有选择 *_ops 功能,也可以显式使用所选数学库的函数和运算符。

为了进行显式转换,使用方法 clone_as_cgmath()clone_as_nalgebra()。要使用具有 *_ops 功能的特征进行显式转换,请使用 clone_as_foreign()。在这种情况下,解引用工作如同重新解释。适配器或外部的数学对象可以转换/重新解释为类似对象。

将来,每个后端都将驻留在独立的包中。

例如,要使用具有运算符及其函数的 nalgebra,包括 math_adapter,如下所示

math_adapter = { version = "*", features = [ "nalgebra_ops" ] }

数学对象

每个数学库定义了自己的数学对象版本。在大多数情况下,它们的布局相同,但即使如此,编译器也会将它们视为不同的对象。math_adapter 也提供了这样的数学对象,以及将或从外部类似对象转换/重新解释的手段。这些包括

  • 向量,它们被称为 X1< T >、X2< T >、X3< T >、X4< T > ... Xn< T >。
  • 矩阵,它们被称为 MatX1< T >、MatX2< T >、MatX3< T > ... MatXn< T >。
  • 四元数,它被称为 Quat< T >。
  • 欧拉角,它被称为 Euler< T >。
  • 分解变换,它被称为 TransformationDecomposed< T >

转换与重新解释

要应用来自另一个库的函数,您可以将数学对象转换为或重新解释。它们之间有什么区别?

重新解释发生在编译时。如果两个相似结构的布局(大小、对齐、填充和顺序)相同,则可以进行重新解释。例如,结构 cgmath::Vector2nalgebra::Vector2 是不同的结构,但它们具有完全相同的布局。这就是为什么可以将一个解释为另一个,反之亦然是安全的。重新解释告诉编译器使用一个数据结构,就像它是另一个一样。它没有运行时和编译时的成本。如 as_foreign()as_foreign_mut()as_cgmath()as_cgmath_mut()as_nalgebra()as_nalgebra_mut() 这样的方法重新解释数学对象。

转换是不同的。转换是指从另一个结构的组件中重新构建一个新的结构实例。它具有非零的运行时和编译时成本。尽管它经常被编译器优化为重新解释。使用转换而不是重新解释更安全。问题是重新解释基于对布局的几个假设,这些假设可能被数学库的作者或编译器的作者更改。在理论上!在实践中,这是不太可能的。甚至更有可能,大多数数学库都定义了具有 #[ repr( C ) ] 的对象,这 限制了此类结构的布局,并保护了它们免受未来更改的影响。

每个结构都可以转换为另一个语义上相似的具有不同布局的结构,但只有相同的布局时才能进行重新解释。因此,实现了几个特质。

  • *NominalInterface - 接口,暴露转换和访问元素的功能。
  • *BasicInterface - 接口,扩展 X2NominalInterface 并暴露创建此类新实例的功能。
  • *CanonicalInterface - 接口,扩展 X2BasicInterface 并暴露重新解释的功能。

关系:Canonical > Basic > Nominal

示例 :: 元素

向量的元素数量编码在类型名称中,长度为 X2 的向量,长度为 2X3 的向量,以此类推。每个结构都实现了构造函数 make()make_nan()make_default(),以构造该类型的新实例。要访问元素,请使用方法 x()y()z()_0()_1()_2()

#[ cfg( feature = "use_std" ) ]
{
  use math_adapter::prelude::*;
  use math_adapter::X2;

  // vector of length 2 and its elements
  let src1 = X2::make( 1, 3 );
  assert_eq!( src1.x(), 1 );
  assert_eq!( src1.y(), 3 );
  assert_eq!( src1._0(), 1 );
  assert_eq!( src1._1(), 3 );
}

示例 :: 运算符

选择一个功能 *_ops 以重用选择数学库的运算符和函数。

#[ cfg( feature = "use_std" ) ]
{
  use math_adapter::prelude::*;
  use math_adapter::X2;

  // if back-end math lib is chosen then operators and functions are available
  #[ cfg( feature = "cgmath_ops" ) ]
  {
    let src1 = X2::make( 1, 2 );
    let src2 = X2::make( 3, 4 );
    let got = src1 + src2;
    let exp = X2::make( 4, 6 );
    assert_eq!( got, exp );
    println!( "src1 + src2 : {:?}", got );
  }

  // enable feature *_ops to get access to functions
  #[ cfg( feature = "cgmath_ops" ) ]
  {
    let src = X2::make( 1, 2 );
    assert_eq!( src.sum(), 3 ); /* sum comes from `cgmath` */
    println!( "src.sum() : {:?}", src.sum() );
  }
}

示例 :: 互操作性

功能 *_ops 意味着请求使用选择数学库的运算符和函数。但不是选择单个后端数学库,您可以使用多个。使用方法 as_*_clone()as_*as_*_mut 将原始数学对象转换或重新解释为所选后端的对等物。您不必在每个调用中使用相同的后端,您可以针对特定调用选择数学库,并结合每个数学库的最佳之处。

#[ cfg( feature = "use_std" ) ]
{
  use math_adapter::prelude::*;
  use math_adapter::X2;

  // ! compile-time error, because if no `*_ops` feature was chosen
  // {
  //   let src = X2::make( 1, 3 ); /* make a canonical 2D vector */
  //   println!( "src.sum() : {:?}", src.sum() ); /* use `sum()` of chosen math lib back-end */
  // }

  // enable feature *_ops should be enabled to get access to functions
  #[ cfg( all( feature = "cgmath", feature = "nalgebra" ) ) ]
  {
    let src = X2::make( 1, 3 ); /* make a canonical 2D vector */
    println!( "src.as_cgmath().sum() : {:?}", src.as_cgmath().sum() ); /* use `sum()` of `cgmath` */
    println!( "src.as_nalgebra().sum() : {:?}", src.as_nalgebra().sum() ); /* use `sum()` of `nalgebra` */
  }

  // you can convert / reinterpret any vector.
  // for example you can create `cgmath::Vector2`, but apply a function of `nalgebra::Vector2`
  #[ cfg( all( feature = "cgmath", feature = "nalgebra" ) ) ]
  {
    let src = math_adapter::cgmath::Vector2::< i32 >::make( 1, 3 ); /* make a `cgmath` 2D vector */
    println!( "src.as_nalgebra().sum() : {:?}", src.as_nalgebra().sum() ); /* use `sum()` of `nalgebra` */
  }
}

添加到您的项目

cargo add math_adapter

从存储库尝试

git clone https://github.com/Wandalen/wMath
cd wMath
cd module/math_adapter/sample/rust/math_adapter_trivial_sample
cargo run

依赖关系

~3–22MB
~294K SLoC