如果使用 Biopython 來處理由分子動力學 (MD) 程式產生的 PDB 檔案,在讀取時很快就會遇到原子缺失的問題。典型的錯誤訊息指出原子將會遺失。
WARNING: Residue (' ', 1, ' ') redefined at line 31.
PDBConstructionException: Blank altlocs in duplicate residue SOL (' ', 1, ' ') at line 31.
Exception ignored.
Some atoms or residues will be missing in the data structure.
問題很簡單,這些檔案可能很大,包含數十萬個原子和殘基(例如,每個水分子都是一個獨立的殘基),而 PDB 格式在 ATOM 或 HETATM 記錄的適當欄位中沒有足夠的空間來容納原子編號 (serial) > 99,999 和殘基編號 (resSeq) > 9999。因此,這些數字只是以模 100,000 (serial) 或模 10,000 (resSeq) 的方式寫入。這會在鏈中產生重複的條目,並且 Bio.PDB.PDBParser
(或更確切地說,是 StructureBuilder)會發出警告。其結果是並非所有原子都被讀取。
下方的 程式碼 從 Bio.PDB.StructureBuilder.StructureBuilder
衍生出一個新的類別,該類別會在必要時簡單地增加 resSeq。由於它不是很小心,所以被稱為 SloppyStructureBuilder
。
還有一個名為 SloppyPDBIO
的新類別,它會寫入具有經包裹處理的 serial 和 resSeq 的 pdb 檔案,使產生的 pdb 檔案符合 合法的 PDB 格式。
載入一個大型 pdb 檔案並再次將其寫出。如何對 pdb 檔案進行有趣的操作,例如刪除配體周圍的一些水分子,將在另一篇文章中討論。
from Bio.PDB import PDBParser
import xpdb # this is the module described below
# read
sloppyparser = PDBParser(
PERMISSIVE=True, structure_builder=xpdb.SloppyStructureBuilder()
)
structure = sloppyparser.get_structure("MD_system", "my_big_fat.pdb")
# ... do something here ...
# write
sloppyio = xpdb.SloppyPDBIO()
sloppyio.set_structure(structure)
sloppyio.save("new_big_fat.pdb")
這是 Python 實作。將其儲存為模組 xpdb.py
並放在 PYTHONPATH
上的某個位置。
# xpdb.py -- extensions to Bio.PDB
# (c) 2009 Oliver Beckstein
# Relased under the same license as Biopython.
# See https://biopython.dev.org.tw/wiki/Reading_large_PDB_files
import sys
import Bio.PDB
import Bio.PDB.StructureBuilder
from Bio.PDB.Residue import Residue
class SloppyStructureBuilder(Bio.PDB.StructureBuilder.StructureBuilder):
"""Cope with resSeq < 10,000 limitation by just incrementing internally.
# Q: What's wrong here??
# Some atoms or residues will be missing in the data structure.
# WARNING: Residue (' ', 8954, ' ') redefined at line 74803.
# PDBConstructionException: Blank altlocs in duplicate residue SOL
# (' ', 8954, ' ') at line 74803.
#
# A: resSeq only goes to 9999 --> goes back to 0 (PDB format is not really
# good here)
"""
# NOTE/TODO:
# - H and W records are probably not handled yet (don't have examples
# to test)
def __init__(self, verbose=False):
Bio.PDB.StructureBuilder.StructureBuilder.__init__(self)
self.max_resseq = -1
self.verbose = verbose
def init_residue(self, resname, field, resseq, icode):
"""Initiate a new Residue object.
Arguments:
o resname - string, e.g. "ASN"
o field - hetero flag, "W" for waters, "H" for
hetero residues, otherwise blanc.
o resseq - int, sequence identifier
o icode - string, insertion code
"""
if field != " ":
if field == "H":
# The hetero field consists of
# H_ + the residue name (e.g. H_FUC)
field = "H_" + resname
res_id = (field, resseq, icode)
if resseq > self.max_resseq:
self.max_resseq = resseq
if field == " ":
fudged_resseq = False
while self.chain.has_id(res_id) or resseq == 0:
# There already is a residue with the id (field, resseq, icode)
# resseq == 0 catches already wrapped residue numbers which
# do not trigger the has_id() test.
#
# Be sloppy and just increment...
# (This code will not leave gaps in resids... I think)
#
# XXX: shouldn't we also do this for hetero atoms and water??
self.max_resseq += 1
resseq = self.max_resseq
res_id = (field, resseq, icode) # use max_resseq!
fudged_resseq = True
if fudged_resseq and self.verbose:
sys.stderr.write(
"Residues are wrapping (Residue "
+ "('%s', %i, '%s') at line %i)."
% (field, resseq, icode, self.line_counter)
+ ".... assigning new resid %d.\n" % self.max_resseq
)
residue = Residue(res_id, resname, self.segid)
self.chain.add(residue)
self.residue = residue
class SloppyPDBIO(Bio.PDB.PDBIO):
"""PDBIO class that can deal with large pdb files as used in MD simulations
- resSeq simply wrap and are printed modulo 10,000.
- atom numbers wrap at 99,999 and are printed modulo 100,000
"""
# The format string is derived from the PDB format as used in PDBIO.py
# (has to be copied to the class because of the package layout it is not
# externally accessible)
_ATOM_FORMAT_STRING = (
"%s%5i %-4s%c%3s %c%4i%c " + "%8.3f%8.3f%8.3f%6.2f%6.2f %4s%2s%2s\n"
)
def _get_atom_line(
self,
atom,
hetfield,
segid,
atom_number,
resname,
resseq,
icode,
chain_id,
element=" ",
charge=" ",
):
"""Returns an ATOM string that is guaranteed to fit the ATOM format.
- Resid (resseq) is wrapped (modulo 10,000) to fit into %4i (4I) format
- Atom number (atom_number) is wrapped (modulo 100,000) to fit into
%5i (5I) format
"""
if hetfield != " ":
record_type = "HETATM"
else:
record_type = "ATOM "
name = atom.get_fullname()
altloc = atom.get_altloc()
x, y, z = atom.get_coord()
bfactor = atom.get_bfactor()
occupancy = atom.get_occupancy()
args = (
record_type,
atom_number % 100000,
name,
altloc,
resname,
chain_id,
resseq % 10000,
icode,
x,
y,
z,
occupancy,
bfactor,
segid,
element,
charge,
)
return self._ATOM_FORMAT_STRING % args
# convenience functions
sloppyparser = Bio.PDB.PDBParser(
PERMISSIVE=True, structure_builder=SloppyStructureBuilder()
)
def get_structure(pdbfile, pdbid="system"):
return sloppyparser.get_structure(pdbid, pdbfile)
SloppyStructureBuilder()
被用作一個小型 Python 模組 edPDB 的基礎,用於編輯 PDB 檔案,以準備進行 MD 模擬。