Skip to content

to_mmcif, to_pdb: keep link atoms present only as altlocs - #443

Open
CV-GPhL wants to merge 1 commit into
project-gemmi:masterfrom
CV-GPhL:fix/to-mmcif-conn-altloc-lookup
Open

CV-GPhL wants to merge 1 commit into
project-gemmi:masterfrom
CV-GPhL:fix/to-mmcif-conn-altloc-lookup

Conversation

@CV-GPhL

@CV-GPhL CV-GPhL commented Sep 28, 2026

Copy link
Copy Markdown
Member

A link partner can have no altloc - a PDB LINK record with a blank altloc
column, or an mmCIF _struct_conn row with pdbx_ptnrN_label_alt_id ? - while
the atom it names exists only in alternative conformations. find_cra() matches
altlocs strictly ('\0' matches only atoms without an altloc), so the writers
do not find the atom:

  • _struct_conn: ? for the atom name, ptnr2_symmetry and pdbx_dist_value
  • LINK: blank atom name, symmetry and distance
  • SSBOND: the record is not written at all

Example

A zinc bound to HIS 64 NE2, which has two conformers; the LINK gives no altloc:

import gemmi

PDB = """\
LINK        ZN    ZN A 301                 NE2 HIS A  64     1555   1555  2.10
CRYST1   30.000   30.000   30.000  90.00  90.00  90.00 P 1
ATOM      1  NE2AHIS A  64      12.100  10.000  10.000  0.60 20.00           N
ATOM      2  NE2BHIS A  64      12.600  10.000  10.000  0.40 20.00           N
HETATM    3 ZN    ZN A 301      10.000  10.000  10.000  1.00 20.00          ZN
"""
st = gemmi.read_pdb_string(PDB)
print(st.make_pdb_headers().splitlines()[1])
tags = ['id', 'ptnr1_label_atom_id', 'ptnr2_label_atom_id', 'pdbx_dist_value']
print(*st.make_mmcif_document()[0].find('_struct_conn.', tags)[0])

Before (0.7.5):

LINK        ZN    ZN A 301                     HIS A  64     1555
metalc1 ZN ? ?

After:

LINK        ZN    ZN A 301                 NE2 HIS A  64     1555   1555  2.10
metalc1 ZN NE2 2.1000
The same for a disulfide read from mmCIF (the SSBOND record was lost)

CYS 5 SG has conformers A and B; the _struct_conn row has alt_id ?.

import gemmi

CIF = """\
data_example
_cell.length_a 30
_cell.length_b 30
_cell.length_c 30
_cell.angle_alpha 90
_cell.angle_beta 90
_cell.angle_gamma 90
_symmetry.space_group_name_H-M 'P 1'
loop_
_atom_site.group_PDB
_atom_site.id
_atom_site.type_symbol
_atom_site.label_atom_id
_atom_site.label_alt_id
_atom_site.label_comp_id
_atom_site.label_asym_id
_atom_site.label_seq_id
_atom_site.Cartn_x
_atom_site.Cartn_y
_atom_site.Cartn_z
_atom_site.occupancy
_atom_site.B_iso_or_equiv
_atom_site.auth_seq_id
_atom_site.auth_asym_id
ATOM 1 S SG A CYS A 5  10.00 10.00 10.00 0.70 20 5  A
ATOM 2 S SG B CYS A 5  10.00 11.60 11.60 0.30 20 5  A
ATOM 3 S SG . CYS A 20 12.04 10.00 10.00 1.00 20 20 A
loop_
_struct_conn.id
_struct_conn.conn_type_id
_struct_conn.ptnr1_label_asym_id
_struct_conn.ptnr1_label_comp_id
_struct_conn.ptnr1_label_seq_id
_struct_conn.ptnr1_label_atom_id
_struct_conn.pdbx_ptnr1_label_alt_id
_struct_conn.ptnr2_label_asym_id
_struct_conn.ptnr2_label_comp_id
_struct_conn.ptnr2_label_seq_id
_struct_conn.ptnr2_label_atom_id
_struct_conn.pdbx_ptnr2_label_alt_id
disulf1 disulf A CYS 5 SG ? A CYS 20 SG ?
"""
st = gemmi.read_structure_string(CIF)
print(st.make_pdb_headers())

Before (0.7.5): only the CRYST1 line - no SSBOND.

After:

SSBOND   1 CYS A    5    CYS A   20                          1555   1555  2.04
CRYST1   30.000   30.000   30.000  90.00  90.00  90.00 P 1

Change

New find_connection_atoms(st, con) (declared in to_pdb.hpp, next to
use_hetatm()): find_cra() for both partners, and if a partner without altloc
names an atom that is present only in alternative conformations, the closest
pair of atoms of the same conformer - as complete_ssbond() already does when
reading SSBOND records. It is used by write_struct_conn() and by the SSBOND
and LINK writers.

  • No altloc is added: the alt_id / altloc column stays as the Connection has it
    (? / blank for such a partner).
  • Links whose atoms are found by the strict lookup are written as before.
  • Links whose atoms are really absent (LINKR gap) are written as before.

Tests

tests/test_mol.py: test_link_to_altloc_atoms and
test_ssbond_to_altloc_atoms, both failing without the change. Locally the
Python tests and cpptest pass.

Not in this PR: whether such a link should become one link per conformer, as
the wwPDB writes them (one _struct_conn row per altloc) - I will raise that
separately in an issue.

A link partner without altloc (a LINK record with a blank altloc column,
or a _struct_conn row with pdbx_ptnrN_label_alt_id ?) can name an atom
that exists only as altloc A, B, ... The strict altloc matching of
find_cra() does not find it, so the _struct_conn and LINK writers wrote
? or blank for the atom name, symmetry and distance, and the SSBOND
writer skipped the record.

New find_connection_atoms() falls back to the closest pair of atoms of
the same conformer, as complete_ssbond() does when reading. The atom
names, symmetry and distance are written; no altloc is added. Atoms that
are really absent (LINKR gap) are handled as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant