class TestAmberToolsToolkitWrapper: """Test the AmberToolsToolkitWrapper""" @pytest.mark.skipif( not RDKitToolkitWrapper.is_available() or not AmberToolsToolkitWrapper.is_available(), reason='RDKitToolkit and AmberToolsToolkit not available') def test_compute_partial_charges(self): """Test OpenEyeToolkitWrapper compute_partial_charges()""" toolkit_registry = ToolkitRegistry( toolkit_precedence=[AmberToolsToolkitWrapper, RDKitToolkitWrapper]) smiles = '[H]C([H])([H])C([H])([H])[H]' molecule = Molecule.from_smiles(smiles, toolkit_registry=toolkit_registry) molecule.generate_conformers(toolkit_registry=toolkit_registry) # TODO: Implementation of these tests is pending a decision on the API for our charge model with pytest.raises(NotImplementedError) as excinfo: charge_model = 'notARealChargeModel' molecule.compute_partial_charges(toolkit_registry=toolkit_registry ) #, charge_model=charge_model) # ['cm1', 'cm2'] for charge_model in ['gas', 'mul', 'bcc']: with pytest.raises(NotImplementedError) as excinfo: molecule.compute_partial_charges( toolkit_registry=toolkit_registry ) #, charge_model=charge_model) charge_sum = 0 * unit.elementary_charge for pc in molecule._partial_charges: charge_sum += pc assert charge_sum < 0.01 * 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_registry) # , charge_model=charge_model) charge_sum = 0 * unit.elementary_charge for pc in molecule._partial_charges: charge_sum += pc assert charge_sum < 0.002 * unit.elementary_charge @pytest.mark.skipif( not RDKitToolkitWrapper.is_available() or not AmberToolsToolkitWrapper.is_available(), reason='RDKitToolkit and AmberToolsToolkit not available') def test_compute_partial_charges_net_charge(self): """Test OpenEyeToolkitWrapper compute_partial_charges() on a molecule with a net +1 charge""" toolkit_registry = ToolkitRegistry( toolkit_precedence=[AmberToolsToolkitWrapper, RDKitToolkitWrapper]) smiles = '[H]C([H])([H])[N+]([H])([H])[H]' molecule = Molecule.from_smiles(smiles, toolkit_registry=toolkit_registry) molecule.generate_conformers(toolkit_registry=toolkit_registry) with pytest.raises(NotImplementedError) as excinfo: charge_model = 'notARealChargeModel' molecule.compute_partial_charges(toolkit_registry=toolkit_registry ) #, charge_model=charge_model) # TODO: Figure out why ['cm1', 'cm2'] fail for charge_model in ['gas', 'mul', 'bcc']: with pytest.raises(NotImplementedError) as excinfo: molecule.compute_partial_charges( toolkit_registry=toolkit_registry ) #, charge_model=charge_model) charge_sum = 0 * unit.elementary_charge for pc in molecule._partial_charges: charge_sum += pc assert 0.99 * unit.elementary_charge < charge_sum < 1.01 * 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_registry) 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_to_from_rdkit_core_props_filled(self): """Test RDKitToolkitWrapper to_rdkit() and from_rdkit() when given populated core property fields""" toolkit_wrapper = RDKitToolkitWrapper() # 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]([F])=[C](\[F])[C]([H])([H])[C@@]([Cl])([Br])[C]([H])([H])[H]' 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(18)] for fbo, bond in zip(fractional_bond_orders, molecule.bonds): bond.fractional_bond_order = fbo # Do a first conversion to/from oemol rdmol = molecule.to_rdkit() molecule2 = Molecule.from_rdkit(rdmol) # 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
def test_generate_conformers(self): """Test RDKitToolkitWrapper generate_conformers()""" toolkit_wrapper = RDKitToolkitWrapper() smiles = '[H]C([H])([H])C([H])([H])[H]' molecule = toolkit_wrapper.from_smiles(smiles) molecule.generate_conformers()
class TestRDKitToolkitWrapper: """Test the RDKitToolkitWrapper""" @pytest.mark.skipif(not RDKitToolkitWrapper.is_available(), reason='RDKit Toolkit not available') def test_smiles(self): """Test RDKitToolkitWrapper to_smiles() and from_smiles()""" toolkit_wrapper = RDKitToolkitWrapper() # This differs from OE's expected output 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) #print(smiles, smiles2) assert smiles == smiles2 @pytest.mark.skipif(not RDKitToolkitWrapper.is_available(), reason='RDKit Toolkit not available') @pytest.mark.parametrize("smiles,exception_regex", [ (r"C\C(F)=C(/F)CC(C)(Cl)Br", "Undefined chiral centers"), (r"C\C(F)=C(/F)C[C@@](C)(Cl)Br", None), (r"CC(F)=C(F)C[C@@](C)(Cl)Br", "Bonds with undefined stereochemistry") ]) def test_smiles_missing_stereochemistry(self, smiles, exception_regex): """Test RDKitToolkitWrapper to_smiles() and from_smiles() when given ambiguous stereochemistry""" toolkit_wrapper = RDKitToolkitWrapper() if exception_regex is not None: with pytest.raises(UndefinedStereochemistryError, match=exception_regex): Molecule.from_smiles(smiles, toolkit_registry=toolkit_wrapper) Molecule.from_smiles(smiles, toolkit_registry=toolkit_wrapper, allow_undefined_stereo=True) else: Molecule.from_smiles(smiles, toolkit_registry=toolkit_wrapper) # TODO: test_smiles_round_trip @pytest.mark.skipif(not RDKitToolkitWrapper.is_available(), reason='RDKit Toolkit not available') def test_smiles_add_H(self): """Test RDKitToolkitWrapper to_smiles() and from_smiles()""" toolkit_wrapper = RDKitToolkitWrapper() input_smiles = 'CC' # This differs from OE's expected output due to different canonicalization schemes 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 smiles2 == expected_output_smiles @pytest.mark.skipif(not RDKitToolkitWrapper.is_available(), reason='OpenEye Toolkit not available') def test_rdkit_from_smiles_hydrogens_are_explicit(self): """ Test to ensure that RDKitToolkitWrapper.from_smiles has the proper behavior with respect to its hydrogens_are_explicit kwarg """ toolkit_wrapper = RDKitToolkitWrapper() smiles_impl = "C#C" with pytest.raises( ValueError, match= "but RDKit 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 = "[H][C]#[C][H]" 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 @pytest.mark.skipif(not RDKitToolkitWrapper.is_available(), reason='RDKit Toolkit not available') def test_smiles_charged(self): """Test RDKitWrapper functions for reading/writing charged SMILES""" toolkit_wrapper = RDKitToolkitWrapper() # This differs from OE'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 @pytest.mark.skipif(not RDKitToolkitWrapper.is_available(), reason='RDKit Toolkit not available') def test_to_from_rdkit_core_props_filled(self): """Test RDKitToolkitWrapper to_rdkit() and from_rdkit() when given populated core property fields""" toolkit_wrapper = RDKitToolkitWrapper() # 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]([F])=[C](\[F])[C]([H])([H])[C@@]([Cl])([Br])[C]([H])([H])[H]' 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(18)] for fbo, bond in zip(fractional_bond_orders, molecule.bonds): bond.fractional_bond_order = fbo # Do a first conversion to/from oemol rdmol = molecule.to_rdkit() molecule2 = Molecule.from_rdkit(rdmol) # 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 # TODO: This should be its own test @pytest.mark.skipif(not RDKitToolkitWrapper.is_available(), reason='RDKit Toolkit not available') def test_to_from_rdkit_core_props_unset(self): """Test RDKitToolkitWrapper to_rdkit() and from_rdkit() when given empty core property fields""" toolkit_wrapper = RDKitToolkitWrapper() # 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]([F])=[C](\[F])[C]([H])([H])[C@]([Cl])([Br])[C]([H])([H])[H]' 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_rdkit() molecule2 = Molecule.from_rdkit(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 @pytest.mark.skipif(not RDKitToolkitWrapper.is_available(), reason='RDKit Toolkit not available') def test_get_sdf_coordinates(self): """Test RDKitToolkitWrapper for importing a single set of coordinates from a sdf file""" toolkit_wrapper = RDKitToolkitWrapper() 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) assert_almost_equal(molecule.conformers[0][5][1] / unit.angstrom, 2.0104, decimal=4) # Find a multiconformer SDF file @pytest.mark.skip #@pytest.mark.skipif(not RDKitToolkitWrapper.is_available(), reason='RDKit Toolkit not available') def test_get_multiconformer_sdf_coordinates(self): """Test RDKitToolkitWrapper for importing a single set of coordinates from a sdf file""" raise NotImplementedError toolkit_wrapper = RDKitToolkitWrapper() 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) # Unskip this when we implement PDB-reading support for RDKitToolkitWrapper @pytest.mark.skip @pytest.mark.skipif(not RDKitToolkitWrapper.is_available(), reason='RDKit Toolkit not available') def test_get_pdb_coordinates(self): """Test RDKitToolkitWrapper for importing a single set of coordinates from a pdb file""" toolkit_wrapper = RDKitToolkitWrapper() filename = get_data_file_path('molecules/toluene.pdb') molecule = Molecule.from_file(filename, toolkit_registry=toolkit_wrapper) assert len(molecule._conformers) == 1 assert molecule._conformers[0].shape == (15, 3) # Unskip this when we implement PDB-reading support for RDKitToolkitWrapper @pytest.mark.skip @pytest.mark.skipif(not RDKitToolkitWrapper.is_available(), reason='RDKit Toolkit not available') def test_load_aromatic_pdb(self): """Test OpenEyeToolkitWrapper for importing molecule conformers""" toolkit_wrapper = RDKitToolkitWrapper() filename = get_data_file_path('molecules/toluene.pdb') molecule = Molecule.from_file(filename, toolkit_registry=toolkit_wrapper) assert len(molecule._conformers) == 1 assert molecule._conformers[0].shape == (15, 3) @pytest.mark.skipif(not RDKitToolkitWrapper.is_available(), reason='RDKit Toolkit not available') def test_generate_conformers(self): """Test RDKitToolkitWrapper generate_conformers()""" toolkit_wrapper = RDKitToolkitWrapper() smiles = '[H]C([H])([H])C([H])([H])[H]' molecule = toolkit_wrapper.from_smiles(smiles) molecule.generate_conformers()