diff --git a/docs/releasehistory.md b/docs/releasehistory.md index 908ee3675..f1e4ea44f 100644 --- a/docs/releasehistory.md +++ b/docs/releasehistory.md @@ -12,6 +12,8 @@ Releases follow the `major.minor.micro` scheme recommended by [PEP440](https://w ### Behavior changes +- [PR #2231](https://github.com/openforcefield/openff-toolkit/pull/2231): Makes `OpenEyeToolkitWrapper.from_openeye` not raise an undefined-stereo error/warning for planar Ns if they have at least one trivalent carbon neighbor (This isn't a perfect solution but does handle a large fraction of the annoying over-policing of nitrogen stereo by OpenEyeToolkitWrapper). Resolves , [Issue #1547](https://github.com/openforcefield/openff-toolkit/issues/1547), and [Issue #725](https://github.com/openforcefield/openff-toolkit/issues/725). + ### Bugfixes ### New features diff --git a/openff/toolkit/_tests/test_molecule.py b/openff/toolkit/_tests/test_molecule.py index db6c9ca50..f141cca69 100644 --- a/openff/toolkit/_tests/test_molecule.py +++ b/openff/toolkit/_tests/test_molecule.py @@ -228,17 +228,11 @@ def mini_drug_bank(xfail_mols=None, wip_mols=None): # All the molecules that raise UndefinedStereochemistryError when read by OETK() openeye_drugbank_undefined_stereo_mols = { "DrugBank_1634", - "DrugBank_1700", "DrugBank_1962", "DrugBank_2519", - "DrugBank_2987", - "DrugBank_3502", "DrugBank_3930", - "DrugBank_4161", - "DrugBank_4162", "DrugBank_5043", "DrugBank_5418", - "DrugBank_6531", } # All the molecules that raise UndefinedStereochemistryError when read by RDKTKW(). @@ -254,8 +248,6 @@ def mini_drug_bank(xfail_mols=None, wip_mols=None): } -# Missing stereo in OE but not RDK: 'DrugBank_2987', 'DrugBank_3502', 'DrugBank_4161', -# 'DrugBank_4162', 'DrugBank_6531', 'DrugBank_1700', drugbank_stereogenic_in_rdkit_but_not_openeye = { "DrugBank_5329", "DrugBank_7124", @@ -1124,23 +1116,11 @@ def test_to_from_iupac(self): Test basic behavior of the IUPAC conversion functions. More rigorous testing of the toolkity wrapper behavior is in test_toolkits.py """ - from openff.toolkit.utils.toolkits import ( - InvalidIUPACNameError, - UndefinedStereochemistryError, - ) + from openff.toolkit.utils.toolkits import InvalidIUPACNameError with pytest.raises(InvalidIUPACNameError): Molecule.from_iupac(".BETA.-PINENE") - # DrugBank_977, tagged as a problem molecule in earlier tests - bad_stereo_iupac = ( - "(~{E},3~{R},5~{S})-7-[4-(4-fluorophenyl)-6-isopropyl-2-" - "[methyl(methylsulfonyl)amino]pyrimidin-5-yl]-3,5-" - "dihydroxy-hept-6-enoic acid" - ) - with pytest.raises(UndefinedStereochemistryError): - Molecule.from_iupac(bad_stereo_iupac) - cholesterol = Molecule.from_smiles( "C[C@H](CCCC(C)C)[C@H]1CC[C@@H]2[C@@]1(CC[C@H]3[C@H]2CC=C4[C@@]3(CC[C@@H](C4)O)C)C" ) @@ -1379,30 +1359,15 @@ def test_to_xyz_no_conformers(self): ) def test_to_from_file(self, molecule, format): """Test that conversion/creation of a molecule to and from a file is consistent.""" - from openff.toolkit.utils.toolkits import UndefinedStereochemistryError - # TODO: Test all file capabilities; the current test is minimal # TODO: This is only for OE. Expand to both OE and RDKit toolkits. - # Molecules that are known to raise UndefinedStereochemistryError. - undefined_stereo_mols = { - "DrugBank_1700", - "DrugBank_2987", - "DrugBank_3502", - "DrugBank_4161", - "DrugBank_4162", - "DrugBank_6531", - } - undefined_stereo = molecule.name in undefined_stereo_mols # The file is automatically deleted outside the with-clause. with NamedTemporaryFile(suffix="." + format) as iofile: # If this has undefined stereo, check that the exception is raised. extension = os.path.splitext(iofile.name)[1][1:] molecule.to_file(iofile.name, extension) - if undefined_stereo: - with pytest.raises(UndefinedStereochemistryError): - Molecule.from_file(iofile.name) - molecule2 = Molecule.from_file(iofile.name, allow_undefined_stereo=undefined_stereo) + molecule2 = Molecule.from_file(iofile.name) assert molecule == molecule2 # TODO: Test to make sure properties are preserved? # NOTE: We can't read pdb files and expect chemical information to be preserved diff --git a/openff/toolkit/_tests/test_toolkits.py b/openff/toolkit/_tests/test_toolkits.py index f48f4c68f..dc8af7c45 100644 --- a/openff/toolkit/_tests/test_toolkits.py +++ b/openff/toolkit/_tests/test_toolkits.py @@ -91,24 +91,19 @@ def get_mini_drug_bank(toolkit_class, xfail_mols=None): "DrugBank_5415", "DrugBank_5418", "DrugBank_2955", - "DrugBank_2987", "DrugBank_5555", "DrugBank_472", "DrugBank_5737", "DrugBank_3332", "DrugBank_3461", "DrugBank_794", - "DrugBank_3502", "DrugBank_6026", "DrugBank_3622", - "DrugBank_977", "DrugBank_3693", "DrugBank_3726", "DrugBank_3739", "DrugBank_6222", - "DrugBank_6232", "DrugBank_3844", - "DrugBank_6295", "DrugBank_6304", "DrugBank_6305", "DrugBank_3930", @@ -116,48 +111,35 @@ def get_mini_drug_bank(toolkit_class, xfail_mols=None): "DrugBank_6353", "DrugBank_6355", "DrugBank_6401", - "DrugBank_4161", - "DrugBank_4162", "DrugBank_6509", - "DrugBank_6531", "DrugBank_1570", "DrugBank_4249", "DrugBank_1634", "DrugBank_1659", "DrugBank_6647", - "DrugBank_1700", "DrugBank_1721", - "DrugBank_1742", "DrugBank_1802", - "DrugBank_6775", "DrugBank_1849", - "DrugBank_1864", "DrugBank_6875", "DrugBank_1897", "DrugBank_4593", "DrugBank_1962", - "DrugBank_4662", "DrugBank_7049", "DrugBank_4702", "DrugBank_2095", "DrugBank_4778", - "DrugBank_2141", "DrugBank_2148", "DrugBank_2178", "DrugBank_4865", "DrugBank_2208", "DrugBank_2210", "DrugBank_2276", - "DrugBank_4959", - "DrugBank_4964", "DrugBank_5043", "DrugBank_2429", "DrugBank_5076", "DrugBank_2465", "DrugBank_2519", "DrugBank_2538", - "DrugBank_5158", - "DrugBank_5176", "DrugBank_2592", ] @@ -196,9 +178,7 @@ def get_mini_drug_bank(toolkit_class, xfail_mols=None): ] openeye_iupac_bad_stereo = [ - "DrugBank_977", "DrugBank_1634", - "DrugBank_1700", "DrugBank_1962", "DrugBank_2148", "DrugBank_2178", @@ -208,32 +188,24 @@ def get_mini_drug_bank(toolkit_class, xfail_mols=None): "DrugBank_2538", "DrugBank_2592", "DrugBank_2651", - "DrugBank_2987", "DrugBank_3332", - "DrugBank_3502", "DrugBank_3622", "DrugBank_3726", "DrugBank_3844", "DrugBank_3930", - "DrugBank_4161", - "DrugBank_4162", "DrugBank_4778", "DrugBank_4593", - "DrugBank_4959", "DrugBank_5043", "DrugBank_5076", - "DrugBank_5176", "DrugBank_5418", "DrugBank_5737", "DrugBank_5902", - "DrugBank_6295", "DrugBank_6304", "DrugBank_6305", "DrugBank_6329", "DrugBank_6355", "DrugBank_6401", "DrugBank_6509", - "DrugBank_6531", "DrugBank_6647", "DrugBank_390", "DrugBank_810", @@ -241,7 +213,6 @@ def get_mini_drug_bank(toolkit_class, xfail_mols=None): "DrugBank_4346", "DrugBank_7124", "DrugBank_2799", - "DrugBank_4662", "DrugBank_4865", "DrugBank_2465", ] @@ -352,6 +323,69 @@ def test_smiles_missing_stereochemistry(self): else: Molecule.from_smiles(smiles, toolkit_registry=toolkit_wrapper) + @pytest.mark.parametrize( + "smiles,expected_stereo,exception_regex", + [ + # Trivalent N with a trivalent (aromatic) carbon neighbor: Unlike RDKit, OpenEye + # perceives this N as stereogenic, however actual molecular forces may make it + # planar. These first two cases ensures that either planar or pyramidal + # configurations are accepted. + ("C[N@](CC)c1c(Br)cccc1", "R", None), + ("CN(CC)c1c(Br)cccc1", None, None), + # Trivalent N with no trivalent carbon neighbor: an ordinary stereocenter, so + # unspecified stereo here must still raise as normal. + ("C[N@](CC)CCC", "R", None), + ("CN(CC)CCC", None, "unspecified stereochemistry"), + ], + ) + def test_smiles_nitrogen_chirality_skip(self, smiles, expected_stereo, exception_regex): + """ + Test that a trivalent nitrogen with a trivalent carbon neighbor does not trigger + UndefinedStereochemistryError when its stereochemistry is unspecified, while an + otherwise-equivalent nitrogen without such a neighbor still does. + """ + toolkit_wrapper = OpenEyeToolkitWrapper() + + if exception_regex is not None: + with pytest.raises(UndefinedStereochemistryError, match=exception_regex): + toolkit_wrapper.from_smiles(smiles) + molecule = toolkit_wrapper.from_smiles(smiles, allow_undefined_stereo=True) + else: + molecule = toolkit_wrapper.from_smiles(smiles) + + nitrogen_stereo = [atom.stereochemistry for atom in molecule.atoms if atom.symbol == "N"] + assert nitrogen_stereo == [expected_stereo] + + @pytest.mark.parametrize( + "filename,expected_stereo,exception_regex", + [ + ("bromoaniline_pyramidal_n.sdf", "R", None), + ("bromoaniline_planar_n.sdf", None, None), + ("trialkylamine_pyramidal_n.sdf", "R", None), + ("trialkylamine_planar_n.sdf", None, "unspecified stereochemistry"), + ], + ) + def test_from_file_nitrogen_chirality_skip(self, filename, expected_stereo, exception_regex): + """ + Same as test_smiles_nitrogen_chirality_skip, but exercising the from_file/SDF-reading + code path, which perceives stereochemistry from 3D coordinates via OE3DToInternalStereo + rather than from SMILES parity. + """ + toolkit_wrapper = OpenEyeToolkitWrapper() + file_path = get_data_file_path(f"molecules/{filename}") + + if exception_regex is not None: + with pytest.raises(UndefinedStereochemistryError, match=exception_regex): + toolkit_wrapper.from_file(file_path, file_format="sdf") + molecules = toolkit_wrapper.from_file(file_path, file_format="sdf", allow_undefined_stereo=True) + else: + molecules = toolkit_wrapper.from_file(file_path, file_format="sdf") + + nitrogen_stereo = [ + atom.stereochemistry for molecule in molecules for atom in molecule.atoms if atom.symbol == "N" + ] + assert nitrogen_stereo == [expected_stereo] + def test_openeye_from_smiles_radical(self): """Test that parsing an SMILES with a radical raises RadicalsNotSupportedError.""" with pytest.raises(RadicalsNotSupportedError): @@ -2019,6 +2053,47 @@ def test_smiles_missing_stereochemistry(self, smiles, exception_regex): else: Molecule.from_smiles(smiles, toolkit_registry=toolkit_wrapper) + @pytest.mark.parametrize( + "smiles", + [ + "C[N@](CC)c1c(Br)cccc1", + "CN(CC)c1c(Br)cccc1", + "C[N@](CC)CCC", + "CN(CC)CCC", + ], + ) + def test_smiles_nitrogen_chirality_not_perceived(self, smiles): + """ + RDKitToolkitWrapper does not perceive stereochemistry for these trivalent nitrogens, + whether or not they have a trivalent carbon neighbor and whether or not the SMILES + specifies @/@@ -- unlike OpenEyeToolkitWrapper, see + TestOpenEyeToolkitWrapper.test_smiles_nitrogen_chirality_skip. + """ + toolkit_wrapper = RDKitToolkitWrapper() + molecule = toolkit_wrapper.from_smiles(smiles) + assert all(atom.stereochemistry is None for atom in molecule.atoms if atom.symbol == "N") + + @pytest.mark.parametrize( + "filename", + [ + "bromoaniline_pyramidal_n.sdf", + "bromoaniline_planar_n.sdf", + "trialkylamine_pyramidal_n.sdf", + "trialkylamine_planar_n.sdf", + ], + ) + def test_from_file_nitrogen_chirality_not_perceived(self, filename): + """ + RDKitToolkitWrapper does not perceive stereochemistry for these trivalent nitrogens from + 3D coordinates either -- unlike OpenEyeToolkitWrapper, see + TestOpenEyeToolkitWrapper.test_from_file_nitrogen_chirality_skip. + """ + toolkit_wrapper = RDKitToolkitWrapper() + file_path = get_data_file_path(f"molecules/{filename}") + molecules = toolkit_wrapper.from_file(file_path, file_format="sdf") + for molecule in molecules: + assert all(atom.stereochemistry is None for atom in molecule.atoms if atom.symbol == "N") + # TODO: test_smiles_round_trip def test_smiles_add_H(self): diff --git a/openff/toolkit/data/molecules/bromoaniline_planar_n.sdf b/openff/toolkit/data/molecules/bromoaniline_planar_n.sdf new file mode 100644 index 000000000..87acff2c4 --- /dev/null +++ b/openff/toolkit/data/molecules/bromoaniline_planar_n.sdf @@ -0,0 +1,52 @@ +bromoaniline_planar_n + RDKit 3D + + 23 23 0 0 0 0 0 0 0 0999 V2000 + -1.4458 -1.7176 0.0170 C 0 0 0 0 0 0 0 0 0 0 0 0 + -0.8538 -0.4284 0.3054 N 0 0 0 0 0 0 0 0 0 0 0 0 + -1.7577 0.6556 0.6347 C 0 0 0 0 0 0 0 0 0 0 0 0 + -2.5959 1.0759 -0.5526 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.5492 -0.1859 0.2793 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1.4468 -0.8382 1.0940 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.8233 -2.1401 2.3400 Br 0 0 0 0 0 0 0 0 0 0 0 0 + 2.7990 -0.5585 1.0200 C 0 0 0 0 0 0 0 0 0 0 0 0 + 3.2940 0.3726 0.1385 C 0 0 0 0 0 0 0 0 0 0 0 0 + 2.4042 1.0326 -0.6830 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1.0591 0.7505 -0.6053 C 0 0 0 0 0 0 0 0 0 0 0 0 + -0.8041 -2.2408 -0.7495 H 0 0 0 0 0 0 0 0 0 0 0 0 + -1.3905 -2.3170 0.9344 H 0 0 0 0 0 0 0 0 0 0 0 0 + -2.4538 -1.6308 -0.4308 H 0 0 0 0 0 0 0 0 0 0 0 0 + -1.1854 1.5565 0.9356 H 0 0 0 0 0 0 0 0 0 0 0 0 + -2.4752 0.4083 1.4382 H 0 0 0 0 0 0 0 0 0 0 0 0 + -3.5063 0.4622 -0.6417 H 0 0 0 0 0 0 0 0 0 0 0 0 + -1.9744 1.0650 -1.4963 H 0 0 0 0 0 0 0 0 0 0 0 0 + -2.9400 2.1339 -0.4441 H 0 0 0 0 0 0 0 0 0 0 0 0 + 3.4618 -1.1096 1.6935 H 0 0 0 0 0 0 0 0 0 0 0 0 + 4.3565 0.5683 0.1054 H 0 0 0 0 0 0 0 0 0 0 0 0 + 2.7812 1.7746 -1.3875 H 0 0 0 0 0 0 0 0 0 0 0 0 + 0.4076 1.3108 -1.2862 H 0 0 0 0 0 0 0 0 0 0 0 0 + 1 2 1 0 + 2 3 1 0 + 3 4 1 0 + 2 5 1 0 + 5 6 1 0 + 6 7 1 0 + 6 8 2 0 + 8 9 1 0 + 9 10 2 0 + 10 11 1 0 + 11 5 2 0 + 1 12 1 0 + 1 13 1 0 + 1 14 1 0 + 3 15 1 0 + 3 16 1 0 + 4 17 1 0 + 4 18 1 0 + 4 19 1 0 + 8 20 1 0 + 9 21 1 0 + 10 22 1 0 + 11 23 1 0 +M END +$$$$ diff --git a/openff/toolkit/data/molecules/bromoaniline_pyramidal_n.sdf b/openff/toolkit/data/molecules/bromoaniline_pyramidal_n.sdf new file mode 100644 index 000000000..b910d10ff --- /dev/null +++ b/openff/toolkit/data/molecules/bromoaniline_pyramidal_n.sdf @@ -0,0 +1,52 @@ +bromoaniline_pyramidal_n + PyMOL3.1 3D 0 + + 23 23 0 0 0 0 0 0 0 0999 V2000 + -1.4458 -1.7176 0.0170 C 0 0 0 0 0 0 0 0 0 0 0 0 + -1.0546 -0.9425 1.3015 N 0 0 0 0 0 0 0 0 0 0 0 0 + -1.9585 0.1415 1.6308 C 0 0 0 0 0 0 0 0 0 0 0 0 + -2.7967 0.5618 0.4435 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.3484 -0.7000 1.2754 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1.2460 -1.3523 2.0901 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.6225 -2.6542 3.3361 Br 0 0 0 0 0 0 0 0 0 0 0 0 + 2.5982 -1.0726 2.0161 C 0 0 0 0 0 0 0 0 0 0 0 0 + 3.0932 -0.1415 1.1346 C 0 0 0 0 0 0 0 0 0 0 0 0 + 2.2034 0.5185 0.3131 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.8583 0.2364 0.3908 C 0 0 0 0 0 0 0 0 0 0 0 0 + -0.8014 -2.2429 -0.7458 H 0 0 0 0 0 0 0 0 0 0 0 0 + -1.4210 -2.3706 0.8984 H 0 0 0 0 0 0 0 0 0 0 0 0 + -2.4518 -1.6307 -0.4352 H 0 0 0 0 0 0 0 0 0 0 0 0 + -1.3862 1.0424 1.9317 H 0 0 0 0 0 0 0 0 0 0 0 0 + -2.6760 -0.1058 2.4343 H 0 0 0 0 0 0 0 0 0 0 0 0 + -3.7071 -0.0519 0.3544 H 0 0 0 0 0 0 0 0 0 0 0 0 + -2.1752 0.5509 -0.5002 H 0 0 0 0 0 0 0 0 0 0 0 0 + -3.1408 1.6198 0.5520 H 0 0 0 0 0 0 0 0 0 0 0 0 + 3.2610 -1.6237 2.6896 H 0 0 0 0 0 0 0 0 0 0 0 0 + 4.1557 0.0542 1.1015 H 0 0 0 0 0 0 0 0 0 0 0 0 + 2.5804 1.2605 -0.3914 H 0 0 0 0 0 0 0 0 0 0 0 0 + 0.2068 0.7967 -0.2901 H 0 0 0 0 0 0 0 0 0 0 0 0 + 1 2 1 0 0 0 0 + 1 12 1 0 0 0 0 + 1 13 1 0 0 0 0 + 1 14 1 0 0 0 0 + 2 3 1 0 0 0 0 + 2 5 1 0 0 0 0 + 3 4 1 0 0 0 0 + 3 15 1 0 0 0 0 + 3 16 1 0 0 0 0 + 4 17 1 0 0 0 0 + 4 18 1 0 0 0 0 + 4 19 1 0 0 0 0 + 5 6 1 0 0 0 0 + 6 7 1 0 0 0 0 + 6 8 2 0 0 0 0 + 8 9 1 0 0 0 0 + 8 20 1 0 0 0 0 + 9 10 2 0 0 0 0 + 9 21 1 0 0 0 0 + 10 11 1 0 0 0 0 + 10 22 1 0 0 0 0 + 5 11 2 0 0 0 0 + 11 23 1 0 0 0 0 +M END +$$$$ diff --git a/openff/toolkit/data/molecules/trialkylamine_planar_n.sdf b/openff/toolkit/data/molecules/trialkylamine_planar_n.sdf new file mode 100644 index 000000000..3751782ab --- /dev/null +++ b/openff/toolkit/data/molecules/trialkylamine_planar_n.sdf @@ -0,0 +1,49 @@ +trialkylamine_planar_n + PyMOL3.1 3D 0 + + 22 21 0 0 0 0 0 0 0 0999 V2000 + 1.5671 -1.9836 0.6756 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1.1633 -1.1939 0.1163 N 0 0 0 0 0 0 0 0 0 0 0 0 + 1.9220 0.0298 -0.0288 C 0 0 0 0 0 0 0 0 0 0 0 0 + 2.0276 0.5305 -1.4263 C 0 0 0 0 0 0 0 0 0 0 0 0 + -0.0958 -1.2069 -0.5523 C 0 0 0 0 0 0 0 0 0 0 0 0 + -1.0877 -0.1791 -0.1401 C 0 0 0 0 0 0 0 0 0 0 0 0 + -2.3818 -0.3379 -0.9513 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.6067 -2.4121 0.9715 H 0 0 0 0 0 0 0 0 0 0 0 0 + 2.3670 -2.7534 0.8168 H 0 0 0 0 0 0 0 0 0 0 0 0 + 1.8646 -1.1190 1.3006 H 0 0 0 0 0 0 0 0 0 0 0 0 + 1.5761 0.8139 0.6860 H 0 0 0 0 0 0 0 0 0 0 0 0 + 2.9610 -0.2278 0.3352 H 0 0 0 0 0 0 0 0 0 0 0 0 + 1.7758 -0.1767 -2.2122 H 0 0 0 0 0 0 0 0 0 0 0 0 + 3.0764 0.8910 -1.6369 H 0 0 0 0 0 0 0 0 0 0 0 0 + 1.4036 1.4599 -1.5303 H 0 0 0 0 0 0 0 0 0 0 0 0 + -0.5610 -2.2096 -0.3073 H 0 0 0 0 0 0 0 0 0 0 0 0 + 0.0762 -1.2086 -1.6381 H 0 0 0 0 0 0 0 0 0 0 0 0 + -0.7198 0.8393 -0.4434 H 0 0 0 0 0 0 0 0 0 0 0 0 + -1.3329 -0.1127 0.9145 H 0 0 0 0 0 0 0 0 0 0 0 0 + -3.0989 -0.9594 -0.3654 H 0 0 0 0 0 0 0 0 0 0 0 0 + -2.8552 0.6389 -1.1264 H 0 0 0 0 0 0 0 0 0 0 0 0 + -2.1681 -0.7840 -1.9528 H 0 0 0 0 0 0 0 0 0 0 0 0 + 1 2 1 0 0 0 0 + 1 8 1 0 0 0 0 + 1 9 1 0 0 0 0 + 1 10 1 0 0 0 0 + 2 3 1 0 0 0 0 + 2 5 1 0 0 0 0 + 3 4 1 0 0 0 0 + 3 11 1 0 0 0 0 + 3 12 1 0 0 0 0 + 4 13 1 0 0 0 0 + 4 14 1 0 0 0 0 + 4 15 1 0 0 0 0 + 5 6 1 0 0 0 0 + 5 16 1 0 0 0 0 + 5 17 1 0 0 0 0 + 6 7 1 0 0 0 0 + 6 18 1 0 0 0 0 + 6 19 1 0 0 0 0 + 7 20 1 0 0 0 0 + 7 21 1 0 0 0 0 + 7 22 1 0 0 0 0 +M END +$$$$ diff --git a/openff/toolkit/data/molecules/trialkylamine_pyramidal_n.sdf b/openff/toolkit/data/molecules/trialkylamine_pyramidal_n.sdf new file mode 100644 index 000000000..2497e70b3 --- /dev/null +++ b/openff/toolkit/data/molecules/trialkylamine_pyramidal_n.sdf @@ -0,0 +1,49 @@ +trialkylamine_pyramidal_n + RDKit 3D + + 22 21 0 0 0 0 0 0 0 0999 V2000 + 0.8993 -1.2407 1.6996 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.8702 -0.7836 0.3321 N 0 0 0 0 0 0 0 0 0 0 0 0 + 1.6704 0.4116 0.1731 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1.6903 0.9636 -1.2089 C 0 0 0 0 0 0 0 0 0 0 0 0 + -0.4361 -0.7159 -0.2349 C 0 0 0 0 0 0 0 0 0 0 0 0 + -1.3453 0.3355 0.2922 C 0 0 0 0 0 0 0 0 0 0 0 0 + -2.7041 0.2649 -0.4195 C 0 0 0 0 0 0 0 0 0 0 0 0 + -0.0760 -1.5279 2.0994 H 0 0 0 0 0 0 0 0 0 0 0 0 + 1.5771 -2.1309 1.7213 H 0 0 0 0 0 0 0 0 0 0 0 0 + 1.4018 -0.4620 2.3061 H 0 0 0 0 0 0 0 0 0 0 0 0 + 1.4168 1.1801 0.9415 H 0 0 0 0 0 0 0 0 0 0 0 0 + 2.7216 0.0955 0.4440 H 0 0 0 0 0 0 0 0 0 0 0 0 + 1.3466 0.3002 -1.9981 H 0 0 0 0 0 0 0 0 0 0 0 0 + 2.7350 1.2875 -1.4882 H 0 0 0 0 0 0 0 0 0 0 0 0 + 1.1031 1.9222 -1.2287 H 0 0 0 0 0 0 0 0 0 0 0 0 + -0.9262 -1.7071 0.0087 H 0 0 0 0 0 0 0 0 0 0 0 0 + -0.3488 -0.6809 -1.3302 H 0 0 0 0 0 0 0 0 0 0 0 0 + -0.9560 1.3488 -0.0012 H 0 0 0 0 0 0 0 0 0 0 0 0 + -1.5050 0.3695 1.3646 H 0 0 0 0 0 0 0 0 0 0 0 0 + -3.4014 -0.3488 0.1978 H 0 0 0 0 0 0 0 0 0 0 0 0 + -3.1447 1.2673 -0.5203 H 0 0 0 0 0 0 0 0 0 0 0 0 + -2.5890 -0.1489 -1.4506 H 0 0 0 0 0 0 0 0 0 0 0 0 + 1 2 1 0 + 2 3 1 0 + 3 4 1 0 + 2 5 1 0 + 5 6 1 0 + 6 7 1 0 + 1 8 1 0 + 1 9 1 0 + 1 10 1 0 + 3 11 1 0 + 3 12 1 0 + 4 13 1 0 + 4 14 1 0 + 4 15 1 0 + 5 16 1 0 + 5 17 1 0 + 6 18 1 0 + 6 19 1 0 + 7 20 1 0 + 7 21 1 0 + 7 22 1 0 +M END +$$$$ diff --git a/openff/toolkit/utils/openeye_wrapper.py b/openff/toolkit/utils/openeye_wrapper.py index 2a33c4a3e..a91bb065f 100644 --- a/openff/toolkit/utils/openeye_wrapper.py +++ b/openff/toolkit/utils/openeye_wrapper.py @@ -1153,6 +1153,18 @@ def from_openeye( for oeatom in oemol.GetAtoms(): if oeatom.IsChiral(): + # OpenEye is a bit more expansive than RDKit in its definition of "chiral", which leads to a + # large number of false alarms for planar trivalent nitrogens. So here we check to see whether + # a trivalent nitrogen has a trivalent carbon neighbor and squelch the warning/error if so. + skip_tricky_nitrogen = False + if oeatom.IsNitrogen() and oeatom.GetDegree() == 3: + for oebond in oeatom.GetBonds(): + neighbor = oebond.GetNbr(oeatom) + if neighbor.IsCarbon() and (neighbor.GetDegree() == 3): + skip_tricky_nitrogen = True + if skip_tricky_nitrogen: + continue + if not (oeatom.HasStereoSpecified()): unspec_chiral = True problematic_atoms.append(oeatom) @@ -1182,7 +1194,7 @@ def describe_oeatom(oeatom) -> str: return description if (len(problematic_atoms) != 0 or len(problematic_bonds) != 0) and not allow_undefined_stereo: - msg = f"OEMol has unspecified stereochemistry. {oemol.GetTitle()=}" + msg = f"OEMol has unspecified stereochemistry. {oemol.GetTitle()=}\n" if len(problematic_atoms) != 0: msg += "Problematic atoms are:\n"