"""Targeted tests of score.py, the part a wrong answer would be laundered through. Plain stdlib unittest, so it runs with `python3 tools/battery/test_score.py` (or `python3 -m unittest`) from this directory, without pytest and without a battery run. """ import unittest import score def report(sg, sgno, cell, **extra): """The three report keys the scorer needs, plus whatever a case adds.""" return dict({"SPACE_GROUP_NAME": sg, "SPACE_GROUP_NUMBER": str(sgno), "UNIT_CELL_CONSTANTS": " ".join(f"{x:.3f}" for x in cell)}, **extra) # A deposited trigonal reference and the higher group our reduction reads on it: the shape of # the accepted-alternative rows, with a synthetic cell, not a real one. TRIGONAL = [80.0, 80.0, 150.0, 90.0, 90.0, 120.0] DEPOSITED = {"sg": "P 32", "sgno": 145, "cell": TRIGONAL, "dmin": 2.0} def entry(**extra): return dict({"id": "test_set", "arm": "open", "ref": DEPOSITED}, **extra) class AcceptedAlternatives(unittest.TestCase): def test_deposition_still_decides_on_its_own(self): """Without alternatives the higher group is a symmetry failure, as before.""" r = score.judge(entry(), report("P 31 2 1", 152, TRIGONAL), "") self.assertEqual((r["verdict"], r["cause"]), ("fail", "sym_over")) def test_either_answer_passes(self): """Both the deposited group and the alternative pass, and only the second is marked.""" e = entry(ref_alternatives=[{"sg": "P 31 2 1", "sgno": 152, "why": "a stated reason"}]) dep = score.judge(e, report("P 32", 145, TRIGONAL), "") self.assertEqual((dep["verdict"], dep["cause"], dep["accepted_alt"]), ("pass", None, None)) alt = score.judge(e, report("P 31 2 1", 152, TRIGONAL), "") self.assertEqual((alt["verdict"], alt["cause"]), ("pass", "accepted_alternative")) self.assertEqual(alt["accepted_alt"], "P 31 2 1") self.assertIn("a stated reason", alt["accepted_alt_why"]) # the row still describes the answer against the DEPOSITION, not against the alternative self.assertEqual(alt["sg_ref"], "P 32") def test_the_other_hand_of_an_alternative_passes_too(self): """P 31 2 1 and P 32 2 1 differ by hand only, which intensities cannot decide.""" e = entry(ref_alternatives=[{"sg": "P 31 2 1", "sgno": 152, "why": "a stated reason"}]) r = score.judge(e, report("P 32 2 1", 154, TRIGONAL), "") self.assertEqual((r["verdict"], r["cause"]), ("pass", "accepted_alternative")) def test_a_third_answer_still_fails(self): """An alternative accepts one other answer, not any other answer.""" e = entry(ref_alternatives=[{"sg": "P 31 2 1", "sgno": 152, "why": "a stated reason"}]) r = score.judge(e, report("P 31 1 2", 151, TRIGONAL), "") self.assertEqual(r["verdict"], "fail") def test_alternative_cell(self): """A supercell alternative: the deposited cell is its (0,1/2,1/2) sublattice, so the deposition alone scores the answer as a doubled lattice and the alternative accepts it.""" sub = [35.869, 39.297, 100.916, 98.3, 90.32, 90.09] sup = [35.869, 39.297, 199.976, 87.087, 90.341, 90.09] # c' = b + 2c, centring removed e = {"id": "test_cell", "arm": "open", "ref": {"sg": "P 1", "sgno": 1, "cell": sub}} r = score.judge(e, report("P 1", 1, sup), "") self.assertEqual((r["verdict"], r["cause"]), ("fail", "lattice_doubled")) e["ref_alternatives"] = [{"cell": sup, "why": "a stated reason"}] r = score.judge(e, report("P 1", 1, sup), "") self.assertEqual((r["verdict"], r["cause"]), ("pass", "accepted_alternative")) self.assertTrue(r["accepted_alt"].startswith("cell ")) self.assertEqual(r["volume_ratio"], 2.0) # still reported against the deposition def test_a_justification_is_mandatory(self): """An accepted alternative with no reason is a schema error, not a silent pass: this is the check that keeps the mechanism from becoming a way to launder a failure.""" for alt in ({"sg": "P 31 2 1", "sgno": 152}, {"sg": "P 31 2 1", "sgno": 152, "why": " "}): with self.assertRaises(ValueError) as cm: score.judge(entry(ref_alternatives=[alt]), report("P 31 2 1", 152, TRIGONAL), "") self.assertIn("why", str(cm.exception)) def test_an_alternative_must_replace_something(self): with self.assertRaises(ValueError): score.judge(entry(ref_alternatives=[{"why": "a stated reason"}]), report("P 32", 145, TRIGONAL), "") def test_the_manifest_rows_are_valid(self): """Every committed row's alternatives pass the schema check.""" import json import os for name in ("open.json", "inhouse.json"): path = os.path.join(os.path.dirname(os.path.abspath(__file__)), name) with open(path) as f: for e in json.load(f)["sets"]: 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()