diff --git a/broker/JFJochStateMachine.cpp b/broker/JFJochStateMachine.cpp index 97c880ded..29ee052d0 100644 --- a/broker/JFJochStateMachine.cpp +++ b/broker/JFJochStateMachine.cpp @@ -556,6 +556,10 @@ void JFJochStateMachine::MeasurementThread() { SetState(JFJochState::Idle, "Stream receiver (writer or downstream analysis) cannot cope with data; reduce frame rate", 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 SetState(JFJochState::Idle, "Data collection without problems", diff --git a/receiver/JFJochReceiver.cpp b/receiver/JFJochReceiver.cpp index 42910ecb1..b7d0d1cdc 100644 --- a/receiver/JFJochReceiver.cpp +++ b/receiver/JFJochReceiver.cpp @@ -206,6 +206,11 @@ void JFJochReceiver::SendEndMessage() { 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) zmq_metadata_socket->EndDataCollection(message); diff --git a/receiver/JFJochReceiverOutput.h b/receiver/JFJochReceiverOutput.h index edf086d85..f6ce317b2 100644 --- a/receiver/JFJochReceiverOutput.h +++ b/receiver/JFJochReceiverOutput.h @@ -26,6 +26,9 @@ struct JFJochReceiverOutput { std::string writer_err; std::optional images_written; 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; }; diff --git a/receiver/JFJochReceiverRugnux.cpp b/receiver/JFJochReceiverRugnux.cpp index b4e9daf2a..d64e65528 100644 --- a/receiver/JFJochReceiverRugnux.cpp +++ b/receiver/JFJochReceiverRugnux.cpp @@ -4,7 +4,7 @@ #include "JFJochReceiverRugnux.h" #include -#include +#include #include #include #include @@ -26,6 +26,17 @@ namespace { 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) { return static_cast(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 - // result, which a later pass over the same image overwrites - so the canonical pass, the last to - // read every image, is what stays - and its steps into the log. + // What rugnux reports as it goes: the per-image results of its canonical pass into the receiver's + // plots and scan result - the plots accumulate, so an image reported by the pre-pass as well would be + // counted twice - and its steps into the log. class LiveObserver : public RugnuxObserver { JFJochReceiverPlots &plots; ScanResultGenerator &scan_result; const AzimuthalIntegrationMapping &mapping; RugnuxLiveStatus &status; Logger &logger; + std::atomic canonical{false}; public: LiveObserver(JFJochReceiverPlots &plots, ScanResultGenerator &scan_result, const AzimuthalIntegrationMapping &mapping, RugnuxLiveStatus &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 { + if (!canonical) + return; plots.Add(msg, AzimuthalIntegrationProfile(mapping)); scan_result.Add(msg); } @@ -166,27 +181,23 @@ void JFJochReceiverRugnux::MeasurementThread() { const auto slots = image_buffer.GetStatus().total_slots; live_status.Start(experiment.GetImageNum()); if (slots < experiment.GetImageNum()) { - logger.Error("Image buffer holds {} images, the collection has {}: no rugnux analysis for it", - slots, experiment.GetImageNum()); + skipped_warning = fmt::format("no rugnux analysis: the image buffer holds {} images, the collection " + "has {}", slots, experiment.GetImageNum()); + logger.Error("{}", skipped_warning); live_status.Finish(RugnuxLiveState::Skipped, "the image buffer cannot hold the whole collection"); - } else { - // The start message as the writer gets it, so rugnux sees the dataset an HDF5 master states. - std::vector 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); - } + } else + StartRugnux(rugnux_done, probe_done); measurement_started.count_down(); } catch (const JFJochException &e) { logger.ErrorException(e); Cancel(e); measurement_started.count_down(); 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"); @@ -229,21 +240,43 @@ void JFJochReceiverRugnux::MeasurementThread() { 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(); current_status.SetProgress({}); current_status.SetStatus(GetStatus()); } +void JFJochReceiverRugnux::StartRugnux(std::future &rugnux_done, std::future &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 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() { // Below the forward threads: every thread rugnux starts inherits this, so an image arriving finds a // core however busy rugnux keeps the machine. // The process-wide ParallelFor pool is not started by this thread, so it is lowered separately. setpriority(PRIO_PROCESS, static_cast(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 { const auto dataset = reader.GetDataset(); const RugnuxLiveSetup setup = MakeRugnuxLiveSetup(*dataset, rugnux_nthreads, model); @@ -255,7 +288,7 @@ void JFJochReceiverRugnux::RugnuxThread() { result = process.Run(&observer); } // 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); std::vector warnings; for (const auto &w: doc.warnings) @@ -269,8 +302,8 @@ void JFJochReceiverRugnux::RugnuxThread() { logger.Error("rugnux live: {}", e.what()); live_status.Finish(RugnuxLiveState::Failed, e.what()); } - if (!parallel_detail::WorkerPool::Instance().SetNice(0)) - logger.Warning("rugnux live: the ParallelFor pool stays at nice 10 - raising it back needs CAP_SYS_NICE"); + if (pool_niced && !parallel_detail::WorkerPool::Instance().SetNice(0)) + logger.Warning("rugnux live: the ParallelFor pool could not be raised back to nice 0"); } void JFJochReceiverRugnux::ProbeThread() { @@ -288,7 +321,8 @@ void JFJochReceiverRugnux::ProbeThread() { const int64_t frames = std::lround(PROBE_DEG / std::abs(gonio->GetIncrement_deg())); if (2 * frames > static_cast(reader.GetNumberOfImages())) 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(frames); setup.config.rotation_postrefine_geometry = false; setup.config.run_scaling = false; @@ -317,32 +351,48 @@ void JFJochReceiverRugnux::ProbeThread() { } void JFJochReceiverRugnux::AddToEndMessage(EndMessage &message) { - std::unique_lock ul(rugnux_m); - if (!rugnux_result) - return; - const EndMessage &r = rugnux_result->end_message; - message.indexing_rate = r.indexing_rate; - message.bkg_estimate = r.bkg_estimate; - message.spindle_blind_fraction = r.spindle_blind_fraction; - message.ice_ring_score_mean = r.ice_ring_score_mean; - message.az_int_result = r.az_int_result; - message.rotation_lattice = r.rotation_lattice; - message.rotation_lattice_type = r.rotation_lattice_type; - message.rotation_extra_lattices = r.rotation_extra_lattices; - message.unit_cell = r.unit_cell; - message.space_group_name = r.space_group_name; - message.space_group_number = r.space_group_number; - message.reindex_matrix = r.reindex_matrix; - message.refined_beam_center_x = r.refined_beam_center_x; - message.refined_beam_center_y = r.refined_beam_center_y; - message.refined_poni_rot1 = r.refined_poni_rot1; - message.refined_poni_rot2 = r.refined_poni_rot2; - message.refined_poni_rot3 = r.refined_poni_rot3; - message.refined_rotation_axis = r.refined_rotation_axis; - // Merged data, map coefficients and placed model, for the writer to write beside the master. Only - // here, with rugnux_result set: every file in scratch_dir is then whole - rugnux writes synchronously - // and a cut never stops a write half-way - while a run that threw may have left one half-written. - FillRugnuxResultBlocks(scratch_dir + "/p", message); + // A failure here costs the end message the analysis only: it goes out as the receiver made it. + try { + std::unique_lock ul(rugnux_m); + if (!rugnux_result) + return; + const EndMessage &r = rugnux_result->end_message; + message.indexing_rate = r.indexing_rate; + message.bkg_estimate = r.bkg_estimate; + message.spindle_blind_fraction = r.spindle_blind_fraction; + message.ice_ring_score_mean = r.ice_ring_score_mean; + message.az_int_result = r.az_int_result; + message.rotation_lattice = r.rotation_lattice; + message.rotation_lattice_type = r.rotation_lattice_type; + message.rotation_extra_lattices = r.rotation_extra_lattices; + message.unit_cell = r.unit_cell; + message.space_group_name = r.space_group_name; + message.space_group_number = r.space_group_number; + message.reindex_matrix = r.reindex_matrix; + message.refined_beam_center_x = r.refined_beam_center_x; + message.refined_beam_center_y = r.refined_beam_center_y; + message.refined_poni_rot1 = r.refined_poni_rot1; + message.refined_poni_rot2 = r.refined_poni_rot2; + message.refined_poni_rot3 = r.refined_poni_rot3; + message.refined_rotation_axis = r.refined_rotation_axis; + // Merged data, map coefficients and placed model, for the writer to write beside the master. Moved: + // 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) { @@ -433,6 +483,9 @@ void JFJochReceiverRugnux::ForwardThread(uint32_t id) { } catch (const JFJochException &e) { logger.ErrorException(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(images_collected) / static_cast(experiment.GetFrameNum()); } +JFJochReceiverOutput JFJochReceiverRugnux::GetFinalStatistics() const { + JFJochReceiverOutput ret = JFJochReceiver::GetFinalStatistics(); + ret.analysis_warning = skipped_warning; + return ret; +} + float JFJochReceiverRugnux::GetProgress() const { if (experiment.GetFrameNum() == 0) return 0.0; diff --git a/receiver/JFJochReceiverRugnux.h b/receiver/JFJochReceiverRugnux.h index 22a3e9742..445b7ed13 100644 --- a/receiver/JFJochReceiverRugnux.h +++ b/receiver/JFJochReceiverRugnux.h @@ -31,8 +31,8 @@ class JFJochReceiverRugnux : public JFJochReceiver { const int rugnux_nthreads; const std::shared_ptr model; 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; + std::string skipped_warning; // why the collection was not analysed, for the broker's status std::vector> forward_futures; std::future measurement; @@ -50,6 +50,9 @@ class JFJochReceiverRugnux : public JFJochReceiver { std::atomic last_image_ns{0}; 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 &rugnux_done, std::future &probe_done); void ForwardThread(uint32_t id); void RugnuxThread(); void ProbeThread(); @@ -82,6 +85,7 @@ public: float GetEfficiency() const override; float GetProgress() const override; + JFJochReceiverOutput GetFinalStatistics() const override; void SetSpotFindingSettings(const SpotFindingSettings &spot_finding_settings) override {} }; diff --git a/rugnux/Rugnux.h b/rugnux/Rugnux.h index d135973e2..dbcda7a17 100644 --- a/rugnux/Rugnux.h +++ b/rugnux/Rugnux.h @@ -861,6 +861,10 @@ public: virtual void OnPhase(const std::string &phase) {} virtual void OnProgress(uint64_t done, uint64_t total) {} 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 OnNote(const std::string &text) {} }; diff --git a/rugnux/RugnuxImagePass.cpp b/rugnux/RugnuxImagePass.cpp index 9c0e0ef87..641a2883f 100644 --- a/rugnux/RugnuxImagePass.cpp +++ b/rugnux/RugnuxImagePass.cpp @@ -354,8 +354,10 @@ void Rugnux::ImagePass(PipelineLocals &p) { bragg_overlapped_major += bragg.overlapped_major; }; - if (observer) + if (observer) { observer->OnPhase("Processing images"); + observer->OnImagePass(p.write_output && !geometry_prepass); + } Step(fmt::format("{}{}, {} images", !per_image_analysis ? "Azimuthal integration" : calibration ? "Image pass" diff --git a/rugnux/RugnuxLive.cpp b/rugnux/RugnuxLive.cpp index df46c56e6..c5902a9e0 100644 --- a/rugnux/RugnuxLive.cpp +++ b/rugnux/RugnuxLive.cpp @@ -36,6 +36,8 @@ RugnuxLiveSetup MakeRugnuxLiveSetup(const JFJochReaderDataset &dataset, int nthr ProcessConfig &c = s.config; c.mode = ProcessMode::FullAnalysis; 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.spot_finding = RugnuxDefaultSpotFinding(true); c.rotation_indexing = true; diff --git a/tests/RugnuxResultFilesTest.cpp b/tests/RugnuxResultFilesTest.cpp index 7b83db30c..7f0b99792 100644 --- a/tests/RugnuxResultFilesTest.cpp +++ b/tests/RugnuxResultFilesTest.cpp @@ -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"); const std::filesystem::path dir = std::filesystem::temp_directory_path() / "rugnux_result_files_test"; std::filesystem::remove_all(dir); @@ -115,7 +115,12 @@ TEST_CASE("RugnuxResultFiles: rugnux's files -> END blocks -> CBOR -> writer, by OutputProvenance provenance; provenance.command_line = "rugnux -N 8 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). 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]); } WriteMtz(maps, original + "_maps.mtz"); + end.map_coefficients = maps; WritePlacedModel(TestModel(), TEST_CELL, experiment.GetSpaceGroupOrP1(), original, logger); - - EndMessage end{}; - end.max_image_number = 100; - FillRugnuxResultBlocks(original, end); - REQUIRE(end.merged); - REQUIRE(end.map_coefficients); - REQUIRE(end.placed_model); + end.placed_model = PlacedModelFromStructure(TestModel(), TEST_CELL, experiment.GetSpaceGroupOrP1().xhm()); REQUIRE(end.merged->columns.size() == 16); // with the anomalous columns CHECK(end.merged->columns[0].values.size() == reflections.size()); 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); } + +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); +} diff --git a/writer/RugnuxResultBlocks.cpp b/writer/RugnuxResultBlocks.cpp index e3b882a72..de9ada77a 100644 --- a/writer/RugnuxResultBlocks.cpp +++ b/writer/RugnuxResultBlocks.cpp @@ -3,56 +3,11 @@ #include "RugnuxResultBlocks.h" -#include -#include - -#include -#include - -namespace { - -UnitCell ToUnitCell(const gemmi::UnitCell &c) { - return UnitCell{static_cast(c.a), static_cast(c.b), static_cast(c.c), - static_cast(c.alpha), static_cast(c.beta), static_cast(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(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(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 PlacedModelFromStructure(const gemmi::Structure &st, const UnitCell &cell, + const std::string &data_space_group_name) { PlacedModelMessage p; p.name = st.name; - p.unit_cell = ToUnitCell(st.cell); + p.unit_cell = cell; p.space_group_name = data_space_group_name; p.model_space_group_name = st.spacegroup_hm; bool has_aniso = false; @@ -83,15 +38,3 @@ PlacedModelMessage PlacedModelFromStructure(const gemmi::Structure &st, const st p.aniso.clear(); 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); - } -} diff --git a/writer/RugnuxResultBlocks.h b/writer/RugnuxResultBlocks.h index 8441cbb55..5205c8144 100644 --- a/writer/RugnuxResultBlocks.h +++ b/writer/RugnuxResultBlocks.h @@ -9,13 +9,8 @@ #include "../common/JFJochMessages.h" -// The END-message blocks (common/JFJochMessages.h) read back from the files rugnux wrote at -// `prefix`: .mtz into `merged`, _maps.mtz into `map_coefficients` and -// _model.cif into `placed_model`, each only where its file exists. The writer makes the same -// files again from them (WriteRugnuxResultFiles, writer/WriteRugnuxFiles.h). -void FillRugnuxResultBlocks(const std::string &prefix, EndMessage &end); - -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); +// The placed model of a rugnux run as the END message carries it (PlacedModelMessage). cell and +// data_space_group_name are the reflection files' beside it, which is what WritePlacedModel is to be given; +// the model's own group is taken from `st`. +PlacedModelMessage PlacedModelFromStructure(const gemmi::Structure &st, const UnitCell &cell, + const std::string &data_space_group_name);