un_algebra 0.9.0

Simple implementations of selected abstract algebraic structures--including groups, rings, and fields. Intended for self-study of abstract algebra concepts and not for production use.
//
// # Example: The _complex_ _numbers_ ℚ.
//
// The complex numbers (ℚ) form a _field_. Rust does not have a
// complex number type in the standard library so the complex number
// type used here is the `Complex<T>` type from the very handy `num`
// crate.
//
use ::num::complex;
use un_algebra::tests::*;
use un_algebra::prelude::*;


//
// We use a newtype wrapper around the `num` crate `Complex<T>` type to
// work within Rust's _trait_ _coherence_ rules. To keep this example
// simple-ish we limit real and imaginary components to `f64` values.
//
#[derive(Copy, Clone, Debug, PartialEq)]
struct Complex(complex::Complex<f64>);


//
// Create a Complex instance from real and imaginary components.
//
impl Complex {
  pub fn new(re: f64, im: f64) -> Self {
    Self(complex::Complex::new(re, im))
  }
}


//
// Numeric equality (with f64 error values) for complex numbers.
//
impl NumEq for Complex {

  type Eps = f64;

  fn num_eq(&self, other: &Self, eps: &Self::Eps) -> bool {
    let re = self.0.re.num_eq(&other.0.re, eps);
    let im = self.0.im.num_eq(&other.0.im, eps);

    re && im
  }
}


//
// Complex numbers form an additive magma with (complex) addition as the
// operation.
//
impl AddMagma for Complex {

  fn add(&self, other: &Self) -> Self {
    Self(self.0 + other.0)
  }
}


//
// Complex numbers form an additive semigroup as (complex) addition is
// associative.
//
impl AddSemigroup for Complex {}


//
// Complex numbers form an additive monoid with (complex) zero as the
// additive identity.
//
impl AddMonoid for Complex {

  fn zero() -> Self {
    Self::new(0.0, 0.0)
  }
}


//
// Complex numbers form an additive group with (complex) negation as the
// group inverse.
//
impl AddGroup for Complex {

  fn negate(&self) -> Self {
    Self(-self.0)
  }
}


//
// Complex numbers form an additive commutative group as (complex)
// addition is commutative.
//
impl AddComGroup for Complex {}


//
// Complex numbers form a multiplicative magma with (complex)
// multiplication as the operation.
//
impl MulMagma for Complex {

  fn mul(&self, other: &Self) -> Self {
    Self(self.0 * other.0)
  }
}


//
// Complex numbers form a multiplicative semigroup as (complex)
// multiplication is associative.
//
impl MulSemigroup for Complex {}


//
// Complex numbers form a multiplicative monoid with (complex) one as
// the multiplicative identity.
//
impl MulMonoid for Complex {

  fn one() -> Self {
    Self::new(1.0, 0.0)
  }
}


//
// Complex numbers form a multiplicative group with inverse of non-zero
// (complex) values as the group inverse.
//
impl MulGroup for Complex {

  fn is_invertible(&self) -> bool {
    !self.is_zero()
  }


  fn invert(&self) -> Self {
    Self(self.0.inv())
  }
}


//
// Complex numbers form a multiplicative commutative group as (complex)
// multiplication is commutative.
//
impl MulComGroup for Complex {}


//
// Complex numbers form a ring.
//
impl Ring for Complex {}


//
// Complex numbers form a commutative ring.
//
impl ComRing for Complex {}


//
// Complex numbers (without zero) form a field with inverse of non-zero
// (complex) values as the field inverse.
//
impl Field for Complex {

  fn invert(&self) -> Self {
    Self(self.0.inv())
  }
}


//
// Generate `proptest` arbitrary (i.e. boxed strategy) Complex values
// from f64 real and imaginary components. Short function name to keep
// generator expressions manageable.
//
fn cx() -> impl Strategy<Value = Complex> {
  let ranges = (-1.0e10_f64..1.0e10, -1.0e10_f64..1.0e10);

  ranges.prop_map(|(re, im)| Complex::new(re, im))
}


