spindle: the severity no longer rides on the indexing seed or on which indexer is configured
The score gated on 60 spots, but the seed escalation stops at the leanest seed that indexes - 30 spots on precisely the clean frames a grid scan produces - so the value was absent exactly where beamline automation most needs it, and absence maps to "engage": the protocol would have fired on every good frame, which degenerates the trigger into "always". The floor itself stays where it was calibrated; what changes is what it gates. When no escalation pass could answer, one severity-only pass runs over the full spot list - the row search alone, no reduction, no refinement - purely to produce the number. The same was true of the indexer choice: only the FFT family computes a row shortlist, so a deployment configured with the known-cell indexer - the ordinary online stills path - never produced the score at all. Where the severity-only pass has no row search to run, the severity is read off the rows of the winning lattice instead, which any indexer produces: the lattice's shortest few distinct directions, as many as the FFT shortlist resolves in practice, fed through the same window and scoring with equal magnitudes. The count parity is load-bearing - a worst case over every enumerable lattice direction fires on 100% of harmless mounts of a generic triclinic cell against 74% for this selection at theta_max = 15 deg, and an always-firing trigger decides nothing - while the diad-detection rate stays 1.00 on the monoclinic classes either way, a dropped axis row being recovered by the pair normals exactly as an invisible one is. A frame that neither indexed nor reached the spot floor still reports nothing, which is the honest answer and maps to the recoverable error. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N
This commit is contained in:
@@ -246,6 +246,49 @@ TEST_CASE("FFTIndexer","[Indexing]") {
|
||||
logger.Info("Time: {} ms", std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count());
|
||||
}
|
||||
|
||||
TEST_CASE("FFTIndexer_SpindleSeverity", "[Indexing][Spindle]") {
|
||||
// End to end through the real indexer: a crystal whose shortest row lies on the spindle must
|
||||
// come back with a severity of 1 from a normal run, from a severity-only run - which must not
|
||||
// index anything - and not at all when the frame is below the spot floor.
|
||||
UnitCell uc{39, 45, 78, 90, 90, 90};
|
||||
CrystalLattice cl(uc);
|
||||
|
||||
DiffractionExperiment experiment(DetJF4M());
|
||||
experiment.DetectorDistance_mm(75).BeamY_pxl(1136).BeamX_pxl(1090).IncidentEnergy_keV(12.4);
|
||||
// The 39 A axis of the cell lies along x, and so does the spindle.
|
||||
experiment.Goniometer(GoniometerAxis("omega", 0, 0.1f, Coord(1, 0, 0), {}));
|
||||
|
||||
IndexingSettings settings;
|
||||
settings.Algorithm(IndexingAlgorithmEnum::FFT)
|
||||
.FFT_MaxUnitCell_A(250.0).FFT_HighResolution_A(2 * M_PI / 3.0);
|
||||
experiment.ImportIndexingSettings(settings).SetUnitCell(uc);
|
||||
|
||||
std::unique_ptr<Indexer> indexer = CreateIndexer(experiment);
|
||||
REQUIRE(indexer);
|
||||
indexer->Setup(experiment);
|
||||
|
||||
std::vector<Coord> vec;
|
||||
for (int h = -2; h < 10; h++)
|
||||
for (int k = -5; k < 10; k++)
|
||||
for (int l = -3; l < 10; l++)
|
||||
vec.push_back(h * cl.Astar() + k * cl.Bstar() + l * cl.Cstar());
|
||||
|
||||
auto full = indexer->Run(vec);
|
||||
REQUIRE(full.spindle_blind_fraction.has_value());
|
||||
CHECK_THAT(*full.spindle_blind_fraction, Catch::Matchers::WithinAbs(1.0f, 1e-4));
|
||||
|
||||
auto severity_only = indexer->Run(vec, /*severity_only=*/true);
|
||||
CHECK(severity_only.lattice.empty());
|
||||
CHECK_FALSE(severity_only.executed);
|
||||
REQUIRE(severity_only.spindle_blind_fraction.has_value());
|
||||
CHECK_THAT(*severity_only.spindle_blind_fraction, Catch::Matchers::WithinAbs(1.0f, 1e-4));
|
||||
|
||||
// Below the spot floor there is no value - the CANNOT-SAY state - not a middling one.
|
||||
const std::vector<Coord> few(vec.begin(), vec.begin() + 40);
|
||||
auto starved = indexer->Run(few, /*severity_only=*/true);
|
||||
CHECK_FALSE(starved.spindle_blind_fraction.has_value());
|
||||
}
|
||||
|
||||
TEST_CASE("PostIndexingRefinement_MultiLattice_TwoCrystals_BraggPrediction","[Indexing]") {
|
||||
Logger logger("PostIndexingRefinement_MultiLattice_TwoCrystals_BraggPrediction");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user