rugnux_anomalous.py: read ANODE peak labels for any scatterer, not just sulfur

ANODE names its ranked peaks after the SFAC element it was given, so the labels
read S1, S2, ... on a sulfur case but FE1, MN1, ZN1, SE1 as soon as the model
carries a heavier scatterer. The parser matched "S" followed by digits, so on
any such dataset it dropped the ENTIRE peak list and the dataset failed the gate
as "no peaks" - however strong its signal actually was. One heme case reported
no peaks when its true top peak is 13.79 sigma at 3.02x the off-site floor.

The bug is silent and it mis-gates exactly the datasets most likely to widen the
arbiter set, since a heavy scatterer is what makes a weakly diffracting crystal
usable as an arbiter in the first place.

Byte-identical on the standing all-sulfur set, verified by diffing the gate table
before and after.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-06 00:20:41 +02:00
co-authored by Claude Opus 5
parent 227f1bf1b4
commit d80f48e6bd
+8 -1
View File
@@ -98,6 +98,7 @@ if __name__ == "__main__" and not os.environ.get("RUGNUX_ANO_BOOTSTRAPPED"):
import argparse
import json
import math
import re
import shutil
import statistics
import datetime
@@ -111,6 +112,12 @@ CCP4_SETUP_DEFAULT = "/opt/xtal/ccp4-9/bin/ccp4.setup-sh"
# leading-letters rule gets them wrong.
ANOM = {"S", "CL", "P", "CA", "K", "FE", "ZN", "SE", "BR", "CU", "MN", "NI", "CO", "I"}
# ANODE names its ranked peaks after the SFAC element it was given, so the labels read
# S1, S2, ... on a sulfur case but FE1, MN1, ZN1, ... as soon as the model carries a
# heavier scatterer. Matching only "S<n>" silently drops the whole peak list on exactly
# those datasets, which then fail the gate as "no peaks" however strong they are.
PEAK_LABEL = re.compile(r"^[A-Za-z]{1,2}\d+$")
# ANODE is run with -s4.0, so its ranked peak list stops at ~4 sigma. An empty off-site
# list therefore means "below 4", not "zero".
FLOOR_MIN = 4.0
@@ -282,7 +289,7 @@ def parse_lsa(lsa, pdb, spag):
atoms.append((f[3], float(f[0])))
except ValueError:
pass
elif mode == "peaks" and len(f) == 8 and f[0][0] == "S" and f[0][1:].isdigit():
elif mode == "peaks" and len(f) == 8 and PEAK_LABEL.match(f[0]):
try:
peaks.append({"xyz": tuple(float(v) for v in f[1:4]), "h": float(f[4]),
"dist": float(f[6]), "near": f[7]})