1use std::collections::VecDeque;
4
5use crate::elements::element_by_symbol;
6use crate::{Error, Neighbor, Structure, neighbors};
7
8#[derive(Debug, Clone, Copy, PartialEq)]
10pub struct Bond {
11 pub i: usize,
13 pub j: usize,
15 pub image: [i32; 3],
17 pub distance: f64,
19 pub order: f64,
21 pub explicit: bool,
23}
24
25#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize)]
27pub struct ExplicitBond {
28 pub i: usize,
30 pub j: usize,
32 pub image: [i32; 3],
34 pub order: f64,
36 pub remove: bool,
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize)]
42pub struct TopologyBond {
43 pub i: usize,
45 pub j: usize,
47 pub image: [i32; 3],
49}
50
51#[derive(Debug, Clone, Copy, PartialEq)]
53pub struct BondRules {
54 pub scale: f64,
56 pub minimum_distance: f64,
58}
59
60impl Default for BondRules {
61 fn default() -> Self {
62 Self {
63 scale: 1.15,
64 minimum_distance: 0.4,
65 }
66 }
67}
68
69#[derive(Debug, Clone, PartialEq)]
71pub struct BondGraph {
72 pub bonds: Vec<Bond>,
74 adjacency: Vec<Vec<usize>>,
76}
77
78impl BondGraph {
79 pub fn from_explicit(structure: &Structure) -> Result<Self, Error> {
91 if !structure.is_p1() {
92 return Err(Error::InvalidInput(
93 "an explicit-only bond graph needs a P1 structure".to_owned(),
94 ));
95 }
96 let p1 = structure.p1()?;
97 let mut bonds = Vec::new();
98 for explicit in structure.explicit_bonds() {
99 if explicit.remove {
100 continue;
101 }
102 let distance = explicit_distance(&p1, explicit).unwrap_or(0.0);
103 bonds.push(Bond {
104 i: explicit.i,
105 j: explicit.j,
106 image: explicit.image,
107 distance,
108 order: explicit.order,
109 explicit: true,
110 });
111 }
112 let mut adjacency = vec![Vec::new(); p1.atoms.len()];
113 for (index, bond) in bonds.iter().enumerate() {
114 if let Some(list) = adjacency.get_mut(bond.i) {
115 list.push(index);
116 }
117 if bond.j != bond.i
118 && let Some(list) = adjacency.get_mut(bond.j)
119 {
120 list.push(index);
121 }
122 }
123 Ok(Self { bonds, adjacency })
124 }
125
126 #[must_use]
128 pub fn n_atoms(&self) -> usize {
129 self.adjacency.len()
130 }
131
132 #[must_use]
134 pub fn degree(&self, index: usize) -> usize {
135 self.adjacency.get(index).map_or(0, Vec::len)
136 }
137
138 #[must_use]
140 pub fn bonds_of(&self, index: usize) -> &[usize] {
141 self.adjacency.get(index).map_or(&[], Vec::as_slice)
142 }
143
144 #[must_use]
146 pub fn components(&self) -> Vec<Vec<usize>> {
147 let mut seen = vec![false; self.n_atoms()];
148 let mut components = Vec::new();
149 for start in 0..self.n_atoms() {
150 if seen.get(start).copied().unwrap_or(true) {
151 continue;
152 }
153 let mut component = Vec::new();
154 let mut queue = VecDeque::from([start]);
155 while let Some(index) = queue.pop_front() {
156 if seen.get(index).copied().unwrap_or(true) {
157 continue;
158 }
159 if let Some(slot) = seen.get_mut(index) {
160 *slot = true;
161 }
162 component.push(index);
163 for bond_index in self.bonds_of(index) {
164 if let Some(bond) = self.bonds.get(*bond_index) {
165 let other = if bond.i == index { bond.j } else { bond.i };
166 if !seen.get(other).copied().unwrap_or(true) {
167 queue.push_back(other);
168 }
169 }
170 }
171 }
172 component.sort_unstable();
173 components.push(component);
174 }
175 components
176 }
177}
178
179pub fn perceive_bonds(structure: &Structure, rules: BondRules) -> Result<BondGraph, Error> {
185 if !rules.scale.is_finite() || rules.scale <= 0.0 {
186 return Err(Error::InvalidInput(
187 "bond scale must be positive and finite".to_owned(),
188 ));
189 }
190 if !rules.minimum_distance.is_finite() || rules.minimum_distance < 0.0 {
191 return Err(Error::InvalidInput(
192 "bond minimum distance must be non-negative and finite".to_owned(),
193 ));
194 }
195 let p1 = structure.p1()?;
196 let mut radii = Vec::with_capacity(p1.atoms.len());
197 let mut dummy = Vec::with_capacity(p1.atoms.len());
198 let mut metal = Vec::with_capacity(p1.atoms.len());
199 for atom in &p1.atoms {
200 let element = element_by_symbol(&atom.element)?;
201 radii.push(element.covalent_radius);
202 dummy.push(atom.element == "X");
203 metal.push(element.is_metal);
204 }
205 let maximum = radii.iter().copied().fold(0.0, f64::max);
206 let cutoff = 2.0 * maximum * rules.scale;
207 if cutoff <= 0.0 {
208 return Ok(BondGraph {
209 bonds: Vec::new(),
210 adjacency: vec![Vec::new(); p1.atoms.len()],
211 });
212 }
213 let found = neighbors(&p1_to_structure(&p1)?, cutoff)?;
214 let mut bonds = Vec::new();
215 for Neighbor {
216 i,
217 j,
218 image,
219 distance,
220 } in found
221 {
222 if i == j
223 || distance < rules.minimum_distance
224 || dummy.get(i).copied().unwrap_or(true)
225 || dummy.get(j).copied().unwrap_or(true)
226 || (metal.get(i).copied().unwrap_or(false) && metal.get(j).copied().unwrap_or(false))
227 {
228 continue;
229 }
230 let Some(radius_i) = radii.get(i) else {
231 continue;
232 };
233 let Some(radius_j) = radii.get(j) else {
234 continue;
235 };
236 if distance <= (radius_i + radius_j) * rules.scale {
237 bonds.push(Bond {
238 i,
239 j,
240 image,
241 distance,
242 order: 1.0,
243 explicit: false,
244 });
245 }
246 }
247 for explicit in structure.explicit_bonds() {
248 let distance = explicit_distance(&p1, explicit);
249 if explicit.remove {
250 bonds.retain(|bond| {
251 !same_bond(bond, explicit.i, explicit.j, explicit.image)
252 && !same_contact(bond, explicit, distance)
253 });
254 continue;
255 }
256 if let Some(existing) = bonds
257 .iter_mut()
258 .find(|bond| same_bond(bond, explicit.i, explicit.j, explicit.image))
259 {
260 existing.order = explicit.order;
261 existing.explicit = true;
262 continue;
263 }
264 if let Some(existing) = bonds
265 .iter_mut()
266 .find(|bond| same_contact(bond, explicit, distance))
267 {
268 existing.order = explicit.order;
269 existing.explicit = true;
270 continue;
271 }
272 if !structure.is_p1() {
273 continue;
278 }
279 if let (Some(distance), Some(_)) = (distance, p1.atoms.get(explicit.j)) {
280 let (i, j, image) = canonical(explicit.i, explicit.j, explicit.image);
281 bonds.push(Bond {
282 i,
283 j,
284 image,
285 distance,
286 order: explicit.order,
287 explicit: true,
288 });
289 }
290 }
291 let mut adjacency = vec![Vec::new(); p1.atoms.len()];
292 for (index, bond) in bonds.iter().enumerate() {
293 if let Some(list) = adjacency.get_mut(bond.i) {
294 list.push(index);
295 }
296 if let Some(list) = adjacency.get_mut(bond.j) {
297 list.push(index);
298 }
299 }
300 Ok(BondGraph { bonds, adjacency })
301}
302
303impl Structure {
304 pub fn bond_graph(&self) -> Result<BondGraph, Error> {
310 perceive_bonds(self, BondRules::default())
311 }
312}
313
314fn p1_to_structure(cell: &crate::P1Cell) -> Result<Structure, Error> {
315 let mut builder = crate::StructureBuilder::new("p1", cell.lattice);
316 for (index, atom) in cell.atoms.iter().enumerate() {
317 builder = builder.site(
318 crate::Site::new(
319 format!("{}{}", atom.element, index + 1),
320 &atom.element,
321 atom.frac,
322 )?
323 .with_occupancy(atom.occupancy)?,
324 );
325 }
326 builder.build()
327}
328
329fn same_bond(bond: &Bond, i: usize, j: usize, image: [i32; 3]) -> bool {
330 (bond.i == i && bond.j == j && bond.image == image)
331 || (bond.i == j && bond.j == i && bond.image == [-image[0], -image[1], -image[2]])
332}
333
334fn canonical(i: usize, j: usize, image: [i32; 3]) -> (usize, usize, [i32; 3]) {
335 if i > j
336 || (i == j
337 && (image[0] < 0
338 || (image[0] == 0 && image[1] < 0)
339 || (image[0] == 0 && image[1] == 0 && image[2] < 0)))
340 {
341 (j, i, [-image[0], -image[1], -image[2]])
342 } else {
343 (i, j, image)
344 }
345}
346
347fn explicit_distance(cell: &crate::P1Cell, explicit: &ExplicitBond) -> Option<f64> {
348 let first = cell.atoms.get(explicit.i)?;
349 let second = cell.atoms.get(explicit.j)?;
350 let delta = [
351 second.frac[0] + f64::from(explicit.image[0]) - first.frac[0],
352 second.frac[1] + f64::from(explicit.image[1]) - first.frac[1],
353 second.frac[2] + f64::from(explicit.image[2]) - first.frac[2],
354 ];
355 let matrix = cell.lattice.matrix();
356 let cart = [
357 delta[0] * matrix[0][0] + delta[1] * matrix[1][0] + delta[2] * matrix[2][0],
358 delta[0] * matrix[0][1] + delta[1] * matrix[1][1] + delta[2] * matrix[2][1],
359 delta[0] * matrix[0][2] + delta[1] * matrix[1][2] + delta[2] * matrix[2][2],
360 ];
361 Some(cart.iter().map(|value| value * value).sum::<f64>().sqrt())
362}
363
364fn same_contact(bond: &Bond, explicit: &ExplicitBond, distance: Option<f64>) -> bool {
371 let same_pair = (bond.i == explicit.i && bond.j == explicit.j)
372 || (bond.i == explicit.j && bond.j == explicit.i);
373 same_pair && distance.is_some_and(|value| (bond.distance - value).abs() <= 1e-3)
374}