Rugnux report: warn only where the evidence supports the claim
Report-layer fixes from the warnings audit of the rc173 full battery. Processing is unchanged (merged MTZ/HKL bit-identical on the sets checked). - SUPERCELL_POSSIBLE stays a key and a summary line; a warning only where the rocking part reaches 20% (sub-cells that refine normally rocked at 2.1-16.2%, the accepted doubled cells at 4.2% and 29.1%). The summary says where further lattice domains may put spots on the half-integer nodes. - LATTICE_TRANSLATION only where the Patterson at the vector reaches 90% of the origin (UNDECLARED_LATTICE_TRANSLATION_PCT, new); the two admitted vectors on the battery read 79% and 85% on crystals refining in the deposited cell, and are now reported as a very strong pseudo-translation. PSEUDO_TRANSLATION warns from a 20% peak (xtriage convention; detected peaks were 8.1% on one set, 24.8-63.2% on the rest). - <|L|> outside 0.375-0.55 (on the adopted or the pre-search merge) is TWINNING_VERDICT= NOT_READABLE with no warning, instead of a twin or SYMMETRY_SUSPECT reading. - NO_LATTICE no longer fires on a rotation run that indexed the sweep with no frame indexed on its own; INDEXING_RATE is --developer on rotation. - A rotation run that finds no lattice writes a report (VERDICT= FAILED, NO_LATTICE) before exiting 1, instead of an input-parameter error and no report (NoLatticeFound). - Powder and Ice summary lines, ICE_* keys, and a POWDER_RINGS flag from a 0.5 spot fraction. - SWEEP_GAPS warns only where the degraded ranges cover 10% of the sweep (77 -> 48 sets). - A single rotation sweep no longer carries an INDEXING_AMBIGUITY warning (one orientation matrix, consistent hand); INDEXING_AMBIGUITY_OPERATORS and an Indexing choice line instead. - A DOMAIN more than 10 deg away is a second crystal; the MULTIPLE_LATTICES sum is documented. - Mosaicity line and docs name XDS's suggested REFLECTING_RANGE_E.S.D. as the comparable number (0.90x median over 34 in-house sets; per-image SIGMAR runs ~1.35x higher). - HARMONIC_CONTAMINATION, SCALING_NOT_CONVERGED, POWDER_RINGS in the documented vocabulary. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D1G8gJVAy6gp1K5Dz3NE5C
This commit is contained in:
1 parent
a1816e905e
commit
8a8c992cca
10 files changed
+606
-83
No files matched your search
@@ -816,11 +816,31 @@ TEST_CASE("ResultReport_TranslationalNCS", "[Diagnostics]") {
|
||||
result.tncs.detected = false;
|
||||
result.tncs.near_extinct_class = false;
|
||||
result.tncs.undeclared_lattice_translations.push_back({0.5, 0.5, 0.0});
|
||||
result.tncs.undeclared_lattice_translation_pct.push_back(98.0);
|
||||
const auto lat = RenderResultReport("prefix", "in.h5", x, result);
|
||||
CHECK(lat.find("\nTNCS_DETECTED= FALSE\n") != std::string::npos);
|
||||
CHECK(lat.find("\nUNDECLARED_LATTICE_TRANSLATION= 0.500 0.500 0.000\n") != std::string::npos);
|
||||
CHECK(lat.find("\nUNDECLARED_LATTICE_TRANSLATION_PCT= 98.0\n") != std::string::npos);
|
||||
CHECK(lat.find("\nPATHOLOGY_FLAGS= LATTICE_TRANSLATION\n") != std::string::npos);
|
||||
CHECK(lat.find("\nTNCS_SUBLATTICE=") == std::string::npos);
|
||||
|
||||
// Admitted at 75% but short of a lattice translation's height: the claim is a very strong
|
||||
// pseudo-translation, which is what molecular replacement has to be told.
|
||||
result.tncs.undeclared_lattice_translation_pct = {80.0};
|
||||
const auto strong = RenderResultReport("prefix", "in.h5", x, result);
|
||||
CHECK(strong.find("\nPATHOLOGY_FLAGS= PSEUDO_TRANSLATION\n") != std::string::npos);
|
||||
CHECK(strong.find("VERY STRONG, nearly a lattice translation") != std::string::npos);
|
||||
CHECK(strong.find("invariant under the translation") == std::string::npos);
|
||||
|
||||
// Significant, but a peak this weak is not one molecular replacement needs declared.
|
||||
result.tncs.undeclared_lattice_translations.clear();
|
||||
result.tncs.undeclared_lattice_translation_pct.clear();
|
||||
result.tncs.detected = true;
|
||||
result.tncs.peak_percent = 8.1;
|
||||
const auto weak = RenderResultReport("prefix", "in.h5", x, result);
|
||||
CHECK(weak.find("\nTNCS_DETECTED= TRUE\n") != std::string::npos);
|
||||
CHECK(weak.find("PSEUDO_TRANSLATION") == std::string::npos);
|
||||
CHECK(weak.find("weak - below the 20% that matters for molecular replacement") != std::string::npos);
|
||||
}
|
||||
|
||||
TEST_CASE("ResultReport_GeometryNotConverged", "[Diagnostics]") {
|
||||
@@ -961,3 +981,178 @@ TEST_CASE("ResultReport_Mosaicity", "[Diagnostics]") {
|
||||
CHECK(text.find("MOSAICITY_DEG_P90= 0.105") != std::string::npos);
|
||||
CHECK(text.find("Mosaicity 0.091 deg") != std::string::npos);
|
||||
}
|
||||
|
||||
TEST_CASE("ResultReport_SupercellDemoted", "[Diagnostics]") {
|
||||
DiffractionExperiment x(DetJF(1));
|
||||
ProcessResult result;
|
||||
result.consensus_cell = UnitCell{.a = 50.0f, .b = 60.0f, .c = 70.0f,
|
||||
.alpha = 90.0f, .beta = 90.0f, .gamma = 90.0f};
|
||||
result.has_merge_statistics = true;
|
||||
ProcessResult::SupercellClass sc;
|
||||
sc.h = 0; sc.k = 0; sc.l = 1;
|
||||
sc.occupancy_pct = 18.0;
|
||||
sc.rock_pct = 5.0;
|
||||
sc.rock_se_pct = 0.4;
|
||||
sc.i_over_sigma = 1.2;
|
||||
sc.doubled_cell = UnitCell{.a = 50.0f, .b = 60.0f, .c = 140.0f, .alpha = 90.0f, .beta = 90.0f, .gamma = 90.0f};
|
||||
result.supercell = sc;
|
||||
|
||||
// Possible, as correct sub-cells also read: stated, not warned about.
|
||||
auto text = RenderResultReport("p", "in.h5", x, result);
|
||||
CHECK(text.find("\nSUPERCELL_POSSIBLE= TRUE\n") != std::string::npos);
|
||||
CHECK(text.find("\nPATHOLOGY_FLAGS= NONE\n") != std::string::npos);
|
||||
CHECK(text.find("weak Bragg-like intensity in class 0 0 1") != std::string::npos);
|
||||
CHECK(text.find("may put spots there") == std::string::npos);
|
||||
|
||||
// Further domains of the crystal can put spots on the half-integer nodes, and the line says so.
|
||||
LeftoverLatticeCensus census;
|
||||
census.primary_intensity_pct = 70.0;
|
||||
census.lattices.push_back(LeftoverLattice{.kind = LeftoverLatticeKind::TwinDomain, .cell = *result.consensus_cell,
|
||||
.volume_ratio = 1.0, .misorientation_deg = 179.5, .z = 50.0,
|
||||
.spots_pct = 8.0, .intensity_pct = 7.0, .sweep_concentration = 0.3});
|
||||
result.leftover_lattices = census;
|
||||
text = RenderResultReport("p", "in.h5", x, result);
|
||||
CHECK(text.find("the further lattice domains found may put spots there") != std::string::npos);
|
||||
|
||||
// A rocking part no correct sub-cell reached is a condition.
|
||||
result.supercell->rock_pct = 29.0;
|
||||
text = RenderResultReport("p", "in.h5", x, result);
|
||||
CHECK(text.find("\nPATHOLOGY_FLAGS= SUPERCELL_POSSIBLE\n") != std::string::npos);
|
||||
CHECK(text.find("POSSIBLE - class 0 0 1 rocks like Bragg reflections (29.0%)") != std::string::npos);
|
||||
}
|
||||
|
||||
TEST_CASE("ResultReport_TwinningOutsidePhysicalRange", "[Diagnostics]") {
|
||||
DiffractionExperiment x(DetJF(1));
|
||||
ProcessResult result;
|
||||
result.consensus_cell = UnitCell{.a = 70.0f, .b = 70.0f, .c = 70.0f,
|
||||
.alpha = 90.0f, .beta = 90.0f, .gamma = 90.0f};
|
||||
result.has_merge_statistics = true;
|
||||
result.twinning.l_test_pairs = 5000;
|
||||
result.twinning.mean_abs_l = 0.355;
|
||||
result.twinning.second_moment = 1.60;
|
||||
result.twinning.twinning_suspected = true;
|
||||
result.twinning.adopted_operators_suspect = true;
|
||||
|
||||
// Below the 0.375 of a perfect twin: no twin fraction can give it, so neither a twin nor a false
|
||||
// operator is read from it.
|
||||
auto text = RenderResultReport("p", "in.h5", x, result);
|
||||
CHECK(text.find("\nTWINNING_VERDICT= NOT_READABLE\n") != std::string::npos);
|
||||
CHECK(text.find("TWINNING ") == std::string::npos);
|
||||
CHECK(text.find("\nPATHOLOGY_FLAGS= NONE\n") != std::string::npos);
|
||||
CHECK(text.find("Twinning not readable (<|L|> 0.355") != std::string::npos);
|
||||
CHECK(text.find("SYMMETRY SUSPECT") == std::string::npos);
|
||||
|
||||
// Inside the range on the adopted merge but far above 0.5 on the merge before the search: the
|
||||
// statistic is distorted on these data, and the low adopted reading is not evidence either.
|
||||
result.twinning.mean_abs_l = 0.395;
|
||||
TwinningAnalysisResult pre;
|
||||
pre.l_test_pairs = 5000;
|
||||
pre.mean_abs_l = 0.590;
|
||||
result.pre_promotion_twinning = pre;
|
||||
text = RenderResultReport("p", "in.h5", x, result);
|
||||
CHECK(text.find("\nTWINNING_VERDICT= NOT_READABLE\n") != std::string::npos);
|
||||
CHECK(text.find("0.590 before the space-group search") != std::string::npos);
|
||||
|
||||
// An ordinary reading keeps its verdict.
|
||||
result.pre_promotion_twinning.reset();
|
||||
result.twinning.adopted_operators_suspect = false;
|
||||
result.twinning.mean_abs_l = 0.419;
|
||||
text = RenderResultReport("p", "in.h5", x, result);
|
||||
CHECK(text.find("\nTWINNING_VERDICT= INDICATED\n") != std::string::npos);
|
||||
CHECK(text.find("\nPATHOLOGY_FLAGS= TWINNING\n") != std::string::npos);
|
||||
}
|
||||
|
||||
TEST_CASE("ResultReport_RotationLatticeWithoutIndexedFrames", "[Diagnostics]") {
|
||||
DiffractionExperiment x(DetJF(1));
|
||||
// A rotation run that indexed the sweep as a whole while no frame indexed on its own.
|
||||
ProcessResult result;
|
||||
result.images_processed = 1800;
|
||||
result.indexing_rate = 0.0f;
|
||||
result.rotation_lattice_found = true;
|
||||
result.consensus_cell = UnitCell{.a = 50.0f, .b = 60.0f, .c = 70.0f,
|
||||
.alpha = 90.0f, .beta = 100.0f, .gamma = 90.0f};
|
||||
const auto text = RenderResultReport("p", "in.h5", x, result);
|
||||
CHECK(text.find("NO_LATTICE") == std::string::npos);
|
||||
CHECK(text.find("No image indexed") == std::string::npos);
|
||||
CHECK(text.find("\nLATTICE_FOUND= TRUE\n") != std::string::npos);
|
||||
|
||||
// The no-crystal exit: the CLI hands over only the reason. The verdict has no image count to quote.
|
||||
ProcessResult none;
|
||||
none.warnings.push_back({PathologyCode::NO_LATTICE, "No crystal lattice was found: none"});
|
||||
const auto failed = RenderResultReport("p", "in.h5", x, none);
|
||||
CHECK(failed.find("\nVERDICT= FAILED\n") != std::string::npos);
|
||||
CHECK(failed.find("\nPATHOLOGY_FLAGS= NO_LATTICE\n") != std::string::npos);
|
||||
CHECK(failed.find("No crystal lattice was found in these frames") != std::string::npos);
|
||||
CHECK(failed.find("UNIT_CELL_CONSTANTS=") == std::string::npos);
|
||||
CHECK(failed.find("SPACE_GROUP_NUMBER=") == std::string::npos);
|
||||
}
|
||||
|
||||
TEST_CASE("ResultReport_PowderAndIce", "[Diagnostics]") {
|
||||
DiffractionExperiment x(DetJF(1));
|
||||
ProcessResult result;
|
||||
result.consensus_cell = UnitCell{.a = 79.0f, .b = 79.0f, .c = 38.0f,
|
||||
.alpha = 90.0f, .beta = 90.0f, .gamma = 90.0f};
|
||||
result.has_merge_statistics = true;
|
||||
|
||||
auto text = RenderResultReport("p", "in.h5", x, result);
|
||||
CHECK(text.find("Powder no rings detected") != std::string::npos);
|
||||
CHECK(text.find("ICE_RINGS_DETECTED") == std::string::npos);
|
||||
|
||||
result.powder.rings_q_recipA = {1.61f, 1.71f, 1.82f};
|
||||
result.powder.spot_fraction = 0.20f;
|
||||
result.ice_ring_score = 2.69f;
|
||||
result.ice_spot_ratio = 1.08f;
|
||||
result.ice_rings_detected = true;
|
||||
result.ice_flagged_fraction = 0.257;
|
||||
text = RenderResultReport("p", "in.h5", x, result);
|
||||
CHECK(text.find("Powder 3 rings holding 20% of the spots found") != std::string::npos);
|
||||
CHECK(text.find("\nICE_RINGS_DETECTED= TRUE\n") != std::string::npos);
|
||||
CHECK(text.find("\nICE_REFLECTIONS_ON_RINGS_PCT= 25.7\n") != std::string::npos);
|
||||
CHECK(text.find("Ice rings DETECTED: the 25.7% of the reflections") != std::string::npos);
|
||||
CHECK(text.find("POWDER_RINGS ") == std::string::npos);
|
||||
CHECK(text.find("\nPATHOLOGY_FLAGS= NONE\n") != std::string::npos);
|
||||
|
||||
// Most of the spots on rings is a condition.
|
||||
result.powder.spot_fraction = 0.74f;
|
||||
text = RenderResultReport("p", "in.h5", x, result);
|
||||
CHECK(text.find("\nPATHOLOGY_FLAGS= POWDER_RINGS\n") != std::string::npos);
|
||||
}
|
||||
|
||||
TEST_CASE("ResultReport_SweepGapsMaterial", "[Diagnostics]") {
|
||||
DiffractionExperiment x(DetJF(1));
|
||||
ProcessResult result;
|
||||
result.consensus_cell = UnitCell{.a = 79.0f, .b = 79.0f, .c = 38.0f,
|
||||
.alpha = 90.0f, .beta = 90.0f, .gamma = 90.0f};
|
||||
result.has_merge_statistics = true;
|
||||
result.merge_statistics.sweep_quality = TestSweepQuality();
|
||||
// Two frames of a long sweep: in the table and the summary, not a condition of the data set.
|
||||
result.merge_statistics.sweep_quality.sweep_deg = 360.0f;
|
||||
result.merge_statistics.sweep_quality.ranges.resize(1);
|
||||
result.merge_statistics.sweep_quality.ranges[0].rotation_deg = 1.0f;
|
||||
auto text = RenderResultReport("p", "in.h5", x, result);
|
||||
CHECK(text.find("SWEEP_GAPS") == std::string::npos);
|
||||
CHECK(text.find("Frames 100-149") == std::string::npos);
|
||||
RunProvenance dev;
|
||||
dev.developer = true;
|
||||
CHECK(RenderResultReport("p", "in.h5", x, result, dev).find("Frames 100-149") != std::string::npos);
|
||||
|
||||
result.merge_statistics.sweep_quality.ranges[0].rotation_deg = 40.0f;
|
||||
text = RenderResultReport("p", "in.h5", x, result);
|
||||
CHECK(text.find("\nPATHOLOGY_FLAGS= SWEEP_GAPS\n") != std::string::npos);
|
||||
}
|
||||
|
||||
TEST_CASE("ResultReport_SecondCrystal", "[Diagnostics]") {
|
||||
DiffractionExperiment x(DetJF(1));
|
||||
ProcessResult result;
|
||||
result.consensus_cell = UnitCell{.a = 79.0f, .b = 79.0f, .c = 38.0f,
|
||||
.alpha = 90.0f, .beta = 90.0f, .gamma = 90.0f};
|
||||
LeftoverLatticeCensus census;
|
||||
census.primary_intensity_pct = 80.0;
|
||||
census.lattices.push_back(LeftoverLattice{.kind = LeftoverLatticeKind::Domain, .cell = *result.consensus_cell,
|
||||
.volume_ratio = 1.0, .misorientation_deg = 38.9, .z = 50.0,
|
||||
.spots_pct = 12.0, .intensity_pct = 14.0, .sweep_concentration = 0.3});
|
||||
result.leftover_lattices = census;
|
||||
const auto text = RenderResultReport("p", "in.h5", x, result);
|
||||
CHECK(text.find("second crystal, 38.9 deg from the main lattice") != std::string::npos);
|
||||
CHECK(text.find("split crystal, ") == std::string::npos);
|
||||
}
|
||||
Reference in new issue
Block a user