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::E7001).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 From<SpatialError> for Diagnostic {
132 fn from(error: SpatialError) -> Self {
133 error.into_diagnostic().with_message(error.to_string())
134 }
135}
136
137impl From<&SpatialError> for Diagnostic {
138 fn from(error: &SpatialError) -> Self {
139 Self::from(*error)
140 }
141}
142
143impl fmt::Display for SpatialError {
144 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
145 match self {
146 Self::Memory(error) => error.fmt(f),
147 Self::Cancelled => f.write_str("spatial operation cancelled"),
148 Self::InvalidCutoff => f.write_str("cutoff must be finite and non-negative"),
149 Self::AtomOutOfBounds(atom) => write!(f, "atom {atom} is outside the coordinate array"),
150 Self::InvalidCell => f.write_str("unit cell is degenerate or non-finite"),
151 Self::StaleNeighborList => {
152 f.write_str("positions moved beyond the neighbour-list displacement skin")
153 }
154 Self::InvalidOption(option) => write!(f, "invalid spatial option: {option}"),
155 Self::PeriodicImageLimitExceeded { required, maximum } => write!(
156 f,
157 "periodic k-d query requires {required} images but the configured limit is {maximum}"
158 ),
159 Self::NumericRangeExceeded => {
160 f.write_str("a derived spatial value exceeds its representable numeric range")
161 }
162 Self::WorkerPanicked => f.write_str("a spatial worker thread panicked"),
163 }
164 }
165}
166
167impl std::error::Error for SpatialError {}
168
169impl fmt::Display for SpatialOption {
170 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
171 f.write_str(match self {
172 Self::BruteForcePairLimit => "brute_force_pair_limit",
173 Self::KdTargetMinimum => "kd_target_minimum",
174 Self::KdQueryRatio => "kd_query_ratio",
175 Self::UnresolvedBackend => "unresolved_backend",
176 Self::NeighborSkinRatio => "neighbor_skin.cutoff_ratio",
177 Self::NeighborSkinMinimum => "neighbor_skin.minimum",
178 Self::MaximumCellCount => "cell_grid.maximum_cell_count",
179 Self::CellGrowthFactor => "cell_grid.edge_growth_factor",
180 Self::KdPeriodicImageLimit => "kd_periodic.maximum_image_count",
181 })
182 }
183}
184
185impl From<molframe_core::MemoryBudgetError> for SpatialError {
186 fn from(error: molframe_core::MemoryBudgetError) -> Self {
187 Self::Memory(error)
188 }
189}
190
191#[cfg(test)]
192#[path = "types_tests.rs"]
193mod tests;