Structural Bioinformatics

Lesson 2 of 13 · 11 min

The PDB and mmCIF/PDB File Formats

The Protein Data Bank has distributed atomic coordinates in two very different encodings. The legacy PDB format packs every atom into an 80-column, fixed-width ASCII line where meaning is determined entirely by character position, not by delimiters. The modern PDBx/mmCIF format stores the same information as named items inside a data block. Since 2014 mmCIF is the canonical master format of the wwPDB archive, and for the largest structures no legacy PDB file is produced at all.

The ATOM Record

text
         1         2         3         4         5         6         7
1234567890123456789012345678901234567890123456789012345678901234567890123456789
ATOM      1  N   MET A   1      20.154  29.699   5.276  1.00 49.05           N
HETATM 2402  O   HOH A 501      15.163  10.281  20.310  1.00 28.00           O
TER    1234      MET A  76

Fields occupy fixed 1-based column ranges: the record name in 1-6, atom serial in 7-11, atom name in 13-16, the alternate-location indicator (altLoc) in 17, residue name in 18-20, the single-column chain identifier in 22, residue sequence number (resSeq) in 23-26, the insertion code (iCode) in 27, the orthogonal coordinates x, y and z as 8.3f floats in 31-38, 39-46 and 47-54, occupancy as a 6.2f float in 55-60, the temperature (B) factor as a 6.2f float in 61-66, and the right-justified element symbol in 77-78. The atom name is itself alignment-sensitive: the element part is right-justified in columns 13-14, so a two-letter element like calcium fills both and its ion is written as CA followed by two spaces, while a one-letter element is shifted right so a protein's C-alpha carbon puts its C in column 14 and reads as a space, then CA, then a space, which is how parsers disambiguate a calcium ion from an alpha carbon. TER closes a polymer chain and only carries a serial, residue name, chain ID and resSeq.

python
line = "ATOM      1  N   MET A   1      20.154  29.699   5.276  1.00 49.05           N"

atom = {
    "record":     line[0:6].strip(),
    "serial":     int(line[6:11]),
    "name":       line[12:16].strip(),
    "altLoc":     line[16].strip(),
    "resName":    line[17:20].strip(),
    "chainID":    line[21].strip(),
    "resSeq":     int(line[22:26]),
    "iCode":      line[26].strip(),
    "x":          float(line[30:38]),
    "y":          float(line[38:46]),
    "z":          float(line[46:54]),
    "occupancy":  float(line[54:60]),
    "tempFactor": float(line[60:66]),
    "element":    line[76:78].strip(),
}
print(atom)
{'record': 'ATOM', 'serial': 1, 'name': 'N', 'altLoc': '', 'resName': 'MET', 'chainID': 'A', 'resSeq': 1, 'iCode': '', 'x': 20.154, 'y': 29.699, 'z': 5.276, 'occupancy': 1.0, 'tempFactor': 49.05, 'element': 'N'}

ANISOU and Metadata

text
HEADER    PLANT PROTEIN                           30-APR-81   1CRN
CRYST1   40.960   18.650   22.520  90.00  90.77  90.00 P 21          2
ATOM      1  N   THR A   1      17.047  14.099   3.625  1.00 13.79           N
ANISOU    1  N   THR A   1     1509   1920   1810    -14    354     35       N

The mmCIF Data Block

text
data_1CRN
#
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_entity_id
_atom_site.label_seq_id
_atom_site.pdbx_PDB_ins_code
_atom_site.Cartn_x
_atom_site.Cartn_y
_atom_site.Cartn_z
_atom_site.occupancy
_atom_site.B_iso_or_equiv
_atom_site.pdbx_formal_charge
_atom_site.auth_seq_id
_atom_site.auth_comp_id
_atom_site.auth_asym_id
_atom_site.auth_atom_id
_atom_site.pdbx_PDB_model_num
ATOM 1 N N . THR A 1 1 ? 17.047 14.099 3.625 1.00 13.79 ? 1 THR A N 1
#

In the data block every column is named by an _atom_site item and rows are whitespace-delimited, so a period means not-applicable (here the empty altLoc) and a question mark means missing or unknown (here no insertion code and no formal charge). The label items are the canonical, program-assigned identifiers while the auth items preserve the author chain letters and residue numbers that match the old PDB. mmCIF became canonical because the fixed-width format runs out of columns: the serial field holds at most five digits (near 99,999 atoms), resSeq holds four (9,999 residues per chain), and the chain identifier is a single column, so multi-character IDs such as AA cannot be written at all. Ribosomes, viral capsids and large cryo-EM assemblies exceed every one of these limits, which mmCIF, being free-format with no width limit, handles natively.

Try it yourself: Fetch the HIV-1 capsid 3J3Q, which has over 2.4 million atoms and no legacy PDB file, with wget https://files.rcsb.org/download/3J3Q.cif.gz and gunzip it. Load it with gemmi (import gemmi; st = gemmi.read_structure('3J3Q.cif')) and print len(st[0]) chains plus max(len(ch.name) for ch in st[0]). Confirm at least one chain name is longer than one character, the precise case the fixed-column PDB format cannot represent.