rugnux: model validation does the same work in less time
--model validation (battery-only for users) was 18% of the battery's time. Every number it produces is unchanged to the bit (p.mtz, maps, placed model and every model-validation line of the report md5/diff-identical on 11 open sets); only when and where the work runs changes: - The bulk-solvent grid fit (FitModelScale, most of the CPU time) fits each solvent pair on a copy of gemmi::Scaling's target that takes |Fcalc + k_sol exp(-b_sol s^2) Fmask| once per pair instead of at every solver evaluation; same expressions, same types (new test checks a grid point against gemmi's own Scaling fit with ==). - Fcalc density and the solvent mask are made on two threads; the model's structure factors beside the GPU engine reservation. - The indexing probe fits the relabellings concurrently. - The null's replicates run beside the real model's placement (they start from a snapshot of the model as read); one GPU engine per replicate plus one for the real fit instead of a cap of 4 (engines are interchangeable and deterministic). - The 2mFo-DFc, mFo-DFc and anomalous maps are made and written concurrently; the placed model is written beside the reflection files. - A rigid-body zone whose solvent-mask grid needs gemmi's shrink is sent to the CPU when the engines are reserved (ModelMaskGPU::ShrinkIsNoOp), instead of failing on the GPU and validating everything again on the CPU - the same CPU result, without the wasted first attempt. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SVmAWnzCmRKAXVUCdc4iNi
This commit is contained in:
1 parent
42555a55fe
commit
f956eb25e8
11 files changed
+374
-134
No files matched your search
@@ -11,12 +11,93 @@
|
||||
|
||||
namespace {
|
||||
|
||||
// gemmi::Scaling<float> as a grid point fits it: the same parameters, model values, derivatives
|
||||
// and R. The solvent pair is fixed there, so |Fcalc + k_sol exp(-b_sol s^2) Fmask| of a reflection
|
||||
// is the same at every evaluation of the solver; it is taken once per grid point here rather than
|
||||
// at every evaluation, where it was most of the cost of the fit. Every expression is the one
|
||||
// scaling.hpp evaluates, in the same types, so every number is the same to the bit.
|
||||
struct FixedSolventFit {
|
||||
struct Point {
|
||||
gemmi::Miller hkl;
|
||||
float fobs;
|
||||
float fcalc_abs; // std::abs(Scaling::get_fcalc(p))
|
||||
double get_y() const { return fobs; }
|
||||
double get_weight() const { return 1.0; }
|
||||
};
|
||||
const std::vector<gemmi::Vec6> &constraint_matrix;
|
||||
double k_overall = 1.0;
|
||||
gemmi::SMat33<double> b_star{0, 0, 0, 0, 0, 0};
|
||||
std::vector<Point> points;
|
||||
|
||||
explicit FixedSolventFit(const gemmi::Scaling<float> &s) : constraint_matrix(s.constraint_matrix) {}
|
||||
|
||||
// The reflections at the solvent pair of `s`, and its scale as the start of the fit.
|
||||
void Start(const gemmi::Scaling<float> &s) {
|
||||
k_overall = s.k_overall;
|
||||
b_star = s.b_star;
|
||||
points.resize(s.points.size());
|
||||
for (size_t i = 0; i < points.size(); ++i)
|
||||
points[i] = {s.points[i].hkl, s.points[i].fobs, std::abs(s.get_fcalc(s.points[i]))};
|
||||
}
|
||||
|
||||
// Scaling::get_parameters(), set_parameters(), get_overall_scale_factor(), compute_value() and
|
||||
// compute_value_and_derivatives(), with k_sol and b_sol fixed.
|
||||
std::vector<double> get_parameters() const {
|
||||
std::vector<double> ret;
|
||||
ret.push_back(k_overall);
|
||||
for (const gemmi::Vec6 &v : constraint_matrix)
|
||||
ret.push_back(gemmi::vec6_dot(v, b_star));
|
||||
return ret;
|
||||
}
|
||||
void set_parameters(const double *p) {
|
||||
k_overall = p[0];
|
||||
int n = 0;
|
||||
b_star = {0, 0, 0, 0, 0, 0};
|
||||
for (const gemmi::Vec6 &row : constraint_matrix) {
|
||||
double d = p[++n];
|
||||
b_star.u11 += row[0] * d;
|
||||
b_star.u22 += row[1] * d;
|
||||
b_star.u33 += row[2] * d;
|
||||
b_star.u12 += row[3] * d;
|
||||
b_star.u13 += row[4] * d;
|
||||
b_star.u23 += row[5] * d;
|
||||
}
|
||||
}
|
||||
void set_parameters(const std::vector<double> &p) { set_parameters(p.data()); }
|
||||
double get_overall_scale_factor(const gemmi::Miller &hkl) const {
|
||||
return k_overall * std::exp(-0.25 * b_star.r_u_r(hkl));
|
||||
}
|
||||
double compute_value(const Point &p) const {
|
||||
return p.fcalc_abs * (float) get_overall_scale_factor(p.hkl);
|
||||
}
|
||||
double compute_value_and_derivatives(const Point &p, std::vector<double> &dy_da) const {
|
||||
gemmi::Vec3 h(p.hkl);
|
||||
double kaniso = std::exp(-0.25 * b_star.r_u_r(h));
|
||||
double fcalc_abs = p.fcalc_abs;
|
||||
int n = 1;
|
||||
double fe = fcalc_abs * kaniso;
|
||||
double y = k_overall * fe;
|
||||
dy_da[0] = fe;
|
||||
gemmi::SMat33<double> du = {
|
||||
-0.25 * y * (h.x * h.x),
|
||||
-0.25 * y * (h.y * h.y),
|
||||
-0.25 * y * (h.z * h.z),
|
||||
-0.5 * y * (h.x * h.y),
|
||||
-0.5 * y * (h.x * h.z),
|
||||
-0.5 * y * (h.y * h.z),
|
||||
};
|
||||
for (size_t j = 0; j < constraint_matrix.size(); ++j)
|
||||
dy_da[n + j] = gemmi::vec6_dot(constraint_matrix[j], du);
|
||||
return y;
|
||||
}
|
||||
};
|
||||
|
||||
// R-factor of the current parameters over the fitted reflections. This is what the grid is
|
||||
// selected on, and it is the quantity the scale exists to make small.
|
||||
double RFactor(const gemmi::Scaling<float> &scaling) {
|
||||
double RFactor(const FixedSolventFit &fit) {
|
||||
double num = 0, den = 0;
|
||||
for (const auto &p : scaling.points) {
|
||||
num += std::fabs(p.fobs - scaling.compute_value(p));
|
||||
for (const auto &p : fit.points) {
|
||||
num += std::fabs(p.fobs - fit.compute_value(p));
|
||||
den += p.fobs;
|
||||
}
|
||||
return den > 0 ? num / den : 1.0;
|
||||
@@ -66,25 +147,32 @@ ModelScaleReport FitModelScale(gemmi::Scaling<float> &scaling, ModelScaleBox box
|
||||
gemmi::SMat33<double> best_b_star{0, 0, 0, 0, 0, 0};
|
||||
|
||||
struct PointFit { double k_sol, b_sol, r = NAN, k_overall = 1.0; gemmi::SMat33<double> b_star{0, 0, 0, 0, 0, 0}; };
|
||||
auto fit_point = [](gemmi::Scaling<float> &s, PointFit &pf) {
|
||||
auto fit_point = [](gemmi::Scaling<float> &s, FixedSolventFit &fit, PointFit &pf) {
|
||||
s.k_sol = pf.k_sol;
|
||||
s.b_sol = pf.b_sol;
|
||||
s.fit_isotropic_b_approximately(); // a fresh starting point for this solvent pair
|
||||
s.fit_parameters(); // k_overall + anisotropic B only
|
||||
pf.r = RFactor(s);
|
||||
pf.k_overall = s.k_overall;
|
||||
pf.b_star = s.b_star;
|
||||
fit.Start(s);
|
||||
gemmi::LevMar levmar;
|
||||
levmar.fit(fit); // k_overall + anisotropic B only
|
||||
s.k_overall = fit.k_overall;
|
||||
s.b_star = fit.b_star;
|
||||
pf.r = RFactor(fit);
|
||||
pf.k_overall = fit.k_overall;
|
||||
pf.b_star = fit.b_star;
|
||||
};
|
||||
auto try_points = [&](std::vector<PointFit> &pts) {
|
||||
if (independent)
|
||||
ParallelChunks(static_cast<int>(pts.size()), nthreads, [&](int lo, int hi) {
|
||||
gemmi::Scaling<float> local = scaling;
|
||||
FixedSolventFit fit(local);
|
||||
for (int i = lo; i < hi; ++i)
|
||||
fit_point(local, pts[i]);
|
||||
fit_point(local, fit, pts[i]);
|
||||
});
|
||||
else
|
||||
else {
|
||||
FixedSolventFit fit(scaling);
|
||||
for (auto &pf : pts)
|
||||
fit_point(scaling, pf);
|
||||
fit_point(scaling, fit, pf);
|
||||
}
|
||||
for (const auto &pf : pts) {
|
||||
++report.n_grid;
|
||||
// A diverged fit gives r = NaN; latched as best_r it wins every later r < best_r.
|
||||
|
||||
Reference in new issue
Block a user