diff --git a/tools/battery/score.py b/tools/battery/score.py index d033a7239..6d213c039 100644 --- a/tools/battery/score.py +++ b/tools/battery/score.py @@ -298,17 +298,26 @@ def judge(entry, rep, run_note): # the reference is among the groups it offered, and the ONLY axis the two differ on is # one it named. A screw called wrongly where the row WAS measured meets none of them # and stays a failure. + # WHICH axis carries the disagreement is a property of the setting, and the two sides + # are named in different ones: the reference in the deposition's axis labels, ours - and + # SPACE_GROUP_SCREW_UNDETERMINED with it - in our own. Where the cells are a permutation + # of each other the two never meet: a deposited P 21 21 2 differs from our P 21 21 21 on + # c, while the same group written in our axes differs on b, the axis actually left open. + # So the axes are compared against the offered group, which is already in our setting; + # the reference being among the offered is a condition anyway, and a group offered under + # more than one setting passes if any of them fits. undetermined = set((rep.get("SPACE_GROUP_SCREW_UNDETERMINED") or "NONE").split()) offered = [gemmi.find_spacegroup_by_name(x.strip()) for x in (rep.get("SPACE_GROUP_ALTERNATIVES") or "").split("|")] - differ = screw_axes_differing(space_group(rep.get("SPACE_GROUP_NAME"), r["sgno"]), - space_group(cand.get("sg"), cand["sgno"])) - if (undetermined != {"NONE"} and differ and differ <= undetermined - and any(o and o.number == cand["sgno"] for o in offered)): - return n, ("unscored", "screw_undetermined", - f"{r['sg']} vs reference {sg_cand}: the screw along " - f"{', '.join(sorted(differ))} is undeterminable from these data " - f"(offered {rep.get('SPACE_GROUP_ALTERNATIVES')})") + ours = space_group(rep.get("SPACE_GROUP_NAME"), r["sgno"]) + for o in (x for x in offered if x and x.number == cand["sgno"]): + differ = screw_axes_differing(ours, o) + if undetermined != {"NONE"} and differ and differ <= undetermined: + here = f" (here {o.hm})" if o.hm != sg_cand else "" + return n, ("unscored", "screw_undetermined", + f"{r['sg']} vs reference {sg_cand}{here}: the screw along " + f"{', '.join(sorted(differ))} is undeterminable from these data " + f"(offered {rep.get('SPACE_GROUP_ALTERNATIVES')})") return n, ("fail", "sym_screw", f"{r['sg']} vs reference {sg_cand}") cause = ("sym_under" if order < order_ref else "sym_over" if order > order_ref else "sym_other") diff --git a/tools/battery/test_score.py b/tools/battery/test_score.py index 79d5d2e76..51898bc8a 100644 --- a/tools/battery/test_score.py +++ b/tools/battery/test_score.py @@ -93,5 +93,53 @@ class AcceptedAlternatives(unittest.TestCase): score.alternatives(e) +# A screw along an axis the sweep never recorded: an unanswerable question, not a wrong answer. +# The cells are synthetic, and the second is the first cyclically permuted. +ORTHO = [97.0, 108.0, 113.0, 90.0, 90.0, 90.0] +ORTHO_PERMUTED = [113.0, 97.0, 108.0, 90.0, 90.0, 90.0] + + +class ScrewUndetermined(unittest.TestCase): + def judge(self, ref_cell, undetermined, alternatives): + """Our P 21 21 21 on ORTHO against a deposited P 21 21 2 on ref_cell.""" + e = {"id": "test_screw", "arm": "open", + "ref": {"sg": "P 21 21 2", "sgno": 18, "cell": ref_cell}} + return score.judge(e, report("P 21 21 21", 19, ORTHO, + SPACE_GROUP_SCREW_UNDETERMINED=undetermined, + SPACE_GROUP_ALTERNATIVES=alternatives), "") + + def test_same_setting(self): + """The plain case: the reference is offered verbatim and the open axis is the one they + differ on.""" + r = self.judge(ORTHO, "c", "P 21 21 2") + self.assertEqual((r["verdict"], r["cause"]), ("unscored", "screw_undetermined")) + + def test_permuted_setting(self): + """The deposition's axes are a cyclic permutation of ours, so it names the open screw c + while we name it b. The comparison must be made in OUR setting - against the offered + group of the reference's number - or the rule misses a row it covers.""" + r = self.judge(ORTHO_PERMUTED, "b", "P 21 2 21") + self.assertEqual((r["verdict"], r["cause"]), ("unscored", "screw_undetermined")) + self.assertIn("the screw along b", r["reason"]) + + def test_a_wrong_screw_still_fails(self): + """The open axis is not the axis the two differ on: in our own setting the disagreement + sits on b while the run says it could not determine a, so the b row WAS measured. That is + a wrong answer and stays one - the new framing must not turn every permutation into an + excuse.""" + r = self.judge(ORTHO_PERMUTED, "a", "P 21 2 21") + self.assertEqual((r["verdict"], r["cause"]), ("fail", "sym_screw")) + + def test_nothing_undetermined_still_fails(self): + """Every axial row measured: no rule to apply.""" + r = self.judge(ORTHO, "NONE", "") + self.assertEqual((r["verdict"], r["cause"]), ("fail", "sym_screw")) + + def test_the_reference_must_be_among_the_offered(self): + """An open axis alone does not excuse a group the run never offered.""" + r = self.judge(ORTHO, "b", "P 21 21 21") + self.assertEqual((r["verdict"], r["cause"]), ("fail", "sym_screw")) + + if __name__ == "__main__": unittest.main()