From d80f48e6bd453635cd0cf5d75a81da52c7fa34b5 Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Thu, 6 Aug 2026 00:20:41 +0200 Subject: [PATCH] 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) --- rugnux_anomalous.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/rugnux_anomalous.py b/rugnux_anomalous.py index 49da8ef6..056a142f 100644 --- a/rugnux_anomalous.py +++ b/rugnux_anomalous.py @@ -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" 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]})