GithubHelp home page GithubHelp logo

algebra's People

Contributors

brendanzab avatar fizyk20 avatar flaper87 avatar gitter-badger avatar omasanori avatar sebcrozet avatar wadelma avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

algebra's Issues

Wrapper struct for ops are annoying to use.

The solution we chose for #18 is to parametrize traits over the operator symbol (which is identified using structs). Thus we don't have MagmaAdditive nor MagmaMultiplicative traits. The down side is that we cannot use operator overloading without first wrapping our values into a struct so that we can "see" them as part of a Magma<Additive> or Magma<Multiplicative>.

Thus something like this, for elements of a field:

(a + b) * c

has to become something like that:

Wm((Wa(a) + Wa(b))) * Wm(c)

where Wm and Wa are wrappers to access the addition and multiplication operations.

There are very common places where wrapper structs could be avoided because we already know that, e.g., a type implements Magma<Additive> iff. it implements Add<...> because of default implementations like that one. What I propose here is, for example, to modify the ring trait from this:

pub trait RingApprox
    : GroupAbelianApprox<Additive>
    + MonoidApprox<Multiplicative>
{ }

to that:

pub trait RingApprox
    : GroupAbelianApprox<Additive>
    + MonoidApprox<Multiplicative>
    + Add<Self, Output = Self>  // Addition operator.
    + Sub<Self, Output = Self>  // Additive divisibility.
    + Neg<Output = Self>          // Additive inverse.
    + Mul<Self, Output = Self>  // Multiplication operator. 
{ }

Because RingApprox explicitly depends on the Additive and Multiplicative operators, it is fine to add the other operator-overloading-related constraints because we know they have been implemented as well because of our default implementations of the respective magmas. This trick may be applied to any trait that explicitly mention Additive and Multiplicative on their constraints.

Separate equality constraints from structure traits

I would like to try and separate the equality stuff from the structure traits. I'm thinking something like this:

pub trait MagmaAdditive
    : Add<Self, Self>
    + Eq
{}

would become:

pub trait MagmaAdditive
    : Add<Self, Self>
{}

pub trait MagmaAdditiveLaw
    : MagmaAdditive
    + Eq
{}

I believe the scalaz folks are doing something like this.

I think this is important to have because there are many situations in which a monoid can be defined but there is no decidable equality. It is more of an issue for non-numeric instances, e.g., a monoid of endomorphisms.

It's also an issue for containers which are clearly monoids (e.g., Vec<A>, DList<A>) but where the particular A does not have Eq.

Reorder trait names

darinmorrison: bjz: kind of bikeshedding but what do you think about naming things like QuasigroupAdditiveApprox vs ApproxAdditiveQuasigroup? I prefer the former since structure properties sort nicely (e.g., in docs). I find it's good for readability too since the property becomes more specific as you read it to the right.

Algebra and unsafe

If somebody wants to make use of algebraic properties in unsafe code, they cannot trust normal traits.
Should there be some kind of unsafe marker trait that is needed for unsafe to trust algebraic traits?
Something like: unsafe trait CorrectAlgebra {}

Abstract operators out of structural traits

There is a lot of code duplication for all the structural traits because all of them exist twice for addition and multiplication. The problem is also that one can not implement a magma, quasigroup, etc for any other operator than + or *. I would feel uncomfortable misusing these for, say, a wedge product. There are furthermore other built-in operators, for which it makes sense to implement these categories.

First I thought it might not be possible to do this to the type system, but then I tried and came up with a solution that allows us to actually be generic over the kind of operator. I haven't thought this through to the very end, but it looks like it works so far.

Closed operators

First of all, let's find an abstraction over closed operators.

/// A type closed under a binary operation
pub trait Closed<Op> {

    /// Apply the operator to two arguments
    fn op(&self, other: &Self) -> Self;
}

This trait provides a similar signature as the stdlib operators and it is generic over some type Op. Now what is Op? The simplest way to connect this trait to the language operators (or any other trait that qualifies) is to introduce a zero-sized struct for each and implement a concrete instance of this trait.

/// Addition
pub struct Addition;

impl<T> Closed<Addition> for T
    where T: Add<T, T>
{
    fn op(&self, other: &T) -> T {
        *self + *other
    }
}

This can be done analogously for all relevant operators and works nicely thanks to multidispatch. It can also be extended to arbitrary functions that can be used as an operator, e.g. a cross-product or a wedge product for differentials, etc.

The type parameter of the Closed trait can not be inferred, which makes for a bit of an ugly signature using this generically:

fn commute<T: Closed<Op>, Op>(a: &T, b: &T) -> T {
    b.op(a)
}

#[test]
fn int_ops_commute() {
    let (a, b) = (3i, 2i);
    assert_eq!(commute::<_, Addition>(&a, &b), a + b);
    assert_eq!(commute::<_, Multiplication>(&a, &b), a * b);
}

Note that one may define operators more generically in terms of right-hand side and result but I wanted to keep this example simple.

Algebraic abstractions

Now this abstraction provides some benefits. For once we only need one generic definition for a Magma now instead of two and it automatically extends to all closed operators.

/// A magma
pub trait Magma<Op>: Closed<Op> + Eq {}

impl<T, Op> Magma<Op> for T
    where T: Closed<Op> + Eq
{}

Another nice feature is that we can do stuff with more operators at the same time. Enter the quasigroup, which has two separate notions of division.

/// Left divisible
pub trait LeftDivisible<Op>: Closed<Op> {}

