broker rugnux live: no files, END built from the result in memory, and review fixes
No files. The receiver no longer makes a /tmp scratch directory or reads rugnux's files back: AddToEndMessage moves the merged MtzTable and the map coefficients out of ProcessResult::outputs and converts the placed model with PlacedModelFromStructure (now given the data cell, as WritePlacedModel is). FillRugnuxResultBlocks and MtzTableFromFile are gone; their test now builds the blocks from the same in-memory outputs and still checks the writer reproduces rugnux's own files byte for byte. The live status report is built from the result with the writer's file prefix as its label. AddToEndMessage never throws: a failure is logged and the END goes without the blocks. Review fixes: - MakeRugnuxLiveSetup sets nthreads_auto, as a bare `rugnux` does, so the per-image loop keeps to GPUWorkersPerCard per card instead of starting one GPU worker per CPU thread; the 60-degree probe gets a quarter of the threads (at least 2). - A failure setting rugnux up (start message, reader, thread start) marks the live status Failed and nothing else; the collection goes on as in Lite. MeasurementThread and ForwardThread also catch std::exception, so no exception skips the latch or the reader's Ended(). - The ParallelFor pool is lowered to nice 10 only where it can be raised back (CAP_SYS_NICE, or RLIMIT_NICE reaching 0); otherwise it is left alone and that is logged once. - The preview and metadata sockets get the END without the result blocks. - Receiver plots and scan result are fed only from the canonical pass (RugnuxObserver::OnImagePass): the pre-pass reports [0, E) again at the header geometry and the plots accumulate, so it would count those images twice. The canonical pass reads every image once. - A collection the image buffer cannot hold is still not analysed, and now says so in the broker status (Warning) as well as the rugnux status. With rugnux live off nothing changes: the extra END resets are of empty blocks and analysis_warning is empty. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5028aac0ee
commit
e16db07ffd
11 files changed
+183
-134
No files matched your search
@@ -556,6 +556,10 @@ void JFJochStateMachine::MeasurementThread() {
|
|||||||
SetState(JFJochState::Idle,
|
SetState(JFJochState::Idle,
|
||||||
"Stream receiver (writer or downstream analysis) cannot cope with data; reduce frame rate",
|
"Stream receiver (writer or downstream analysis) cannot cope with data; reduce frame rate",
|
||||||
BrokerStatus::MessageSeverity::Warning);
|
BrokerStatus::MessageSeverity::Warning);
|
||||||
|
else if (!tmp_output.receiver_output.analysis_warning.empty())
|
||||||
|
SetState(JFJochState::Idle,
|
||||||
|
"Data collection without problems, but " + tmp_output.receiver_output.analysis_warning,
|
||||||
|
BrokerStatus::MessageSeverity::Warning);
|
||||||
else
|
else
|
||||||
SetState(JFJochState::Idle,
|
SetState(JFJochState::Idle,
|
||||||
"Data collection without problems",
|
"Data collection without problems",
|
||||||
|
|||||||
@@ -206,6 +206,11 @@ void JFJochReceiver::SendEndMessage() {
|
|||||||
logger.Info("Disconnected from writers");
|
logger.Info("Disconnected from writers");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The result blocks (AddToEndMessage) are megabytes only the writer needs.
|
||||||
|
message.merged.reset();
|
||||||
|
message.map_coefficients.reset();
|
||||||
|
message.placed_model.reset();
|
||||||
|
|
||||||
if (zmq_metadata_socket != nullptr)
|
if (zmq_metadata_socket != nullptr)
|
||||||
zmq_metadata_socket->EndDataCollection(message);
|
zmq_metadata_socket->EndDataCollection(message);
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,9 @@ struct JFJochReceiverOutput {
|
|||||||
std::string writer_err;
|
std::string writer_err;
|
||||||
std::optional<int64_t> images_written;
|
std::optional<int64_t> images_written;
|
||||||
MeanProcessingTime processing_time;
|
MeanProcessingTime processing_time;
|
||||||
|
// A problem with the analysis alone (the images are all there), for the broker's status: why a live
|
||||||
|
// rugnux run was skipped. Empty otherwise.
|
||||||
|
std::string analysis_warning;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
#include "JFJochReceiverRugnux.h"
|
#include "JFJochReceiverRugnux.h"
|
||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <filesystem>
|
#include <linux/capability.h>
|
||||||
#include <sys/resource.h>
|
#include <sys/resource.h>
|
||||||
#include <sys/syscall.h>
|
#include <sys/syscall.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
@@ -26,6 +26,17 @@ namespace {
|
|||||||
std::chrono::steady_clock::now().time_since_epoch()).count();
|
std::chrono::steady_clock::now().time_since_epoch()).count();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Whether this process may set a thread back to nice 0 once it has lowered it: CAP_SYS_NICE, or an
|
||||||
|
// RLIMIT_NICE that reaches 0 (the limit is 20 - nice).
|
||||||
|
bool CanRaiseNiceToZero() {
|
||||||
|
__user_cap_header_struct header{_LINUX_CAPABILITY_VERSION_3, 0};
|
||||||
|
__user_cap_data_struct caps[_LINUX_CAPABILITY_U32S_3]{};
|
||||||
|
if (syscall(SYS_capget, &header, caps) == 0 && (caps[0].effective & (1u << CAP_SYS_NICE)))
|
||||||
|
return true;
|
||||||
|
rlimit limit{};
|
||||||
|
return getrlimit(RLIMIT_NICE, &limit) == 0 && (limit.rlim_cur == RLIM_INFINITY || limit.rlim_cur >= 20);
|
||||||
|
}
|
||||||
|
|
||||||
double SecondsSince(int64_t ns) {
|
double SecondsSince(int64_t ns) {
|
||||||
return static_cast<double>(SteadyNowNs() - ns) / 1e9;
|
return static_cast<double>(SteadyNowNs() - ns) / 1e9;
|
||||||
}
|
}
|
||||||
@@ -49,21 +60,25 @@ namespace {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// What rugnux reports as it goes: its per-image results into the receiver's plots and scan
|
// What rugnux reports as it goes: the per-image results of its canonical pass into the receiver's
|
||||||
// result, which a later pass over the same image overwrites - so the canonical pass, the last to
|
// plots and scan result - the plots accumulate, so an image reported by the pre-pass as well would be
|
||||||
// read every image, is what stays - and its steps into the log.
|
// counted twice - and its steps into the log.
|
||||||
class LiveObserver : public RugnuxObserver {
|
class LiveObserver : public RugnuxObserver {
|
||||||
JFJochReceiverPlots &plots;
|
JFJochReceiverPlots &plots;
|
||||||
ScanResultGenerator &scan_result;
|
ScanResultGenerator &scan_result;
|
||||||
const AzimuthalIntegrationMapping &mapping;
|
const AzimuthalIntegrationMapping &mapping;
|
||||||
RugnuxLiveStatus &status;
|
RugnuxLiveStatus &status;
|
||||||
Logger &logger;
|
Logger &logger;
|
||||||
|
std::atomic<bool> canonical{false};
|
||||||
public:
|
public:
|
||||||
LiveObserver(JFJochReceiverPlots &plots, ScanResultGenerator &scan_result,
|
LiveObserver(JFJochReceiverPlots &plots, ScanResultGenerator &scan_result,
|
||||||
const AzimuthalIntegrationMapping &mapping, RugnuxLiveStatus &status, Logger &logger)
|
const AzimuthalIntegrationMapping &mapping, RugnuxLiveStatus &status, Logger &logger)
|
||||||
: plots(plots), scan_result(scan_result), mapping(mapping), status(status), logger(logger) {}
|
: plots(plots), scan_result(scan_result), mapping(mapping), status(status), logger(logger) {}
|
||||||
|
|
||||||
|
void OnImagePass(bool c) override { canonical = c; }
|
||||||
void OnImageProcessed(const DataMessage &msg) override {
|
void OnImageProcessed(const DataMessage &msg) override {
|
||||||
|
if (!canonical)
|
||||||
|
return;
|
||||||
plots.Add(msg, AzimuthalIntegrationProfile(mapping));
|
plots.Add(msg, AzimuthalIntegrationProfile(mapping));
|
||||||
scan_result.Add(msg);
|
scan_result.Add(msg);
|
||||||
}
|
}
|
||||||
@@ -166,27 +181,23 @@ void JFJochReceiverRugnux::MeasurementThread() {
|
|||||||
const auto slots = image_buffer.GetStatus().total_slots;
|
const auto slots = image_buffer.GetStatus().total_slots;
|
||||||
live_status.Start(experiment.GetImageNum());
|
live_status.Start(experiment.GetImageNum());
|
||||||
if (slots < experiment.GetImageNum()) {
|
if (slots < experiment.GetImageNum()) {
|
||||||
logger.Error("Image buffer holds {} images, the collection has {}: no rugnux analysis for it",
|
skipped_warning = fmt::format("no rugnux analysis: the image buffer holds {} images, the collection "
|
||||||
slots, experiment.GetImageNum());
|
"has {}", slots, experiment.GetImageNum());
|
||||||
|
logger.Error("{}", skipped_warning);
|
||||||
live_status.Finish(RugnuxLiveState::Skipped, "the image buffer cannot hold the whole collection");
|
live_status.Finish(RugnuxLiveState::Skipped, "the image buffer cannot hold the whole collection");
|
||||||
} else {
|
} else
|
||||||
// The start message as the writer gets it, so rugnux sees the dataset an HDF5 master states.
|
StartRugnux(rugnux_done, probe_done);
|
||||||
std::vector<uint8_t> start_cbor;
|
|
||||||
image_buffer.GetStartMessage(start_cbor);
|
|
||||||
reader.Open(CBORStream2Deserialize(start_cbor)->start_message.value());
|
|
||||||
char dir_template[] = "/tmp/jfjoch_rugnux_XXXXXX";
|
|
||||||
if (mkdtemp(dir_template) == nullptr)
|
|
||||||
throw JFJochException(JFJochExceptionCategory::FileWriteError, "Cannot create rugnux scratch directory");
|
|
||||||
scratch_dir = dir_template;
|
|
||||||
rugnux_done = std::async(std::launch::async, &JFJochReceiverRugnux::RugnuxThread, this);
|
|
||||||
probe_done = std::async(std::launch::async, &JFJochReceiverRugnux::ProbeThread, this);
|
|
||||||
}
|
|
||||||
measurement_started.count_down();
|
measurement_started.count_down();
|
||||||
} catch (const JFJochException &e) {
|
} catch (const JFJochException &e) {
|
||||||
logger.ErrorException(e);
|
logger.ErrorException(e);
|
||||||
Cancel(e);
|
Cancel(e);
|
||||||
measurement_started.count_down();
|
measurement_started.count_down();
|
||||||
throw;
|
throw;
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
logger.Error("Cancelling data collection due to exception: {}", e.what());
|
||||||
|
cancelled = true;
|
||||||
|
measurement_started.count_down();
|
||||||
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Info("Receiving started");
|
logger.Info("Receiving started");
|
||||||
@@ -229,21 +240,43 @@ void JFJochReceiverRugnux::MeasurementThread() {
|
|||||||
written_s, end_sent_s);
|
written_s, end_sent_s);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!scratch_dir.empty()) {
|
|
||||||
std::error_code ec;
|
|
||||||
std::filesystem::remove_all(scratch_dir, ec);
|
|
||||||
}
|
|
||||||
end_time = std::chrono::system_clock::now();
|
end_time = std::chrono::system_clock::now();
|
||||||
current_status.SetProgress({});
|
current_status.SetProgress({});
|
||||||
current_status.SetStatus(GetStatus());
|
current_status.SetStatus(GetStatus());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void JFJochReceiverRugnux::StartRugnux(std::future<void> &rugnux_done, std::future<void> &probe_done) {
|
||||||
|
// A failure here costs the analysis only: the images go on to the writer as they would without it.
|
||||||
|
try {
|
||||||
|
// The start message as the writer gets it, so rugnux sees the dataset an HDF5 master states.
|
||||||
|
std::vector<uint8_t> start_cbor;
|
||||||
|
image_buffer.GetStartMessage(start_cbor);
|
||||||
|
reader.Open(CBORStream2Deserialize(start_cbor)->start_message.value());
|
||||||
|
rugnux_done = std::async(std::launch::async, &JFJochReceiverRugnux::RugnuxThread, this);
|
||||||
|
probe_done = std::async(std::launch::async, &JFJochReceiverRugnux::ProbeThread, this);
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
logger.Error("rugnux live: cannot start - {}", e.what());
|
||||||
|
live_status.Finish(RugnuxLiveState::Failed, e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void JFJochReceiverRugnux::RugnuxThread() {
|
void JFJochReceiverRugnux::RugnuxThread() {
|
||||||
// Below the forward threads: every thread rugnux starts inherits this, so an image arriving finds a
|
// Below the forward threads: every thread rugnux starts inherits this, so an image arriving finds a
|
||||||
// core however busy rugnux keeps the machine.
|
// core however busy rugnux keeps the machine.
|
||||||
// The process-wide ParallelFor pool is not started by this thread, so it is lowered separately.
|
// The process-wide ParallelFor pool is not started by this thread, so it is lowered separately.
|
||||||
setpriority(PRIO_PROCESS, static_cast<id_t>(syscall(SYS_gettid)), 10);
|
setpriority(PRIO_PROCESS, static_cast<id_t>(syscall(SYS_gettid)), 10);
|
||||||
parallel_detail::WorkerPool::Instance().SetNice(10);
|
// Only where it can be raised back afterwards: the pool outlives the collection, and left at nice 10
|
||||||
|
// it would slow every analysis the broker runs after it.
|
||||||
|
const bool pool_niced = CanRaiseNiceToZero();
|
||||||
|
if (pool_niced)
|
||||||
|
parallel_detail::WorkerPool::Instance().SetNice(10);
|
||||||
|
else {
|
||||||
|
static std::once_flag logged;
|
||||||
|
std::call_once(logged, [this] {
|
||||||
|
logger.Warning("rugnux live: the ParallelFor pool is left at its priority - lowering it needs "
|
||||||
|
"the right to raise it back (CAP_SYS_NICE or RLIMIT_NICE)");
|
||||||
|
});
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const auto dataset = reader.GetDataset();
|
const auto dataset = reader.GetDataset();
|
||||||
const RugnuxLiveSetup setup = MakeRugnuxLiveSetup(*dataset, rugnux_nthreads, model);
|
const RugnuxLiveSetup setup = MakeRugnuxLiveSetup(*dataset, rugnux_nthreads, model);
|
||||||
@@ -255,7 +288,7 @@ void JFJochReceiverRugnux::RugnuxThread() {
|
|||||||
result = process.Run(&observer);
|
result = process.Run(&observer);
|
||||||
}
|
}
|
||||||
// The verdict and summary of the report a `rugnux` run writes, without writing it.
|
// The verdict and summary of the report a `rugnux` run writes, without writing it.
|
||||||
const ReportDocument doc = BuildReportDocument(scratch_dir + "/p", experiment.GetFilePrefix(),
|
const ReportDocument doc = BuildReportDocument(experiment.GetFilePrefix(), experiment.GetFilePrefix(),
|
||||||
setup.experiment, result);
|
setup.experiment, result);
|
||||||
std::vector<std::string> warnings;
|
std::vector<std::string> warnings;
|
||||||
for (const auto &w: doc.warnings)
|
for (const auto &w: doc.warnings)
|
||||||
@@ -269,8 +302,8 @@ void JFJochReceiverRugnux::RugnuxThread() {
|
|||||||
logger.Error("rugnux live: {}", e.what());
|
logger.Error("rugnux live: {}", e.what());
|
||||||
live_status.Finish(RugnuxLiveState::Failed, e.what());
|
live_status.Finish(RugnuxLiveState::Failed, e.what());
|
||||||
}
|
}
|
||||||
if (!parallel_detail::WorkerPool::Instance().SetNice(0))
|
if (pool_niced && !parallel_detail::WorkerPool::Instance().SetNice(0))
|
||||||
logger.Warning("rugnux live: the ParallelFor pool stays at nice 10 - raising it back needs CAP_SYS_NICE");
|
logger.Warning("rugnux live: the ParallelFor pool could not be raised back to nice 0");
|
||||||
}
|
}
|
||||||
|
|
||||||
void JFJochReceiverRugnux::ProbeThread() {
|
void JFJochReceiverRugnux::ProbeThread() {
|
||||||
@@ -288,7 +321,8 @@ void JFJochReceiverRugnux::ProbeThread() {
|
|||||||
const int64_t frames = std::lround(PROBE_DEG / std::abs(gonio->GetIncrement_deg()));
|
const int64_t frames = std::lround(PROBE_DEG / std::abs(gonio->GetIncrement_deg()));
|
||||||
if (2 * frames > static_cast<int64_t>(reader.GetNumberOfImages()))
|
if (2 * frames > static_cast<int64_t>(reader.GetNumberOfImages()))
|
||||||
return; // a sweep this short is decided on soon enough by the run itself
|
return; // a sweep this short is decided on soon enough by the run itself
|
||||||
RugnuxLiveSetup setup = MakeRugnuxLiveSetup(*dataset, rugnux_nthreads, nullptr);
|
// A quarter of the threads: it runs beside the main run, and each of its workers holds GPU memory too.
|
||||||
|
RugnuxLiveSetup setup = MakeRugnuxLiveSetup(*dataset, std::max(2, rugnux_nthreads / 4), nullptr);
|
||||||
setup.config.end_image = static_cast<int>(frames);
|
setup.config.end_image = static_cast<int>(frames);
|
||||||
setup.config.rotation_postrefine_geometry = false;
|
setup.config.rotation_postrefine_geometry = false;
|
||||||
setup.config.run_scaling = false;
|
setup.config.run_scaling = false;
|
||||||
@@ -317,32 +351,48 @@ void JFJochReceiverRugnux::ProbeThread() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void JFJochReceiverRugnux::AddToEndMessage(EndMessage &message) {
|
void JFJochReceiverRugnux::AddToEndMessage(EndMessage &message) {
|
||||||
std::unique_lock ul(rugnux_m);
|
// A failure here costs the end message the analysis only: it goes out as the receiver made it.
|
||||||
if (!rugnux_result)
|
try {
|
||||||
return;
|
std::unique_lock ul(rugnux_m);
|
||||||
const EndMessage &r = rugnux_result->end_message;
|
if (!rugnux_result)
|
||||||
message.indexing_rate = r.indexing_rate;
|
return;
|
||||||
message.bkg_estimate = r.bkg_estimate;
|
const EndMessage &r = rugnux_result->end_message;
|
||||||
message.spindle_blind_fraction = r.spindle_blind_fraction;
|
message.indexing_rate = r.indexing_rate;
|
||||||
message.ice_ring_score_mean = r.ice_ring_score_mean;
|
message.bkg_estimate = r.bkg_estimate;
|
||||||
message.az_int_result = r.az_int_result;
|
message.spindle_blind_fraction = r.spindle_blind_fraction;
|
||||||
message.rotation_lattice = r.rotation_lattice;
|
message.ice_ring_score_mean = r.ice_ring_score_mean;
|
||||||
message.rotation_lattice_type = r.rotation_lattice_type;
|
message.az_int_result = r.az_int_result;
|
||||||
message.rotation_extra_lattices = r.rotation_extra_lattices;
|
message.rotation_lattice = r.rotation_lattice;
|
||||||
message.unit_cell = r.unit_cell;
|
message.rotation_lattice_type = r.rotation_lattice_type;
|
||||||
message.space_group_name = r.space_group_name;
|
message.rotation_extra_lattices = r.rotation_extra_lattices;
|
||||||
message.space_group_number = r.space_group_number;
|
message.unit_cell = r.unit_cell;
|
||||||
message.reindex_matrix = r.reindex_matrix;
|
message.space_group_name = r.space_group_name;
|
||||||
message.refined_beam_center_x = r.refined_beam_center_x;
|
message.space_group_number = r.space_group_number;
|
||||||
message.refined_beam_center_y = r.refined_beam_center_y;
|
message.reindex_matrix = r.reindex_matrix;
|
||||||
message.refined_poni_rot1 = r.refined_poni_rot1;
|
message.refined_beam_center_x = r.refined_beam_center_x;
|
||||||
message.refined_poni_rot2 = r.refined_poni_rot2;
|
message.refined_beam_center_y = r.refined_beam_center_y;
|
||||||
message.refined_poni_rot3 = r.refined_poni_rot3;
|
message.refined_poni_rot1 = r.refined_poni_rot1;
|
||||||
message.refined_rotation_axis = r.refined_rotation_axis;
|
message.refined_poni_rot2 = r.refined_poni_rot2;
|
||||||
// Merged data, map coefficients and placed model, for the writer to write beside the master. Only
|
message.refined_poni_rot3 = r.refined_poni_rot3;
|
||||||
// here, with rugnux_result set: every file in scratch_dir is then whole - rugnux writes synchronously
|
message.refined_rotation_axis = r.refined_rotation_axis;
|
||||||
// and a cut never stops a write half-way - while a run that threw may have left one half-written.
|
// Merged data, map coefficients and placed model, for the writer to write beside the master. Moved:
|
||||||
FillRugnuxResultBlocks(scratch_dir + "/p", message);
|
// the result is not read again.
|
||||||
|
if (!rugnux_result->outputs)
|
||||||
|
return;
|
||||||
|
RugnuxOutputs &o = *rugnux_result->outputs;
|
||||||
|
if (o.merged)
|
||||||
|
message.merged = std::move(o.merged->mtz);
|
||||||
|
if (o.maps)
|
||||||
|
message.map_coefficients = std::move(o.maps->coefficients);
|
||||||
|
if (o.placed_model)
|
||||||
|
message.placed_model = PlacedModelFromStructure(*o.placed_model->model, o.placed_model->cell,
|
||||||
|
o.placed_model->space_group.xhm());
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
logger.Error("rugnux live: the end message goes without the analysis results - {}", e.what());
|
||||||
|
message.merged.reset();
|
||||||
|
message.map_coefficients.reset();
|
||||||
|
message.placed_model.reset();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void JFJochReceiverRugnux::Configure(const StartMessage &msg) {
|
void JFJochReceiverRugnux::Configure(const StartMessage &msg) {
|
||||||
@@ -433,6 +483,9 @@ void JFJochReceiverRugnux::ForwardThread(uint32_t id) {
|
|||||||
} catch (const JFJochException &e) {
|
} catch (const JFJochException &e) {
|
||||||
logger.ErrorException(e);
|
logger.ErrorException(e);
|
||||||
Cancel(e);
|
Cancel(e);
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
logger.Error("Cancelling data collection due to exception: {}", e.what());
|
||||||
|
cancelled = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -477,6 +530,12 @@ float JFJochReceiverRugnux::GetEfficiency() const {
|
|||||||
return static_cast<float>(images_collected) / static_cast<float>(experiment.GetFrameNum());
|
return static_cast<float>(images_collected) / static_cast<float>(experiment.GetFrameNum());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JFJochReceiverOutput JFJochReceiverRugnux::GetFinalStatistics() const {
|
||||||
|
JFJochReceiverOutput ret = JFJochReceiver::GetFinalStatistics();
|
||||||
|
ret.analysis_warning = skipped_warning;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
float JFJochReceiverRugnux::GetProgress() const {
|
float JFJochReceiverRugnux::GetProgress() const {
|
||||||
if (experiment.GetFrameNum() == 0)
|
if (experiment.GetFrameNum() == 0)
|
||||||
return 0.0;
|
return 0.0;
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ class JFJochReceiverRugnux : public JFJochReceiver {
|
|||||||
const int rugnux_nthreads;
|
const int rugnux_nthreads;
|
||||||
const std::shared_ptr<const RugnuxModel> model;
|
const std::shared_ptr<const RugnuxModel> model;
|
||||||
const double deadline_s; // after the last image, the analysis is cut (0 = never)
|
const double deadline_s; // after the last image, the analysis is cut (0 = never)
|
||||||
std::string scratch_dir; // rugnux's own output, read back into the end message
|
|
||||||
RugnuxLiveStatus &live_status;
|
RugnuxLiveStatus &live_status;
|
||||||
|
std::string skipped_warning; // why the collection was not analysed, for the broker's status
|
||||||
|
|
||||||
std::vector<std::future<void>> forward_futures;
|
std::vector<std::future<void>> forward_futures;
|
||||||
std::future<void> measurement;
|
std::future<void> measurement;
|
||||||
@@ -50,6 +50,9 @@ class JFJochReceiverRugnux : public JFJochReceiver {
|
|||||||
std::atomic<int64_t> last_image_ns{0};
|
std::atomic<int64_t> last_image_ns{0};
|
||||||
|
|
||||||
void MeasurementThread();
|
void MeasurementThread();
|
||||||
|
// Opens the reader on the collection and starts the rugnux and probe threads; a failure is the
|
||||||
|
// analysis's alone (live status Failed), never the collection's.
|
||||||
|
void StartRugnux(std::future<void> &rugnux_done, std::future<void> &probe_done);
|
||||||
void ForwardThread(uint32_t id);
|
void ForwardThread(uint32_t id);
|
||||||
void RugnuxThread();
|
void RugnuxThread();
|
||||||
void ProbeThread();
|
void ProbeThread();
|
||||||
@@ -82,6 +85,7 @@ public:
|
|||||||
|
|
||||||
float GetEfficiency() const override;
|
float GetEfficiency() const override;
|
||||||
float GetProgress() const override;
|
float GetProgress() const override;
|
||||||
|
JFJochReceiverOutput GetFinalStatistics() const override;
|
||||||
|
|
||||||
void SetSpotFindingSettings(const SpotFindingSettings &spot_finding_settings) override {}
|
void SetSpotFindingSettings(const SpotFindingSettings &spot_finding_settings) override {}
|
||||||
};
|
};
|
||||||
@@ -861,6 +861,10 @@ public:
|
|||||||
virtual void OnPhase(const std::string &phase) {}
|
virtual void OnPhase(const std::string &phase) {}
|
||||||
virtual void OnProgress(uint64_t done, uint64_t total) {}
|
virtual void OnProgress(uint64_t done, uint64_t total) {}
|
||||||
virtual void OnImageProcessed(const DataMessage &msg) {}
|
virtual void OnImageProcessed(const DataMessage &msg) {}
|
||||||
|
// An image pass starts: OnImageProcessed then reports its images. `canonical` on the pass whose
|
||||||
|
// per-image results the run keeps - every image once, at the final geometry; a pre-pass or a probe pass
|
||||||
|
// reports some of the images again, or at another geometry.
|
||||||
|
virtual void OnImagePass(bool canonical) {}
|
||||||
virtual void OnStep(const std::string &title) {}
|
virtual void OnStep(const std::string &title) {}
|
||||||
virtual void OnNote(const std::string &text) {}
|
virtual void OnNote(const std::string &text) {}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -354,8 +354,10 @@ void Rugnux::ImagePass(PipelineLocals &p) {
|
|||||||
bragg_overlapped_major += bragg.overlapped_major;
|
bragg_overlapped_major += bragg.overlapped_major;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (observer)
|
if (observer) {
|
||||||
observer->OnPhase("Processing images");
|
observer->OnPhase("Processing images");
|
||||||
|
observer->OnImagePass(p.write_output && !geometry_prepass);
|
||||||
|
}
|
||||||
Step(fmt::format("{}{}, {} images",
|
Step(fmt::format("{}{}, {} images",
|
||||||
!per_image_analysis ? "Azimuthal integration"
|
!per_image_analysis ? "Azimuthal integration"
|
||||||
: calibration ? "Image pass"
|
: calibration ? "Image pass"
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ RugnuxLiveSetup MakeRugnuxLiveSetup(const JFJochReaderDataset &dataset, int nthr
|
|||||||
ProcessConfig &c = s.config;
|
ProcessConfig &c = s.config;
|
||||||
c.mode = ProcessMode::FullAnalysis;
|
c.mode = ProcessMode::FullAnalysis;
|
||||||
c.nthreads = nthreads;
|
c.nthreads = nthreads;
|
||||||
|
// As a bare `rugnux` with no -N: the per-image loop keeps to what a card can take (ImagePass).
|
||||||
|
c.nthreads_auto = true;
|
||||||
c.export_unmerged = false;
|
c.export_unmerged = false;
|
||||||
c.spot_finding = RugnuxDefaultSpotFinding(true);
|
c.spot_finding = RugnuxDefaultSpotFinding(true);
|
||||||
c.rotation_indexing = true;
|
c.rotation_indexing = true;
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ namespace {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("RugnuxResultFiles: rugnux's files -> END blocks -> CBOR -> writer, byte for byte", "[rugnux_result_files]") {
|
TEST_CASE("RugnuxResultFiles: rugnux's outputs -> END blocks -> CBOR -> writer, byte for byte", "[rugnux_result_files]") {
|
||||||
Logger logger("RugnuxResultFiles");
|
Logger logger("RugnuxResultFiles");
|
||||||
const std::filesystem::path dir = std::filesystem::temp_directory_path() / "rugnux_result_files_test";
|
const std::filesystem::path dir = std::filesystem::temp_directory_path() / "rugnux_result_files_test";
|
||||||
std::filesystem::remove_all(dir);
|
std::filesystem::remove_all(dir);
|
||||||
@@ -115,7 +115,12 @@ TEST_CASE("RugnuxResultFiles: rugnux's files -> END blocks -> CBOR -> writer, by
|
|||||||
OutputProvenance provenance;
|
OutputProvenance provenance;
|
||||||
provenance.command_line = "rugnux -N 8 input_master.h5";
|
provenance.command_line = "rugnux -N 8 input_master.h5";
|
||||||
provenance.input_file = "/some/where/input_master.h5";
|
provenance.input_file = "/some/where/input_master.h5";
|
||||||
WriteMtzReflections(reflections, TEST_CELL, experiment, provenance, original + ".mtz");
|
// The files as rugnux writes them from its outputs (RugnuxOutputFiles.cpp), and the same outputs as the
|
||||||
|
// broker puts them in the END message (JFJochReceiverRugnux::AddToEndMessage).
|
||||||
|
EndMessage end{};
|
||||||
|
end.max_image_number = 100;
|
||||||
|
end.merged = MergedMtzTable(reflections, TEST_CELL, experiment, provenance);
|
||||||
|
WriteMtz(*end.merged, original + ".mtz");
|
||||||
|
|
||||||
// Map coefficients as model validation lays them out (ModelMaps::coefficients).
|
// Map coefficients as model validation lays them out (ModelMaps::coefficients).
|
||||||
MtzTable maps;
|
MtzTable maps;
|
||||||
@@ -137,15 +142,10 @@ TEST_CASE("RugnuxResultFiles: rugnux's files -> END blocks -> CBOR -> writer, by
|
|||||||
maps.columns[c].values.push_back(row[c]);
|
maps.columns[c].values.push_back(row[c]);
|
||||||
}
|
}
|
||||||
WriteMtz(maps, original + "_maps.mtz");
|
WriteMtz(maps, original + "_maps.mtz");
|
||||||
|
end.map_coefficients = maps;
|
||||||
|
|
||||||
WritePlacedModel(TestModel(), TEST_CELL, experiment.GetSpaceGroupOrP1(), original, logger);
|
WritePlacedModel(TestModel(), TEST_CELL, experiment.GetSpaceGroupOrP1(), original, logger);
|
||||||
|
end.placed_model = PlacedModelFromStructure(TestModel(), TEST_CELL, experiment.GetSpaceGroupOrP1().xhm());
|
||||||
EndMessage end{};
|
|
||||||
end.max_image_number = 100;
|
|
||||||
FillRugnuxResultBlocks(original, end);
|
|
||||||
REQUIRE(end.merged);
|
|
||||||
REQUIRE(end.map_coefficients);
|
|
||||||
REQUIRE(end.placed_model);
|
|
||||||
REQUIRE(end.merged->columns.size() == 16); // with the anomalous columns
|
REQUIRE(end.merged->columns.size() == 16); // with the anomalous columns
|
||||||
CHECK(end.merged->columns[0].values.size() == reflections.size());
|
CHECK(end.merged->columns[0].values.size() == reflections.size());
|
||||||
CHECK(end.placed_model->atom_name.size() == 2 * 3 * 6 + 2);
|
CHECK(end.placed_model->atom_name.size() == 2 * 3 * 6 + 2);
|
||||||
@@ -242,3 +242,31 @@ TEST_CASE("RugnuxResultFiles: an MTZ table that describes no file is refused", "
|
|||||||
|
|
||||||
std::filesystem::remove(path);
|
std::filesystem::remove(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("RugnuxResultFiles: a placed model block that describes no model is refused", "[rugnux_result_files]") {
|
||||||
|
Logger logger("RugnuxResultFiles");
|
||||||
|
const PlacedModelMessage good = PlacedModelFromStructure(TestModel(), TEST_CELL, "P 43 21 2");
|
||||||
|
REQUIRE_NOTHROW(PlacedModelStructure(good));
|
||||||
|
|
||||||
|
PlacedModelMessage short_field = good;
|
||||||
|
short_field.b_iso.pop_back();
|
||||||
|
CHECK_THROWS(PlacedModelStructure(short_field));
|
||||||
|
|
||||||
|
PlacedModelMessage bad_aniso = good;
|
||||||
|
bad_aniso.aniso.resize(5);
|
||||||
|
CHECK_THROWS(PlacedModelStructure(bad_aniso));
|
||||||
|
|
||||||
|
// The writer reports it and goes on: no file, no crash.
|
||||||
|
const std::filesystem::path dir = std::filesystem::temp_directory_path() / "rugnux_result_files_bad_model";
|
||||||
|
std::filesystem::remove_all(dir);
|
||||||
|
std::filesystem::create_directories(dir);
|
||||||
|
const std::string prefix = (dir / "run").string();
|
||||||
|
EndMessage end{};
|
||||||
|
end.placed_model = short_field;
|
||||||
|
REQUIRE_NOTHROW(WriteRugnuxResultFiles(end, prefix, false, logger));
|
||||||
|
end.placed_model = good;
|
||||||
|
end.placed_model->space_group_name = "not a group";
|
||||||
|
REQUIRE_NOTHROW(WriteRugnuxResultFiles(end, prefix, false, logger));
|
||||||
|
CHECK(!std::filesystem::exists(prefix + "_model.cif"));
|
||||||
|
std::filesystem::remove_all(dir);
|
||||||
|
}
|
||||||
@@ -3,56 +3,11 @@
|
|||||||
|
|
||||||
#include "RugnuxResultBlocks.h"
|
#include "RugnuxResultBlocks.h"
|
||||||
|
|
||||||
#include <filesystem>
|
PlacedModelMessage PlacedModelFromStructure(const gemmi::Structure &st, const UnitCell &cell,
|
||||||
#include <stdexcept>
|
const std::string &data_space_group_name) {
|
||||||
|
|
||||||
#include <gemmi/mtz.hpp>
|
|
||||||
#include <gemmi/mmread_gz.hpp>
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
UnitCell ToUnitCell(const gemmi::UnitCell &c) {
|
|
||||||
return UnitCell{static_cast<float>(c.a), static_cast<float>(c.b), static_cast<float>(c.c),
|
|
||||||
static_cast<float>(c.alpha), static_cast<float>(c.beta), static_cast<float>(c.gamma)};
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string SpaceGroupName(const gemmi::Mtz &mtz) {
|
|
||||||
return mtz.spacegroup != nullptr ? mtz.spacegroup->xhm() : std::string();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
MtzTable MtzTableFromFile(const std::string &path) {
|
|
||||||
const gemmi::Mtz mtz = gemmi::read_mtz_file(path);
|
|
||||||
MtzTable t;
|
|
||||||
t.title = mtz.title;
|
|
||||||
t.unit_cell = ToUnitCell(mtz.cell);
|
|
||||||
t.space_group_name = SpaceGroupName(mtz);
|
|
||||||
t.history = mtz.history;
|
|
||||||
t.sort_order = mtz.sort_order;
|
|
||||||
// A dataset id is its position in the table.
|
|
||||||
const auto position = [&mtz, &path](int id) {
|
|
||||||
for (size_t i = 0; i < mtz.datasets.size(); i++)
|
|
||||||
if (mtz.datasets[i].id == id)
|
|
||||||
return static_cast<int32_t>(i);
|
|
||||||
throw std::runtime_error(path + ": no dataset " + std::to_string(id));
|
|
||||||
};
|
|
||||||
for (const auto &d : mtz.datasets)
|
|
||||||
t.datasets.push_back({d.project_name, d.crystal_name, d.dataset_name, static_cast<float>(d.wavelength)});
|
|
||||||
for (const auto &c : mtz.columns)
|
|
||||||
t.columns.push_back({c.label, c.type, position(c.dataset_id), {c.begin(), c.end()}});
|
|
||||||
for (const auto &b : mtz.batches) {
|
|
||||||
MtzTable::Batch batch{b.number, b.title, {b.ints.begin(), b.ints.end()}, b.floats, b.axes};
|
|
||||||
batch.ints[20] = position(b.dataset_id());
|
|
||||||
t.batches.push_back(std::move(batch));
|
|
||||||
}
|
|
||||||
return t;
|
|
||||||
}
|
|
||||||
|
|
||||||
PlacedModelMessage PlacedModelFromStructure(const gemmi::Structure &st, const std::string &data_space_group_name) {
|
|
||||||
PlacedModelMessage p;
|
PlacedModelMessage p;
|
||||||
p.name = st.name;
|
p.name = st.name;
|
||||||
p.unit_cell = ToUnitCell(st.cell);
|
p.unit_cell = cell;
|
||||||
p.space_group_name = data_space_group_name;
|
p.space_group_name = data_space_group_name;
|
||||||
p.model_space_group_name = st.spacegroup_hm;
|
p.model_space_group_name = st.spacegroup_hm;
|
||||||
bool has_aniso = false;
|
bool has_aniso = false;
|
||||||
@@ -83,15 +38,3 @@ PlacedModelMessage PlacedModelFromStructure(const gemmi::Structure &st, const st
|
|||||||
p.aniso.clear();
|
p.aniso.clear();
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
void FillRugnuxResultBlocks(const std::string &prefix, EndMessage &end) {
|
|
||||||
if (std::filesystem::exists(prefix + ".mtz"))
|
|
||||||
end.merged = MtzTableFromFile(prefix + ".mtz");
|
|
||||||
if (std::filesystem::exists(prefix + "_maps.mtz"))
|
|
||||||
end.map_coefficients = MtzTableFromFile(prefix + "_maps.mtz");
|
|
||||||
if (std::filesystem::exists(prefix + "_model.cif")) {
|
|
||||||
const gemmi::Structure st = gemmi::read_structure_gz(prefix + "_model.cif");
|
|
||||||
end.placed_model = PlacedModelFromStructure(st, end.merged ? end.merged->space_group_name
|
|
||||||
: st.spacegroup_hm);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -9,13 +9,8 @@
|
|||||||
|
|
||||||
#include "../common/JFJochMessages.h"
|
#include "../common/JFJochMessages.h"
|
||||||
|
|
||||||
// The END-message blocks (common/JFJochMessages.h) read back from the files rugnux wrote at
|
// The placed model of a rugnux run as the END message carries it (PlacedModelMessage). cell and
|
||||||
// `prefix`: <prefix>.mtz into `merged`, <prefix>_maps.mtz into `map_coefficients` and
|
// data_space_group_name are the reflection files' beside it, which is what WritePlacedModel is to be given;
|
||||||
// <prefix>_model.cif into `placed_model`, each only where its file exists. The writer makes the same
|
// the model's own group is taken from `st`.
|
||||||
// files again from them (WriteRugnuxResultFiles, writer/WriteRugnuxFiles.h).
|
PlacedModelMessage PlacedModelFromStructure(const gemmi::Structure &st, const UnitCell &cell,
|
||||||
void FillRugnuxResultBlocks(const std::string &prefix, EndMessage &end);
|
const std::string &data_space_group_name);
|
||||||
|
|
||||||
MtzTable MtzTableFromFile(const std::string &path);
|
|
||||||
// data_space_group_name: the group of the reflection files beside the model, which is what
|
|
||||||
// WritePlacedModel is to be given; the model's own group is taken from `st`.
|
|
||||||
PlacedModelMessage PlacedModelFromStructure(const gemmi::Structure &st, const std::string &data_space_group_name);
|
|
||||||
Reference in new issue
Block a user