chemrust_nasl/coordination_sites/
coord_circle.rs1use std::{
2 collections::HashSet,
3 f64::consts::{FRAC_PI_2, FRAC_PI_8},
4 ops::ControlFlow,
5};
6
7use kiddo::{ImmutableKdTree, SquaredEuclidean};
8use nalgebra::Point3;
9
10use crate::{
11 geometry::{
12 approx_cmp_f64, Circle3d, CircleSphereIntersection, FloatOrdering, Intersect, Sphere,
13 },
14 CoordResult, DelegatePoint, MultiCoordPoint,
15};
16
17#[derive(Debug, Clone, Copy, PartialEq)]
18pub struct CoordCircle {
19 pub(crate) circle: Circle3d,
20 pub(crate) atom_ids: [usize; 2],
21}
22
23impl CoordCircle {
24 pub fn new(circle: Circle3d, atom_ids: [usize; 2]) -> Self {
25 Self { circle, atom_ids }
26 }
27
28 pub fn get_possible_point(
29 &self,
30 kdtree: &ImmutableKdTree<f64, 3>,
31 dist: f64,
32 ) -> Option<DelegatePoint<2>> {
33 let step_frac_pi_32 = FRAC_PI_8 / 4.0;
34 let possible_position: Vec<f64> = (0..32)
35 .map(|i| FRAC_PI_2 + i as f64 * step_frac_pi_32)
36 .collect();
37 let p = possible_position.iter().try_for_each(|&theta| {
38 let query = self.circle().get_point_on_circle(theta);
39 let neighbours =
40 kdtree.within::<SquaredEuclidean>(&query.into(), (dist + 1e-5_f64).powi(2));
41 if !neighbours.iter().any(|nb| {
42 matches!(
43 approx_cmp_f64(nb.distance, dist.powi(2)),
44 FloatOrdering::Less
45 )
46 }) {
47 if neighbours.len() <= 2 {
48 ControlFlow::Break(query)
49 } else {
50 ControlFlow::Continue(())
51 }
52 } else {
53 ControlFlow::Continue(())
54 }
55 });
56 match p {
57 ControlFlow::Continue(_) => None,
58 ControlFlow::Break(pos) => Some(DelegatePoint::<2>::new(pos, self.atom_ids)),
59 }
60 }
61
62 fn get_common_neighbours(
63 &self,
64 kdtree: &ImmutableKdTree<f64, 3>,
65 points: &[Point3<f64>],
66 dist: f64,
67 ) -> HashSet<usize> {
68 let each_neighbors: Vec<Vec<usize>> = self
69 .atom_ids
70 .iter()
71 .map(|&i| {
72 let query: [f64; 3] = points[i].into();
73 kdtree
74 .within::<SquaredEuclidean>(&query, 4.0 * dist.powi(2))
75 .iter()
76 .skip(1)
77 .map(|nb| nb.item as usize)
78 .collect()
79 })
80 .collect();
81 let mut common_neighbors: HashSet<usize> =
82 each_neighbors.concat().iter().cloned().collect();
83 self.atom_ids.iter().for_each(|i| {
85 common_neighbors.remove(i);
86 });
87 common_neighbors
88 }
89 fn classify_neighbour_results(
90 &self,
91 neighbour_results: Vec<CoordResult>,
92 ) -> Option<CoordResult> {
93 if neighbour_results
94 .iter()
95 .any(|res| matches!(res, CoordResult::Invalid))
96 {
97 None
98 } else if neighbour_results
99 .iter()
100 .all(|res| matches!(res, CoordResult::Empty))
101 {
102 Some(CoordResult::Circle(*self))
103 } else {
104 let coord_points: Vec<MultiCoordPoint> = neighbour_results
105 .iter()
106 .filter_map(|res| {
107 if let CoordResult::SinglePoint(coord_point) = res {
108 Some(coord_point.clone())
109 } else {
110 None
111 }
112 })
113 .collect();
114 Some(CoordResult::Points(coord_points))
115 }
116 }
117 pub(crate) fn common_neighbours_intersect(
121 &self,
122 kdtree: &ImmutableKdTree<f64, 3>,
123 points: &[Point3<f64>],
124 dist: f64,
125 ) -> Option<CoordResult> {
126 let common_neighbors: HashSet<usize> = self.get_common_neighbours(kdtree, points, dist);
129 let neighbor_results: Vec<CoordResult> = common_neighbors
130 .iter()
131 .map(|&i| {
132 let p = points[i];
133 let sphere = Sphere::new(p, dist);
135 let circle_sphere = self.circle.intersect(&sphere);
136
137 circle_sphere.to_coord_result(&self.atom_ids, i)
159 })
160 .collect();
161 self.classify_neighbour_results(neighbor_results)
162 }
163
164 pub fn atom_ids(&self) -> [usize; 2] {
165 self.atom_ids
166 }
167
168 pub fn circle(&self) -> Circle3d {
169 self.circle
170 }
171}
172
173impl CircleSphereIntersection {
174 pub fn to_coord_result(self, circle_id: &[usize; 2], sphere_id: usize) -> CoordResult {
175 match self {
176 CircleSphereIntersection::Zero => CoordResult::Empty,
177 CircleSphereIntersection::Single(p) => {
178 let mut atom_id = [circle_id[0], circle_id[1], sphere_id].to_vec();
179 atom_id.sort();
180 CoordResult::SinglePoint(MultiCoordPoint::new(p, atom_id))
181 }
182 CircleSphereIntersection::Double(p1, p2) => {
183 let p = if let FloatOrdering::Greater = approx_cmp_f64(p1.z, p2.z) {
184 p1
185 } else {
186 p2
187 };
188 let mut atom_id = [circle_id[0], circle_id[1], sphere_id].to_vec();
189 atom_id.sort();
190 CoordResult::SinglePoint(MultiCoordPoint::new(p, atom_id))
191 }
192 CircleSphereIntersection::Circle(_) => CoordResult::Invalid,
196 CircleSphereIntersection::InsideSphere => CoordResult::Invalid,
197 CircleSphereIntersection::SphereInCircle => CoordResult::Invalid,
198 CircleSphereIntersection::Invalid => CoordResult::Invalid,
199 }
200 }
201}