Files
Jungfraujoch/rugnux/ReportDocument.h
T
leonarski_fandClaude Opus 5 848de4b7dc rugnux: the geometry walk continues only while re-integration realises a gain
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>
2026-09-19 11:37:55 +02:00

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
};