From a1d2bee331844aac437a3c545c4bda8a4e857212 Mon Sep 17 00:00:00 2001 From: Jeff Wagner Date: Wed, 4 Dec 2024 09:57:41 -0800 Subject: [PATCH] Convert insertion code or chain id empty string to single character space in to_openeye (#1971) * Fix issue #1967 and add test * update releasehistory * remove unused stringio import --- docs/releasehistory.md | 2 ++ openff/toolkit/_tests/test_toolkits.py | 28 +++++++++++++++++++++++++ openff/toolkit/utils/openeye_wrapper.py | 14 +++++++++++-- 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/docs/releasehistory.md b/docs/releasehistory.md index 9de7a6fb2..eef753aaa 100644 --- a/docs/releasehistory.md +++ b/docs/releasehistory.md @@ -14,6 +14,8 @@ Releases follow the `major.minor.micro` scheme recommended by [PEP440](https://w ### Behavior changes ### Bugfixes +- [PR #1971](https://github.com/openforcefield/openff-toolkit/pull/1971): Fixes bug where OpenEyeToolkitWrapper would write coordinate-less PDB atoms if insertion_code or chain_id was an empty string ([Issue #1967](https://github.com/openforcefield/openff-toolkit/issues/1967)) + ### New features diff --git a/openff/toolkit/_tests/test_toolkits.py b/openff/toolkit/_tests/test_toolkits.py index d68dd4ac3..dd36b4d08 100644 --- a/openff/toolkit/_tests/test_toolkits.py +++ b/openff/toolkit/_tests/test_toolkits.py @@ -1041,6 +1041,34 @@ def test_write_pdb_preserving_atom_order(self): assert water_from_pdb_split[1].split()[2].rstrip() == "O" assert water_from_pdb_split[2].split()[2].rstrip() == "H" + def test_write_pdb_blank_chain_id_insertion_code(self): + """ + Ensure PDB files are written with coords by OE when chain ID or insertion code is blank string. + (reference: https://github.com/openforcefield/openff-toolkit/issues/1967). + """ + toolkit = OpenEyeToolkitWrapper() + water = Molecule() + water.add_atom(1, 0, False) + water.add_atom(8, 0, False) + water.add_atom(1, 0, False) + water.add_bond(0, 1, 1, False) + water.add_bond(1, 2, 1, False) + water.add_conformer( + Quantity( + np.array([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]), + unit.angstrom, + ) + ) + water.atoms[0].metadata["insertion_code"] = "" + water.atoms[1].metadata["chain_id"] = "" + with NamedTemporaryFile(suffix='.pdb') as of: + water.to_file(of.name, "pdb", toolkit_registry=toolkit) + roundtripped = toolkit.from_file(of.name, file_format='pdb') + np.testing.assert_allclose( + water.conformers[0].m_as(unit.angstrom), + roundtripped[0].conformers[0].m_as(unit.angstrom) + ) + def test_get_sdf_coordinates(self): """Test OpenEyeToolkitWrapper for importing a single set of coordinates from a sdf file""" diff --git a/openff/toolkit/utils/openeye_wrapper.py b/openff/toolkit/utils/openeye_wrapper.py index 778e02155..3e902ec52 100644 --- a/openff/toolkit/utils/openeye_wrapper.py +++ b/openff/toolkit/utils/openeye_wrapper.py @@ -1664,12 +1664,22 @@ def to_openeye( res.SetResidueNumber(1) if "insertion_code" in off_atom.metadata: - res.SetInsertCode(off_atom.metadata["insertion_code"]) + # Replace blank string with single character space to avoid OE PDB writing issue + # https://github.com/openforcefield/openff-toolkit/issues/1967 + if off_atom.metadata["insertion_code"] == "": + res.SetInsertCode(" ") + else: + res.SetInsertCode(off_atom.metadata["insertion_code"]) else: res.SetInsertCode(" ") if "chain_id" in off_atom.metadata: - res.SetChainID(off_atom.metadata["chain_id"]) + # Replace blank string with single character space to avoid OE PDB writing issue + # https://github.com/openforcefield/openff-toolkit/issues/1967 + if off_atom.metadata["chain_id"] == "": + res.SetChainID(" ") + else: + res.SetChainID(off_atom.metadata["chain_id"]) else: res.SetChainID(" ")