chemrust_nasl/coordination_sites/
coord_circle.rs

1use 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        // Remove self ids included when searching NNs.
84        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    /// # Returns
118    /// `None` when there is atoms closer than the required distance
119    /// `Some` for 1. `CoordResult::Circle`2. `CoordResult::Points`
120    pub(crate) fn common_neighbours_intersect(
121        &self,
122        kdtree: &ImmutableKdTree<f64, 3>,
123        points: &[Point3<f64>],
124        dist: f64,
125    ) -> Option<CoordResult> {
126        // Only common neighbors of the associated atoms are possible to
127        // form further connections
128        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                // circle-sphere intersection
134                let sphere = Sphere::new(p, dist);
135                let circle_sphere = self.circle.intersect(&sphere);
136
137                // #[cfg(debug_assertions)]
138                // {
139                //     if i == 44 && self.atom_ids() == [24, 26] {
140                //         let cs_cc = self.circle().center() - sphere.center();
141                //         let cut_at = self.circle().n().dot(&cs_cc);
142                //         println!("Cut at and radius");
143                //         dbg!(cut_at.abs(), sphere.radius());
144                //         let new_circle_center = p + self.circle().n().scale(cut_at);
145                //         let new_circle_radius = (dist.powi(2) - cut_at.powi(2)).sqrt();
146                //         dbg!(new_circle_center, new_circle_radius);
147                //         dbg!(self.circle.center(), self.circle.radius());
148                //         let c1c2 = new_circle_center - self.circle.center();
149                //         let r1r2_sum_squared = (new_circle_radius + self.circle.radius()).powi(2);
150                //         dbg!(r1r2_sum_squared - c1c2.norm_squared());
151                //         dbg!(r1r2_sum_squared.sqrt() - c1c2.norm());
152                //         dbg!(approx_cmp_f64(c1c2.norm_squared(), r1r2_sum_squared));
153                //         let r1r2_diff_squared = (new_circle_radius - self.circle.radius()).powi(2);
154                //         dbg!(approx_cmp_f64(c1c2.norm_squared(), r1r2_diff_squared));
155                //         dbg!(circle_sphere);
156                //     }
157                // }
158                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            // Actually impossible in our current case that every sphere
193            // has the same radius, and thus there can't be a circle has the
194            // same radius as the sphere
195            CircleSphereIntersection::Circle(_) => CoordResult::Invalid,
196            CircleSphereIntersection::InsideSphere => CoordResult::Invalid,
197            CircleSphereIntersection::SphereInCircle => CoordResult::Invalid,
198            CircleSphereIntersection::Invalid => CoordResult::Invalid,
199        }
200    }
201}