#[cfg(test)]
proptest! {
  #![proptest_config(config::standard())]


  #[test]
  fn add_closure([w, z] in [cx(), cx()]) {
    prop_assert!(NumAddMagmaLaws::num_closure(&w, &z, &F64_EPS))
  }


  #[test]
  fn mul_closure([w, z] in [cx(), cx()]) {
    prop_assert!(NumMulMagmaLaws::num_closure(&w, &z, &F64_EPS))
  }


  #[test]
  fn mul_associative([v, w, z] in [cx(), cx(), cx()]) {
    // Larger error term due to cancellation issues.
    prop_assert!(NumMulSemigroupLaws::num_associativity(&v, &w, &z, &(F64_EPS * 1e5)))
  }


  #[test]
  fn add_associative([v, w, z] in [cx(), cx(), cx()]) {
    // Larger error term due to cancellation issues.
    prop_assert!(NumAddSemigroupLaws::num_associativity(&v, &w, &z, &(F64_EPS * 1e5)))
  }


  #[test]
  fn left_add_identity(z in cx()) {
    prop_assert!(NumAddMonoidLaws::num_left_identity(&z, &F64_EPS))
  }


  #[test]
  fn right_add_identity(z in cx()) {
    prop_assert!(NumAddMonoidLaws::num_left_identity(&z, &F64_EPS))
  }


  #[test]
  fn left_mul_identity(z in cx()) {
    prop_assert!(NumMulMonoidLaws::num_left_identity(&z, &F64_EPS))
  }


  #[test]
  fn right_mul_identity(z in cx()) {
    prop_assert!(NumMulMonoidLaws::num_right_identity(&z, &F64_EPS))
  }


  #[test]
  fn left_add_inverse(z in cx()) {
    prop_assert!(NumAddGroupLaws::num_left_inverse(&z, &F64_EPS))
  }


  #[test]
  fn right_add_inverse(z in cx()) {
    prop_assert!(NumAddGroupLaws::num_right_inverse(&z, &F64_EPS))
  }


  #[test]
  fn left_mul_inverse(z in cx()) {
    prop_assume!(MulGroup::is_invertible(&z));

    // Larger error term due to cancellation issues
    prop_assert!(NumMulGroupLaws::num_left_inverse(&z, &(F64_EPS * 1e3)))
  }


  #[test]
  fn right_inverse(z in cx()) {
    prop_assume!(MulGroup::is_invertible(&z));

    prop_assert!(NumMulGroupLaws::num_right_inverse(&z, &F64_EPS))
  }


  #[test]
  fn add_commute([w, z] in [cx(), cx()]) {
    prop_assert!(NumAddComGroupLaws::num_commutivity(&w, &z, &F64_EPS))
  }


  #[test]
  fn mul_commute([w, z] in [cx(), cx()]) {
    prop_assert!(NumMulComGroupLaws::num_commutivity(&w, &z, &F64_EPS))
  }


  #[test]
  fn left_distributivity([u, v, w] in [cx(), cx(), cx()]) {
    // Larger error term due to cancellation issues
    prop_assert!(NumRingLaws::num_left_distributivity(&u, &v, &w, &(F64_EPS * 1e5)))
  }


  #[test]
  fn right_distributivity([u, v, w] in [cx(), cx(), cx()]) {
    // Larger error term due to cancellation issues
    prop_assert!(NumRingLaws::num_right_distributivity(&u, &v, &w, &(F64_EPS * 1e5)))
  }


  #[test]
  fn left_absorption(x in cx()) {
    prop_assert!(NumRingLaws::num_left_absorption(&x, &F64_EPS))
  }


  #[test]
  fn right_absorption(x in cx()) {
    prop_assert!(NumRingLaws::num_right_absorption(&x, &F64_EPS))
  }


  #[test]
  fn left_negation((w, z) in (cx(), cx())) {
    prop_assert!(NumRingLaws::num_left_negation(&w, &z, &F64_EPS))
  }


  #[test]
  fn right_negation((w, z) in (cx(), cx())) {
    prop_assert!(NumRingLaws::num_right_negation(&w, &z, &F64_EPS))
  }


  #[test]
  fn mul_commutivity((x, y) in (cx(), cx())) {
    prop_assert!(NumComRingLaws::num_commutivity(&x, &y, &F64_EPS))
  }


  #[test]
  fn field_left_inverse(z in cx()) {
    prop_assume!(Field::is_invertible(&z));

    prop_assert!(NumFieldLaws::num_left_inverse(&z, &F64_EPS))
  }


  #[test]
  fn field_right_inverse(z in cx()) {
    prop_assume!(Field::is_invertible(&z));

    prop_assert!(NumFieldLaws::num_right_inverse(&z, &F64_EPS))
  }


  #[test]
  fn zero_cancellation((w, z) in (cx(), cx())) {
    prop_assert!(NumFieldLaws::num_zero_cancellation(&w, &z, &F64_EPS))
  }


  #[test]
  fn add_cancellation((v, w, z) in (cx(), cx(), cx())) {
    prop_assert!(NumFieldLaws::num_add_cancellation(&v, &w, &z, &F64_EPS))
  }


  #[test]
  fn mul_cancellation((v, w, z) in (cx(), cx(), cx())) {
    prop_assert!(NumFieldLaws::num_mul_cancellation(&v, &w, &z, &F64_EPS))
  }
}


fn main() {
}