writePDB(box=...) for CRYST1 and parsePDB(hexadecimal=True) override - #2276
jamesmkrieger wants to merge 2 commits into
Conversation
…rlier `parsePDB` decodes large serials and residue numbers by auto-detection: a field containing uppercase letters is read as hybrid36, anything else as hex, and plain-decimal mode is left only once a resnum decreases from a value at or above 9999. That works on a file whose numbering starts decimal and later overflows, which is what `writePDB` produces, but not on one that is hex throughout: there is no decrease to detect and an uppercase hex serial is taken for hybrid36 and can come back negative. `hexadecimal=True` skips the detection and reads every serial and resnum as hex. The mixed numbering `writePDB` emits cannot be decoded either way -- `2710` is a valid decimal string as well as hex for 10000 -- so the docstring says so, and points at `hybrid36=True` for writing instead, whose range always begins with a letter. The wraparound check also required `acount > 2`, which needlessly waited an extra atom and missed the overflow in a very short file; `acount >= 2` is the loosest bound that keeps `resnums[acount-2]` a valid index. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
An AtomGroup carries no unit cell, so prody had no way to write one and every PDB it wrote came out with no `CRYST1` record. `box=` takes three lengths in Angstrom, six cell parameters with angles in degrees, or a 3x3 array of box vectors whose rows are a, b, c, from which the lengths and angles are computed, and writes the record between the REMARK block and the coordinates. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
7176e60 to
f42680f
Compare
|
This PR also suffers from the sporadic insty parallel bug that is fixed in PR #2277 |
There was a problem hiding this comment.
Looks good.
TEST1:
I tested the following code:
from prody import *
import numpy as np
p = parsePDB('1tqn')
writePDB('1tqn_test.pdb', p)
writePDB('1tqn_box3.pdb', p, box=[192.4497, 192.4497, 192.4497])
writePDB('1tqn_box6.pdb', p, box=[90, 80, 70, 90, 90, 120])
box = np.array([[90.0, 0.0, 0.0], [0.0, 80.0, 0.0], [0.0, 0.0, 70.0]])
writePDB('1tqn_box_vectors.pdb', p, box=box)
p1 = parsePDB('1tqn_box3.pdb')
p2 = parsePDB('1tqn_box6.pdb')
p3 = parsePDB('1tqn_box_vectors.pdb')
print(p1)
print(p2)
print(p3)
Output:
@> Connecting wwPDB FTP server RCSB PDB (USA).
@> Downloading PDB files via FTP failed, trying HTTP.
@> 1tqn downloaded (1tqn.pdb.gz)
@> PDB download via HTTP completed (1 downloaded, 0 failed).
@> 3999 atoms and 1 coordinate set(s) were parsed in 0.12s.
@> 3999 atoms and 1 coordinate set(s) were parsed in 0.04s.
@> 3999 atoms and 1 coordinate set(s) were parsed in 0.04s.
@> 3999 atoms and 1 coordinate set(s) were parsed in 0.04s.
AtomGroup 1tqn_box3
AtomGroup 1tqn_box6
AtomGroup 1tqn_box_vectors
Checks:
$ grep CRYST1 1tqn_box3.pdb
CRYST1 192.450 192.450 192.450 90.00 90.00 90.00 P 1 1
$ grep CRYST1 1tqn_box6.pdb
CRYST1 90.000 80.000 70.000 90.00 90.00 120.00 P 1 1
$ grep CRYST1 1tqn_box_vectors.pdb
CRYST1 90.000 80.000 70.000 90.00 90.00 90.00 P 1 1
TEST2:
Code:
from prody import *
with open('hex_test.pdb', 'w') as f:
f.write('ATOM 186A0 CA ALA A3039 10.000 10.000 10.000 1.00 0.00 C\n')
f.write('ATOM 186A1 CA GLY A303A 11.000 10.000 10.000 1.00 0.00 C\n')
f.write('END\n')
p = parsePDB('hex_test.pdb', hexadecimal=True)
print(p.getSerials())
print(p.getResnums())
Output:
@> 2 atoms and 1 coordinate set(s) were parsed in 0.00s.
[100000 100001]
[12345 12346]
Two small independent additions, split into one commit each.
writePDB(..., box=...)writes aCRYST1recordAn
AtomGroupcarries no unit cell, so ProDy had no way to write one and every PDB it wrote came out with noCRYST1.box=accepts three lengths in Å (angles default to 90), six cell parameters with angles in degrees, or a 3×3 array of box vectors whose rows are a, b, c, from which the lengths and angles are computed. The record is written between the REMARK block and the coordinates. DefaultNonewrites nothing, so existing output is unchanged.parsePDB(..., hexadecimal=True)parsePDBdecodes large serials and resnums by auto-detection: a field with uppercase letters is read as hybrid36, anything else as hex, and plain-decimal mode is left only once a resnum decreases from a value ≥ 9999. That fits a file that starts decimal and later overflows, which is whatwritePDBproduces — but not one that is hex throughout: there is no decrease to detect, and an uppercase hex serial is taken for hybrid36 and can come back negative.hexadecimal=Trueskips the detection and reads every field as hex.The docstring states the limit rather than pretending to fix it: the mixed numbering
writePDBemits cannot be decoded either way, since2710is a valid decimal string as well as hex for 10000. It points athybrid36=Truefor writing instead, whose range always begins with a letter and so stays unambiguous. It also notes this is plain hex, not OpenMM's shifted-hex convention.The same commit loosens the wraparound guard from
acount > 2toacount >= 2— the loosest bound that keepsresnums[acount-2]a valid index. The stricter test needlessly waited one extra atom and missed the overflow in a very short file.🤖 Generated with Claude Code