#![forbid(unsafe_code)]
use crate::cdt::action::ActionConfig;
use crate::cdt::ergodic_moves::MoveType;
use crate::cdt::results::Measurement;
use crate::config::validate_schedule;
use crate::errors::{CdtError, CdtResult, ConfigurationSetting};
use crate::geometry::CdtTriangulation2D;
use std::num::NonZeroU32;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SimplexCounts {
pub vertices: usize,
pub edges: usize,
pub triangles: usize,
}
pub const fn invalid_sim_config(
setting: ConfigurationSetting,
provided_value: String,
expected: String,
) -> CdtError {
CdtError::InvalidSimulationConfiguration {
setting,
provided_value,
expected,
}
}
pub fn validate_metropolis_schedule(
temperature: f64,
steps: u32,
thermalization_steps: u32,
measurement_frequency: u32,
) -> CdtResult<()> {
validate_schedule(
temperature,
steps,
thermalization_steps,
measurement_frequency,
invalid_sim_config,
)
}
pub fn validate_temperature(temperature: f64) -> CdtResult<()> {
if temperature.is_finite() && temperature > 0.0 {
Ok(())
} else {
Err(invalid_sim_config(
ConfigurationSetting::Temperature,
temperature.to_string(),
"finite and positive".to_string(),
))
}
}
pub fn simplex_counts(triangulation: &CdtTriangulation2D) -> SimplexCounts {
SimplexCounts {
vertices: triangulation.vertex_count(),
edges: triangulation.edge_count(),
triangles: triangulation.face_count(),
}
}
pub fn action_for(action_config: &ActionConfig, triangulation: &CdtTriangulation2D) -> f64 {
let counts = simplex_counts(triangulation);
action_config.calculate_action(counts.vertices, counts.edges, counts.triangles)
}
pub fn measurement_for(
step: u32,
action: f64,
triangulation: &CdtTriangulation2D,
) -> CdtResult<Measurement> {
let counts = triangulation.simplex_counts()?;
Measurement::try_from_simplex_counts(step, action, counts)?
.try_with_volume_profile(triangulation.volume_profile())
}
pub const fn measurement_is_due(
step: u32,
thermalization_steps: u32,
measurement_frequency: NonZeroU32,
) -> bool {
step >= thermalization_steps && step.is_multiple_of(measurement_frequency.get())
}
pub fn expected_measurement_count(
current_step: u32,
thermalization_steps: u32,
measurement_frequency: NonZeroU32,
) -> Option<usize> {
let first = first_measurement_step(thermalization_steps, measurement_frequency)?;
if first > current_step {
return Some(0);
}
let current_step = u64::from(current_step);
let first = u64::from(first);
let measurement_frequency = u64::from(measurement_frequency.get());
let count = (current_step - first) / measurement_frequency + 1;
usize::try_from(count).ok()
}
pub fn expected_measurement_step(
index: usize,
thermalization_steps: u32,
measurement_frequency: NonZeroU32,
) -> Option<u32> {
let first = first_measurement_step(thermalization_steps, measurement_frequency)?;
let offset = u32::try_from(index)
.ok()?
.checked_mul(measurement_frequency.get())?;
first.checked_add(offset)
}
fn first_measurement_step(
thermalization_steps: u32,
measurement_frequency: NonZeroU32,
) -> Option<u32> {
let measurement_frequency = u64::from(measurement_frequency.get());
let first =
u64::from(thermalization_steps).div_ceil(measurement_frequency) * measurement_frequency;
u32::try_from(first).ok()
}
pub fn proposed_delta_action(
action_config: &ActionConfig,
before: SimplexCounts,
move_type: MoveType,
) -> Option<f64> {
let after = match move_type {
MoveType::Move22 | MoveType::EdgeFlip => before,
MoveType::Move13Add => SimplexCounts {
vertices: before.vertices.checked_add(1)?,
edges: before.edges.checked_add(3)?,
triangles: before.triangles.checked_add(2)?,
},
MoveType::Move31Remove => SimplexCounts {
vertices: before.vertices.checked_sub(1)?,
edges: before.edges.checked_sub(3)?,
triangles: before.triangles.checked_sub(2)?,
},
};
let action_before =
action_config.calculate_action(before.vertices, before.edges, before.triangles);
let action_after = action_config.calculate_action(after.vertices, after.edges, after.triangles);
Some(action_after - action_before)
}
pub fn actions_match(left: f64, right: f64) -> bool {
if !(left.is_finite() && right.is_finite()) {
return false;
}
let scale = left.abs().max(right.abs()).max(1.0);
(left - right).abs() <= f64::EPSILON * scale * 8.0
}
#[cfg(test)]
mod tests {
use super::*;
fn frequency(value: u32) -> NonZeroU32 {
NonZeroU32::new(value).expect("test measurement frequency should be nonzero")
}
#[test]
fn measurement_count_ignores_steps_before_first_post_thermalization_cadence() {
assert_eq!(expected_measurement_count(1, 2, frequency(2)), Some(0));
assert_eq!(expected_measurement_count(2, 2, frequency(2)), Some(1));
assert_eq!(expected_measurement_count(4, 2, frequency(2)), Some(2));
}
#[test]
fn measurement_step_rounds_thermalization_up_to_cadence() {
assert_eq!(expected_measurement_step(0, 3, frequency(2)), Some(4));
assert_eq!(expected_measurement_step(1, 3, frequency(2)), Some(6));
}
#[test]
fn measurement_count_widens_before_including_current_step() {
let expected = usize::try_from(u64::from(u32::MAX) + 1).ok();
assert_eq!(
expected_measurement_count(u32::MAX, 0, frequency(1)),
expected
);
}
#[test]
fn actions_match_rejects_nonfinite_values() {
assert!(!actions_match(f64::NAN, 1.0));
assert!(!actions_match(1.0, f64::INFINITY));
}
}