#![forbid(unsafe_code)]
use thiserror::Error;
#[derive(Clone, Debug, Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum BistellarFlipKindError {
#[error("k must be in 1..=D+1 (k={k_move}, D={dimension})")]
MoveSizeOutOfRange {
k_move: usize,
dimension: usize,
},
#[error("inverse move size D+2-k is not representable (k={k_move}, D={dimension})")]
InverseMoveSizeOverflow {
k_move: usize,
dimension: usize,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BistellarFlipKind {
k: usize,
d: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FlipDirection {
Forward,
Inverse,
}
impl FlipDirection {
#[must_use]
pub const fn inverse(self) -> Self {
match self {
Self::Forward => Self::Inverse,
Self::Inverse => Self::Forward,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum FlipOrientationCheckStage {
BeforeMutation,
AfterTrialMutation,
}
impl BistellarFlipKind {
#[must_use]
pub const fn k(&self) -> usize {
self.k
}
#[must_use]
pub const fn d(&self) -> usize {
self.d
}
pub const fn try_k1(d: usize) -> Result<Self, BistellarFlipKindError> {
Self::try_from_raw(1, d)
}
pub const fn try_k2(d: usize) -> Result<Self, BistellarFlipKindError> {
Self::try_from_raw(2, d)
}
pub const fn try_k3(d: usize) -> Result<Self, BistellarFlipKindError> {
Self::try_from_raw(3, d)
}
const fn try_from_raw(k_move: usize, d: usize) -> Result<Self, BistellarFlipKindError> {
if k_move == 0 || k_move > d.saturating_add(1) {
return Err(BistellarFlipKindError::MoveSizeOutOfRange {
k_move,
dimension: d,
});
}
if k_move == 1 && d == usize::MAX {
return Err(BistellarFlipKindError::InverseMoveSizeOverflow {
k_move,
dimension: d,
});
}
Ok(Self { k: k_move, d })
}
#[must_use]
pub(super) const fn from_validated(k_move: usize, d: usize) -> Self {
Self { k: k_move, d }
}
#[must_use]
pub const fn inverse(self) -> Self {
let k = if self.k <= 2 {
self.d + (2 - self.k)
} else {
self.d - (self.k - 2)
};
Self { k, d: self.d }
}
}
#[derive(Debug, Clone, Copy)]
pub struct ConstK<const K: usize>;
pub trait BistellarMove<const D: usize> {
const K: usize;
}
impl<const D: usize, const K: usize> BistellarMove<D> for ConstK<K> {
const K: usize = K;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn constructors_reject_invalid_move_metadata() {
assert_eq!(
BistellarFlipKind::try_k2(0),
Err(BistellarFlipKindError::MoveSizeOutOfRange {
k_move: 2,
dimension: 0,
})
);
assert_eq!(
BistellarFlipKind::try_k3(1),
Err(BistellarFlipKindError::MoveSizeOutOfRange {
k_move: 3,
dimension: 1,
})
);
assert_eq!(
BistellarFlipKind::try_k1(usize::MAX),
Err(BistellarFlipKindError::InverseMoveSizeOverflow {
k_move: 1,
dimension: usize::MAX,
})
);
}
#[test]
fn inverse_preserves_valid_formula_and_roundtrips() {
for kind in [
BistellarFlipKind::try_k1(3).unwrap(),
BistellarFlipKind::try_k2(3).unwrap(),
BistellarFlipKind::try_k3(3).unwrap(),
BistellarFlipKind::try_k2(usize::MAX).unwrap(),
] {
assert_eq!(kind.inverse().inverse(), kind);
}
assert_eq!(BistellarFlipKind::try_k2(3).unwrap().inverse().k(), 3);
}
}