Repository: biopython/biopython

Issue:
StructureBuilder cannot handle non-canonical altloc ordering written by PDBIO
Parsing a PDB file with disordered atoms where the first instance of the disordered atom has a non-blank altloc but other instances are blank fails - second instance is ignored.  See this example:

 - PDB 5j2u, line 10598: No altloc in first instance of disordered atom H (goes from blank to `B`):
```
ATOM   9132  N   SER B 155      30.962  53.255  26.071  1.00 48.84           N
ATOM   9133  CA  SER B 155      32.293  52.794  26.445  1.00 56.65           C
ATOM   9134  C   SER B 155      33.375  53.714  25.883  1.00 50.84           C
ATOM   9135  O   SER B 155      34.387  53.970  26.534  1.00 53.74           O
ATOM   9136  CB  SER B 155      32.510  51.365  25.951  1.00 57.00           C
ATOM   9137  OG  SER B 155      33.470  50.690  26.748  1.00 59.12           O
ATOM   9138  H   SER B 155      30.485  52.658  25.676  1.00 58.61           H
ATOM   9139  H  BSER B 155      30.485  52.658  25.676  0.00 58.61           H
ATOM   9140  HA  SER B 155      32.369  52.794  27.412  1.00 67.98           H
ATOM   9141  HB2 SER B 155      31.669  50.885  26.004  1.00 68.40           H
ATOM   9142  HB3 SER B 155      32.815  51.390  25.032  1.00 68.40           H
ATOM   9143  HG  SER B 155      33.579  49.907  26.465  1.00 70.94           H
```

PDBParser will do its job right, but then PDBIO will place atom 9139 before 9138, which mangles the round-trip parsing. Looking into `StructureBuilder`, it seems the logic behind the disordered atoms could use some improvement since it checks directly for a non-blank altloc code. Although this is not 'canonical' PDB format, it's a tiny improvement to our parser to make it more robust. We should also maybe add some logic to PDBIO to write blank altlocs first, and then named ones - fixing the problem from both ends.


Discussion:

Resolving patch:
diff --git a/Bio/PDB/Atom.py b/Bio/PDB/Atom.py
--- a/Bio/PDB/Atom.py
+++ b/Bio/PDB/Atom.py
@@ -482,6 +482,13 @@ def __repr__(self):
         """Return disordered atom identifier."""
         return "<Disordered Atom %s>" % self.get_id()
 
+    def disordered_get_list(self):
+        """Return list of atom instances.
+
+        Sorts children by altloc (empty, then alphabetical).
+        """
+        return sorted(self.child_dict.values(), key=lambda a: ord(a.altloc))
+
     def disordered_add(self, atom):
         """Add a disordered atom."""
         # Add atom to dict, use altloc as key


Test patch:
diff --git a/Tests/PDB/disordered.pdb b/Tests/PDB/disordered.pdb
--- a/Tests/PDB/disordered.pdb
+++ b/Tests/PDB/disordered.pdb
@@ -21,7 +21,7 @@ ATOM    218  CB  ARG A  27      56.899  20.533  28.660  1.00 16.52           C
 ATOM    219  CG  ARG A  27      57.901  20.081  27.621  1.00 19.45           C
 ATOM    220  CD  ARG A  27      58.541  21.359  27.019  1.00 22.58           C
 ATOM    221  NE  ARG A  27      59.504  20.850  26.023  1.00 26.89           N
-ATOM    222  CZ AARG A  27      59.081  20.674  24.762  0.50 26.69           C
+ATOM    222  CZ  ARG A  27      59.081  20.674  24.762  0.50 26.69           C
 ATOM    223  CZ BARG A  27      60.798  20.732  26.326  0.50 26.95           C
 ATOM    224  NH1AARG A  27      57.848  21.002  24.386  0.50 27.16           N
 ATOM    225  NH1BARG A  27      61.262  21.064  27.522  0.50 27.39           N