/// A quasigroup
pub trait QuasiGroup<Mag, LeftDiv, RightDiv>:
    Magma<Mag>
    + LeftDivisible<LeftDiv>
    + Closed<RightDiv>
{ /* properties */ }

Currently, we can not write the following code due to rust-lang/rust#18693, which is why the above workaround using the helper trait is necessary. However, it was indicated that the restrictions here should be lifted in the future.

/// A quasigroup
/// NOTE: this does not compile at the moment!
pub trait QuasiGroup<Mag, LeftDiv, RightDiv>:
    Magma<Mag>
    + Closed<LeftDiv>
    + Closed<RightDiv>
{ /* properties */ }

Algebraic structures should automatically be implemented for types satisfying compontent properties

The names of algebraic structures can sometimes be quite intimidating. The fundamental operations and their possible properties are less so. Perhaps one way of achieving this could be:

// Manually impled by client

trait Op<Rhs, Result> { fn op(&self, other: &Rhs) -> Result; } // Mirrors traits from std::ops
trait Associative: Op<Self, Self> { /* associative property */ }
trait Commutative: Op<Self, Self> { /* commutative property */ }
trait Identity: Op<Self, Self> { fn ident() -> Self;  /* preserves self property */ }

// Automatically impled for valid types

trait Magma: Op<Self, Self> {}
impl<T: Op<T, T>> Magma for T {}

trait Semigroup: Magma + Associative {}
impl<T: Magma + Associative> Semigroup for T {}

trait Monoid: Semigroup + Associative {}
impl<T: Semigroup + Associative> Monoid for T {}

Collaboration?

I started implementing a similar library awhile ago for monoids and semigroups here. The focus is a little different since I am primarily motivated by algebra for non-numeric types (at least for now) but the basic ideas are the same. The list of "Inspiring Libraries" on the README is also similar :)

It seems that we have similar goals so rather than duplicating a bunch of effort, perhaps there is some way we can join forces and build something better together? I'd be open to structural changes on my end as long as we could still fit non-numeric stuff into the scope of the library.

Thoughts?

Vector spaces

These can be done, as soon as there are fields. Should also enable nalgebra to begin to migrate to the abstractions used here.

cc @sebcrozet

Identity traits and RFC 1214

Both IdentityAdditive and IdentityMultiplicative have return type of Self without enforcing that Self: Sized.
This gives warning in 1.6.0 stable and errors with 1.7.0 nightlies.

Stable:

src/structure/ident.rs:44:1: 49:2 help: run `rustc --explain E0277` to see a detailed explanation
src/structure/ident.rs:44:1: 49:2 note: `Self` does not have a constant size known at compile-time
src/structure/ident.rs:44:1: 49:2 note: this warning results from recent bug fixes and clarifications; it will become a HARD ERROR in the next release. See RFC 1214 for details.

Nightly:

src/structure/ident.rs:44:1: 49:2 help: run `rustc --explain E0277` to see a detailed explanation
src/structure/ident.rs:44:1: 49:2 note: `Self` does not have a constant size known at compile-time
src/structure/ident.rs:44:1: 49:2 note: required by `core::ops::Mul`
src/structure/ident.rs:48:5: 48:23 error: the trait `core::marker::Sized` is not implemented for the type `Self` [E0277]

All operators should be parameterized for more general cases.

Current approach are not suitable for more general algebras:

  1. Lattice consists of two semigroups, their operators have very similiar properties to each other and should not be assumed as additive-like nor multiplicative-like.

  2. Tensor product of modules consists of several different product-like operators, so I think all operators should be parameterized rather than be concrete instances.

A better approach might be letting all operators be parameterized through generics.
For example:

pub trait Ring<Additive:Op, Multiplicative:Op>
    : RingApprox<Additive, Multiplicative>
    + GroupAbelian<Additive>
+ Monoid<Multiplicative>

instead of

pub trait Ring
    : RingApprox
    + GroupAbelian<Additive>
+ Monoid<Multiplicative>

Discuss practical usages of algebraic structures in real world code

At the end of the day, the intention of this library is to provide a common basis for generic programming in Rust. It would be good to experiment with real world use cases, and then use those to drive the design of the library. It would also allow us to convince people of the benefits of this approach. As asked by @nical on gitter:

I'm going to ask some rather silly questions, I am more used to focusing on maths to fix a specific problem (like building a graphics engine) rather than design abstractions around math concepts.
silly question 1) what are the common use cases for high level math abstractions like this ?
Or phrased differently, can we list (roughly) the motivations

At the end of the day, we want this to be of a benefit as opposed to a hindrance. I can certainly see that we could get side-tracked down the route of mathematical taxonomic insanity without considering whether this is actually useful and confusing to end users. Thoughts?

Wrapper structs to use a richer structure as a lesser one

Given e.g. a group, there are two sensible ways to view it as a magma. In situations like this, it is not possible to express this relation via trait inheritance without parametrizing over operator symbols. See the discussion in #18 for comparison. Wrapper structs essentially provide the same feature without parametrization.

They could be implemented like this:

struct GroupAsDivisiveMagma<G>(G);
struct GroupAsDivisiveMagmaByRef<'a, G>(&'a G);

Both these variants have their downsides. The first one allows one to easily implement the magma trait but as it consumes its value, it is not practical for non-copy types (think of the group formed by large invertible matrices with their multiplication).

The second one serves as a view of the group but it can not satisfy the function signature of the magma trait making it impossible to use with functionality implemented for magmas.

NOTE: the discussion to decide between this and #18 takes place at #18.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.