Skip to content

writePDB(box=...) for CRYST1 and parsePDB(hexadecimal=True) override - #2276

Open
jamesmkrieger wants to merge 2 commits into
prody:mainfrom
jamesmkrieger:jamesk-claude/pdb-cryst1-and-hex
Open

jamesmkrieger wants to merge 2 commits into
prody:mainfrom
jamesmkrieger:jamesk-claude/pdb-cryst1-and-hex

Conversation

@jamesmkrieger

Copy link
Copy Markdown
Contributor

Two small independent additions, split into one commit each.

writePDB(..., box=...) writes a CRYST1 record

An AtomGroup carries no unit cell, so ProDy had no way to write one and every PDB it wrote came out with no CRYST1. 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. Default None writes nothing, so existing output is unchanged.

writePDB('out.pdb', ag, box=[192.4497] * 3)
writePDB('out.pdb', ag, box=[90, 80, 70, 90, 90, 120])
writePDB('out.pdb', ag, box=state.getPeriodicBoxVectors(asNumpy=True) * 10)

parsePDB(..., hexadecimal=True)

parsePDB decodes 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 what writePDB produces — 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=True skips the detection and reads every field as hex.

The docstring states the limit rather than pretending to fix it: the mixed numbering writePDB emits cannot be decoded either way, since 2710 is a valid decimal string as well as hex for 10000. It points at hybrid36=True for 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 > 2 to acount >= 2 — the loosest bound that keeps resnums[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

jamesmkrieger and others added 2 commits September 10, 2026 17:53
…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>
@jamesmkrieger
jamesmkrieger force-pushed the jamesk-claude/pdb-cryst1-and-hex branch from 7176e60 to f42680f Compare September 10, 2026 17:01
@jamesmkrieger

jamesmkrieger commented Sep 10, 2026

Copy link
Copy Markdown
Contributor Author

This PR also suffers from the sporadic insty parallel bug that is fixed in PR #2277

@karolamik13 karolamik13 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants