Merge broker-rugnux-live into rc176: rugnux in jfjoch_broker as a live thread during a DECTRIS rotation collection (off by default), the rugnux library without file I/O (results in ProcessResult::outputs, the CLI writes them through JFJochRugnuxOutput), END carrying the merged table, map coefficients and placed model
Branch reviewed ate16db07ff(review: owner's decisions accepted - _maps.mtz instead of .ccp4 in the viewer, -o "" computes everything incl. MR/validation, Ctrl+C/error paths may leave fewer files, the writer links gemmi/zlib) and merged at55184eb09with its review fixes (END size guard, frame drop as in Lite, outputs released before re-runs, pool priority, docs, frontend gating). Conflicts: rugnux/Rugnux.h (kept rc176's host_memory_budget_bytes, output_prefix removed as on the branch), rugnux/RugnuxPipeline.h (MtzTable future + rc176's GPUMerge pointer), MolecularReplacement.h/.cpp (rc176's GPU cost model, packed transform and split functions with the branch's RugnuxModel signature; search_model with rc176's other_budgets, result.model_name). tests/RugnuxLargeTest.cpp: rc176's host-budget test writes its files through WriteRugnuxOutputs now that the library writes none. Generated code (broker/gen, frontend client, Python client docs, Redoc) regenerated from the merged jfjoch_api.yaml with update_version.sh (version unchanged). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PXc1zD1yxHqaR1DGdCTHL4
This commit is contained in:
commit
78c2a69267
130 files changed
+7612
-1010
No files matched your search
@@ -18,15 +18,11 @@
|
||||
#include <tuple>
|
||||
#include <random>
|
||||
#include <unordered_map>
|
||||
#include <fstream>
|
||||
|
||||
#include <fftw3.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <gemmi/mmread_gz.hpp> // read_structure_gz
|
||||
#include <gemmi/modify.hpp> // remove_hydrogens, remove_alternative_conformations
|
||||
#include <gemmi/polyheur.hpp> // setup_entities, remove_ligands_and_waters
|
||||
#include <gemmi/read_cif.hpp> // read_cif_gz
|
||||
#include <gemmi/dencalc.hpp> // DensityCalculator
|
||||
#include <gemmi/it92.hpp> // IT92 x-ray form factors
|
||||
#include <gemmi/scaling.hpp> // Scaling (bulk solvent + anisotropic B)
|
||||
@@ -906,19 +902,10 @@ void finish_search_model(SearchModel &m) {
|
||||
|
||||
} // namespace
|
||||
|
||||
SearchModel PrepareSearchModel(const std::string &path) {
|
||||
SearchModel PrepareSearchModel(const RugnuxModel &model_in) {
|
||||
SearchModel out;
|
||||
bool modelcif = false;
|
||||
try {
|
||||
out.st = gemmi::read_structure_gz(path, gemmi::CoorFormat::Detect);
|
||||
if (out.st.input_format == gemmi::CoorFormat::Mmcif) {
|
||||
const gemmi::cif::Document doc = gemmi::read_cif_gz(path);
|
||||
modelcif = !doc.blocks.empty() && doc.blocks[0].has_mmcif_category("_ma_qa_metric");
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
out.failure_reason = std::string("cannot read the model: ") + e.what();
|
||||
return out;
|
||||
}
|
||||
const bool modelcif = model_in.modelcif;
|
||||
out.st = model_in.structure;
|
||||
if (out.st.models.empty()) {
|
||||
out.failure_reason = "the model has no atoms";
|
||||
return out;
|
||||
@@ -992,39 +979,15 @@ SearchModel PrepareSearchModel(const std::string &path) {
|
||||
|
||||
// --- domains from the predicted aligned error ---
|
||||
|
||||
std::string PaePathFor(const std::string &model_path) {
|
||||
const size_t slash = model_path.find_last_of("/\\");
|
||||
const std::string dir = slash == std::string::npos ? std::string() : model_path.substr(0, slash + 1);
|
||||
const std::string name = model_path.substr(dir.size());
|
||||
const size_t at = name.rfind("-model_v");
|
||||
if (at == std::string::npos)
|
||||
return {};
|
||||
const size_t dot = name.find('.', at);
|
||||
const std::string version = name.substr(at + 8, dot == std::string::npos ? std::string::npos : dot - at - 8);
|
||||
return dir + name.substr(0, at) + "-predicted_aligned_error_v" + version + ".json";
|
||||
}
|
||||
|
||||
// A prediction cut into domains by its predicted aligned error before molecular replacement, as Oeffner et
|
||||
// al. (2022) Acta Cryst. D78, 1303-1314 do; here by average linkage.
|
||||
std::vector<std::vector<int>> PaeDomains(const std::string &pae_path, const SearchModel &model) {
|
||||
std::vector<std::vector<int>> PaeDomains(const std::vector<std::vector<double>> &pae, const SearchModel &model) {
|
||||
// Two domains a prediction does not place relative to each other with confidence are this far apart in
|
||||
// mean PAE (within one domain it is a few angstroms); a domain is at least this many residues.
|
||||
constexpr double PAE_DOMAIN_A = 10.0;
|
||||
constexpr int DOMAIN_MIN_RESIDUES = 40;
|
||||
// The clustering starts from runs of this many consecutive residues kept.
|
||||
constexpr int PIECE = 5;
|
||||
std::vector<std::vector<double>> pae;
|
||||
try {
|
||||
std::ifstream in(pae_path);
|
||||
if (!in)
|
||||
return {};
|
||||
const nlohmann::json j = nlohmann::json::parse(in);
|
||||
const nlohmann::json &m = j.is_array() ? j.at(0).at("predicted_aligned_error") : j.at("predicted_aligned_error");
|
||||
for (const auto &row : m)
|
||||
pae.push_back(row.get<std::vector<double>>());
|
||||
} catch (const std::exception &) {
|
||||
return {};
|
||||
}
|
||||
const size_t n = pae.size();
|
||||
if (n != static_cast<size_t>(model.residues_read)
|
||||
|| std::any_of(pae.begin(), pae.end(), [n](const std::vector<double> &r) { return r.size() != n; }))
|
||||
@@ -3576,7 +3539,7 @@ std::vector<TranslationPeak> SplitTranslationFunction(const std::vector<Molecula
|
||||
}
|
||||
#endif
|
||||
|
||||
MolecularReplacementResult RunMolecularReplacement(const std::string &model_path, const std::vector<MRMerge> &merges,
|
||||
MolecularReplacementResult RunMolecularReplacement(const RugnuxModel &input, const std::vector<MRMerge> &merges,
|
||||
Logger &logger, size_t nthreads, size_t gpu_resident_bytes) {
|
||||
const auto t0 = std::chrono::steady_clock::now();
|
||||
// What the search may hold on the card: the budget model validation keeps to. What the stream-ordered pool
|
||||
@@ -3596,25 +3559,24 @@ MolecularReplacementResult RunMolecularReplacement(const std::string &model_path
|
||||
#else
|
||||
(void) gpu_resident_bytes;
|
||||
#endif
|
||||
SearchModel model = PrepareSearchModel(model_path);
|
||||
SearchModel model = PrepareSearchModel(input);
|
||||
if (!model.failure_reason.empty()) {
|
||||
MolecularReplacementResult result;
|
||||
result.model_path = model_path;
|
||||
result.model_name = input.name;
|
||||
result.residues_read = model.residues_read;
|
||||
result.failure_reason = model.failure_reason;
|
||||
return result;
|
||||
}
|
||||
model.description = "the whole model";
|
||||
// Only a prediction has a predicted aligned error.
|
||||
const std::string pae_path = model.predicted ? PaePathFor(model_path) : std::string();
|
||||
const std::vector<std::vector<int>> domains = pae_path.empty() ? std::vector<std::vector<int>>()
|
||||
: PaeDomains(pae_path, model);
|
||||
const bool pae = model.predicted && input.pae.has_value();
|
||||
const std::vector<std::vector<int>> domains = pae ? PaeDomains(*input.pae, model) : std::vector<std::vector<int>>();
|
||||
const double t_model = seconds_since(t0);
|
||||
if (!pae_path.empty())
|
||||
logger.Info("Molecular replacement: {} rigid domain(s) in the predicted aligned error {}", domains.size(), pae_path);
|
||||
if (pae)
|
||||
logger.Info("Molecular replacement: {} rigid domain(s) in the predicted aligned error", domains.size());
|
||||
|
||||
MolecularReplacementResult result = search_model(model, merges, logger, nthreads, gpu_budget, other_budgets);
|
||||
result.model_path = model_path;
|
||||
result.model_name = input.name;
|
||||
result.pae_domains = static_cast<int>(domains.size());
|
||||
result.t_model += t_model;
|
||||
// Where the whole model is placed nowhere and its prediction falls apart into domains whose relative
|
||||
@@ -3664,7 +3626,7 @@ MolecularReplacementResult RunMolecularReplacement(const std::string &model_path
|
||||
found[best].candidates[found[best].chosen].space_group.xhm());
|
||||
} else if (best >= 0 && agree) {
|
||||
MolecularReplacementResult chosen = std::move(found[best]);
|
||||
chosen.model_path = model_path;
|
||||
chosen.model_name = input.name;
|
||||
chosen.pae_domains = result.pae_domains;
|
||||
chosen.t_model = result.t_model;
|
||||
chosen.t_transform = result.t_transform;
|
||||
|
||||
Reference in new issue
Block a user