report: name the groups the data could not separate, as keys
SPACE_GROUP_NAME is a scalar and reads like a determination, and the docs nominate a grep of it as the interface. Where several groups predict the same absences it is one of them, picked by convention: the report said so in prose and nothing machine-readable carried it, so a script recorded a coin flip as an answer. Refining against a deposited model in the wrong enantiomorph gives R = 0.549. Four keys, none of which change which group is adopted: the alternatives the data cannot separate; whether the enantiomorph was determined, given, or is undetermined - decidable from the group number alone, so it is answered even where the search did not run; and the higher point group whose promotion was refused, with its reason, which until now existed only as prose. REPORT_VERSION is 5. No existing key changed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N
This commit is contained in:
@@ -11,6 +11,7 @@
|
||||
* `rugnux` leaves an event out of the unmerged MTZ when less of its rocking curve was captured than `--min-captured-fraction`, as the merge does, since the exported rows are declared full.
|
||||
* `rugnux` writes `FreeR_flag` with 0 for the test set and 1 for the working set, the CCP4 convention REFMAC5 defaults to; it was the other way round.
|
||||
* `rugnux -S` refuses or re-seats a fixed space group whose symmetry axes the indexed cell does not carry, not only one whose centring differs; `--mode scale` refuses it too.
|
||||
* The rugnux results report names the groups the data cannot separate, the enantiomorph state and any refused higher point group as `SPACE_GROUP_ALTERNATIVES`, `SPACE_GROUP_ENANTIOMORPH`, `SPACE_GROUP_REFUSED_POINT_GROUP` and `SPACE_GROUP_REFUSED_REASON`; `REPORT_VERSION` is 5.
|
||||
* `rugnux` handles symmetry better: the lattice, the point group, the setting and the systematic absences.
|
||||
* `rugnux` fits the direction of the goniometer axis and not its length, so the cell chosen by the first pass is the one its own refinement scored.
|
||||
* `rugnux` reports the detector geometry it measured - the direct beam, the tilt and the beam centre - and what a single sweep can and cannot determine.
|
||||
|
||||
+48
-1
@@ -20,7 +20,7 @@
|
||||
namespace {
|
||||
// The version of this file format. Bumped when a key is renamed or removed, a table column moves,
|
||||
// or a reason code changes meaning - a consumer can gate on it.
|
||||
constexpr int REPORT_VERSION = 4;
|
||||
constexpr int REPORT_VERSION = 5;
|
||||
|
||||
const char *BANNER = " ******************************************************************************";
|
||||
|
||||
@@ -231,6 +231,53 @@ std::string RenderResultReport(const std::string &output_prefix,
|
||||
|
||||
// ---------------------------------------------- 4. SPACE GROUP DETERMINATION
|
||||
Section(os, "4. SPACE GROUP DETERMINATION");
|
||||
|
||||
// What the data could NOT decide, as keys rather than only as prose. SPACE_GROUP_NAME above is a
|
||||
// scalar and reads like a determination; where several groups predict the same absences it is one
|
||||
// of them, chosen by convention. A script that greps only the name records a coin-flip as an
|
||||
// answer - measured, refining against the deposited model in the wrong enantiomorph gives
|
||||
// R = 0.549 - so the ambiguity has to survive the same grep.
|
||||
{
|
||||
std::string alts;
|
||||
if (result.space_group_search.has_value() && result.space_group.has_value())
|
||||
for (const auto &alt : result.space_group_search->alternatives)
|
||||
if (alt.number != result.space_group->number)
|
||||
alts += (alts.empty() ? "" : " | ") + alt.xhm();
|
||||
Key(os, "SPACE_GROUP_ALTERNATIVES", alts.empty() ? "NONE" : alts);
|
||||
|
||||
// The hand is a separate question from the alternatives list, and it is decidable from the
|
||||
// 22 groups' numbers alone, so it is answered even where the search did not run. Merged
|
||||
// intensities never decide it: an enantiomorphic pair has the same absences and the same
|
||||
// Laue class, and only a model, a substructure or an anomalous signal names the hand.
|
||||
const char *enantiomorph = "NOT_APPLICABLE";
|
||||
if (result.space_group.has_value() && result.space_group->is_enantiomorphic()) {
|
||||
if (result.model_validation.has_value() && result.model_validation->ok)
|
||||
enantiomorph = "DETERMINED_FROM_MODEL";
|
||||
else if (!result.space_group_search.has_value())
|
||||
enantiomorph = "GIVEN"; // -S, or a reference MTZ: the user's assertion
|
||||
else
|
||||
enantiomorph = "UNDETERMINED";
|
||||
}
|
||||
Key(os, "SPACE_GROUP_ENANTIOMORPH", enantiomorph);
|
||||
|
||||
// A higher point group whose operators the intensities confirmed and whose promotion was
|
||||
// refused. It exists only as prose in the search text below, so nothing can act on it - and
|
||||
// a refusal is exactly the case where a user might want to try the higher group as well.
|
||||
const bool refused = result.space_group_search.has_value()
|
||||
&& !result.space_group_search->refused_point_group_hm.empty();
|
||||
Key(os, "SPACE_GROUP_REFUSED_POINT_GROUP",
|
||||
refused ? result.space_group_search->refused_point_group_hm : std::string("NONE"));
|
||||
if (refused)
|
||||
Key(os, "SPACE_GROUP_REFUSED_REASON", result.space_group_search->refused_reason);
|
||||
os << "\n SPACE_GROUP_ALTERNATIVES names every group these data cannot separate from the one\n"
|
||||
<< " adopted (enantiomorphic partners, origin-ambiguous pairs, groups a gap in the data\n"
|
||||
<< " leaves untested); NONE means the absences single the answer out. SPACE_GROUP_ENANTIOMORPH\n"
|
||||
<< " is UNDETERMINED whenever the group is one of the 22 that come in enantiomorphic pairs and\n"
|
||||
<< " nothing outside the merged intensities named the hand - the intensities cannot, so this\n"
|
||||
<< " is the normal outcome, not a failure. SPACE_GROUP_REFUSED_POINT_GROUP is a higher point\n"
|
||||
<< " group the operator correlations supported and the consistency tests would not take.\n";
|
||||
}
|
||||
|
||||
if (result.space_group_search.has_value()) {
|
||||
Key(os, "SPACE_GROUP_SEARCH", "DE_NOVO");
|
||||
os << "\n" << SearchSpaceGroupResultToText(*result.space_group_search) << "\n";
|
||||
|
||||
@@ -69,10 +69,15 @@ TEST_CASE("ResultReport_Render", "[Diagnostics]") {
|
||||
const auto text = RenderResultReport("prefix", "in.h5", x, result);
|
||||
|
||||
// The stable keys a consumer greps for.
|
||||
CHECK(text.find("\nREPORT_VERSION= 4\n") != std::string::npos);
|
||||
CHECK(text.find("\nREPORT_VERSION= 5\n") != std::string::npos);
|
||||
CHECK(text.find("\nOUTPUT_PREFIX= prefix\n") != std::string::npos);
|
||||
CHECK(text.find("\nINDEXING_RATE= 0.8700\n") != std::string::npos);
|
||||
CHECK(text.find("\nSPACE_GROUP_NUMBER= 96\n") != std::string::npos);
|
||||
// What the data could NOT decide, beside what they did. The group here was given rather
|
||||
// than searched for, and it is one of the 22 that come in enantiomorphic pairs.
|
||||
CHECK(text.find("\nSPACE_GROUP_ALTERNATIVES= NONE\n") != std::string::npos);
|
||||
CHECK(text.find("\nSPACE_GROUP_ENANTIOMORPH= GIVEN\n") != std::string::npos);
|
||||
CHECK(text.find("\nSPACE_GROUP_REFUSED_POINT_GROUP= NONE\n") != std::string::npos);
|
||||
CHECK(text.find("\nSWEEP_QUALITY_STATUS= COMPUTED\n") != std::string::npos);
|
||||
CHECK(text.find("\nSWEEP_QUALITY_COUNT= 2\n") != std::string::npos);
|
||||
CHECK(text.find("\nSWEEP_QUALITY_REASONS= no_diffraction crystal_out_of_beam weak_diffraction "
|
||||
|
||||
Reference in New Issue
Block a user