File size: 3,742 Bytes
1a9fdda
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
"""POSITIVE CONTROL. Run this before believing any null against rosetta_score.

Three tests came back null against rosetta_score. Before concluding anything
from that, check whether the score responds to backbone strain AT ALL.

It does not. The most direct possible strain measure -- raw dihedral deviation
from the nearest populated Ramachandran basin -- gives:

    mean dihedral deviation   rho = +0.105  p = 0.03
    max  dihedral deviation   rho = +0.004  p = 0.93
    closure-Jacobian sigma_6  rho = -0.025  p = 0.61

with every per-N correlation non-significant and signs flipping from -0.18 to
+0.25. Rosetta total score is dominated by van der Waals packing, solvation,
H-bonding and rotamer terms; the backbone dihedral contribution is a thin slice,
and these structures were relaxed before scoring, which compresses the range
further.

CONSEQUENCE: the nulls were uninformative, not falsifying. This file is the
evidence behind retraction R1 in FINDINGS.md. A strain-specific observable is
required instead: the rama term alone, ring strain as cyclized minus relaxed
linear, or experimental cyclization yields.
"""
import numpy as np, pandas as pd, os
from numpy.linalg import norm, svd
from holonomy_extract import read_pdb_backbone, closure_jacobian
from scipy.stats import spearmanr

def dih(p0, p1, p2, p3):
    b0, b1, b2 = p0-p1, p2-p1, p3-p2; b1 = b1/norm(b1)
    v = b0 - (b0 @ b1)*b1; w = b2 - (b2 @ b1)*b1
    return np.degrees(np.arctan2(np.cross(b1, v) @ w, v @ w))

# populated basins by residue class
BAS = {'X': [(-63,-43), (-120,130), (-75,145)],
       'G': [(-82,8), (82,-8), (-63,-43), (63,43)],
       'P': [(-65,145), (-65,-30)]}

def dev(res, phi, psi):
    c = res if res in 'GP' else 'X'
    return min(np.hypot(((phi-a+180) % 360)-180, ((psi-b+180) % 360)-180)
               for a, b in BAS[c])

s = pd.read_csv('sample.csv').merge(
        pd.read_parquet('hall.parquet')[['ID','nmer']], on='ID')
rows = []
for _, t in s.iterrows():
    f = "pdbs/" + os.path.basename(t['hf_path'])
    if not os.path.exists(f): continue
    bb = read_pdb_backbone(f)
    ks = sorted(k for k in bb if {'N','CA','C'} <= set(bb[k]))
    n = len(ks); seq = t['sequence']
    if n < 6 or len(seq) != n: continue
    ds = []
    for i in range(n):
        a, b, d = ks[i-1], ks[i], ks[(i+1) % n]
        try:
            ph = dih(bb[a]['C'], bb[b]['N'], bb[b]['CA'], bb[b]['C'])
            ps = dih(bb[b]['N'], bb[b]['CA'], bb[b]['C'], bb[d]['N'])
            ds.append(dev(seq[i], ph, ps))
        except Exception: pass
    if len(ds) < n-1: continue
    J, _ = closure_jacobian({k: bb[k] for k in ks}); sv = svd(J, compute_uv=False)
    rows.append(dict(ID=t['ID'], N=n, score=t['rosetta_score'],
                     dev_mean=float(np.mean(ds)), dev_max=float(np.max(ds)),
                     sigma6=float(sv[5]), cond=float(sv[0]/sv[5])))

d = pd.DataFrame(rows); q = d[d.score < d.score.quantile(.95)]
print(f"{len(d)} structures, {len(q)} after trimming the top 5% of scores\n")
print("=" * 72); print("Does rosetta_score see backbone strain?"); print("=" * 72)
print(f"  {'predictor':<28}{'Spearman rho':>14}{'p':>12}")
for c, lab in [('dev_mean','mean dihedral deviation'),
               ('dev_max','max dihedral deviation'),
               ('sigma6','closure-Jacobian sigma_6'),
               ('cond','condition number')]:
    r = spearmanr(q[c], q.score)
    print(f"  {lab:<28}{r.statistic:>+14.3f}{r.pvalue:>12.2e}")
print("\n  per ring size, mean deviation vs score:")
print(f"  {'N':>4}{'n':>5}{'rho':>9}{'p':>10}")
for N in sorted(q.N.unique()):
    g = q[q.N == N]
    if len(g) < 15: continue
    r = spearmanr(g.dev_mean, g.score)
    print(f"  {N:>4}{len(g):>5}{r.statistic:>+9.3f}{r.pvalue:>10.3f}")