def _generate_charges(self, molecule): """Generates a set of partial charges for a molecule using the specified charge backend. Parameters ---------- molecule: openforcefield.topology.Molecule The molecule to assign charges to. """ if self.charge_backend == BuildTLeapSystem.ChargeBackend.OpenEye: from openforcefield.utils.toolkits import OpenEyeToolkitWrapper toolkit_wrapper = OpenEyeToolkitWrapper() elif self.charge_backend == BuildTLeapSystem.ChargeBackend.AmberTools: from openforcefield.utils.toolkits import ( RDKitToolkitWrapper, AmberToolsToolkitWrapper, ToolkitRegistry, ) toolkit_wrapper = ToolkitRegistry(toolkit_precedence=[ RDKitToolkitWrapper, AmberToolsToolkitWrapper ]) else: raise ValueError(f"Invalid toolkit specification.") molecule.generate_conformers(toolkit_registry=toolkit_wrapper) molecule.compute_partial_charges_am1bcc( toolkit_registry=toolkit_wrapper)
def test_openeye_from_smiles_hydrogens_are_explicit(self): """ Test to ensure that OpenEyeToolkitWrapper.from_smiles has the proper behavior with respect to its hydrogens_are_explicit kwarg """ toolkit_wrapper = OpenEyeToolkitWrapper() smiles_impl = "C#C" with pytest.raises( ValueError, match= "but OpenEye Toolkit interpreted SMILES 'C#C' as having implicit hydrogen" ) as excinfo: offmol = Molecule.from_smiles(smiles_impl, toolkit_registry=toolkit_wrapper, hydrogens_are_explicit=True) offmol = Molecule.from_smiles(smiles_impl, toolkit_registry=toolkit_wrapper, hydrogens_are_explicit=False) assert offmol.n_atoms == 4 smiles_expl = "HC#CH" offmol = Molecule.from_smiles(smiles_expl, toolkit_registry=toolkit_wrapper, hydrogens_are_explicit=True) assert offmol.n_atoms == 4 # It's debatable whether this next function should pass. Strictly speaking, the hydrogens in this SMILES # _are_ explicit, so allowing "hydrogens_are_explicit=False" through here is allowing a contradiction. # We might rethink the name of this kwarg. offmol = Molecule.from_smiles(smiles_expl, toolkit_registry=toolkit_wrapper, hydrogens_are_explicit=False) assert offmol.n_atoms == 4
def test_get_mol2_gaff_atom_types(self): """Test that a warning is raised OpenEyeToolkitWrapper when it detects GAFF atom types in a mol2 file.""" toolkit_wrapper = OpenEyeToolkitWrapper() mol2_file_path = get_data_file_path( 'molecules/AlkEthOH_test_filt1_ff.mol2') with pytest.warns(GAFFAtomTypeWarning, match='SYBYL'): Molecule.from_file(mol2_file_path, toolkit_registry=toolkit_wrapper)
def test_compute_partial_charges_trans_cooh_am1bcc(self): """Test OpenEyeToolkitWrapper for computing partial charges for problematic molecules, as exemplified by Issue 346 (https://github.com/openforcefield/openforcefield/issues/346)""" lysine = Molecule.from_smiles("C(CC[NH3+])C[C@@H](C(=O)O)N") toolkit_wrapper = OpenEyeToolkitWrapper() lysine.generate_conformers(toolkit_registry=toolkit_wrapper) lysine.compute_partial_charges_am1bcc(toolkit_registry=toolkit_wrapper)
def test_get_sdf_coordinates(self): """Test OpenEyeToolkitWrapper for importing a single set of coordinates from a sdf file""" toolkit_wrapper = OpenEyeToolkitWrapper() filename = get_data_file_path('molecules/toluene.sdf') molecule = Molecule.from_file(filename, toolkit_registry=toolkit_wrapper) assert len(molecule._conformers) == 1 assert molecule._conformers[0].shape == (15, 3)
def test_generate_conformers(self): """Test OpenEyeToolkitWrapper generate_conformers()""" toolkit_wrapper = OpenEyeToolkitWrapper() smiles = '[H]C([H])([H])C([H])([H])[H]' molecule = toolkit_wrapper.from_smiles(smiles) molecule.generate_conformers() assert molecule.n_conformers != 0 assert not (molecule.conformers[0] == (0. * unit.angstrom)).all()
def test_smiles_charged(self): """Test OpenEyeToolkitWrapper functions for reading/writing charged SMILES""" toolkit_wrapper = OpenEyeToolkitWrapper() # This differs from RDKit's expected output due to different canonicalization schemes smiles = '[H]C([H])([H])[N+]([H])([H])[H]' molecule = Molecule.from_smiles(smiles, toolkit_registry=toolkit_wrapper) smiles2 = molecule.to_smiles(toolkit_registry=toolkit_wrapper) assert smiles == smiles2
def test_compute_wiberg_bond_orders_charged(self): """Test OpenEyeToolkitWrapper compute_wiberg_bond_orders() on a molecule with net charge +1""" toolkit_wrapper = OpenEyeToolkitWrapper() smiles = '[H]C([H])([H])[N+]([H])([H])[H]' molecule = toolkit_wrapper.from_smiles(smiles) molecule.generate_conformers(toolkit_registry=toolkit_wrapper) for charge_model in ['am1', 'pm3']: molecule.compute_wiberg_bond_orders( toolkit_registry=toolkit_wrapper, charge_model=charge_model)
def test_smiles_add_H(self): """Test OpenEyeToolkitWrapper for adding explicit hydrogens""" toolkit_wrapper = OpenEyeToolkitWrapper() # This differs from RDKit's SMILES due to different canonicalization schemes input_smiles = 'CC' expected_output_smiles = '[H]C([H])([H])C([H])([H])[H]' molecule = Molecule.from_smiles(input_smiles, toolkit_registry=toolkit_wrapper) smiles2 = molecule.to_smiles(toolkit_registry=toolkit_wrapper) assert expected_output_smiles == smiles2
def test_smiles(self): """Test OpenEyeToolkitWrapper to_smiles() and from_smiles()""" toolkit_wrapper = OpenEyeToolkitWrapper() # This differs from RDKit's SMILES due to different canonicalization schemes smiles = '[H]C([H])([H])C([H])([H])[H]' molecule = Molecule.from_smiles(smiles, toolkit_registry=toolkit_wrapper) smiles2 = molecule.to_smiles(toolkit_registry=toolkit_wrapper) assert smiles == smiles2
def test_compute_wiberg_bond_orders(self): """Test OpenEyeToolkitWrapper compute_wiberg_bond_orders()""" toolkit_wrapper = OpenEyeToolkitWrapper() smiles = '[H]C([H])([H])C([H])([H])[H]' molecule = toolkit_wrapper.from_smiles(smiles) molecule.generate_conformers(toolkit_registry=toolkit_wrapper) for charge_model in ['am1', 'pm3']: molecule.compute_wiberg_bond_orders( toolkit_registry=toolkit_wrapper, charge_model=charge_model) print([bond.fractional_bond_order for bond in molecule.bonds])
def __init__( self, smirks=None, label=None, validate_parsable=True, validate_valence_type=True, toolkit_registry=None, ): """Initialize a chemical environment abstract base class. smirks = string, optional if smirks is not None, a chemical environment is built from the provided SMIRKS string label = anything, optional intended to be used to label this chemical environment could be a string, int, or float, or anything validate_parsable: bool, optional, default=True If specified, ensure the provided smirks is parsable validate_valence_type : bool, optional, default=True If specified, ensure the tagged atoms are appropriate to the specified valence type toolkit_registry = string or ToolkitWrapper or ToolkitRegistry. Default = None Either a ToolkitRegistry, ToolkitWrapper, or the strings 'openeye' or 'rdkit', indicating the backend to use for validating the correct connectivity of the SMIRKS during initialization. If None, this function will use the GLOBAL_TOOLKIT_REGISTRY Raises ------ SMIRKSParsingError if smirks was unparsable SMIRKSMismatchError if smirks did not have expected connectivity between tagged atoms and validate_valence_type=True """ # Support string input for toolkit names for legacy reasons if toolkit_registry == "openeye": from openforcefield.utils.toolkits import OpenEyeToolkitWrapper toolkit_registry = OpenEyeToolkitWrapper() elif toolkit_registry == "rdkit": from openforcefield.utils.toolkits import RDKitToolkitWrapper toolkit_registry = RDKitToolkitWrapper() self.smirks = smirks self.label = label if validate_parsable or validate_valence_type: self.validate( validate_valence_type=validate_valence_type, toolkit_registry=toolkit_registry, )
def test_to_from_openeye_core_props_unset(self): """Test OpenEyeToolkitWrapper to_openeye() and from_openeye() when given empty core property fields""" toolkit_wrapper = OpenEyeToolkitWrapper() # Using a simple molecule with tetrahedral and bond stereochemistry input_smiles = r'C\C(F)=C(/F)C[C@](C)(Cl)Br' expected_output_smiles = r'[H]C([H])([H])/C(=C(/C([H])([H])[C@](C([H])([H])[H])(Cl)Br)\F)/F' molecule = Molecule.from_smiles(input_smiles, toolkit_registry=toolkit_wrapper) assert molecule.to_smiles( toolkit_registry=toolkit_wrapper) == expected_output_smiles # Ensure one atom has its stereochemistry specified central_carbon_stereo_specified = False for atom in molecule.atoms: if (atom.atomic_number == 6) and atom.stereochemistry == "R": central_carbon_stereo_specified = True assert central_carbon_stereo_specified # Do a first conversion to/from oemol rdmol = molecule.to_openeye() molecule2 = Molecule.from_openeye(rdmol) # Test that properties survived first conversion assert molecule.name == molecule2.name # NOTE: This expects the same indexing scheme in the original and new molecule central_carbon_stereo_specified = False for atom in molecule2.atoms: if (atom.atomic_number == 6) and atom.stereochemistry == "R": central_carbon_stereo_specified = True assert central_carbon_stereo_specified for atom1, atom2 in zip(molecule.atoms, molecule2.atoms): assert atom1.to_dict() == atom2.to_dict() for bond1, bond2 in zip(molecule.bonds, molecule2.bonds): assert bond1.to_dict() == bond2.to_dict() assert (molecule._conformers == None) assert (molecule2._conformers == None) for pc1, pc2 in zip(molecule._partial_charges, molecule2._partial_charges): pc1_ul = pc1 / unit.elementary_charge pc2_ul = pc2 / unit.elementary_charge assert_almost_equal(pc1_ul, pc2_ul, decimal=6) assert molecule2.to_smiles( toolkit_registry=toolkit_wrapper) == expected_output_smiles
def test_get_mol2_charges(self): """Test OpenEyeToolkitWrapper for importing a mol2 file specifying partial charges""" toolkit_wrapper = OpenEyeToolkitWrapper() filename = get_data_file_path('molecules/toluene_charged.mol2') molecule = Molecule.from_file(filename, toolkit_registry=toolkit_wrapper) assert len(molecule._conformers) == 1 assert molecule._conformers[0].shape == (15, 3) target_charges = unit.Quantity( np.array([ -0.1342, -0.1271, -0.1271, -0.1310, -0.1310, -0.0765, -0.0541, 0.1314, 0.1286, 0.1286, 0.1303, 0.1303, 0.0440, 0.0440, 0.0440 ]), unit.elementary_charge) for pc1, pc2 in zip(molecule._partial_charges, target_charges): pc1_ul = pc1 / unit.elementary_charge pc2_ul = pc2 / unit.elementary_charge assert_almost_equal(pc1_ul, pc2_ul, decimal=4)
def test_compute_wiberg_bond_orders_double_bond(self): """Test OpenEyeToolkitWrapper compute_wiberg_bond_orders() on a molecule with a double bond""" toolkit_wrapper = OpenEyeToolkitWrapper() smiles = r'C\C(F)=C(/F)C[C@@](C)(Cl)Br' molecule = toolkit_wrapper.from_smiles(smiles) molecule.generate_conformers(toolkit_registry=toolkit_wrapper) for charge_model in ['am1', 'pm3']: molecule.compute_wiberg_bond_orders( toolkit_registry=toolkit_wrapper, charge_model=charge_model) # TODO: Add test for equivalent Wiberg orders for equivalent bonds double_bond_has_wbo_near_2 = False for bond in molecule.bonds: if bond.bond_order == 2: if 1.75 < bond.fractional_bond_order < 2.25: double_bond_has_wbo_near_2 = True assert double_bond_has_wbo_near_2
def test_compute_partial_charges(self): """Test OpenEyeToolkitWrapper compute_partial_charges()""" toolkit_wrapper = OpenEyeToolkitWrapper() smiles = '[H]C([H])([H])C([H])([H])[H]' molecule = toolkit_wrapper.from_smiles(smiles) # Ensure that an exception is raised if no conformers are provided with pytest.raises(Exception) as excinfo: molecule.compute_partial_charges(toolkit_registry=toolkit_wrapper) molecule.generate_conformers(toolkit_registry=toolkit_wrapper) # Ensure that an exception is raised if an invalid charge model is passed in with pytest.raises(Exception) as excinfo: charge_model = 'notARealChargeModel' molecule.compute_partial_charges(toolkit_registry=toolkit_wrapper, charge_model=charge_model) # TODO: Test all supported charge models # Note: "amber" and "amberff94" only work for a subset of residue types, so we'll need to find testing data for # those # charge_model = [,'amber','amberff94'] # TODO: 'mmff' and 'mmff94' often assign charges of 0, presumably if the molecule is unrecognized. # charge_model = ['mmff', 'mmff94'] for charge_model in [ 'noop', 'am1bcc', 'am1bccnosymspt', 'am1bccelf10' ]: with pytest.raises(NotImplementedError) as excinfo: molecule.compute_partial_charges( toolkit_registry=toolkit_wrapper ) # , charge_model=charge_model) charge_sum = 0 * unit.elementary_charge for pc in molecule._partial_charges: charge_sum += pc assert charge_sum < 0.001 * unit.elementary_charge # For now, just test AM1-BCC while the SMIRNOFF spec for other charge models gets worked out molecule.compute_partial_charges_am1bcc( toolkit_registry=toolkit_wrapper) # , charge_model=charge_model) charge_sum = 0 * unit.elementary_charge for pc in molecule._partial_charges: charge_sum += pc assert charge_sum < 0.001 * unit.elementary_charge
def test_smiles_missing_stereochemistry(self): """Test OpenEyeToolkitWrapper to_smiles() and from_smiles()""" toolkit_wrapper = OpenEyeToolkitWrapper() unspec_chiral_smiles = r"C\C(F)=C(/F)CC(C)(Cl)Br" spec_chiral_smiles = r"C\C(F)=C(/F)C[C@@](C)(Cl)Br" unspec_db_smiles = r"CC(F)=C(F)C[C@@](C)(Cl)Br" spec_db_smiles = r"C\C(F)=C(/F)C[C@@](C)(Cl)Br" for title, smiles, raises_exception in [ ("unspec_chiral_smiles", unspec_chiral_smiles, True), ("spec_chiral_smiles", spec_chiral_smiles, False), ("unspec_db_smiles", unspec_db_smiles, True), ("spec_db_smiles", spec_db_smiles, False), ]: if raises_exception: with pytest.raises(UndefinedStereochemistryError) as context: molecule = Molecule.from_smiles( smiles, toolkit_registry=toolkit_wrapper) else: molecule = Molecule.from_smiles( smiles, toolkit_registry=toolkit_wrapper)
def _openeye_parameteriser(cls, mol, **kwargs): """ Creates a parameterised system from openeye molecule Parameters ---------- mol : oechem.OEMol """ try: forcefield = ForceField('test_forcefields/smirnoff99Frosst.offxml') molecule = Molecule.from_openeye( mol, allow_undefined_stereo=cls.allow_undefined_stereo) from openforcefield.utils.toolkits import OpenEyeToolkitWrapper molecule.compute_partial_charges_am1bcc( toolkit_registry=OpenEyeToolkitWrapper()) topology = Topology.from_molecules(molecule) openmm_system = forcefield.create_openmm_system( topology, charge_from_molecules=[molecule]) ligand_pmd = parmed.openmm.topsystem.load_topology( topology.to_openmm(), openmm_system, molecule._conformers[0]) except Exception as e: raise ValueError("Parameterisation Failed : {}".format(e)) #TODO ligand_pmd.title = cls.smiles for i in ligand_pmd.residues: i.name = 'LIG' tmp_dir = tempfile.mkdtemp() # We need all molecules as both pdb files (as packmol input) # and mdtraj.Trajectory for restoring bonds later. pdb_filename = tempfile.mktemp(suffix=".pdb", dir=tmp_dir) from openeye import oechem # OpenEye Python toolkits oechem.OEWriteMolecule(oechem.oemolostream(pdb_filename), mol) cls.pdb_filename = pdb_filename cls.ligand_pmd = ligand_pmd
def test_compute_partial_charges_net_charge(self): """Test OpenEyeToolkitWrapper compute_partial_charges() on a molecule with a net +1 charge""" toolkit_wrapper = OpenEyeToolkitWrapper() smiles = '[H]C([H])([H])[N+]([H])([H])[H]' molecule = toolkit_wrapper.from_smiles(smiles) molecule.generate_conformers(toolkit_registry=toolkit_wrapper) with pytest.raises(NotImplementedError) as excinfo: charge_model = 'notARealChargeModel' molecule.compute_partial_charges(toolkit_registry=toolkit_wrapper ) #, charge_model=charge_model) # TODO: Test all supported charge models # TODO: "amber" and "amberff94" only work for a subset of residue types, so we'll need to find testing data for # those # charge_model = [,'amber','amberff94'] # The 'noop' charge model doesn't add up to the formal charge, so we shouldn't test it # charge_model = ['noop'] for charge_model in [ 'mmff', 'mmff94', 'am1bcc', 'am1bccnosymspt', 'am1bccelf10' ]: with pytest.raises(NotImplementedError) as excinfo: molecule.compute_partial_charges( toolkit_registry=toolkit_wrapper ) #, charge_model=charge_model) charge_sum = 0 * unit.elementary_charge for pc in molecule._partial_charges: charge_sum += pc assert 0.999 * unit.elementary_charge < charge_sum < 1.001 * unit.elementary_charge # For now, I'm just testing AM1-BCC (will test more when the SMIRNOFF spec for other charges is finalized) molecule.compute_partial_charges_am1bcc( toolkit_registry=toolkit_wrapper) charge_sum = 0 * unit.elementary_charge for pc in molecule._partial_charges: charge_sum += pc assert 0.999 * unit.elementary_charge < charge_sum < 1.001 * unit.elementary_charge
def test_get_mol2_coordinates(self): """Test OpenEyeToolkitWrapper for importing a single set of molecule coordinates""" toolkit_wrapper = OpenEyeToolkitWrapper() filename = get_data_file_path('molecules/toluene.mol2') molecule1 = Molecule.from_file(filename, toolkit_registry=toolkit_wrapper) assert len(molecule1._conformers) == 1 assert molecule1._conformers[0].shape == (15, 3) assert_almost_equal(molecule1.conformers[0][5][1] / unit.angstrom, 22.98, decimal=2) # Test loading from file-like object with open(filename, 'r') as infile: molecule2 = Molecule(infile, file_format='MOL2', toolkit_registry=toolkit_wrapper) assert molecule1.is_isomorphic(molecule2) assert len(molecule2._conformers) == 1 assert molecule2._conformers[0].shape == (15, 3) assert_almost_equal(molecule2.conformers[0][5][1] / unit.angstrom, 22.98, decimal=2) # Test loading from gzipped mol2 import gzip with gzip.GzipFile(filename + '.gz', 'r') as infile: molecule3 = Molecule(infile, file_format='MOL2', toolkit_registry=toolkit_wrapper) assert molecule1.is_isomorphic(molecule3) assert len(molecule3._conformers) == 1 assert molecule3._conformers[0].shape == (15, 3) assert_almost_equal(molecule3.conformers[0][5][1] / unit.angstrom, 22.98, decimal=2)
import pytest from openforcefield.typing.chemistry import * from openforcefield.utils.toolkits import OPENEYE_AVAILABLE # TODO: Evaluate which tests in this file should be moved to test_toolkits toolkits = [] if OPENEYE_AVAILABLE: from openforcefield.utils.toolkits import OpenEyeToolkitWrapper, RDKitToolkitWrapper toolkits.append("openeye") toolkits.append(OpenEyeToolkitWrapper()) else: from openforcefield.utils.toolkits import RDKitToolkitWrapper toolkits.append("rdkit") toolkits.append(RDKitToolkitWrapper()) class TestChemicalEnvironments: def test_createEnvironments(self): """ Test all types of ChemicalEnvironment objects with defined atoms and bonds Each will be tetrahedral carbons connected by ring single bonds """ carbon = [["#6"], ["X4"]] singleBond = [["-"], ["@"]] atom = AtomChemicalEnvironment("[#6X4:1]", "CT") bond = BondChemicalEnvironment("[#6X4:1]-[#6X4:2]", "CT-CT") angle = AngleChemicalEnvironment("[#6X4:1]-[#6X4:2]-[#6X4:3]", "CT-CT-CT") torsion = TorsionChemicalEnvironment(
def _topology_molecule_to_mol2(topology_molecule, file_name, charge_backend): """Converts an `openforcefield.topology.TopologyMolecule` into a mol2 file, generating a conformer and AM1BCC charges in the process. .. todo :: This function uses non-public methods from the Open Force Field toolkit and should be refactored when public methods become available Parameters ---------- topology_molecule: openforcefield.topology.TopologyMolecule The `TopologyMolecule` to write out as a mol2 file. The atom ordering in this mol2 will be consistent with the topology ordering. file_name: str The filename to write to. charge_backend: BuildTLeapSystem.ChargeBackend The backend to use for conformer generation and partial charge calculation. """ from openforcefield.topology import Molecule from simtk import unit as simtk_unit # Make a copy of the reference molecule so we can run conf gen / charge calc without modifying the original reference_molecule = copy.deepcopy( topology_molecule.reference_molecule) if charge_backend == BuildTLeapSystem.ChargeBackend.OpenEye: from openforcefield.utils.toolkits import OpenEyeToolkitWrapper toolkit_wrapper = OpenEyeToolkitWrapper() reference_molecule.generate_conformers( toolkit_registry=toolkit_wrapper) reference_molecule.compute_partial_charges_am1bcc( toolkit_registry=toolkit_wrapper) elif charge_backend == BuildTLeapSystem.ChargeBackend.AmberTools: from openforcefield.utils.toolkits import RDKitToolkitWrapper, AmberToolsToolkitWrapper, ToolkitRegistry toolkit_wrapper = ToolkitRegistry(toolkit_precedence=[ RDKitToolkitWrapper, AmberToolsToolkitWrapper ]) reference_molecule.generate_conformers( toolkit_registry=toolkit_wrapper) reference_molecule.compute_partial_charges_am1bcc( toolkit_registry=toolkit_wrapper) else: raise ValueError(f'Invalid toolkit specification.') # Get access to the parent topology, so we can look up the topology atom indices later. topology = topology_molecule.topology # Make and populate a new openforcefield.topology.Molecule new_molecule = Molecule() new_molecule.name = reference_molecule.name # Add atoms to the new molecule in the correct order for topology_atom in topology_molecule.atoms: # Force the topology to cache the topology molecule start indices topology.atom(topology_atom.topology_atom_index) new_molecule.add_atom(topology_atom.atom.atomic_number, topology_atom.atom.formal_charge, topology_atom.atom.is_aromatic, topology_atom.atom.stereochemistry, topology_atom.atom.name) # Add bonds to the new molecule for topology_bond in topology_molecule.bonds: # This is a temporary workaround to figure out what the "local" atom index of # these atoms is. In other words it is the offset we need to apply to get the # index if this were the only molecule in the whole Topology. We need to apply # this offset because `new_molecule` begins its atom indexing at 0, not the # real topology atom index (which we do know). index_offset = topology_molecule._atom_start_topology_index # Convert the `.atoms` generator into a list so we can access it by index topology_atoms = list(topology_bond.atoms) new_molecule.add_bond( topology_atoms[0].topology_atom_index - index_offset, topology_atoms[1].topology_atom_index - index_offset, topology_bond.bond.bond_order, topology_bond.bond.is_aromatic, topology_bond.bond.stereochemistry, ) # Transfer over existing conformers and partial charges, accounting for the # reference/topology indexing differences new_conformers = np.zeros((reference_molecule.n_atoms, 3)) new_charges = np.zeros(reference_molecule.n_atoms) # Then iterate over the reference atoms, mapping their indices to the topology # molecule's indexing system for reference_atom_index in range(reference_molecule.n_atoms): # We don't need to apply the offset here, since _ref_to_top_index is # already "locally" indexed for this topology molecule local_top_index = topology_molecule._ref_to_top_index[ reference_atom_index] new_conformers[local_top_index, :] = reference_molecule.conformers[ 0][reference_atom_index].value_in_unit(simtk_unit.angstrom) new_charges[local_top_index] = reference_molecule.partial_charges[ reference_atom_index].value_in_unit( simtk_unit.elementary_charge) # Reattach the units new_molecule.add_conformer(new_conformers * simtk_unit.angstrom) new_molecule.partial_charges = new_charges * simtk_unit.elementary_charge # Write the molecule new_molecule.to_file(file_name, file_format='mol2')
def test_to_from_openeye_core_props_filled(self): """Test OpenEyeToolkitWrapper to_openeye() and from_openeye()""" toolkit_wrapper = OpenEyeToolkitWrapper() # Replacing with a simple molecule with stereochemistry input_smiles = r'C\C(F)=C(/F)C[C@@](C)(Cl)Br' expected_output_smiles = r'[H]C([H])([H])/C(=C(/C([H])([H])[C@@](C([H])([H])[H])(Cl)Br)\F)/F' molecule = Molecule.from_smiles(input_smiles, toolkit_registry=toolkit_wrapper) assert molecule.to_smiles( toolkit_registry=toolkit_wrapper) == expected_output_smiles # Populate core molecule property fields molecule.name = 'Alice' partial_charges = unit.Quantity( np.array([ -.9, -.8, -.7, -.6, -.5, -.4, -.3, -.2, -.1, 0., .1, .2, .3, .4, .5, .6, .7, .8 ]), unit.elementary_charge) molecule.partial_charges = partial_charges coords = unit.Quantity( np.array([['0.0', '1.0', '2.0'], ['3.0', '4.0', '5.0'], ['6.0', '7.0', '8.0'], ['9.0', '10.0', '11.0'], ['12.0', '13.0', '14.0'], ['15.0', '16.0', '17.0'], ['18.0', '19.0', '20.0'], ['21.0', '22.0', '23.0'], ['24.0', '25.0', '26.0'], ['27.0', '28.0', '29.0'], ['30.0', '31.0', '32.0'], ['33.0', '34.0', '35.0'], ['36.0', '37.0', '38.0'], ['39.0', '40.0', '41.0'], ['42.0', '43.0', '44.0'], ['45.0', '46.0', '47.0'], ['48.0', '49.0', '50.0'], ['51.0', '52.0', '53.0']]), unit.angstrom) molecule.add_conformer(coords) # Populate core atom property fields molecule.atoms[2].name = 'Bob' # Ensure one atom has its stereochemistry specified central_carbon_stereo_specified = False for atom in molecule.atoms: if (atom.atomic_number == 6) and atom.stereochemistry == "S": central_carbon_stereo_specified = True assert central_carbon_stereo_specified # Populate bond core property fields fractional_bond_orders = [float(val) for val in range(1, 19)] for fbo, bond in zip(fractional_bond_orders, molecule.bonds): bond.fractional_bond_order = fbo # Do a first conversion to/from oemol oemol = molecule.to_openeye() molecule2 = Molecule.from_openeye(oemol) # Test that properties survived first conversion # assert molecule.to_dict() == molecule2.to_dict() assert molecule.name == molecule2.name # NOTE: This expects the same indexing scheme in the original and new molecule central_carbon_stereo_specified = False for atom in molecule2.atoms: if (atom.atomic_number == 6) and atom.stereochemistry == "S": central_carbon_stereo_specified = True assert central_carbon_stereo_specified for atom1, atom2 in zip(molecule.atoms, molecule2.atoms): assert atom1.to_dict() == atom2.to_dict() for bond1, bond2 in zip(molecule.bonds, molecule2.bonds): assert bond1.to_dict() == bond2.to_dict() assert (molecule._conformers[0] == molecule2._conformers[0]).all() for pc1, pc2 in zip(molecule._partial_charges, molecule2._partial_charges): pc1_ul = pc1 / unit.elementary_charge pc2_ul = pc2 / unit.elementary_charge assert_almost_equal(pc1_ul, pc2_ul, decimal=6) assert molecule2.to_smiles( toolkit_registry=toolkit_wrapper) == expected_output_smiles