1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
use geometric_traits::traits::{
ConnectedComponents as GeometricConnectedComponents,
algorithms::connected_components::ConnectedComponentsResult as GeometricConnectedComponentsResult,
};
use super::{ConcreteAtoms, Smiles, SmilesAtomPolicy, WildcardAtoms, WildcardSmiles};
use crate::atom::Atom;
/// Connected-component view over a parsed [`Smiles`] graph.
///
/// This is a thin wrapper around the `geometric-traits` connected-components
/// implementation, exposed here so callers do not need to depend on
/// `geometric-traits` directly just to inspect disconnected fragments.
pub struct SmilesComponents<'a, AtomPolicy: SmilesAtomPolicy = ConcreteAtoms> {
inner: GeometricConnectedComponentsResult<'a, Smiles<AtomPolicy>, usize>,
}
/// Connected-component view over a [`WildcardSmiles`] graph.
///
/// This mirrors [`SmilesComponents`] while keeping the wildcard-capable public
/// API on [`WildcardSmiles`].
pub struct WildcardSmilesComponents<'a> {
inner: SmilesComponents<'a, WildcardAtoms>,
}
impl<'a, AtomPolicy: SmilesAtomPolicy> SmilesComponents<'a, AtomPolicy> {
#[inline]
pub(crate) const fn new(
inner: GeometricConnectedComponentsResult<'a, Smiles<AtomPolicy>, usize>,
) -> Self {
Self { inner }
}
/// Returns the number of connected components in the graph.
///
/// # Examples
///
/// ```
/// use smiles_rs::prelude::Smiles;
///
/// let smiles: Smiles = "CC.O".parse()?;
/// assert_eq!(smiles.connected_components().number_of_components(), 2);
/// # Ok::<(), smiles_rs::SmilesErrorWithSpan>(())
/// ```
#[inline]
#[must_use]
pub fn number_of_components(&self) -> usize {
self.inner.number_of_components()
}
/// Returns the size of the largest connected component.
///
/// # Examples
///
/// ```
/// use smiles_rs::prelude::Smiles;
///
/// let smiles: Smiles = "CC.O".parse()?;
/// assert_eq!(smiles.connected_components().largest_component_size(), 2);
/// # Ok::<(), smiles_rs::SmilesErrorWithSpan>(())
/// ```
#[inline]
#[must_use]
pub fn largest_component_size(&self) -> usize {
self.inner.largest_component_size()
}
/// Returns the size of the smallest connected component.
///
/// # Examples
///
/// ```
/// use smiles_rs::prelude::Smiles;
///
/// let smiles: Smiles = "CC.O".parse()?;
/// assert_eq!(smiles.connected_components().smallest_component_size(), 1);
/// # Ok::<(), smiles_rs::SmilesErrorWithSpan>(())
/// ```
#[inline]
#[must_use]
pub fn smallest_component_size(&self) -> usize {
self.inner.smallest_component_size()
}
/// Returns the connected-component identifier of the provided node id.
///
/// # Examples
///
/// ```
/// use smiles_rs::prelude::Smiles;
///
/// let smiles: Smiles = "CC.O".parse()?;
/// let components = smiles.connected_components();
///
/// assert_eq!(components.component_of_node(0), components.component_of_node(1));
/// assert_ne!(components.component_of_node(0), components.component_of_node(2));
/// # Ok::<(), smiles_rs::SmilesErrorWithSpan>(())
/// ```
#[inline]
#[must_use]
pub fn component_of_node(&self, node: usize) -> usize {
self.inner.component_of_node(node)
}
/// Returns an iterator over the connected-component identifier of each
/// node.
///
/// # Examples
///
/// ```
/// use smiles_rs::prelude::Smiles;
///
/// let smiles: Smiles = "CC.O".parse()?;
/// let identifiers = smiles.connected_components().component_identifiers().collect::<Vec<_>>();
///
/// assert_eq!(identifiers.len(), smiles.nodes().len());
/// # Ok::<(), smiles_rs::SmilesErrorWithSpan>(())
/// ```
#[inline]
pub fn component_identifiers(&self) -> impl Iterator<Item = usize> + '_ {
self.inner.component_identifiers()
}
/// Returns an iterator over the node ids belonging to the given component.
///
/// # Examples
///
/// ```
/// use smiles_rs::prelude::Smiles;
///
/// let smiles: Smiles = "CC.O".parse()?;
/// let components = smiles.connected_components();
/// let first_component = components.component_of_node(0);
///
/// assert_eq!(components.node_ids_of_component(first_component).collect::<Vec<_>>(), vec![0, 1]);
/// # Ok::<(), smiles_rs::SmilesErrorWithSpan>(())
/// ```
#[inline]
pub fn node_ids_of_component(
&self,
component_identifier: usize,
) -> impl Iterator<Item = usize> + '_ {
self.inner.node_ids_of_component(component_identifier)
}
/// Returns an iterator over the atoms belonging to the given component.
///
/// # Examples
///
/// ```
/// use elements_rs::Element;
/// use smiles_rs::prelude::Smiles;
///
/// let smiles: Smiles = "CC.O".parse()?;
/// let components = smiles.connected_components();
/// let oxygen_component = components.component_of_node(2);
///
/// assert_eq!(
/// components
/// .nodes_of_component(oxygen_component)
/// .map(|atom| atom.element())
/// .collect::<Vec<_>>(),
/// vec![Some(Element::O)]
/// );
/// # Ok::<(), smiles_rs::SmilesErrorWithSpan>(())
/// ```
#[inline]
pub fn nodes_of_component(
&self,
component_identifier: usize,
) -> impl Iterator<Item = Atom> + '_ {
self.inner.nodes_of_component(component_identifier)
}
}
impl<'a> WildcardSmilesComponents<'a> {
#[inline]
pub(crate) const fn new(inner: SmilesComponents<'a, WildcardAtoms>) -> Self {
Self { inner }
}
/// Returns the number of connected components in the graph.
#[inline]
#[must_use]
pub fn number_of_components(&self) -> usize {
self.inner.number_of_components()
}
/// Returns the size of the largest connected component.
#[inline]
#[must_use]
pub fn largest_component_size(&self) -> usize {
self.inner.largest_component_size()
}
/// Returns the size of the smallest connected component.
#[inline]
#[must_use]
pub fn smallest_component_size(&self) -> usize {
self.inner.smallest_component_size()
}
/// Returns the connected-component identifier of the provided node id.
#[inline]
#[must_use]
pub fn component_of_node(&self, node: usize) -> usize {
self.inner.component_of_node(node)
}
/// Returns an iterator over the connected-component identifier of each
/// node.
#[inline]
pub fn component_identifiers(&self) -> impl Iterator<Item = usize> + '_ {
self.inner.component_identifiers()
}
/// Returns an iterator over the node ids belonging to the given component.
#[inline]
pub fn node_ids_of_component(
&self,
component_identifier: usize,
) -> impl Iterator<Item = usize> + '_ {
self.inner.node_ids_of_component(component_identifier)
}
/// Returns an iterator over the atoms belonging to the given component.
#[inline]
pub fn nodes_of_component(
&self,
component_identifier: usize,
) -> impl Iterator<Item = Atom> + '_ {
self.inner.nodes_of_component(component_identifier)
}
}
impl<AtomPolicy: crate::smiles::SmilesAtomPolicy> Smiles<AtomPolicy> {
/// Returns the connected components of the graph.
///
/// This is infallible for [`usize`] component markers, because a graph
/// cannot contain more components than nodes, and node ids are already
/// stored as `usize`.
///
/// # Panics
///
/// Panics if the underlying geometric-traits connected-components routine
/// ever reports marker overflow for `usize` component identifiers. That
/// would indicate a bug in the graph library or an invalid graph size.
///
/// # Examples
///
/// ```
/// use smiles_rs::prelude::Smiles;
///
/// let smiles: Smiles = "CC.O".parse()?;
/// let components = smiles.connected_components();
/// assert_eq!(components.number_of_components(), 2);
/// # Ok::<(), smiles_rs::SmilesErrorWithSpan>(())
/// ```
#[inline]
#[must_use]
pub fn connected_components(&self) -> SmilesComponents<'_, AtomPolicy> {
let components = GeometricConnectedComponents::<usize>::connected_components(self)
.expect("usize markers cannot overflow on a usize-indexed graph");
SmilesComponents::new(components)
}
}
impl WildcardSmiles {
/// Returns the connected components of the wildcard-capable graph.
///
/// This mirrors [`Smiles::connected_components`] while preserving the
/// [`WildcardSmiles`] API surface.
#[inline]
#[must_use]
pub fn connected_components(&self) -> WildcardSmilesComponents<'_> {
WildcardSmilesComponents::new(self.inner().connected_components())
}
}
#[cfg(test)]
mod tests {
use alloc::vec::Vec;
use elements_rs::Element;
use crate::{
atom::{Atom, atom_symbol::AtomSymbol},
bond::{
Bond,
bond_edge::{BondEdge, bond_edge},
},
smiles::{BondMatrixBuilder, Smiles},
};
fn atom(element: Element) -> Atom {
Atom::new_organic_subset(AtomSymbol::Element(element), false)
}
fn smiles_from_edges(atom_nodes: Vec<Atom>, bond_edges: &[BondEdge]) -> Smiles {
let mut builder = BondMatrixBuilder::with_capacity(bond_edges.len());
for edge in bond_edges {
let descriptor = edge.descriptor();
builder
.push_edge_with_descriptor(
edge.source(),
edge.target(),
descriptor,
edge.ring_num(),
)
.unwrap();
}
let number_of_nodes = atom_nodes.len();
Smiles::from_bond_matrix_parts(atom_nodes, builder.finish(number_of_nodes))
}
#[test]
fn connected_components_expose_sizes_identifiers_and_nodes() {
let smiles = smiles_from_edges(
vec![atom(Element::C), atom(Element::O), atom(Element::N), atom(Element::S)],
&[bond_edge(0, 1, Bond::Single, None), bond_edge(2, 3, Bond::Double, None)],
);
let components = smiles.connected_components();
assert_eq!(components.number_of_components(), 2);
assert_eq!(components.largest_component_size(), 2);
assert_eq!(components.smallest_component_size(), 2);
assert_eq!(components.component_of_node(0), components.component_of_node(1));
assert_ne!(components.component_of_node(0), components.component_of_node(2));
let identifiers = components.component_identifiers().collect::<Vec<_>>();
assert_eq!(identifiers.len(), 4);
let first_nodes =
components.node_ids_of_component(components.component_of_node(0)).collect::<Vec<_>>();
assert_eq!(first_nodes, vec![0, 1]);
let second_atoms =
components.nodes_of_component(components.component_of_node(2)).collect::<Vec<_>>();
assert_eq!(second_atoms, vec![atom(Element::N), atom(Element::S)]);
}
}