Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion arc/species/conformers.py
Original file line number Diff line number Diff line change
Expand Up @@ -1465,7 +1465,8 @@ def embed_rdkit(label, mol, num_confs=None, xyz=None):
xyz (dict, optional): The 3D coordinates.

Returns:
RDMol | None: An RDKIt molecule with embedded conformers.
RDMol | None: An RDKIt molecule with embedded conformers,
or ``None`` if no conformers could be embedded.
"""
if num_confs is None and xyz is None:
raise ConformerError(f'Either num_confs or xyz must be set when calling embed_rdkit() for {label}')
Expand All @@ -1482,6 +1483,10 @@ def embed_rdkit(label, mol, num_confs=None, xyz=None):
except Exception as e:
logger.warning(f'Could not embed conformers using RDKit for {label}, failed with: {e}')
return None
if not rd_mol.GetNumConformers():
logger.warning(f'Could not embed conformers using RDKit for {label}, '
f'RDKit returned no conformers without raising an error.')
return None
elif xyz is not None:
rd_conf = Chem.Conformer(rd_mol.GetNumAtoms())
for i in range(rd_mol.GetNumAtoms()):
Expand Down
11 changes: 11 additions & 0 deletions arc/species/conformers_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -681,6 +681,17 @@ def test_embed_rdkit_reports_why_embedding_failed(self):
self.assertIsNone(rd_mol)
self.assertIn('Bad Conformer Id', '\n'.join(captured.output))

def test_embed_rdkit_does_not_return_a_conformer_less_molecule(self):
"""Test that an embedding which yields no conformers returns None rather than an unusable molecule"""
spc = ARCSpecies(label='c-C3H2', smiles='C1#CC1')
with self.assertLogs('arc', level='WARNING') as captured:
rd_mol = conformers.embed_rdkit(label='c-C3H2', mol=spc.mol, num_confs=5)
if rd_mol is not None:
xyzs = conformers.read_rdkit_embedded_conformers(label='c-C3H2', rd_mol=rd_mol)
self.assertIsInstance(xyzs[0], dict)
self.assertIsNone(rd_mol)
self.assertIn('c-C3H2', '\n'.join(captured.output))

def test_rdkit_force_field_abandons_an_optimization_that_raises(self):
"""Test that a raising optimization is logged and attempted once per conformer"""
rd_mol = conformers.embed_rdkit(label='CJ', mol=self.cj_spc.mol, num_confs=1)
Expand Down
8 changes: 5 additions & 3 deletions arc/species/converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -1614,6 +1614,7 @@ def to_rdkit_mol(mol, remove_h=False, sanitize=True):

Returns:
RDMol: An RDKit molecule object corresponding to the input RMG Molecule object.
If sanitization was requested but failed, an unsanitized molecule is returned and a warning is logged.
"""
atom_id_map = dict()

Expand Down Expand Up @@ -1654,9 +1655,10 @@ def to_rdkit_mol(mol, remove_h=False, sanitize=True):
if sanitize:
try:
Chem.SanitizeMol(rd_mol)
except AtomValenceException:
# [C-]#[O+] raises this
pass
except AtomValenceException as e:
logger.warning(f'Could not sanitize the RDKit molecule of {mol_copy.get_formula()} '
f'(multiplicity {mol_copy.multiplicity}), returning an unsanitized RDKit molecule. '
f'Got {e.__class__.__name__}: {e}')
if remove_h:
rd_mol = Chem.RemoveHs(rd_mol, sanitize=sanitize)
return rd_mol
Expand Down
11 changes: 11 additions & 0 deletions arc/species/converter_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -3432,6 +3432,17 @@ def test_to_rdkit_mol(self):
rd_mol_block = Chem.MolToMolBlock(rd_mol).splitlines()
self._check_atom_connectivity_in_rd_mol_block(spc3.mol, rd_mol_block)

def test_to_rdkit_mol_reports_a_sanitization_failure(self):
"""Test that a molecule RDKit refuses to sanitize is reported and still returned"""
spc = ARCSpecies(label='CO', smiles='[C-]#[O+]')
with self.assertLogs('arc', level='WARNING') as captured:
rd_mol = converter.to_rdkit_mol(spc.mol)
self.assertIsInstance(rd_mol, rdchem.Mol)
log = '\n'.join(captured.output)
self.assertIn('AtomValenceException', log)
self.assertIn('Explicit valence', log)
self.assertIn('unsanitized', log)

def test_xyz_to_ase(self):
"""Test the xyz_to_ase function"""
atoms_1 = converter.xyz_to_ase(self.xyz1['dict'])
Expand Down
Loading