1use molframe_core::{Code, Diagnostic};
4use std::fmt;
5
6#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
8#[non_exhaustive]
9pub enum SpatialBackend {
10 BruteForce,
12 CellList,
14 KdTree,
16 NeighborList,
18 Auto,
20}
21
22#[derive(Clone, Copy, PartialEq, Debug)]
24pub struct NeighborPair {
25 pub first: u32,
27 pub second: u32,
29 pub distance_squared: f32,
31}
32
33impl NeighborPair {
34 pub(crate) fn new(left: u32, right: u32, distance_squared: f32) -> Self {
35 Self {
36 first: left.min(right),
37 second: left.max(right),
38 distance_squared,
39 }
40 }
41}
42
43#[derive(Clone, Copy, PartialEq, Eq, Debug)]
45#[non_exhaustive]
46pub enum SpatialError {
47 Memory(molframe_core::MemoryBudgetError),
49 Cancelled,
51 InvalidCutoff,
53 AtomOutOfBounds(u32),
55 InvalidCell,
57 StaleNeighborList,
59 InvalidOption(SpatialOption),
61 PeriodicImageLimitExceeded {
63 required: usize,
65 maximum: usize,
67 },
68 NumericRangeExceeded,
70 WorkerPanicked,
72}
73
74#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
76#[non_exhaustive]
77pub enum SpatialOption {
78 BruteForcePairLimit,
80 KdTargetMinimum,
82 KdQueryRatio,
84 UnresolvedBackend,
86 NeighborSkinRatio,
88 NeighborSkinMinimum,
90 MaximumCellCount,
92 CellGrowthFactor,
94 KdPeriodicImageLimit,
96}
97
98impl SpatialError {
99 #[must_use]
101 pub fn into_diagnostic(self) -> Diagnostic {
102 match self {
103 Self::Memory(error) => {
104 Diagnostic::new(Code::E1901).with_context("execution_memory", error.to_string())
105 }
106 Self::Cancelled => {
107 Diagnostic::new(Code::E1904).with_context("spatial_operation", "cancelled")
108 }
109 Self::InvalidCutoff => Diagnostic::new(Code::E4002),
110 Self::AtomOutOfBounds(atom) => {
111 Diagnostic::new(Code::E6009).with_context("atom", atom.to_string())
112 }
113 Self::InvalidCell => Diagnostic::new(Code::E5004),
114 Self::StaleNeighborList => Diagnostic::new(Code::E9001),
115 Self::InvalidOption(option) => {
116 Diagnostic::new(Code::E4002).with_context("spatial_option", option.to_string())
117 }
118 Self::PeriodicImageLimitExceeded { required, maximum } => Diagnostic::new(Code::E4002)
119 .with_context("required_periodic_images", required.to_string())
120 .with_context("maximum_periodic_images", maximum.to_string()),
121 Self::NumericRangeExceeded => {
122 Diagnostic::new(Code::E4002).with_context("spatial_numeric_range", "exceeded")
123 }
124 Self::WorkerPanicked => {
125 Diagnostic::new(Code::E4002).with_context("spatial_worker", "panicked")
126 }
127 }
128 }
129}
130
131impl fmt::Display for SpatialError {
132 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133 match self {
134 Self::Memory(error) => error.fmt(f),
135 Self::Cancelled => f.write_str("spatial operation cancelled"),
136 Self::InvalidCutoff => f.write_str("cutoff must be finite and non-negative"),
137 Self::AtomOutOfBounds(atom) => write!(f, "atom {atom} is outside the coordinate array"),
138 Self::InvalidCell => f.write_str("unit cell is degenerate or non-finite"),
139 Self::StaleNeighborList => {
140 f.write_str("positions moved beyond the neighbour-list displacement skin")
141 }
142 Self::InvalidOption(option) => write!(f, "invalid spatial option: {option}"),
143 Self::PeriodicImageLimitExceeded { required, maximum } => write!(
144 f,
145 "periodic k-d query requires {required} images but the configured limit is {maximum}"
146 ),
147 Self::NumericRangeExceeded => {
148 f.write_str("a derived spatial value exceeds its representable numeric range")
149 }
150 Self::WorkerPanicked => f.write_str("a spatial worker thread panicked"),
151 }
152 }
153}
154
155impl std::error::Error for SpatialError {}
156
157impl fmt::Display for SpatialOption {
158 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
159 f.write_str(match self {
160 Self::BruteForcePairLimit => "brute_force_pair_limit",
161 Self::KdTargetMinimum => "kd_target_minimum",
162 Self::KdQueryRatio => "kd_query_ratio",
163 Self::UnresolvedBackend => "unresolved_backend",
164 Self::NeighborSkinRatio => "neighbor_skin.cutoff_ratio",
165 Self::NeighborSkinMinimum => "neighbor_skin.minimum",
166 Self::MaximumCellCount => "cell_grid.maximum_cell_count",
167 Self::CellGrowthFactor => "cell_grid.edge_growth_factor",
168 Self::KdPeriodicImageLimit => "kd_periodic.maximum_image_count",
169 })
170 }
171}
172
173impl From<molframe_core::MemoryBudgetError> for SpatialError {
174 fn from(error: molframe_core::MemoryBudgetError) -> Self {
175 Self::Memory(error)
176 }
177}
178
179#[cfg(test)]
180#[path = "types_tests.rs"]
181mod tests;