Files
Jungfraujoch/rugnux/ModelScaling.h
T
leonarski_fandClaude Opus 5 2f727b1975 rugnux --model: fit the bulk-solvent grid points in parallel
FitModelScale fits ~113 (k_sol, b_sol) grid points, each a full
Levenberg-Marquardt over every working reflection, and it is the largest single
cost of --model (every fit_model call: the first fit, the twin-law probe, the
fit after the rigid body, each null replicate). It ran on one thread.

Each point starts from fit_isotropic_b_approximately(), which sets k_overall
and b_star from the data and the point's solvent pair alone, so no point
depends on the one before it. The points of each pass (coarse, then the
refinement around the coarse winner) now run in contiguous chunks, each on its
own copy of the Scaling, and the winner is read off afterwards in grid order
with the serial rule (lowest finite R, first on a tie). Each point's arithmetic
is unchanged, so the result is the serial loop's bit for bit. Where
fit_isotropic_b_approximately() has five or fewer reflections to fit on it
returns without setting anything and points would chain, so there the grid is
still walked serially on the caller's Scaling.

Inside a null replicate (a pool worker) the chunks run inline, as before.

To check: MODEL_* keys and md5 of the maps/.mtz/_model.cif identical with and
without this commit on the audit set; model-phase time on a fit-dominated set.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013nW6FNRP1bBJJ8pfHiByAT
2026-09-19 13:03:22 +02:00

39 lines
1.9 KiB
C

// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#pragma once
#include <cstddef>
#include "gemmi/scaling.hpp"
// Fit the overall scale, the anisotropic B and the flat bulk solvent of a model to observed
// amplitudes, writing the answer into `scaling` (k_overall, b_star, k_sol, b_sol) so that
// gemmi's own scale_data() can then apply it.
//
// The caller decides which reflections are fitted: FitModelScale reads scaling.points, which
// prepare_points() filled. Pass the WORKING set to keep the free reflections out of the scale
// model - that is a property of the call site, not of this function.
//
// Replaces gemmi's fit_isotropic_b_approximately() + fit_parameters(), which is an unbounded
// Levenberg-Marquardt: nothing there stops k_sol and b_sol reaching values a flat solvent model
// is meaningless at (b_sol of 1707 A^2 was measured on this corpus). Here k_sol and b_sol come
// from a grid search over the physical ranges, so an unphysical answer cannot be represented.
// The box the solvent parameters are searched in. The defaults are the physically reasonable
// range reported by Fokine & Urzhumtsev and adopted as phenix's default grid: k_sol in (0.1, 0.8)
// and b_sol in (10, 80) A^2, of which phenix searches b_sol 10-80 in steps of 5.
struct ModelScaleBox {
double k_lo = 0.10, k_hi = 0.60;
double b_lo = 10.0, b_hi = 80.0;
};
struct ModelScaleReport {
int n_points = 0; // reflections the fit actually used
int n_grid = 0; // grid points evaluated
double r_work_fit = 1.0; // R on the fitted reflections, at the chosen solution
};
// nthreads: the grid points are fitted in parallel on up to this many threads; the answer does not
// depend on it.
ModelScaleReport FitModelScale(gemmi::Scaling<float> &scaling, ModelScaleBox box = {}, size_t nthreads = 1);