rugnux: report what --model found, and attribute GEMMI's bundled headers

Two things left open by the previous commit.

A model that was validated left nothing in the results report. R-work, R-free,
the bulk-solvent parameters and the map filenames went to the log and nowhere
else, so a run given --model was, in the file anyone reads afterwards,
indistinguishable from one that was not - and the WARNING line added last commit
was the only trace of a model that failed.

Section 10. MODEL VALIDATION now carries it: MODEL_FILE, the space group the
model is in, R_WORK / R_FREE with their reflection counts, the bulk-solvent
k_sol / b_sol and overall scale, the mean 2Fo-Fc density at the atom centres, the
hand and indexing operators the written reflections were brought into the model's
frame with, and MAPS_PREFIX. A model that could not be used gets
MODEL_VALIDATION= NOT_PERFORMED and the reason instead, in the shape the merging
section already uses. A run that asked for no model has no section at all.

That moves WARNINGS from 10 to 11, which is what REPORT_VERSION is for: it is now
4. No existing key was renamed and no table column moved, so a consumer that greps
for what it needs is unaffected; one that keys on the section number of WARNINGS
is not, hence the bump.

The four third-party headers GEMMI has always bundled - fast_float, half,
pocketfft and tinydir - were never listed in THIRD_PARTY_NOTICES.md, riding
instead under GEMMI's single MPL-2.0 row. They are other authors' code under
other licences (Apache-2.0 OR MIT OR BSL-1.0, MIT, BSD-3-Clause, BSD-2-Clause),
and MPL-2.0 does not speak for them. Each now has its own row and its own licence
text, alongside the PEGTL and sajson entries added with the CIF parser, and a note
records why the six are listed separately from GEMMI. Their terms live in the
headers rather than in LICENSE files, so five of the six texts are kept by hand;
COLLECT.sh says so and copies the one - PEGTL - that ships a file.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WmryXe8ASbNi632sUMfsa
This commit is contained in:
2026-08-27 22:09:51 +02:00
committed by leonarski_f
co-authored by Claude Opus 5
parent 18d3f55325
commit bf4712a3d5
15 changed files with 294 additions and 8 deletions
+10
View File
@@ -50,6 +50,10 @@ These are copied into the source tree (see the path) rather than fetched.
| [GEMMI](https://github.com/project-gemmi/gemmi) | `gemmi_gph/` | Global Phasing Ltd. | MPL-2.0 | [gemmi.txt](licenses/gemmi.txt) |
| [PEGTL](https://github.com/taocpp/PEGTL) | `gemmi_gph/gemmi/third_party/tao/` | Dr. Colin Hirsch and Daniel Frey | MIT | [pegtl.txt](licenses/pegtl.txt) |
| [sajson](https://github.com/chadaustin/sajson) | `gemmi_gph/gemmi/third_party/sajson.h` | Chad Austin | MIT | [sajson.txt](licenses/sajson.txt) |
| [fast_float](https://github.com/fastfloat/fast_float) | `gemmi_gph/gemmi/third_party/fast_float.h` | The fast_float authors (Daniel Lemire et al.) | Apache-2.0 OR MIT OR BSL-1.0 | [fast-float.txt](licenses/fast-float.txt) |
| [half](https://half.sourceforge.net/) | `gemmi_gph/gemmi/third_party/half.hpp` | Christian Rau | MIT | [half.txt](licenses/half.txt) |
| [pocketfft](https://github.com/mreineck/pocketfft) | `gemmi_gph/gemmi/third_party/pocketfft_hdronly.h` | Max-Planck-Society; Peter Bell; MIT (FFTW-derived parts) | BSD-3-Clause | [pocketfft.txt](licenses/pocketfft.txt) |
| [tinydir](https://github.com/cxong/tinydir) | `gemmi_gph/gemmi/third_party/tinydir.h` | Cong Xu, Lautis Sun, Baudouin Feildel, Andargor | BSD-2-Clause | [tinydir.txt](licenses/tinydir.txt) |
| [traccc (ACTS)](https://github.com/acts-project/traccc) | `image_analysis/spot_finding/StrongPixelSet.cpp`, `SpotExtractorGPU.cu` | CERN, for the benefit of the ACTS project | MPL-2.0 | [traccc.txt](licenses/traccc.txt) |
| [xbflash.qspi](https://github.com/Xilinx/XRT) | `tools/xbflash.qspi/` | Xilinx / AMD | Apache-2.0 | [xbflash-qspi.txt](licenses/xbflash-qspi.txt) |
| [wingetopt](https://github.com/alex85k/wingetopt) | `tools/wingetopt/` | Todd C. Miller; The NetBSD Foundation | ISC AND BSD-2-Clause | [wingetopt.txt](licenses/wingetopt.txt) |
@@ -80,6 +84,12 @@ served frontend, so the shipped web UI carries its own attribution.
* **MPL-2.0** (Eigen, GEMMI, libzmq, traccc): file-level copyleft. GEMMI is vendored in `gemmi_gph/`
in trimmed form; libzmq is fetched at build time; Eigen is provided externally (header-only). The
corresponding source is available from each project upstream.
* **GEMMI's own bundled third-party headers** — PEGTL, sajson, fast_float, half, pocketfft and
tinydir, all under `gemmi_gph/gemmi/third_party/` — are listed separately above rather than being
absorbed into GEMMI's row: they are other authors' code under other licences (MIT, BSD and
Apache/MIT/BSL), and GEMMI's MPL-2.0 does not speak for them. Their terms are carried in the
headers themselves rather than in `LICENSE` files, so the `licenses/*.txt` copies are kept by hand;
only PEGTL ships a `LICENSE`, which `COLLECT.sh` copies.
* **traccc** is the one entry that is not a vendored directory. Its sparse connected-component
labelling enters two otherwise first-party files: `StrongPixelSet.cpp` adapts the SparseCCL source,
and `SpotExtractorGPU.cu` follows the design of its GPU counterpart. MPL-2.0 is file-level, so both
+1
View File
@@ -4,6 +4,7 @@
This is an UNSTABLE release. It includes many experimental features, as well as many AI generated fixes. We recommend using rc.152 for production use.
* The rugnux results report records how the run was invoked, what it cost and what it ran on: `COMMAND_LINE=` is the command line as one shell-ready line, `WALL_TIME=` the whole invocation in seconds, and `GPU_COUNT=` / `GPU=` how many GPUs were visible and which ones. The total wall time is also printed on stdout, next to the processing time it is slightly larger than.
* The rugnux results report has a `10. MODEL VALIDATION` section when `--model` was given: R-work / R-free with their reflection counts, the bulk-solvent and scale parameters, the mean 2Fo-Fc density at the atom centres, the reindexing operators and where the maps were written - or `MODEL_VALIDATION= NOT_PERFORMED` and the reason, when the model could not be used. `REPORT_VERSION` is 4; `WARNINGS` moves from section 10 to 11 and no existing key changed.
* `rugnux --model` reads an mmCIF coordinate file as well as a PDB one, gzipped or not, taking the format from the file's content rather than its name. A `.cif` model was rejected before, and the run then finished successfully with no R-free and no maps.
* A model `rugnux --model` cannot use - unreadable, or without atoms, a unit cell or a usable space group - is reported as a `WARNING:` line in the results report instead of only in the log, so a run that was asked to validate and could not is no longer indistinguishable from one that was never asked.
* rugnux says which GPUs it can see before it starts processing, so a machine that turns out to have none - a driver mismatch, a leftover `CUDA_VISIBLE_DEVICES` - is noticed while there is still time to stop rather than after a run that took far longer than it should have.
+10 -1
View File
@@ -553,7 +553,9 @@ Rates, per-image costs and progress remain on stdout only.
Sections, in order: `1. DATA SET`, `2. INDEXING`, `3. GEOMETRY POST-REFINEMENT` (rotation only),
`4. SPACE GROUP DETERMINATION`, `5. SCALING AND MERGING`, `6. TWINNING`, `7. RADIATION DAMAGE`,
`8. SWEEP QUALITY`, `9. DIFFRACTION ANISOTROPY`, `10. WARNINGS`.
`8. SWEEP QUALITY`, `9. DIFFRACTION ANISOTROPY`, `10. MODEL VALIDATION` (only with `--model`),
`11. WARNINGS`. Numbers are fixed: a section that does not apply is left out and the ones after it
keep their numbers.
**`SPOT_RESOLUTION_ESTIMATE=`** in section 1 is how far the merged data are expected to reach, read
off the found spots alone — no lattice, no integration, no merge — so it is there on a run that never
@@ -775,6 +777,13 @@ cell or no usable space group, does not fail the run: it is logged, and the resu
`WARNING: Model validation did not run: …` line, so a run that silently produced no R-free and no
maps cannot be mistaken for one that was never given `--model`.
Either way the results report carries a **`10. MODEL VALIDATION`** section: `R_WORK=` / `R_FREE=`
with their reflection counts, the bulk-solvent and overall scale parameters, the mean 2Fo-Fc density
at the atom centres, the reindexing operators the written reflections were brought into the model's
frame with, and `MAPS_PREFIX=`; or `MODEL_VALIDATION= NOT_PERFORMED` with
`MODEL_VALIDATION_REASON=` when the model could not be used. A run given no `--model` has no such
section at all.
It is a *data-quality lens*, independent of the internal statistics: R-free measures the merged
intensities against external truth, where CC1/2 and R<sub>meas</sub> only measure them against
themselves. It also settles the two things merged intensities alone cannot: the enantiomorph (data
+5 -3
View File
@@ -69,9 +69,11 @@ copy_license bitshuffle-hperf.txt compression/bitshuffle_hperf/LICENSE-APACHE
copy_license lz4.txt compression/lz4/LICENSE
copy_license xilinx-hls-headers.txt fpga/include/LICENSE.HLS_Headers
copy_license gemmi.txt gemmi_gph/LICENSE.txt
# GEMMI bundles PEGTL under its own include tree and under its own (MIT) licence; the CIF parser
# rugnux reads mmCIF models with is built on it. sajson has no LICENSE file of its own - its terms
# are the comment block at the top of the header - so licenses/sajson.txt is kept by hand.
# GEMMI bundles six third-party headers under its own include tree, each under its own licence
# (see THIRD_PARTY_NOTICES.md). Only PEGTL ships a LICENSE file; sajson, fast_float, half, pocketfft
# and tinydir carry their terms in a comment at the top of the header, so licenses/sajson.txt,
# fast-float.txt, half.txt, pocketfft.txt and tinydir.txt are kept by hand - re-extract them from the
# headers when GEMMI is updated.
copy_license pegtl.txt gemmi_gph/gemmi/third_party/tao/LICENSE
copy_license xbflash-qspi.txt tools/xbflash.qspi/LICENSE
copy_license wingetopt.txt tools/wingetopt/LICENSE
+90
View File
@@ -0,0 +1,90 @@
fast_float by Daniel Lemire
fast_float by João Paulo Magalhaes
with contributions from Eugene Golushkov
with contributions from Maksim Kita
with contributions from Marcin Wojdyr
with contributions from Neal Richardson
with contributions from Tim Paine
with contributions from Fabio Pellacini
with contributions from Lénárd Szolnoki
with contributions from Jan Pharago
with contributions from Maya Warrier
with contributions from Taha Khokhar
with contributions from Anders Dalvander
Licensed under the Apache License, Version 2.0, or the
MIT License or the Boost License. This file may not be copied,
modified, or distributed except according to those terms.
MIT License Notice
MIT License
Copyright (c) 2021 The fast_float authors
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
Apache License (Version 2.0) Notice
Copyright 2021 The fast_float authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
BOOST License Notice
Boost Software License - Version 1.0 - August 17th, 2003
Permission is hereby granted, free of charge, to any person or organization
obtaining a copy of the software and accompanying documentation covered by
this license (the "Software") to use, reproduce, display, distribute,
execute, and transmit the Software, and to prepare derivative works of the
Software, and to permit third-parties to whom the Software is furnished to
do so, all subject to the following:
The copyright notices in the Software and this entire statement, including
the above license grant, this restriction and the following disclaimer,
must be included in all copies of the Software, in whole or in part, and
all derivative works of the Software, unless such copies or derivative
works are solely in the form of machine-executable object code generated by
a source language processor.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
+15
View File
@@ -0,0 +1,15 @@
half - IEEE 754-based half-precision floating-point library.
Copyright (c) 2012-2025 Christian Rau <rauy@users.sourceforge.net>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy,
modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+41
View File
@@ -0,0 +1,41 @@
This file is part of pocketfft.
Copyright (C) 2010-2024 Max-Planck-Society
Copyright (C) 2019-2020 Peter Bell
For the odd-sized DCT-IV transforms:
Copyright (C) 2003, 2007-14 Matteo Frigo
Copyright (C) 2003, 2007-14 Massachusetts Institute of Technology
For the prev_good_size search:
Copyright (C) 2024 Tan Ping Liang, Peter Bell
For the safeguards against integer overflow in good_size search:
Copyright (C) 2024 Cris Luengo
Authors: Martin Reinecke, Peter Bell
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.
* Neither the name of the copyright holder nor the names of its contributors may
be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+26
View File
@@ -0,0 +1,26 @@
Copyright (c) 2013-2021, tinydir authors:
- Cong Xu
- Lautis Sun
- Baudouin Feildel
- Andargor <andargor@yahoo.com>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+1
View File
@@ -56,6 +56,7 @@ ModelValidationResult ValidateAgainstModel(const std::vector<MergedReflection> &
std::optional<int> data_space_group_number,
bool probe_indexing_ambiguity) {
ModelValidationResult result;
result.model_path = model_path;
// --- read the atomic model ---
gemmi::Structure st;
+1
View File
@@ -17,6 +17,7 @@ class Logger;
// Result of validating merged intensities against an atomic model.
struct ModelValidationResult {
bool ok = false;
std::string model_path; // the file that was asked for, so the report can name it
// Why it did not run, when ok is false: the file could not be read, had no atoms, no cell or no
// usable space group. A caller that asked for a model must be able to say so rather than finish
// quietly with no R-free and no maps, which reads exactly like a run that was never asked.
+41 -3
View File
@@ -19,7 +19,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 = 3;
constexpr int REPORT_VERSION = 4;
const char *BANNER = " ******************************************************************************";
@@ -344,11 +344,49 @@ std::string RenderResultReport(const std::string &output_prefix,
*std::min_element(an.d_min_axis, an.d_min_axis + 3)));
}
// --------------------------------------------------------------- 10. WARNINGS
// ------------------------------------------------------- 10. MODEL VALIDATION
if (result.model_validation.has_value()) {
const auto &mv = *result.model_validation;
Section(os, "10. MODEL VALIDATION");
Key(os, "MODEL_FILE", mv.model_path);
if (!mv.ok) {
Key(os, "MODEL_VALIDATION", "NOT_PERFORMED");
Key(os, "MODEL_VALIDATION_REASON", mv.failure_reason);
os << "\n A model was given but could not be used, so there are no R-factors and no maps.\n"
<< " Everything else in this report is unaffected: the merge does not depend on the model.\n";
} else {
Key(os, "MODEL_SPACE_GROUP_NUMBER", mv.model_space_group_number);
Key(os, "R_WORK", fmt::format("{:.4f}", mv.r_work));
Key(os, "R_FREE", fmt::format("{:.4f}", mv.r_free));
Key(os, "R_WORK_REFLECTIONS", mv.n_work);
Key(os, "R_FREE_REFLECTIONS", mv.n_free);
Key(os, "BULK_SOLVENT_K_SOL", fmt::format("{:.3f}", mv.k_sol));
Key(os, "BULK_SOLVENT_B_SOL", fmt::format("{:.1f}", mv.b_sol));
Key(os, "SCALE_OVERALL", fmt::format("{:.4f}", mv.k_overall));
Key(os, "MEAN_ATOM_DENSITY_SIGMA", fmt::format("{:.2f}", mv.mean_atom_density_sigma));
// The relabelings that were applied to the written reflections, so a reader can tell
// whether the file is in the indexing it was merged in or in the model's.
Key(os, "MODEL_HAND_OPERATOR", mv.hand_op.triplet());
Key(os, "MODEL_INDEXING_OPERATOR", mv.indexing_op.triplet());
if (!mv.maps_prefix.empty())
Key(os, "MAPS_PREFIX", mv.maps_prefix);
os << "\n R-free here measures the merged intensities against an external structure, which is\n"
<< " what CC1/2 and R_meas cannot do - they only measure the data against themselves. The\n"
<< " model is not refined: it is scaled to the data with a flat bulk solvent and an overall\n"
<< " anisotropic B, so these R-factors are higher than a refined structure's and are a\n"
<< " data-quality reading, not a refinement result.\n";
if (!(mv.hand_op == gemmi::Op::identity()) || !(mv.indexing_op == gemmi::Op::identity()))
os << "\n The written reflections were reindexed into the model's frame - the operators above\n"
<< " say how - so the reflection files, the R-factors and the maps all describe one\n"
<< " indexing.\n";
}
}
// --------------------------------------------------------------- 11. WARNINGS
if (result.cancelled)
warnings.emplace_back(fmt::format("Processing was cancelled after {} images - this report "
"describes an incomplete run", result.images_processed));
Section(os, "10. WARNINGS");
Section(os, "11. WARNINGS");
os << " Everything that needs a person's attention, one line each, marked so a script can find\n"
<< " them with a single grep for \"WARNING:\".\n\n";
Key(os, "WARNING_COUNT", warnings.size());
+1
View File
@@ -3295,6 +3295,7 @@ ProcessResult Rugnux::RunPipeline(RugnuxObserver *observer, bool write_output, b
// A model that was asked for and could not be used has to say so where anyone will see
// it. Without this the run ends successfully with no R-free, no maps and nothing in the
// report - indistinguishable from a run that was never given --model at all.
result.model_validation = validation;
if (!validation.failure_reason.empty())
result.warnings.push_back("Model validation did not run: " + validation.failure_reason);
if (data_sg.has_value()) {
+6
View File
@@ -18,6 +18,7 @@
#include "../image_analysis/spot_finding/SpotFindingSettings.h"
#include "../image_analysis/scale_merge/Merge.h" // MergeStatistics
#include "../image_analysis/scale_merge/TwinningAnalysis.h" // TwinningAnalysisResult
#include "ModelValidation.h" // ModelValidationResult
#include "../image_analysis/scale_merge/SearchSpaceGroup.h" // SearchSpaceGroupResult
#include "../image_analysis/geom_refinement/PostRefine.h" // PostRefineResult
#include "../image_analysis/rotation_indexer/RotationIndexer.h" // RotationIndexerResult
@@ -199,6 +200,11 @@ struct ProcessResult {
// Twinning analysis of the final merged intensities (l_test_pairs == 0 when not computed).
TwinningAnalysisResult twinning;
// What --model found, when one was given. Set even when the validation could not run - its
// failure_reason then says why - so the report can report the failure rather than leave a run
// that was asked to validate looking like one that was not. Unset when no model was asked for.
std::optional<ModelValidationResult> model_validation;
// Space group used for the final re-scale/merge and written to the master file: determined by the
// search when the user did not fix one, otherwise the fixed group.
std::optional<int64_t> space_group_number;
+3
View File
@@ -1669,6 +1669,7 @@ static int RunRugnux(int argc, char **argv) {
// A model that was asked for and could not be used has to reach the report, or the run ends
// looking exactly like one that was never given --model.
std::string model_validation_failure;
std::optional<ModelValidationResult> model_validation;
if (!output_prefix.empty() && !model_pdb.empty()) {
const auto data_sg = experiment.GetSpaceGroupNumber();
// With a reference MTZ the merohedral indexing was already resolved (stills per-image
@@ -1678,6 +1679,7 @@ static int RunRugnux(int argc, char **argv) {
output_prefix, logger,
data_sg ? std::optional<int>(static_cast<int>(*data_sg)) : std::nullopt,
/*probe_indexing_ambiguity=*/reference_data.empty());
model_validation = validation;
if (!validation.failure_reason.empty())
model_validation_failure = validation.failure_reason;
if (data_sg.has_value()) {
@@ -1742,6 +1744,7 @@ static int RunRugnux(int argc, char **argv) {
scale_result.error_model_b = error_model_b;
scale_result.has_reference = !reference_data.empty();
scale_result.twinning = twinning;
scale_result.model_validation = model_validation;
if (!model_validation_failure.empty())
scale_result.warnings.push_back("Model validation did not run: " + model_validation_failure);
provenance.wall_time_s = std::chrono::duration<double>(
+43 -1
View File
@@ -69,7 +69,7 @@ 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= 3\n") != std::string::npos);
CHECK(text.find("\nREPORT_VERSION= 4\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);
@@ -224,6 +224,48 @@ TEST_CASE("ResultReport_ProvenanceKeys", "[Diagnostics]") {
CHECK(without.find("GPU_COUNT=") == std::string::npos);
}
// A model that was asked for gets a section either way: the R-factors when it worked, and why it did
// not when it did not. A run that never asked for one has no section at all.
TEST_CASE("ResultReport_ModelValidationSection", "[Diagnostics]") {
DiffractionExperiment x(DetJF(1));
x.ImagesPerTrigger(600);
ProcessResult result;
result.images_processed = 600;
result.consensus_cell = UnitCell{.a = 79.0f, .b = 79.0f, .c = 38.0f,
.alpha = 90.0f, .beta = 90.0f, .gamma = 90.0f};
CHECK(RenderResultReport("p", "in.h5", x, result).find("MODEL VALIDATION") == std::string::npos);
ModelValidationResult good;
good.ok = true;
good.model_path = "model.cif";
good.model_space_group_number = 96;
good.r_work = 0.1823;
good.r_free = 0.2145;
good.n_work = 16682;
good.n_free = 928;
good.mean_atom_density_sigma = 1.42;
result.model_validation = good;
const auto text = RenderResultReport("p", "in.h5", x, result);
CHECK(text.find("10. MODEL VALIDATION") != std::string::npos);
CHECK(text.find("\nMODEL_FILE= model.cif\n") != std::string::npos);
CHECK(text.find("\nR_FREE= 0.2145\n") != std::string::npos);
CHECK(text.find("\nR_FREE_REFLECTIONS= 928\n") != std::string::npos);
CHECK(text.find("MODEL_VALIDATION= NOT_PERFORMED") == std::string::npos);
ModelValidationResult failed;
failed.model_path = "broken.pdb";
failed.failure_reason = "model broken.pdb has no atoms or no unit cell";
result.model_validation = failed;
const auto failed_text = RenderResultReport("p", "in.h5", x, result);
CHECK(failed_text.find("\nMODEL_VALIDATION= NOT_PERFORMED\n") != std::string::npos);
CHECK(failed_text.find("has no atoms") != std::string::npos);
CHECK(failed_text.find("R_FREE=") == std::string::npos);
}
// get_gpu_description collapses repeats, so a four-card machine reads as one line rather than the
// same name four times. Build-independent: without CUDA there are no names and it is empty.
TEST_CASE("ResultReport_GpuDescription", "[Diagnostics]") {