Module easy_ml::using_custom_types
source · Expand description
Using custom numeric types examples.
§Using a custom numeric type
The following example shows how to make a type defined outside this crate implement Numeric so it can be used by this library for matrix and tensor operations.
In this case the type is BigInt
from num_bigint
but as the type is also outside this crate we need to wrap it in another struct first so we can
implement traits on it due to Rust’s orphan rules.
extern crate easy_ml;
extern crate num_bigint;
extern crate num_traits;
use std::ops::{Add, Sub, Mul, Div, Neg};
use std::iter::Sum;
use easy_ml::numeric::{ZeroOne, FromUsize};
use easy_ml::matrices::Matrix;
use num_bigint::{BigInt, ToBigInt, Sign};
use num_traits::{Zero, One};
#[derive(Clone, Eq, PartialEq, PartialOrd, Debug)]
struct BigIntWrapper(BigInt);
/*
* First we define a utility function to obtain the &BigInt
* from a &BigIntWrapper as we don't want to have to clone
* the BigInt when we can avoid it.
*/
impl BigIntWrapper {
/**
* A utility function to get the &BigInt from a &BigIntWrapper
*/
fn unwrap(&self) -> &BigInt {
let BigIntWrapper(ref x) = *self;
x
}
}
/*
* Then implement the two traits defined in easy_ml onto the wrapper.
*/
impl ZeroOne for BigIntWrapper {
#[inline]
fn zero() -> BigIntWrapper {
BigIntWrapper(Zero::zero())
}
#[inline]
fn one() -> BigIntWrapper {
BigIntWrapper(One::one())
}
}
impl FromUsize for BigIntWrapper {
#[inline]
fn from_usize(n: usize) -> Option<BigIntWrapper> {
let bigint = ToBigInt::to_bigint(&n)?;
Some(BigIntWrapper(bigint))
}
}
/*
* If we weren't wrapping BigInt and could have defined `ZeroOne`
* and `FromUsize` directly on it we would stop here. As we have
* to wrap it, all that's left is a lot of boilerplate to
* redefine most of the traits BigInt already implements on our
* wrapper.
*/
// Define 4 macros to implement the operations Add, Sub, Mul and Div for
// all 4 combinations of by value and by reference BigIntWrappers
// BigIntWrapper op BigIntWrapper
macro_rules! operation_impl_value_value {
(impl $Trait:ident for BigIntWrapper { fn $method:ident }) => {
impl $Trait<BigIntWrapper> for BigIntWrapper {
type Output = BigIntWrapper;
#[inline]
fn $method(self, rhs: BigIntWrapper) -> Self::Output {
// unwrap, do operation unwrapped, then wrap output
BigIntWrapper((self.0).$method(rhs.0))
}
}
}
}
// BigIntWrapper op &BigIntWrapper
macro_rules! operation_impl_value_reference {
(impl $Trait:ident for BigIntWrapper { fn $method:ident }) => {
impl <'a> $Trait<&'a BigIntWrapper> for BigIntWrapper {
type Output = BigIntWrapper;
#[inline]
fn $method(self, rhs: &BigIntWrapper) -> Self::Output {
// unwrap, do operation unwrapped, then wrap output
BigIntWrapper((self.0).$method(rhs.unwrap()))
}
}
}
}
// &BigIntWrapper op BigIntWrapper
macro_rules! operation_impl_reference_value {
(impl $Trait:ident for BigIntWrapper { fn $method:ident }) => {
impl <'a> $Trait<BigIntWrapper> for &'a BigIntWrapper {
type Output = BigIntWrapper;
#[inline]
fn $method(self, rhs: BigIntWrapper) -> Self::Output {
// unwrap, do operation unwrapped, then wrap output
BigIntWrapper(self.unwrap().$method(rhs.0))
}
}
}
}
// &BigIntWrapper op &BigIntWrapper
macro_rules! operation_impl_reference_reference {
(impl $Trait:ident for BigIntWrapper { fn $method:ident }) => {
impl <'a, 'b> $Trait<&'a BigIntWrapper> for &'b BigIntWrapper {
type Output = BigIntWrapper;
#[inline]
fn $method(self, rhs: &BigIntWrapper) -> Self::Output {
// unwrap, do operation unwrapped, then wrap output
BigIntWrapper(self.unwrap().$method(rhs.unwrap()))
}
}
}
}
// Now we can implement these operations for Add, Sub, Mul and Div in one go
// instead of writing them out 4 times each.
operation_impl_value_value! { impl Add for BigIntWrapper { fn add } }
operation_impl_value_value! { impl Sub for BigIntWrapper { fn sub } }
operation_impl_value_value! { impl Mul for BigIntWrapper { fn mul } }
operation_impl_value_value! { impl Div for BigIntWrapper { fn div } }
operation_impl_value_reference! { impl Add for BigIntWrapper { fn add } }
operation_impl_value_reference! { impl Sub for BigIntWrapper { fn sub } }
operation_impl_value_reference! { impl Mul for BigIntWrapper { fn mul } }
operation_impl_value_reference! { impl Div for BigIntWrapper { fn div } }
operation_impl_reference_value! { impl Add for BigIntWrapper { fn add } }
operation_impl_reference_value! { impl Sub for BigIntWrapper { fn sub } }
operation_impl_reference_value! { impl Mul for BigIntWrapper { fn mul } }
operation_impl_reference_value! { impl Div for BigIntWrapper { fn div } }
operation_impl_reference_reference! { impl Add for BigIntWrapper { fn add } }
operation_impl_reference_reference! { impl Sub for BigIntWrapper { fn sub } }
operation_impl_reference_reference! { impl Mul for BigIntWrapper { fn mul } }
operation_impl_reference_reference! { impl Div for BigIntWrapper { fn div } }
/*
* Because Neg is a unary operation there are only two combinations
* so the implementations are written out longhand for ease of understanding.
*/
// - BigIntWrapper
impl Neg for BigIntWrapper {
type Output = BigIntWrapper;
#[inline]
fn neg(self) -> Self::Output {
// unwrap, do operation unwrapped, then wrap output
BigIntWrapper(-(self.0))
}
}
// - &BigIntWrapper
// Like with the macro'd versions for Add, Sub, Mul and Div, we need to define
// the Neg trait on a reference to a BigIntWrapper, which is itself a type.
impl <'a> Neg for &'a BigIntWrapper {
type Output = BigIntWrapper;
#[inline]
fn neg(self) -> Self::Output {
// unwrap, do operation unwrapped, then wrap output
BigIntWrapper(-(self.unwrap()))
}
}
// Finally we need to implement Sum on just types of BigIntWrapper
// to complete the steps to getting Numeric implemented on BigIntWrapper
impl Sum<BigIntWrapper> for BigIntWrapper {
fn sum<I>(iter: I) -> BigIntWrapper
where I: Iterator<Item = BigIntWrapper> {
// unwrap every item in the iterator, do sum unwrapped, then wrap output
BigIntWrapper(iter.map(|wrapped| wrapped.0).sum())
}
}
// For convenience and demonstrations below ToString is also implemented on BigIntWrapper
impl ToString for BigIntWrapper {
#[inline]
fn to_string(&self) -> String {
self.unwrap().to_string()
}
}
let one_million = ToBigInt::to_bigint(&1000000).unwrap();
let wrapped = BigIntWrapper(one_million);
let matrix = Matrix::from_scalar(wrapped);
println!("1000000 x 1000000 = {:?}", (&matrix * &matrix).get_reference(0, 0).to_string());
// Wrapping and unwrapping transformations can be done with map
let unwrapped: Matrix<BigInt> = matrix.map(|wrapped| wrapped.0);
println!("Unwrapped:\n{:?}", unwrapped);
let matrix: Matrix<BigInt> = Matrix::from_scalar(ToBigInt::to_bigint(&-3).unwrap());
let wrapped: Matrix<BigIntWrapper> = matrix.map(|unwrapped| BigIntWrapper(unwrapped));
println!("Wrapped:\n{:?}", wrapped);
// For completeness conversions between our wrapper type and BigInt are included
// so they can be converted with .into()
impl From<BigInt> for BigIntWrapper {
#[inline]
fn from(number: BigInt) -> Self {
BigIntWrapper(number)
}
}
impl From<BigIntWrapper> for BigInt {
#[inline]
fn from(number: BigIntWrapper) -> Self {
number.0
}
}