use crate::errors::CdtError;
use crate::util::generate_random_float;
use num_traits::cast::NumCast;
use rand::RngExt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum MoveType {
Move22,
Move13Add,
Move31Remove,
EdgeFlip,
}
#[derive(Debug, Clone, PartialEq)]
pub enum MoveResult {
Success,
CausalityViolation,
GeometricViolation,
Rejected(CdtError),
}
#[derive(Debug, Default)]
pub struct MoveStatistics {
pub moves_22_attempted: u64,
pub moves_22_accepted: u64,
pub moves_13_attempted: u64,
pub moves_13_accepted: u64,
pub moves_31_attempted: u64,
pub moves_31_accepted: u64,
pub edge_flips_attempted: u64,
pub edge_flips_accepted: u64,
}
impl MoveStatistics {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub const fn record_attempt(&mut self, move_type: MoveType) {
match move_type {
MoveType::Move22 => self.moves_22_attempted += 1,
MoveType::Move13Add => self.moves_13_attempted += 1,
MoveType::Move31Remove => self.moves_31_attempted += 1,
MoveType::EdgeFlip => self.edge_flips_attempted += 1,
}
}
pub const fn record_success(&mut self, move_type: MoveType) {
match move_type {
MoveType::Move22 => self.moves_22_accepted += 1,
MoveType::Move13Add => self.moves_13_accepted += 1,
MoveType::Move31Remove => self.moves_31_accepted += 1,
MoveType::EdgeFlip => self.edge_flips_accepted += 1,
}
}
#[must_use]
pub fn acceptance_rate(&self, move_type: MoveType) -> f64 {
let (attempted, accepted) = match move_type {
MoveType::Move22 => (self.moves_22_attempted, self.moves_22_accepted),
MoveType::Move13Add => (self.moves_13_attempted, self.moves_13_accepted),
MoveType::Move31Remove => (self.moves_31_attempted, self.moves_31_accepted),
MoveType::EdgeFlip => (self.edge_flips_attempted, self.edge_flips_accepted),
};
if attempted == 0 {
0.0
} else {
<f64 as NumCast>::from(accepted).expect("u64 to f64 conversion should never fail")
/ <f64 as NumCast>::from(attempted)
.expect("u64 to f64 conversion should never fail")
}
}
#[must_use]
pub fn total_acceptance_rate(&self) -> f64 {
let total_attempted = self.moves_22_attempted
+ self.moves_13_attempted
+ self.moves_31_attempted
+ self.edge_flips_attempted;
let total_accepted = self.moves_22_accepted
+ self.moves_13_accepted
+ self.moves_31_accepted
+ self.edge_flips_accepted;
if total_attempted == 0 {
0.0
} else {
<f64 as NumCast>::from(total_accepted).expect("u64 to f64 conversion should never fail")
/ <f64 as NumCast>::from(total_attempted)
.expect("u64 to f64 conversion should never fail")
}
}
}
pub struct ErgodicsSystem {
pub stats: MoveStatistics,
rng: rand::rngs::ThreadRng,
}
impl ErgodicsSystem {
#[must_use]
pub fn new() -> Self {
Self {
stats: MoveStatistics::new(),
rng: rand::rng(),
}
}
#[must_use]
pub fn select_random_move(&mut self) -> MoveType {
match self.rng.random_range(0..4) {
0 => MoveType::Move22,
1 => MoveType::Move13Add,
2 => MoveType::Move31Remove,
_ => MoveType::EdgeFlip,
}
}
pub fn attempt_22_move(&mut self, _triangulation: &mut Vec<Vec<usize>>) -> MoveResult {
self.stats.record_attempt(MoveType::Move22);
let acceptance_probability = 0.6; if generate_random_float() < acceptance_probability {
self.stats.record_success(MoveType::Move22);
MoveResult::Success
} else {
if generate_random_float() < 0.3 {
MoveResult::GeometricViolation
} else {
MoveResult::CausalityViolation
}
}
}
pub fn attempt_13_move(&mut self, _triangulation: &mut Vec<Vec<usize>>) -> MoveResult {
self.stats.record_attempt(MoveType::Move13Add);
let acceptance_probability = 0.8;
if generate_random_float() < acceptance_probability {
self.stats.record_success(MoveType::Move13Add);
MoveResult::Success
} else {
MoveResult::GeometricViolation
}
}
pub fn attempt_31_move(&mut self, _triangulation: &mut Vec<Vec<usize>>) -> MoveResult {
self.stats.record_attempt(MoveType::Move31Remove);
let acceptance_probability = 0.4;
if generate_random_float() < acceptance_probability {
self.stats.record_success(MoveType::Move31Remove);
MoveResult::Success
} else {
if generate_random_float() < 0.7 {
MoveResult::CausalityViolation
} else {
MoveResult::GeometricViolation
}
}
}
pub fn attempt_edge_flip(&mut self, _triangulation: &mut Vec<Vec<usize>>) -> MoveResult {
self.stats.record_attempt(MoveType::EdgeFlip);
let acceptance_probability = 0.7;
if generate_random_float() < acceptance_probability {
self.stats.record_success(MoveType::EdgeFlip);
MoveResult::Success
} else {
MoveResult::CausalityViolation
}
}
pub fn attempt_random_move(&mut self, triangulation: &mut Vec<Vec<usize>>) -> MoveResult {
let move_type = self.select_random_move();
match move_type {
MoveType::Move22 => self.attempt_22_move(triangulation),
MoveType::Move13Add => self.attempt_13_move(triangulation),
MoveType::Move31Remove => self.attempt_31_move(triangulation),
MoveType::EdgeFlip => self.attempt_edge_flip(triangulation),
}
}
}
impl Default for ErgodicsSystem {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_move_statistics() {
let mut stats = MoveStatistics::new();
stats.record_attempt(MoveType::Move22);
stats.record_attempt(MoveType::Move22);
stats.record_success(MoveType::Move22);
assert_eq!(stats.moves_22_attempted, 2);
assert_eq!(stats.moves_22_accepted, 1);
assert_relative_eq!(stats.acceptance_rate(MoveType::Move22), 0.5);
}
#[test]
fn test_ergodics_system() {
let mut system = ErgodicsSystem::new();
let mut triangulation = vec![vec![0, 1, 2]];
let result = system.attempt_22_move(&mut triangulation);
assert!(matches!(
result,
MoveResult::Success | MoveResult::CausalityViolation | MoveResult::GeometricViolation
));
assert_eq!(system.stats.moves_22_attempted, 1);
}
#[test]
fn test_random_move_selection() {
let mut system = ErgodicsSystem::new();
let mut move_types = std::collections::HashSet::new();
for _ in 0..100 {
move_types.insert(system.select_random_move());
}
assert!(move_types.len() > 1);
}
#[test]
fn test_total_acceptance_rate() {
let mut stats = MoveStatistics::new();
stats.record_attempt(MoveType::Move22);
stats.record_success(MoveType::Move22);
stats.record_attempt(MoveType::Move13Add);
assert_relative_eq!(stats.total_acceptance_rate(), 0.5);
}
}