use std::collections::{HashMap, HashSet, VecDeque};
use crate::bond::BondOrder;
use crate::molecule::{AtomIdx, BondIdx, Molecule};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct KekuleError {
pub detail: String,
}
impl core::fmt::Display for KekuleError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "kekulization failed: {}", self.detail)
}
}
impl std::error::Error for KekuleError {}
pub type KekuleResult = HashMap<BondIdx, BondOrder>;
pub fn kekulize(mol: &Molecule) -> Result<KekuleResult, KekuleError> {
let mut aromatic_bonds: Vec<BondIdx> = Vec::new();
let mut aromatic_atoms: HashSet<AtomIdx> = HashSet::new();
for (bidx, bond) in mol.bonds() {
if bond.order == BondOrder::Aromatic {
aromatic_bonds.push(bidx);
aromatic_atoms.insert(bond.atom1);
aromatic_atoms.insert(bond.atom2);
}
}
if aromatic_bonds.is_empty() {
return Ok(HashMap::new());
}
let must_match: HashSet<AtomIdx> = aromatic_atoms
.iter()
.copied()
.filter(|&idx| atom_must_be_matched(mol, idx))
.collect();
let mut adj: HashMap<AtomIdx, Vec<(AtomIdx, BondIdx)>> = HashMap::new();
for &bidx in &aromatic_bonds {
let bond = mol.bond(bidx);
if must_match.contains(&bond.atom1) && must_match.contains(&bond.atom2) {
adj.entry(bond.atom1).or_default().push((bond.atom2, bidx));
adj.entry(bond.atom2).or_default().push((bond.atom1, bidx));
}
}
let mut matching: HashMap<AtomIdx, AtomIdx> = HashMap::new();
let mut sorted_atoms: Vec<AtomIdx> = must_match.iter().copied().collect();
sorted_atoms.sort();
run_matching_pass(&sorted_atoms, &adj, &mut matching);
if must_match.iter().any(|&idx| !matching.contains_key(&idx)) {
matching.clear();
let mut rev = sorted_atoms.clone();
rev.reverse();
run_matching_pass(&rev, &adj, &mut matching);
}
if must_match.iter().any(|&idx| !matching.contains_key(&idx)) {
let bridgehead_n: HashSet<AtomIdx> = must_match
.iter()
.copied()
.filter(|&idx| {
mol.atom(idx).element.atomic_number() == 7
&& adj.get(&idx).map_or(0, |v| v.len()) >= 3
})
.collect();
if !bridgehead_n.is_empty() {
let must_match_nb: HashSet<AtomIdx> =
must_match.difference(&bridgehead_n).copied().collect();
let mut adj_nb: HashMap<AtomIdx, Vec<(AtomIdx, BondIdx)>> = HashMap::new();
for &bidx in &aromatic_bonds {
let bond = mol.bond(bidx);
if must_match_nb.contains(&bond.atom1) && must_match_nb.contains(&bond.atom2) {
adj_nb.entry(bond.atom1).or_default().push((bond.atom2, bidx));
adj_nb.entry(bond.atom2).or_default().push((bond.atom1, bidx));
}
}
let mut sorted_nb: Vec<AtomIdx> = must_match_nb.iter().copied().collect();
sorted_nb.sort();
matching.clear();
run_matching_pass(&sorted_nb, &adj_nb, &mut matching);
if must_match_nb.iter().any(|&idx| !matching.contains_key(&idx)) {
matching.clear();
let rev_nb: Vec<AtomIdx> = sorted_nb.iter().copied().rev().collect();
run_matching_pass(&rev_nb, &adj_nb, &mut matching);
}
if must_match_nb.iter().all(|&idx| matching.contains_key(&idx)) {
return Ok(build_kekule_result(&aromatic_bonds, mol, &matching));
}
}
}
if must_match.iter().any(|&idx| !matching.contains_key(&idx)) {
let n = sorted_atoms.len();
let idx_to_int: HashMap<AtomIdx, usize> = sorted_atoms
.iter()
.enumerate()
.map(|(i, &a)| (a, i))
.collect();
let int_adj: Vec<Vec<usize>> = sorted_atoms
.iter()
.map(|&a| {
adj.get(&a)
.map(|nbrs| {
nbrs.iter()
.filter_map(|(nb, _)| idx_to_int.get(nb).copied())
.collect()
})
.unwrap_or_default()
})
.collect();
matching.clear();
let int_mate = blossom_max_matching(n, &int_adj);
for (i, &j) in int_mate.iter().enumerate() {
if j != usize::MAX {
matching.insert(sorted_atoms[i], sorted_atoms[j]);
}
}
}
for &idx in &must_match {
if !matching.contains_key(&idx) {
return Err(KekuleError {
detail: format!(
"atom {} ({}) cannot be assigned a double bond",
idx.0,
mol.atom(idx).element.symbol()
),
});
}
}
Ok(build_kekule_result(&aromatic_bonds, mol, &matching))
}
fn build_kekule_result(
aromatic_bonds: &[BondIdx],
mol: &Molecule,
matching: &HashMap<AtomIdx, AtomIdx>,
) -> KekuleResult {
let mut double_bonds: HashSet<BondIdx> = HashSet::new();
for (&atom, &partner) in matching {
if atom >= partner { continue; }
if let Some((bidx, _)) = mol.bond_between(atom, partner)
&& mol.bond(bidx).order == BondOrder::Aromatic
{
double_bonds.insert(bidx);
}
}
aromatic_bonds
.iter()
.map(|&bidx| {
let order = if double_bonds.contains(&bidx) {
BondOrder::Double
} else {
BondOrder::Single
};
(bidx, order)
})
.collect()
}
pub fn apply_kekule(mol: &Molecule, kekule: &KekuleResult) -> Molecule {
use crate::molecule::MoleculeBuilder;
let mut builder = MoleculeBuilder::new();
for (_, atom) in mol.atoms() {
builder.add_atom(atom.clone());
}
for (bidx, bond) in mol.bonds() {
let order = kekule.get(&bidx).copied().unwrap_or(bond.order);
builder
.add_bond(bond.atom1, bond.atom2, order)
.expect("duplicate bond during apply_kekule");
}
builder.build()
}
fn augment(
start: AtomIdx,
adj: &HashMap<AtomIdx, Vec<(AtomIdx, BondIdx)>>,
matching: &mut HashMap<AtomIdx, AtomIdx>,
visited: &mut HashSet<AtomIdx>,
) -> bool {
let mut parent: HashMap<AtomIdx, AtomIdx> = HashMap::new();
let mut queue: std::collections::VecDeque<AtomIdx> = std::collections::VecDeque::new();
queue.push_back(start);
'bfs: while let Some(v) = queue.pop_front() {
let Some(neighbors) = adj.get(&v) else {
continue;
};
for &(u, _) in neighbors {
if !visited.insert(u) {
continue;
}
parent.insert(u, v);
match matching.get(&u).copied() {
None => {
let mut cur = u;
loop {
let prev = parent[&cur];
let prev_old_match = matching.get(&prev).copied();
matching.insert(prev, cur);
matching.insert(cur, prev);
match prev_old_match {
None | Some(_) if prev == start => break,
Some(m) => cur = m,
None => break,
}
}
break 'bfs;
}
Some(partner) => {
if visited.insert(partner) {
parent.insert(partner, u);
queue.push_back(partner);
}
}
}
}
}
matching.contains_key(&start)
}
fn run_matching_pass(
atoms: &[AtomIdx],
adj: &HashMap<AtomIdx, Vec<(AtomIdx, BondIdx)>>,
matching: &mut HashMap<AtomIdx, AtomIdx>,
) {
for &start in atoms {
if matching.contains_key(&start) {
continue;
}
let mut visited: HashSet<AtomIdx> = HashSet::new();
visited.insert(start);
augment(start, adj, matching, &mut visited);
}
}
const NONE: usize = usize::MAX;
fn blossom_max_matching(n: usize, adj: &[Vec<usize>]) -> Vec<usize> {
let mut mate = vec![NONE; n];
for v in 0..n {
if mate[v] == NONE {
blossom_augment(v, n, adj, &mut mate);
}
}
mate
}
fn blossom_augment(root: usize, n: usize, adj: &[Vec<usize>], mate: &mut [usize]) {
let mut base: Vec<usize> = (0..n).collect();
let mut parent: Vec<usize> = vec![NONE; n];
let mut is_outer: Vec<bool> = vec![false; n];
is_outer[root] = true;
let mut queue: VecDeque<usize> = VecDeque::new();
queue.push_back(root);
'bfs: while let Some(v) = queue.pop_front() {
for &w in &adj[v] {
if base[v] == base[w] { continue; } if mate[v] == w { continue; }
if is_outer[w] {
let b = blossom_lca(v, w, &base, &parent, mate, n);
blossom_mark_path(v, b, w, &mut base, &mut parent, &mut is_outer, &mut queue, mate, n);
blossom_mark_path(w, b, v, &mut base, &mut parent, &mut is_outer, &mut queue, mate, n);
} else if parent[w] == NONE {
parent[w] = v;
if mate[w] == NONE {
let mut cur = w;
while cur != NONE {
let prev = parent[cur];
let prev_old = mate[prev];
mate[cur] = prev;
mate[prev] = cur;
cur = prev_old;
}
break 'bfs;
}
let u = mate[w];
if !is_outer[u] {
is_outer[u] = true;
parent[u] = w;
queue.push_back(u);
}
}
}
}
}
fn blossom_lca(
mut a: usize,
mut b: usize,
base: &[usize],
parent: &[usize],
mate: &[usize],
n: usize,
) -> usize {
let mut visited = vec![false; n];
loop {
a = base[a];
visited[a] = true;
if mate[a] == NONE { break; } a = parent[mate[a]]; }
loop {
b = base[b];
if visited[b] { return b; } b = parent[mate[b]];
}
}
#[allow(clippy::too_many_arguments)]
fn blossom_mark_path(
mut x: usize,
b: usize,
child: usize,
base: &mut [usize],
parent: &mut [usize],
is_outer: &mut [bool],
queue: &mut VecDeque<usize>,
mate: &[usize],
n: usize,
) {
let mut ch = child;
while base[x] != b {
let bx = base[x];
let bmx = base[mate[x]];
for slot in base.iter_mut().take(n) {
if *slot == bx || *slot == bmx {
*slot = b;
}
}
parent[x] = ch;
let mx = mate[x];
if !is_outer[mx] {
is_outer[mx] = true;
queue.push_back(mx);
}
ch = mx;
x = parent[mx];
}
}
fn atom_must_be_matched(mol: &Molecule, idx: AtomIdx) -> bool {
let atom = mol.atom(idx);
match atom.element.atomic_number() {
8 | 16 | 34 => false,
5 => true,
7 if matches!(atom.hydrogen_count, Some(h) if h > 0) => false,
7 if atom.charge < 0 => false,
7 if atom.charge == 0
&& mol
.neighbors(idx)
.any(|(_, bidx)| mol.bond(bidx).order != BondOrder::Aromatic) => false,
7 => true,
_ if atom.charge < 0 => false,
_ if mol.neighbors(idx).any(|(_, bidx)| {
let o = mol.bond(bidx).order;
o == BondOrder::Double || o == BondOrder::Triple
}) => false,
_ => true,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::atom::Atom;
use crate::element::Element;
use crate::molecule::MoleculeBuilder;
fn benzene() -> Molecule {
let mut b = MoleculeBuilder::new();
let atoms: Vec<_> = (0..6)
.map(|_| b.add_atom(Atom::aromatic(Element::C)))
.collect();
for i in 0..6 {
b.add_bond(atoms[i], atoms[(i + 1) % 6], BondOrder::Aromatic)
.unwrap();
}
b.build()
}
fn pyridine() -> Molecule {
let mut b = MoleculeBuilder::new();
let c1 = b.add_atom(Atom::aromatic(Element::C));
let c2 = b.add_atom(Atom::aromatic(Element::C));
let c3 = b.add_atom(Atom::aromatic(Element::C));
let n = b.add_atom(Atom::aromatic(Element::N));
let c4 = b.add_atom(Atom::aromatic(Element::C));
let c5 = b.add_atom(Atom::aromatic(Element::C));
let atoms = [c1, c2, c3, n, c4, c5];
for i in 0..6 {
b.add_bond(atoms[i], atoms[(i + 1) % 6], BondOrder::Aromatic)
.unwrap();
}
b.build()
}
fn furan() -> Molecule {
let mut b = MoleculeBuilder::new();
let o = b.add_atom(Atom::aromatic(Element::O));
let c1 = b.add_atom(Atom::aromatic(Element::C));
let c2 = b.add_atom(Atom::aromatic(Element::C));
let c3 = b.add_atom(Atom::aromatic(Element::C));
let c4 = b.add_atom(Atom::aromatic(Element::C));
let atoms = [o, c1, c2, c3, c4];
for i in 0..5 {
b.add_bond(atoms[i], atoms[(i + 1) % 5], BondOrder::Aromatic)
.unwrap();
}
b.build()
}
fn pyrrole() -> Molecule {
let mut b = MoleculeBuilder::new();
let mut n_atom = Atom::aromatic(Element::N);
n_atom.hydrogen_count = Some(1);
let n = b.add_atom(n_atom);
let c1 = b.add_atom(Atom::aromatic(Element::C));
let c2 = b.add_atom(Atom::aromatic(Element::C));
let c3 = b.add_atom(Atom::aromatic(Element::C));
let c4 = b.add_atom(Atom::aromatic(Element::C));
let atoms = [n, c1, c2, c3, c4];
for i in 0..5 {
b.add_bond(atoms[i], atoms[(i + 1) % 5], BondOrder::Aromatic)
.unwrap();
}
b.build()
}
#[test]
fn test_kekulize_benzene() {
let mol = benzene();
let result = kekulize(&mol).expect("benzene kekulization failed");
assert_eq!(result.len(), 6);
let doubles = result.values().filter(|&&o| o == BondOrder::Double).count();
let singles = result.values().filter(|&&o| o == BondOrder::Single).count();
assert_eq!(doubles, 3, "benzene must have 3 double bonds");
assert_eq!(singles, 3, "benzene must have 3 single bonds");
}
#[test]
fn test_kekulize_pyridine() {
let mol = pyridine();
let result = kekulize(&mol).expect("pyridine kekulization failed");
let doubles = result.values().filter(|&&o| o == BondOrder::Double).count();
assert_eq!(doubles, 3, "pyridine must have 3 double bonds");
}
#[test]
fn test_kekulize_furan() {
let mol = furan();
let result = kekulize(&mol).expect("furan kekulization failed");
let doubles = result.values().filter(|&&o| o == BondOrder::Double).count();
assert_eq!(doubles, 2, "furan must have 2 double bonds");
}
#[test]
fn test_kekulize_pyrrole() {
let mol = pyrrole();
let result = kekulize(&mol).expect("pyrrole kekulization failed");
let doubles = result.values().filter(|&&o| o == BondOrder::Double).count();
assert_eq!(doubles, 2, "pyrrole must have 2 double bonds");
}
#[test]
fn test_kekulize_naphthalene() {
let mut b = MoleculeBuilder::new();
let atoms: Vec<_> = (0..10)
.map(|_| b.add_atom(Atom::aromatic(Element::C)))
.collect();
let ring1 = [0, 1, 2, 3, 4, 9];
for i in 0..ring1.len() {
b.add_bond(
atoms[ring1[i]],
atoms[ring1[(i + 1) % ring1.len()]],
BondOrder::Aromatic,
)
.unwrap();
}
let ring2 = [4, 5, 6, 7, 8, 9];
for i in 0..ring2.len() {
let a = atoms[ring2[i]];
let bb = atoms[ring2[(i + 1) % ring2.len()]];
if mol_has_no_bond_yet(&b, a, bb) {
b.add_bond(a, bb, BondOrder::Aromatic).unwrap();
}
}
let mol = b.build();
let result = kekulize(&mol).expect("naphthalene kekulization failed");
let doubles = result.values().filter(|&&o| o == BondOrder::Double).count();
assert_eq!(doubles, 5, "naphthalene must have 5 double bonds");
}
#[test]
fn test_apply_kekule() {
let mol = benzene();
let kekule = kekulize(&mol).unwrap();
let kekule_mol = apply_kekule(&mol, &kekule);
for (_, bond) in kekule_mol.bonds() {
assert_ne!(
bond.order,
BondOrder::Aromatic,
"apply_kekule should remove all aromatic bonds"
);
}
}
#[test]
fn test_no_aromatic_bonds_noop() {
let mut b = MoleculeBuilder::new();
let c1 = b.add_atom(Atom::new(Element::C));
let c2 = b.add_atom(Atom::new(Element::C));
b.add_bond(c1, c2, BondOrder::Single).unwrap();
let mol = b.build();
let result = kekulize(&mol).unwrap();
assert!(result.is_empty());
}
fn mol_has_no_bond_yet(b: &MoleculeBuilder, a: AtomIdx, bb: AtomIdx) -> bool {
for (_, partner) in b.atom_neighbors(a) {
if partner == bb {
return false;
}
}
true
}
#[test]
fn test_kekulize_azulene() {
let mut b = MoleculeBuilder::new();
let a: Vec<_> = (0..10)
.map(|_| b.add_atom(Atom::aromatic(Element::C)))
.collect();
for i in 0..5 {
b.add_bond(a[i], a[(i + 1) % 5], BondOrder::Aromatic).unwrap();
}
for (x, y) in [(4usize, 5usize), (5, 6), (6, 7), (7, 8), (8, 9), (9, 0)] {
if mol_has_no_bond_yet(&b, a[x], a[y]) {
b.add_bond(a[x], a[y], BondOrder::Aromatic).unwrap();
}
}
let mol = b.build();
let result = kekulize(&mol).expect("azulene kekulization failed");
let doubles = result.values().filter(|&&o| o == BondOrder::Double).count();
assert_eq!(doubles, 5, "azulene needs 5 double bonds");
}
#[test]
fn test_kekulize_acenaphthylene() {
let mut b = MoleculeBuilder::new();
let a: Vec<_> = (0..12)
.map(|_| b.add_atom(Atom::aromatic(Element::C)))
.collect();
for (x, y) in [(0,1),(1,2),(2,3),(3,4),(4,9),(9,0)] {
b.add_bond(a[x], a[y], BondOrder::Aromatic).unwrap();
}
for (x, y) in [(4,5),(5,6),(6,7),(7,8),(8,9)] {
b.add_bond(a[x], a[y], BondOrder::Aromatic).unwrap();
}
for (x, y) in [(0,11),(11,10),(10,1)] {
b.add_bond(a[x], a[y], BondOrder::Aromatic).unwrap();
}
let mol = b.build();
let result = kekulize(&mol).expect("acenaphthylene kekulization failed");
let doubles = result.values().filter(|&&o| o == BondOrder::Double).count();
assert_eq!(doubles, 6, "acenaphthylene needs 6 double bonds");
}
fn biphenylene() -> Molecule {
let mut b = MoleculeBuilder::new();
let a: Vec<_> = (0..12)
.map(|_| b.add_atom(Atom::aromatic(Element::C)))
.collect();
for i in 0..6 {
b.add_bond(a[i], a[(i + 1) % 6], BondOrder::Aromatic).unwrap();
}
for i in 0..6 {
b.add_bond(a[6 + i], a[6 + (i + 1) % 6], BondOrder::Aromatic).unwrap();
}
b.add_bond(a[5], a[6], BondOrder::Aromatic).unwrap();
b.add_bond(a[0], a[11], BondOrder::Aromatic).unwrap();
b.build()
}
fn anthracene() -> Molecule {
let mut b = MoleculeBuilder::new();
let a: Vec<_> = (0..14)
.map(|_| b.add_atom(Atom::aromatic(Element::C)))
.collect();
for i in 0..6 {
b.add_bond(a[i], a[(i + 1) % 6], BondOrder::Aromatic).unwrap();
}
for (x, y) in [(4,9),(9,8),(8,7),(7,6),(6,5)] {
b.add_bond(a[x], a[y], BondOrder::Aromatic).unwrap();
}
for (x, y) in [(7,13),(13,12),(12,11),(11,10),(10,6)] {
b.add_bond(a[x], a[y], BondOrder::Aromatic).unwrap();
}
b.build()
}
#[test]
fn test_kekulize_biphenylene() {
let mol = biphenylene();
let result = kekulize(&mol).expect("biphenylene kekulization should succeed");
let doubles = result.values().filter(|&&o| o == BondOrder::Double).count();
assert_eq!(doubles, 6, "biphenylene needs 6 double bonds");
}
#[test]
fn test_kekulize_anthracene() {
let mol = anthracene();
let result = kekulize(&mol).expect("anthracene kekulization should succeed");
let doubles = result.values().filter(|&&o| o == BondOrder::Double).count();
assert_eq!(doubles, 7, "anthracene needs 7 double bonds");
}
#[test]
fn test_kekulize_biphenylene_double_bond_count() {
let mol = biphenylene();
let result = kekulize(&mol).expect("biphenylene kekulization");
let singles = result.values().filter(|&&o| o == BondOrder::Single).count();
let doubles = result.values().filter(|&&o| o == BondOrder::Double).count();
assert_eq!(singles + doubles, 14, "biphenylene has 14 aromatic bonds");
}
#[test]
fn test_kekulize_large_fused_6rings() {
let mol = {
let mut b = MoleculeBuilder::new();
let a: Vec<_> = (0..16)
.map(|_| b.add_atom(Atom::aromatic(Element::C)))
.collect();
for i in 0..6 { b.add_bond(a[i], a[(i+1)%6], BondOrder::Aromatic).unwrap(); }
for (x,y) in [(4,9),(9,8),(8,7),(7,6),(6,5)] {
b.add_bond(a[x], a[y], BondOrder::Aromatic).unwrap();
}
for (x,y) in [(2,11),(11,10),(10,13),(13,12),(12,1)] {
b.add_bond(a[x], a[y], BondOrder::Aromatic).unwrap();
}
for (x,y) in [(7,15),(15,14),(14,11)] {
b.add_bond(a[x], a[y], BondOrder::Aromatic).unwrap();
}
b.build()
};
let result = kekulize(&mol).expect("4-ring PAH kekulization should succeed");
let doubles = result.values().filter(|&&o| o == BondOrder::Double).count();
assert!(doubles >= 6, "4-ring PAH needs at least 6 double bonds, got {doubles}");
}
#[test]
fn test_kekulize_deterministic() {
let mol1 = biphenylene();
let mol2 = biphenylene();
let r1 = kekulize(&mol1).expect("pass1");
let r2 = kekulize(&mol2).expect("pass2");
assert_eq!(
r1.values().filter(|&&o| o == BondOrder::Double).count(),
r2.values().filter(|&&o| o == BondOrder::Double).count(),
"kekulization must be deterministic"
);
}
#[test]
fn test_kekulize_fluoranthene() {
let mut b = MoleculeBuilder::new();
let a: Vec<_> = (0..16)
.map(|_| b.add_atom(Atom::aromatic(Element::C)))
.collect();
for i in 0..6 { b.add_bond(a[i], a[(i+1)%6], BondOrder::Aromatic).unwrap(); }
for (x,y) in [(5,6),(6,7),(7,8),(8,9),(9,0)] {
if mol_has_no_bond_yet(&b, a[x], a[y]) {
b.add_bond(a[x], a[y], BondOrder::Aromatic).unwrap();
}
}
for (x,y) in [(2,10),(10,11),(11,12),(12,13),(13,1)] {
if mol_has_no_bond_yet(&b, a[x], a[y]) {
b.add_bond(a[x], a[y], BondOrder::Aromatic).unwrap();
}
}
for (x,y) in [(8,14),(14,15),(15,13),(13,9)] {
if mol_has_no_bond_yet(&b, a[x], a[y]) {
b.add_bond(a[x], a[y], BondOrder::Aromatic).unwrap();
}
}
let mol = b.build();
let result = kekulize(&mol).expect("fluoranthene-like kekulization failed");
let doubles = result.values().filter(|&&o| o == BondOrder::Double).count();
assert_eq!(doubles, 8, "fluoranthene-like structure needs 8 double bonds");
}
#[test]
fn kekulize_indolizine() {
let mut b = MoleculeBuilder::new();
let c: Vec<_> = (0..9)
.map(|i| if i == 3 { b.add_atom(Atom::aromatic(Element::N)) }
else { b.add_atom(Atom::aromatic(Element::C)) })
.collect();
for (x, y) in [(0,1),(1,2),(2,3),(3,4),(4,5),(5,6),(6,7),(7,3),(7,8),(8,0)] {
b.add_bond(c[x], c[y], BondOrder::Aromatic).unwrap();
}
let mol = b.build();
let result = kekulize(&mol);
assert!(result.is_ok(), "indolizine kekulization failed: {:?}", result.err());
let doubles = result.unwrap().values().filter(|&&o| o == BondOrder::Double).count();
assert_eq!(doubles, 4, "indolizine: 4 double bonds (N lone-pair donor)");
}
#[test]
fn kekulize_quinolizine() {
let mut b = MoleculeBuilder::new();
let c: Vec<_> = (0..10)
.map(|i| if i == 3 { b.add_atom(Atom::aromatic(Element::N)) }
else { b.add_atom(Atom::aromatic(Element::C)) })
.collect();
for (x, y) in [(0,1),(1,2),(2,3),(3,4),(4,5),(5,6),(6,7),(7,8),(8,3),(8,9),(9,0)] {
b.add_bond(c[x], c[y], BondOrder::Aromatic).unwrap();
}
let mol = b.build();
let result = kekulize(&mol);
assert!(result.is_ok(), "quinolizine kekulization failed: {:?}", result.err());
let doubles = result.unwrap().values().filter(|&&o| o == BondOrder::Double).count();
assert_eq!(doubles, 5, "quinolizine: 5 double bonds");
}
#[test]
fn kekulize_corannulene() {
let mut b = MoleculeBuilder::new();
let a: Vec<_> = (0..20).map(|_| b.add_atom(Atom::aromatic(Element::C))).collect();
let edges: &[(usize,usize)] = &[
(0,1),(1,2),(2,3),(3,4),(4,0),
(0,5),(1,7),(2,9),(3,11),(4,13),
(5,6),(6,7),(7,8),(8,9),(9,10),(10,11),(11,12),(12,13),(13,14),(14,5),
(5,15),(6,15),(7,16),(8,16),(9,17),(10,17),(11,18),(12,18),(13,19),(14,19),
];
for &(x, y) in edges {
b.add_bond(a[x], a[y], BondOrder::Aromatic).unwrap();
}
let mol = b.build();
let result = kekulize(&mol);
assert!(result.is_ok(), "corannulene kekulization failed: {:?}", result.err());
let doubles = result.unwrap().values().filter(|&&o| o == BondOrder::Double).count();
assert_eq!(doubles, 10, "corannulene: 10 double bonds");
}
#[test]
fn kekulize_boron_azine() {
let mut b = MoleculeBuilder::new();
let atoms: Vec<_> = (0..6).map(|i| {
if i == 0 { b.add_atom(Atom::aromatic(Element::B)) }
else if i == 5 { b.add_atom(Atom::aromatic(Element::N)) }
else { b.add_atom(Atom::aromatic(Element::C)) }
}).collect();
for i in 0..6 {
b.add_bond(atoms[i], atoms[(i + 1) % 6], BondOrder::Aromatic).unwrap();
}
let mol = b.build();
let result = kekulize(&mol);
assert!(result.is_ok(), "b1ccccn1 kekulization failed: {:?}", result.err());
let doubles = result.unwrap().values().filter(|&&o| o == BondOrder::Double).count();
assert_eq!(doubles, 3, "b1ccccn1: 3 double bonds");
}
}