fix three bugs in dynamics/analysis.py (dropped hinges, IndexError, wrong collectivity norm) - #2262
fix three bugs in dynamics/analysis.py (dropped hinges, IndexError, wrong collectivity norm)#2262steps-re wants to merge 3 commits into
Conversation
…rong collectivity norm getGlobalHinges was silently discarding hinges from every chain but the last: the accumulation block was indented one level too high, outside the per-chain loop, so it only ever ran once using the leftover h/l/fst from the final chain. Moved it inside the loop. Also fixed an off-by-one in the same function: chains stores inclusive (fst, lst) pairs but the vecs slice used the exclusive vecs[fst:lst], dropping the last residue of every chain from hinge detection. Now vecs[fst:lst+1, i]. getHinges raised IndexError (regs[0] on an empty list) whenever an eigenvector segment had no sign change at all. Added a guard that returns an empty hinge list in that case, matching the function's normal return type. calcCollectivity normalized u2in by dividing by sqrt(sum) instead of sum when masses were supplied, which doesn't produce a valid probability distribution per Bruschweiler 1995 eq. 5 (the exponent no longer sums the correct entropy term). Only the masses path was affected; the default unit-mass path is unchanged since sum and sqrt(sum) both equal 1 there. Added prody/tests/dynamics/test_analysis_hinge_bugs.py with synthetic, offline, deterministic repros for all three (multi-chain AtomGroup + monkeypatched _getModeVecs for the hinge bugs, hand-computed Bruschweiler values for collectivity). All 6 new tests fail on the prior code and pass after the fix; existing dynamics test modules show no regressions. Co-Authored-By: Claude <noreply@anthropic.com>
stoneandbone
left a comment
There was a problem hiding this comment.
Hi @steps-re , thank you very much for reviewing the code and submitting a PR. Yes, point 1-2 are both valid. I meant to push these changes earlier, along other changes. But thank you for catching them.
As to Bruschweiler 1995 eq. 5-6., I do see your point about proper representation of the probability function, which is required to equal to 1 in order to compute the associated entropy. But with the square root, it does not. Just to have another pairs of eyes to confirm if @jamesmkrieger or @AnthonyBogetti can take a quick look, that would be great!
jamesmkrieger
left a comment
There was a problem hiding this comment.
Looks good to me
Give the tests file a more generic name like test_hinges
Also double check that the collectivity fix doesn’t break the no masses case
Per review: give the tests file a more generic name. Co-Authored-By: Claude <noreply@anthropic.com>
|
renamed it to on the no-masses case: it comes out identical, and the reason is that the normalization line sits outside the the two only diverge if someone passes a raw unnormalized array, which the old normalizes to 1.414 and 0.707 in those rows rather than 1, so it was not a probability distribution and the entropy term was off. collectivity should not depend on how the input array is scaled, so i think new is the behavior you want, but flagging it since it is a real behavior change for that input.
|
|
great. Probably the test_anmd.py failures are from importing openmm in a wrong way or having a wrong version of it installed. I'll have a look at it |
|
There's already a file test_hinge_finder.py, so probably your new hinge tests should go there. We could do with a general prody/tests/dynamics/test_dynamics_analysis.py really though for general things like collectivity |
|
ok, the main test_anmd problem is that openmm imports were before type checks. There were also a few others, so I am making a branch and PR to fix these |
… own module Per review: the hinge regressions belong with the existing hinge tests rather than in a new file, and collectivity is general enough to want a test_dynamics_analysis.py of its own for that kind of thing. No test bodies changed. All four still fail against the pre-fix analysis.py and pass after it. Co-Authored-By: Claude <noreply@anthropic.com>
|
moved them. the three hinge classes are in test_hinge_finder.py now next to the GNM ones, and collectivity got its own test_dynamics_analysis.py like you suggested. test_hinges.py is gone. test bodies are unchanged. i ran them against the pre-fix analysis.py and the same four still fail there and pass after, so the move didn't quietly defang anything. prody/tests/dynamics is 155 passed with the 5 test_anmd simtk import failures you're already on, and those show up without my changes too. |
|
Great, ready to merge |
disclosing up front: this was put together with help from claude (anthropic's coding agent), then reproduced and verified by me before opening. first pr i'm opening on prody.
three separate bugs in
prody/dynamics/analysis.py, all found while looking at the hinge-finding code. each is small and independent so happy to split into separate prs if you'd rather.1. getGlobalHinges silently drops every chain's hinges but the last
the
if trim is not False / elseblock that accumulates hinges is indented at the same level asfor fst, lst in chains:, so it runs once after the chain loop finishes, using only the leftoverh/l/fstfrom the last chain. for any multi-chain structure, every earlier chain's hinges are silently thrown away. fix is to move the accumulation inside the loop.there's also an off-by-one in the same spot:
chainsstores inclusive(fst, lst)pairs, but the code slicesvecs[fst:lst, i](exclusive), which drops each chain's last residue from hinge detection. fixed tovecs[fst:lst+1, i].2. getHinges crashes on a segment with no sign change
merged = [regs[0]]raises IndexError whenregsis empty, which happens whenever an eigenvector segment has no sign crossing (realistic for short segments, and more likely once bug #1 is splitting by chain). added a guard to return no hinges instead of crashing.3. calcCollectivity mis-normalizes when masses are supplied
Bruschweiler 1995 needs the per-atom msd normalized into a probability distribution (
p_i = u_i^2 / sum(u_j^2)) before the shannon-entropy step. the code divides bysqrt(sum(u_j^2))instead. this cancels on the default path because mode vectors are already unit-normalized (sum == sqrt(sum) == 1), so it only shows up whenmassesis passed, which is the documented-supported path. for v=[0.6,0.8], masses=[1,4] the current result is ~0.9878 vs the correct ~0.9269 (~35% off). fix is to divide by the sum, not its sqrt.verification
added prody/tests/dynamics/test_analysis_hinge_bugs.py (offline, deterministic, no downloads): 4 tests targeting the bugs + 2 control/regression guards. confirmed all 4 fail on current main (IndexError, dropped-chain hinge mismatch, wrong collectivity value) and pass after the fixes. ran the full prody/tests/dynamics/ suite, no regressions (existing test_hinge_finder.py still passes). the default no-masses collectivity path is unchanged.
mike