Skip to content

Commit

Permalink
Merge pull request #255 from choderalab/update/assigncharges
Browse files Browse the repository at this point in the history
Updates for OEAssignCharges to allow use of new, improved OE charging method
  • Loading branch information
davidlmobley authored May 17, 2017
2 parents 9f66bfe + 68c19a6 commit 77a71ac
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 18 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ script:
- conda install --yes --use-local ${PACKAGENAME}-dev
# Install testing dependencies
- conda install --yes --quiet nose nose-timer
- conda install --yes --quiet openmmtools
- conda install --yes --quiet openmmtools
- conda install --yes --quiet packmol
- conda install --yes --quiet rdkit
# Test the package
Expand Down
20 changes: 16 additions & 4 deletions openmoltools/openeye.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@

# Note: We recommend having every function return *copies* of input, to avoid headaches associated with in-place changes

def get_charges(molecule, max_confs=800, strictStereo=True, normalize=True, keep_confs=None):
def get_charges(molecule, max_confs=800, strictStereo=True,
normalize=True, keep_confs=None, legacy=True):
"""Generate charges for an OpenEye OEMol molecule.
Parameters
Expand All @@ -34,6 +35,9 @@ def get_charges(molecule, max_confs=800, strictStereo=True, normalize=True, keep
Otherwise, keep_confs = N will return an OEMol with up to N
generated conformations. Multiple conformations are still used to
*determine* the charges.
legacy : bool, default=True
If False, uses the new OpenEye charging engine.
See https://docs.eyesopen.com/toolkits/python/quacpactk/OEProtonFunctions/OEAssignCharges.html#
Returns
-------
Expand Down Expand Up @@ -62,10 +66,16 @@ def get_charges(molecule, max_confs=800, strictStereo=True, normalize=True, keep

charged_copy = generate_conformers(molecule, max_confs=max_confs, strictStereo=strictStereo) # Generate up to max_confs conformers

status = oequacpac.OEAssignPartialCharges(charged_copy, oequacpac.OECharges_AM1BCCSym) # AM1BCCSym recommended by Chris Bayly to KAB+JDC, Oct. 20 2014.
if not legacy:
# 2017.2.1 OEToolkits new charging function
status = oequacpac.OEAssignCharges(charged_copy, oequacpac.OEAM1BCCCharges())
if not status: raise(RuntimeError("OEAssignCharges failed."))
else:
# AM1BCCSym recommended by Chris Bayly to KAB+JDC, Oct. 20 2014.
status = oequacpac.OEAssignPartialCharges(charged_copy, oequacpac.OECharges_AM1BCCSym)
if not status: raise(RuntimeError("OEAssignPartialCharges returned error code %d" % status))


if not status:
raise(RuntimeError("OEAssignPartialCharges returned error code %d" % status))

#Determine conformations to return
if keep_confs == None:
Expand All @@ -78,6 +88,8 @@ def get_charges(molecule, max_confs=800, strictStereo=True, normalize=True, keep
#Copy coordinates to single conformer
charged_copy.SetCoords( original )
elif keep_confs > 0:
logger.debug("keep_confs was set to %s. Molecule positions will be reset." % keep_confs)

#Otherwise if a number is provided, return this many confs if available
for k, conf in enumerate( charged_copy.GetConfs() ):
if k > keep_confs - 1:
Expand Down
38 changes: 25 additions & 13 deletions openmoltools/tests/test_openeye.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def test_butanol_unnormalized():
m0 = openmoltools.openeye.iupac_to_oemol("butanol")
m0.SetTitle("MyCustomTitle")
m1 = openmoltools.openeye.get_charges(m0, normalize=False, keep_confs=1)
eq(m0.NumAtoms(), m1.NumAtoms())
eq(m0.NumAtoms(), m1.NumAtoms())
assert m1.NumConfs() == 1, "This OEMol was created to have a single conformation."
assert m1.NumAtoms() == 15, "Butanol should have 15 atoms"
assert m0.GetTitle() == m1.GetTitle(), "The title of the molecule should not be changed by normalization."
Expand Down Expand Up @@ -91,19 +91,19 @@ def test_butanol():
eq(m0.NumAtoms(), m1.NumAtoms())
assert m1.NumConfs() >= 2, "Butanol should have multiple conformers."
assert m1.NumAtoms() == 15, "Butanol should have 15 atoms"

all_data = {}
for k, molecule in enumerate(m1.GetConfs()):
names_to_charges, str_repr = openmoltools.openeye.get_names_to_charges(molecule)
all_data[k] = names_to_charges
eq(sum(names_to_charges.values()), 0.0, decimal=7) # Net charge should be zero

# Build a table of charges indexed by conformer number and atom name
all_data = pd.DataFrame(all_data)

# The standard deviation along the conformer axis should be zero if all conformers have same charges
eq(all_data.std(1).values, np.zeros(m1.NumAtoms()), decimal=7)

with utils.enter_temp_directory():
# Try saving to disk as mol2
openmoltools.openeye.molecule_to_mol2(m1, "out.mol2")
Expand All @@ -128,10 +128,10 @@ def test_benzene():
print(m1.NumConfs())
assert m1.NumConfs() == 1, "Benezene should have 1 conformer"
assert m1.NumAtoms() == 12, "Benezene should have 12 atoms"

names_to_charges, str_repr = openmoltools.openeye.get_names_to_charges(m1)
eq(sum(names_to_charges.values()), 0.0, decimal=7) # Net charge should be zero

with utils.enter_temp_directory():
# Try saving to disk as mol2
openmoltools.openeye.molecule_to_mol2(m1, "out.mol2")
Expand All @@ -149,7 +149,7 @@ def test_benzene():


@skipIf(not HAVE_OE, "Cannot test openeye module without OpenEye tools.")
def test_link_in_utils():
def test_link_in_utils():
m0 = openmoltools.openeye.iupac_to_oemol("benzene")
m1 = openmoltools.openeye.get_charges(m0)
with utils.enter_temp_directory():
Expand All @@ -170,7 +170,7 @@ def test_smiles():

@skipIf(not HAVE_OE, "Cannot test openeye module without OpenEye tools.")
def test_ffxml():
with utils.enter_temp_directory():
with utils.enter_temp_directory():
m0 = openmoltools.openeye.smiles_to_oemol("CCCCO")
charged0 = openmoltools.openeye.get_charges(m0)
m1 = openmoltools.openeye.smiles_to_oemol("ClC(Cl)(Cl)Cl")
Expand All @@ -182,7 +182,7 @@ def test_ffxml():
@skipIf(not HAVE_OE, "Cannot test openeye module without OpenEye tools.")
def test_ffxml_simulation():
"""Test converting toluene and benzene smiles to oemol to ffxml to openmm simulation."""
with utils.enter_temp_directory():
with utils.enter_temp_directory():
m0 = openmoltools.openeye.smiles_to_oemol("Cc1ccccc1")
charged0 = openmoltools.openeye.get_charges(m0)
m1 = openmoltools.openeye.smiles_to_oemol("c1ccccc1")
Expand All @@ -208,11 +208,11 @@ def test_ffxml_simulation():
for k, ligand in enumerate(ligands):
ligand_traj = trajectories[k]
ligand_traj.center_coordinates()

eq(ligand_traj.n_atoms, n_atoms[k])
eq(ligand_traj.n_frames, 1)

#Move the pre-centered ligand sufficiently far away from the protein to avoid a clash.
#Move the pre-centered ligand sufficiently far away from the protein to avoid a clash.
min_atom_pair_distance = ((ligand_traj.xyz[0] ** 2.).sum(1) ** 0.5).max() + ((protein_traj.xyz[0] ** 2.).sum(1) ** 0.5).max() + 0.3
ligand_traj.xyz += np.array([1.0, 0.0, 0.0]) * min_atom_pair_distance

Expand Down Expand Up @@ -252,7 +252,7 @@ def test_charge_fail2():

@skipIf(not HAVE_OE, "Cannot test openeye module without OpenEye tools.")
def test_charge_success1():
with utils.enter_temp_directory():
with utils.enter_temp_directory():
openmoltools.openeye.smiles_to_antechamber(smiles_fails_with_strictStereo, "test.mol2", "test.frcmod", strictStereo=False)


Expand All @@ -261,6 +261,18 @@ def test_charge_success2():
m = openmoltools.openeye.smiles_to_oemol(smiles_fails_with_strictStereo)
m = openmoltools.openeye.get_charges(m, strictStereo=False)

@skipIf(not HAVE_OE, "Cannot test openeye module without OpenEye tools.")
@raises(RuntimeError)
def test_oeassigncharges_fail():
# Fail test for OEToolkits (2017.2.1) new charging function
m = openmoltools.openeye.smiles_to_oemol(smiles_fails_with_strictStereo)
m = openmoltools.openeye.get_charges(m, strictStereo=False, legacy=False)

@skipIf(not HAVE_OE, "Cannot test openeye module without OpenEye tools.")
def test_oeassigncharges_success():
# Success test for OEToolkits (2017.2.1) new charging function
m = openmoltools.openeye.iupac_to_oemol("butanol")
m = openmoltools.openeye.get_charges(m, legacy=False)

@skipIf(not HAVE_OE, "Cannot test openeye module without OpenEye tools.")
@skipIf(not HAVE_PARMED, "Cannot test without Parmed Chemistry.")
Expand Down

0 comments on commit 77a71ac

Please sign in to comment.