fn permutations<T: Clone>(collection: &[T], group_size: usize) -> Vec<Vec<T>> {
let mut groupings: Vec<Vec<T>> = vec![];
for chunk in collection.chunks(group_size) {
if chunk.len() != group_size {
continue;
}
groupings.push(chunk.to_vec());
}
let mut reversed_collection = collection.to_vec();
reversed_collection.reverse();
for chunk in reversed_collection.chunks(group_size) {
if chunk.len() != group_size {
continue;
}
groupings.push(chunk.to_vec());
}
groupings
}
fn cayley_product<T: Copy>(collection: &Vec<T>) -> Vec<Vec<T>> {
let mut pairs: Vec<Vec<T>> = vec![];
for x in collection {
for y in collection {
pairs.push(vec![*x, *y]);
}
}
pairs
}
#[derive(Debug)]
pub enum PropertyError {
CommutativityError,
AssociativityError,
CancellativityError,
IdentityError,
InvertibilityError,
Other(String),
}
impl std::fmt::Display for PropertyError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
let msg = match self {
PropertyError::CommutativityError => "Operation is not commutative!",
PropertyError::AssociativityError => "Operation is not associative!",
PropertyError::CancellativityError => "Operation is not cancellative!",
PropertyError::IdentityError => "Operation has no valid identity!",
PropertyError::InvertibilityError => "Operation is not invertible!",
PropertyError::Other(error) => error,
};
write!(f, "{msg}")
}
}
pub enum PropertyType<'a, T> {
Commutative,
Abelian,
Associative,
Cancellative,
WithIdentity(T),
Invertible(T, &'a dyn Fn(T, T) -> T),
}
impl<'a, T: Copy + PartialEq> PropertyType<'a, T> {
pub fn holds_over(&self, op: &dyn Fn(T, T) -> T, domain_sample: &Vec<T>) -> bool {
match self {
Self::Commutative | Self::Abelian => Self::commutativity_holds_over(op, domain_sample),
Self::Associative => Self::associativity_holds_over(op, domain_sample),
Self::Cancellative => Self::cancellative_holds_over(op, domain_sample),
Self::WithIdentity(identity) => Self::identity_holds_over(op, domain_sample, *identity),
Self::Invertible(identity, inv) => {
Self::invertibility_holds_over(op, inv, domain_sample, *identity)
}
}
}
fn commutativity_holds_over(op: &dyn Fn(T, T) -> T, domain_sample: &Vec<T>) -> bool {
if domain_sample.len() < 2 {
return true;
}
return permutations(domain_sample, 2).iter().all(|pair| {
let left = (op)(pair[0], pair[1]);
let right = (op)(pair[1], pair[0]);
left == right
});
}
fn associativity_holds_over(op: &dyn Fn(T, T) -> T, domain_sample: &Vec<T>) -> bool {
if domain_sample.len() < 3 {
return true;
}
return permutations(domain_sample, 3).iter().all(|triple| {
let left_first = (op)((op)(triple[0], triple[1]), triple[2]);
let right_first = (op)(triple[0], (op)(triple[1], triple[2]));
left_first == right_first
});
}
fn identity_holds_over(op: &dyn Fn(T, T) -> T, domain_sample: &[T], identity: T) -> bool {
return domain_sample.iter().all(|e| {
let from_left = (op)(identity, *e);
let from_right = (op)(*e, identity);
(*e == from_left) && (*e == from_right)
});
}
fn cancellative_holds_over(op: &dyn Fn(T, T) -> T, domain_sample: &Vec<T>) -> bool {
if domain_sample.len() < 3 {
return true;
}
let left_cancellative = permutations(domain_sample, 3).iter().all(|triple| {
if (op)(triple[0], triple[1]) == (op)(triple[0], triple[2]) {
return triple[1] == triple[2];
}
true
});
let right_cancellative = permutations(domain_sample, 3).iter().all(|triple| {
if (op)(triple[1], triple[0]) == (op)(triple[2], triple[0]) {
return triple[1] == triple[2];
}
true
});
left_cancellative && right_cancellative
}
fn invertibility_holds_over(
op: &dyn Fn(T, T) -> T,
inv: &dyn Fn(T, T) -> T,
domain_sample: &Vec<T>,
identity: T,
) -> bool {
if domain_sample.len() < 2 {
return true;
}
return permutations(domain_sample, 2).iter().all(|pair| {
let inverse_works = (inv)(pair[0], pair[0]) == identity;
let left_composition_works = (inv)((op)(pair[0], pair[1]), pair[1]) == pair[0];
let right_composition_works = (inv)((op)(pair[1], pair[0]), pair[1]) == pair[0];
inverse_works && left_composition_works && right_composition_works
});
}
}
impl<'a, T> PartialEq for PropertyType<'a, T> {
fn eq(&self, other: &PropertyType<'a, T>) -> bool {
match self {
Self::Commutative | Self::Abelian => {
matches!(other, Self::Commutative) | matches!(other, Self::Abelian)
}
Self::Associative => matches!(other, Self::Associative),
Self::Cancellative => matches!(other, Self::Cancellative),
Self::WithIdentity(_) => matches!(other, Self::WithIdentity(_)),
Self::Invertible(_, _) => matches!(other, Self::Invertible(_, _)),
}
}
}
pub trait BinaryOperation<T: Copy + PartialEq> {
fn operation(&self) -> &dyn Fn(T, T) -> T;
fn properties(&self) -> Vec<PropertyType<'_, T>>;
fn is(&self, property: PropertyType<'_, T>) -> bool {
self.properties().contains(&property)
}
fn input_history(&self) -> &Vec<T>;
fn cache(&mut self, input: T);
fn with(&mut self, left: T, right: T) -> Result<T, PropertyError> {
self.cache(left);
self.cache(right);
for property in self.properties() {
if property.holds_over(self.operation(), self.input_history()) {
continue;
}
match property {
PropertyType::Commutative | PropertyType::Abelian => {
return Err(PropertyError::CommutativityError);
}
PropertyType::Associative => {
return Err(PropertyError::AssociativityError);
}
PropertyType::Cancellative => {
return Err(PropertyError::CancellativityError);
}
PropertyType::WithIdentity(_) => {
return Err(PropertyError::IdentityError);
}
PropertyType::Invertible(_, _) => {
return Err(PropertyError::InvertibilityError);
}
}
}
return Ok((self.operation())(left, right));
}
}
pub struct AbelianOperation<'a, T> {
op: &'a dyn Fn(T, T) -> T,
history: Vec<T>,
}
impl<'a, T> AbelianOperation<'a, T> {
pub fn new(op: &'a dyn Fn(T, T) -> T) -> Self {
Self {
op,
history: vec![],
}
}
}
impl<'a, T: Copy + PartialEq> BinaryOperation<T> for AbelianOperation<'a, T> {
fn operation(&self) -> &dyn Fn(T, T) -> T {
self.op
}
fn properties(&self) -> Vec<PropertyType<'_, T>> {
vec![PropertyType::Commutative, PropertyType::Abelian]
}
fn input_history(&self) -> &Vec<T> {
&self.history
}
fn cache(&mut self, input: T) {
self.history.push(input);
}
}
pub struct AssociativeOperation<'a, T> {
op: &'a dyn Fn(T, T) -> T,
history: Vec<T>,
}
impl<'a, T> AssociativeOperation<'a, T> {
pub fn new(op: &'a dyn Fn(T, T) -> T) -> Self {
Self {
op,
history: vec![],
}
}
}
impl<'a, T: Copy + PartialEq> BinaryOperation<T> for AssociativeOperation<'a, T> {
fn operation(&self) -> &dyn Fn(T, T) -> T {
self.op
}
fn properties(&self) -> Vec<PropertyType<'_, T>> {
vec![PropertyType::Associative]
}
fn input_history(&self) -> &Vec<T> {
&self.history
}
fn cache(&mut self, input: T) {
self.history.push(input);
}
}
pub struct CancellativeOperation<'a, T> {
op: &'a dyn Fn(T, T) -> T,
history: Vec<T>,
}
impl<'a, T> CancellativeOperation<'a, T> {
pub fn new(op: &'a dyn Fn(T, T) -> T) -> Self {
Self {
op,
history: vec![],
}
}
}
impl<'a, T: Copy + PartialEq> BinaryOperation<T> for CancellativeOperation<'a, T> {
fn operation(&self) -> &dyn Fn(T, T) -> T {
self.op
}
fn properties(&self) -> Vec<PropertyType<'_, T>> {
vec![PropertyType::Cancellative]
}
fn input_history(&self) -> &Vec<T> {
&self.history
}
fn cache(&mut self, input: T) {
self.history.push(input);
}
}
pub struct IdentityOperation<'a, T> {
op: &'a dyn Fn(T, T) -> T,
identity: T,
history: Vec<T>,
}
impl<'a, T> IdentityOperation<'a, T> {
pub fn new(op: &'a dyn Fn(T, T) -> T, identity: T) -> Self {
Self {
op,
identity,
history: vec![],
}
}
}
impl<'a, T: Copy + PartialEq> BinaryOperation<T> for IdentityOperation<'a, T> {
fn operation(&self) -> &dyn Fn(T, T) -> T {
self.op
}
fn properties(&self) -> Vec<PropertyType<'_, T>> {
vec![PropertyType::WithIdentity(self.identity)]
}
fn input_history(&self) -> &Vec<T> {
&self.history
}
fn cache(&mut self, input: T) {
self.history.push(input);
}
}
pub struct MonoidOperation<'a, T> {
op: &'a dyn Fn(T, T) -> T,
identity: T,
history: Vec<T>,
}
impl<'a, T> MonoidOperation<'a, T> {
pub fn new(op: &'a dyn Fn(T, T) -> T, identity: T) -> Self {
Self {
op,
identity,
history: vec![],
}
}
}
impl<'a, T: Copy + PartialEq> BinaryOperation<T> for MonoidOperation<'a, T> {
fn operation(&self) -> &dyn Fn(T, T) -> T {
self.op
}
fn properties(&self) -> Vec<PropertyType<'_, T>> {
vec![
PropertyType::Associative,
PropertyType::WithIdentity(self.identity),
]
}
fn input_history(&self) -> &Vec<T> {
&self.history
}
fn cache(&mut self, input: T) {
self.history.push(input);
}
}
pub struct LoopOperation<'a, T> {
op: &'a dyn Fn(T, T) -> T,
identity: T,
history: Vec<T>,
}
impl<'a, T> LoopOperation<'a, T> {
pub fn new(op: &'a dyn Fn(T, T) -> T, identity: T) -> Self {
Self {
op,
identity,
history: vec![],
}
}
}
impl<'a, T: Copy + PartialEq> BinaryOperation<T> for LoopOperation<'a, T> {
fn operation(&self) -> &dyn Fn(T, T) -> T {
self.op
}
fn properties(&self) -> Vec<PropertyType<'_, T>> {
vec![
PropertyType::Cancellative,
PropertyType::WithIdentity(self.identity),
]
}
fn input_history(&self) -> &Vec<T> {
&self.history
}
fn cache(&mut self, input: T) {
self.history.push(input);
}
}
pub struct InvertibleOperation<'a, T> {
op: &'a dyn Fn(T, T) -> T,
inv: &'a dyn Fn(T, T) -> T,
identity: T,
history: Vec<T>,
}
impl<'a, T> InvertibleOperation<'a, T> {
pub fn new(op: &'a dyn Fn(T, T) -> T, inv: &'a dyn Fn(T, T) -> T, identity: T) -> Self {
Self {
op,
inv,
identity,
history: vec![],
}
}
}
impl<'a, T: Copy + PartialEq> BinaryOperation<T> for InvertibleOperation<'a, T> {
fn operation(&self) -> &dyn Fn(T, T) -> T {
self.op
}
fn properties(&self) -> Vec<PropertyType<'_, T>> {
vec![
PropertyType::WithIdentity(self.identity),
PropertyType::Invertible(self.identity, self.inv),
]
}
fn input_history(&self) -> &Vec<T> {
&self.history
}
fn cache(&mut self, input: T) {
self.history.push(input);
}
}
pub struct GroupOperation<'a, T> {
op: &'a dyn Fn(T, T) -> T,
inv: &'a dyn Fn(T, T) -> T,
identity: T,
history: Vec<T>,
}
impl<'a, T> GroupOperation<'a, T> {
pub fn new(op: &'a dyn Fn(T, T) -> T, inv: &'a dyn Fn(T, T) -> T, identity: T) -> Self {
Self {
op,
inv,
identity,
history: vec![],
}
}
}
impl<'a, T: Copy + PartialEq> BinaryOperation<T> for GroupOperation<'a, T> {
fn operation(&self) -> &dyn Fn(T, T) -> T {
self.op
}
fn properties(&self) -> Vec<PropertyType<'_, T>> {
vec![
PropertyType::Associative,
PropertyType::WithIdentity(self.identity),
PropertyType::Invertible(self.identity, self.inv),
]
}
fn input_history(&self) -> &Vec<T> {
&self.history
}
fn cache(&mut self, input: T) {
self.history.push(input);
}
}
pub fn binop_is_invertible<T: Copy + PartialEq>(binop: &dyn BinaryOperation<T>) -> bool {
for property in binop.properties() {
if let PropertyType::Invertible(_, _) = property {
return true;
}
}
false
}
pub fn binop_has_invertible_identity<T: Copy + PartialEq>(
binop: &dyn BinaryOperation<T>,
identity: T,
) -> bool {
assert!(binop_is_invertible(binop));
for property in binop.properties() {
if let PropertyType::Invertible(binop_identity, _) = property {
return binop_identity == identity;
}
}
false
}
#[cfg(test)]
mod tests {
use super::{cayley_product, permutations};
#[test]
fn pair_permutations() {
let v = &[1, 2, 3];
let pairs = permutations(v, 2);
assert!(pairs.contains(&vec![1, 2]));
assert!(pairs.contains(&vec![3, 2]));
}
#[test]
fn cayley_product_works() {
let v = vec![1, 2, 3];
let product = cayley_product(&v);
assert!(
product
== vec![
vec![1, 1],
vec![1, 2],
vec![1, 3],
vec![2, 1],
vec![2, 2],
vec![2, 3],
vec![3, 1],
vec![3, 2],
vec![3, 3]
]
);
}
}