Skip to content

Commit 60e74f9

Browse files
authored
Merge pull request #293 from OpenBioSim/fix_292
Fix issue #292
2 parents 750cada + 83f64b6 commit 60e74f9

File tree

6 files changed

+48
-83
lines changed

6 files changed

+48
-83
lines changed

doc/source/changelog.rst

+2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ organisation on `GitHub <https://github.com/openbiosim/sire>`__.
1919
* Please add an item to this CHANGELOG for any new features or bug fixes when creating a PR.
2020
* Allow user to force fresh inference of stereochemistry when converting to RDKit format.
2121
* Fix setting of positive formal charge when reading SDF files.
22+
* Only use ``atom->setNoImplicit(True)`` inside custom RDKit sterochemistry inference function.
23+
* Fix redistribution of excess QM charge and make it the default behaviour.
2224

2325
`2024.4.0 <https://github.com/openbiosim/sire/compare/2024.3.1...2024.4.0>`__ - Feb 2025
2426
----------------------------------------------------------------------------------------

src/sire/qm/__init__.py

+6-3
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def create_engine(
1818
cutoff="7.5A",
1919
neighbour_list_frequency=0,
2020
mechanical_embedding=False,
21-
redistribute_charge=False,
21+
redistribute_charge=True,
2222
map=None,
2323
):
2424
"""
@@ -68,8 +68,11 @@ def create_engine(
6868
6969
redistribute_charge : bool
7070
Whether to redistribute charge of the QM atoms to ensure that the total
71-
charge of the QM region is an integer. Excess charge is redistributed
72-
over the non QM atoms within the residues involved in the QM region.
71+
charge of the QM region is an integer. If the QM region is a an entire
72+
molecule, then the charge on each atom is shifted so that the total
73+
charge is an integer. Alternatively, when the QM region is part of a
74+
molecule, the excess charge is redistributed over the MM atoms within
75+
the residues of the QM region.
7376
7477
Returns
7578
-------

src/sire/qm/_emle.py

+6-3
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ def emle(
107107
calculator,
108108
cutoff="7.5A",
109109
neighbour_list_frequency=0,
110-
redistribute_charge=False,
110+
redistribute_charge=True,
111111
map=None,
112112
):
113113
"""
@@ -137,8 +137,11 @@ def emle(
137137
138138
redistribute_charge : bool
139139
Whether to redistribute charge of the QM atoms to ensure that the total
140-
charge of the QM region is an integer. Excess charge is redistributed
141-
over the non QM atoms within the residues involved in the QM region.
140+
charge of the QM region is an integer. If the QM region is a an entire
141+
molecule, then the charge on each atom is shifted so that the total
142+
charge is an integer. Alternatively, when the QM region is part of a
143+
molecule, the excess charge is redistributed over the MM atoms within
144+
the residues of the QM region.
142145
143146
Returns
144147
-------

src/sire/qm/_utils.py

+14-4
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ def _check_charge(mols, qm_atoms, map, redistribute_charge=False, tol=1e-6):
102102
"""
103103

104104
import math as _math
105+
import sys as _sys
105106

106107
from sire.units import e_charge as _e_charge
107108

@@ -127,8 +128,16 @@ def _check_charge(mols, qm_atoms, map, redistribute_charge=False, tol=1e-6):
127128
# Redistribute the charge over the QM atoms.
128129
qm_frac = excess_charge / len(qm_atoms)
129130

130-
# Redistribute the charge over the non QM atoms.
131-
rem_frac = excess_charge / (residues.num_atoms() - len(qm_atoms))
131+
# Work out the number of non-QM atoms.
132+
num_mm = residues.num_atoms() - len(qm_atoms)
133+
134+
# Redistribute the charge over the MM atoms.
135+
if num_mm > 0:
136+
rem_frac = excess_charge / num_mm
137+
138+
print(
139+
f"Redistributing excess QM charge of {excess_charge}", file=_sys.stderr
140+
)
132141

133142
# Loop over the residues.
134143
for res in residues:
@@ -140,10 +149,11 @@ def _check_charge(mols, qm_atoms, map, redistribute_charge=False, tol=1e-6):
140149

141150
# Loop over the atoms in the residue.
142151
for atom in res:
143-
# Shift the charge.
152+
# Shift the charge of the QM atoms.
144153
if atom in qm_atoms:
145154
cursor[atom][charge_prop] -= qm_frac
146-
else:
155+
# Shift the charge of the MM atoms.
156+
elif num_mm > 0:
147157
cursor[atom][charge_prop] += rem_frac
148158

149159
# Commit the changes.

tests/convert/test_rdkit.py

+17-5
Original file line numberDiff line numberDiff line change
@@ -122,16 +122,11 @@ def test_rdkit_infer_bonds(ejm55_sdf, ejm55_gro):
122122
sdf = ejm55_sdf[0].molecule()
123123
gro = ejm55_gro["not (protein or water)"].molecule()
124124

125-
from rdkit import Chem
126-
127125
assert sdf.smiles() == gro.smiles()
128126

129127
match_sdf = sdf["smarts [NX3][CX3](=[OX1])[#6]"]
130128
match_gro = gro["smarts [NX3][CX3](=[OX1])[#6]"]
131129

132-
print(match_sdf)
133-
print(match_gro)
134-
135130
assert len(match_sdf) == 1
136131
assert len(match_gro) == 1
137132

@@ -167,3 +162,20 @@ def test_rdkit_preserve_info(ala_mols, ejm55_gro):
167162

168163
assert res0.name() == res1.name()
169164
assert res0.number() == res1.number()
165+
166+
167+
@pytest.mark.skipif(
168+
"rdkit" not in sr.convert.supported_formats(),
169+
reason="rdkit support is not available",
170+
)
171+
def test_rdkit_force_infer():
172+
mol = sr.load_test_files("missing_cyanide_bond.sdf")[0]
173+
174+
rdmol = sr.convert.to(mol, "rdkit", map={"force_stereo_inference": False})
175+
rdmol_infer = sr.convert.to(mol, "rdkit", map={"force_stereo_inference": True})
176+
177+
bond = rdmol.GetBonds()[0].GetBondType().name
178+
bond_infer = rdmol_infer.GetBonds()[0].GetBondType().name
179+
180+
assert bond == "SINGLE"
181+
assert bond_infer == "TRIPLE"

wrapper/Convert/SireRDKit/sire_rdkit.cpp

+3-68
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,7 @@ namespace SireRDKit
411411
{
412412
if (atom->getAtomicNum() > 1)
413413
{
414+
atom->setNoImplicit(true);
414415
atoms.append(std::make_pair(get_nb_unpaired_electrons(*atom),
415416
atom));
416417
}
@@ -646,9 +647,6 @@ namespace SireRDKit
646647

647648
a->setAtomicNum(element.nProtons());
648649

649-
// don't automatically add hydrogens
650-
a->setNoImplicit(true);
651-
652650
elements.append(element);
653651

654652
try
@@ -791,74 +789,11 @@ namespace SireRDKit
791789
infer_bond_info(molecule);
792790
}
793791

794-
// try each sanitisation step in turn, skipping failed
795-
try
796-
{
797-
RDKit::MolOps::cleanUp(molecule);
798-
}
799-
catch (...)
800-
{
801-
}
802-
803-
try
804-
{
805-
molecule.updatePropertyCache();
806-
}
807-
catch (...)
808-
{
809-
}
810-
811-
try
812-
{
813-
RDKit::MolOps::symmetrizeSSSR(molecule);
814-
}
815-
catch (...)
816-
{
817-
}
818-
819-
try
820-
{
821-
RDKit::MolOps::Kekulize(molecule);
822-
}
823-
catch (...)
824-
{
825-
}
826-
827-
try
828-
{
829-
RDKit::MolOps::assignRadicals(molecule);
830-
}
831-
catch (...)
832-
{
833-
}
834-
835-
try
836-
{
837-
RDKit::MolOps::setAromaticity(molecule);
838-
}
839-
catch (...)
840-
{
841-
}
842-
843-
try
844-
{
845-
RDKit::MolOps::setConjugation(molecule);
846-
}
847-
catch (...)
848-
{
849-
}
850-
792+
// sanitze the molecule.
851793
try
852794
{
853-
RDKit::MolOps::setHybridization(molecule);
854-
}
855-
catch (...)
856-
{
857-
}
795+
RDKit::MolOps::sanitizeMol(molecule);
858796

859-
try
860-
{
861-
RDKit::MolOps::cleanupChirality(molecule);
862797
}
863798
catch (...)
864799
{

0 commit comments

Comments
 (0)