The rotation geometry walk kept re-integrating while the post-refinement at the adopted geometry still committed a move, up to four rounds. It never ended on its own: every walking run hit the cap, and with the cap lifted to twelve every one hit twelve. Each round's fit predicted a held-out gain the re-integration at its geometry then did not realise - the realised residual stayed flat from the first round, or passed a minimum and rose again while the distance crept on along the distance/cell-scale degeneracy - so the cap decided where on the drift the run stopped. A round is now kept only when the held-out residual measured by the pass re-integrated at its geometry falls below the best round's by more than the standard error of the difference. The first round that does not ends the walk, and the run goes back to the best round (one more pass, since the canonical files were written by the last). The walk also stops when re-indexing pushes the fitted cell back on the same length in the same direction on two rounds running: the fit keeps asking for a cell nothing ratifies. The cap is 8; a walk still paying at the cap keeps its last (= best) round, sets geometry_not_converged, and the report flags GEOMETRY_NOT_CONVERGED. Either way the kept round is judged against the header-geometry pass by RefinedPassIsWorse, as before. PASS_DECISION lists distance and realised residual per round and why the walk ended. Runs whose post-refinement does not commit a move of more than one step do not walk and are unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
113 lines
6.6 KiB
C++
113 lines
6.6 KiB
C++
// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
#pragma once
|
|
|
|
#include <cstdint>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
// One populated result object, rendered more than once.
|
|
//
|
|
// The report used to be printed as the writer walked the results, which fixed the order of the file
|
|
// to the order of the pipeline and made a summary at the top impossible - the warnings that decide
|
|
// the verdict are produced by the sections the verdict has to sit above. BuildReportDocument()
|
|
// therefore populates this document, and a renderer walks it afterwards: the text renderer in
|
|
// ResultReport.cpp reads ONLY from here and prints nothing on its own. A second rendering is a
|
|
// second walk over the same document, never a second hand-maintained emitter.
|
|
|
|
// The TYPE of a pathology, from a closed vocabulary a consumer can switch on, carried beside the
|
|
// open free-text description that says what actually happened to this crystal. One code per warning
|
|
// PRODUCER: a code appears in PATHOLOGY_FLAGS if and only if its warning fired, so lighting an
|
|
// indicator is one grep and needs no prose parsing. Add a producer, add a code here.
|
|
namespace PathologyCode {
|
|
constexpr const char *NO_LATTICE = "NO_LATTICE"; // nothing indexed
|
|
constexpr const char *INDEXING_AMBIGUITY = "INDEXING_AMBIGUITY"; // alternative indexing unresolved
|
|
constexpr const char *SYMMETRY_AMBIGUITY = "SYMMETRY_AMBIGUITY"; // the data do not pick a point group
|
|
constexpr const char *CENTERING_UNTESTED = "CENTERING_UNTESTED"; // centring from the metric only
|
|
constexpr const char *UNUSABLE_MERGE = "UNUSABLE_MERGE"; // the merged data carry no signal
|
|
constexpr const char *LOW_COMPLETENESS = "LOW_COMPLETENESS"; // too little of reciprocal space
|
|
constexpr const char *SWEEP_GAPS = "SWEEP_GAPS"; // ranges that delivered much less
|
|
constexpr const char *GONIO_SCALE = "GONIO_SCALE"; // the stage rotation is mis-scaled
|
|
constexpr const char *SPINDLE_CAP = "SPINDLE_CAP"; // the mounting cost unique data
|
|
constexpr const char *ANISOTROPY = "ANISOTROPY"; // strongly direction-dependent
|
|
constexpr const char *TWINNING = "TWINNING"; // twinning indicated
|
|
constexpr const char *MODEL_HAND = "MODEL_HAND"; // data and model in opposite hands
|
|
constexpr const char *MODEL_NOT_VALIDATED = "MODEL_NOT_VALIDATED"; // a model was given, not usable
|
|
constexpr const char *CANCELLED = "CANCELLED"; // the run did not finish
|
|
constexpr const char *RESOLUTION_FIT = "RESOLUTION_FIT"; // the CC1/2 fall-off is not a fall-off
|
|
constexpr const char *FLIGHT_PATH = "FLIGHT_PATH"; // the assumed flight-path medium matters here
|
|
constexpr const char *PSEUDO_TRANSLATION = "PSEUDO_TRANSLATION"; // translational pseudo-symmetry indicated
|
|
// Both warnings under this code say the same thing to a consumer - a translation the cell in use
|
|
// does not declare is present in the data, so the centring or the cell is wrong - and differ only
|
|
// in how exact it is. ANISOTROPY and RESOLUTION_FIT are already shared this way.
|
|
constexpr const char *LATTICE_TRANSLATION = "LATTICE_TRANSLATION"; // a translation the cell does not declare
|
|
constexpr const char *HARMONIC_CONTAMINATION = "HARMONIC_CONTAMINATION"; // the beam carries a higher harmonic
|
|
constexpr const char *GEOMETRY_NOT_CONVERGED = "GEOMETRY_NOT_CONVERGED"; // the geometry walk ran out of rounds
|
|
}
|
|
|
|
// A closed type plus an open description. The type is what machinery switches on; the text is what a
|
|
// person reads, and it is free-form on purpose - the vocabulary must not have to grow a code every
|
|
// time a sentence gains a number.
|
|
struct ReportWarning {
|
|
std::string code;
|
|
std::string text;
|
|
// Advisories about the pipeline's own internals rather than about this crystal. They still fire,
|
|
// still count in the developer report, and are kept out of the default one - a warning nobody can
|
|
// act on is how users learn to skip the warnings.
|
|
bool developer_only = false;
|
|
};
|
|
|
|
// A value with its type kept, so a rendering other than text does not have to parse the string back.
|
|
// `text` is always the exact spelling the text report prints, because that spelling is the interface.
|
|
struct ReportValue {
|
|
enum class Type { Text, Integer, Real, Boolean, Enumerated };
|
|
Type type = Type::Text;
|
|
std::string text;
|
|
double real = 0.0;
|
|
int64_t integer = 0;
|
|
bool boolean = false;
|
|
std::vector<std::string> domain; // Enumerated only: the closed vocabulary this value comes from
|
|
};
|
|
|
|
// Content that is a table in every rendering. The pre-formatted text lines and the typed cells carry
|
|
// the same content: the text renderer prints the former, another rendering reads the latter.
|
|
struct ReportTable {
|
|
std::vector<std::string> columns;
|
|
std::string text_header;
|
|
std::vector<std::string> text_rows;
|
|
std::vector<std::vector<ReportValue>> cells;
|
|
};
|
|
|
|
struct ReportEntry {
|
|
enum class Kind { Key, Prose, Blank, Table };
|
|
Kind kind = Kind::Key;
|
|
std::string key; // Key
|
|
ReportValue value; // Key
|
|
std::string prose; // Prose: a paragraph, already line-wrapped for the text rendering
|
|
ReportTable table; // Table
|
|
// Written only when the developer report was asked for. The document always holds everything;
|
|
// which rendering shows what is the renderer's decision, so nothing is lost by building once.
|
|
bool developer_only = false;
|
|
// The other half of the same idea: a short paragraph that exists only because the long one it
|
|
// summarises was moved out. Printing both would say the same thing twice.
|
|
bool default_only = false;
|
|
};
|
|
|
|
struct ReportSection {
|
|
std::string title; // empty for the header block, which has no banner of its own
|
|
std::vector<ReportEntry> entries;
|
|
bool developer_only = false;
|
|
};
|
|
|
|
// The whole report as data. Sections in the order they are written; warnings and the verdict live
|
|
// here rather than in a section, because the SUMMARY section is composed from them after every other
|
|
// section has been built.
|
|
struct ReportDocument {
|
|
std::vector<ReportSection> sections;
|
|
std::vector<ReportWarning> warnings;
|
|
std::string verdict; // closed: OK | WARNINGS | UNUSABLE | FAILED
|
|
std::string verdict_text; // one to three sentences, free text
|
|
std::vector<std::string> pathology_flags; // PathologyCode::*, deduplicated, in first-seen order
|
|
};
|