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
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 76Fields 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.
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
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 NThe mmCIF Data Block
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.