Refinement: the likelihood's D and beta searches by Newton's method; D on the working set from the last fit's
Deterministic, rounding changes. The per-bin ML fit took 4 rounds x (40 + 40) golden-section steps on the free set and a 50-step golden section for D on the working set - each step a Bessel I0/I1 per row, ~4.6M per Refit at 92k reflections, 27-37% of a trial's wall on the high-resolution sets. Each one-dimensional search is now a safeguarded Newton iteration on the analytic first and second derivatives (bracketed on the sign of the derivative, bisection where a step leaves the bracket or the curvature is not positive), within the same brackets; the free-set alternation keeps its 4 rounds from the moments, so beta is the one it was (a converged joint fit was tried: the 4 rounds are not converged in the low-resolution bins, and converging them moves 6iu9's FOM by -0.003, so it was not kept). D on the working set starts from the bin's D of the context's previous Refit (XrayRowModel::bin_d), restored with a trial's fit and cleared at a trial's first Refit, so a trial depends on nothing its slot ran before (side by side = one at a time, md5). 14 parity sets, score only: R-work/R-free identical to 4 digits, mean |FOM - REFMAC| 0.01186 -> 0.01186, D within 1e-5 and var within 1e-4 (relative) per reflection. refit.ml (TIMING sum, -N 8): 5reo 1.47 -> 0.11 s, 5f6m 3.88 -> 0.26, 9yl4 1.37 -> 0.11, 5f6m MD start 4.97 -> 0.46. Wall: 5reo 2.03 -> 1.75, 5f6m 3.45 -> 2.60, 9yl4 7.99 -> 7.36, 7d98 4.04 -> 3.78, 5f6m MD start 6.10 -> 4.82 s. 5f6m MD starts 01-04 median R-free 0.1693 -> 0.1692. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015CeEGgpKdfkCTJb3YNMJcu (cherry picked from commit a91511945e0ccd7145a93877c0c231e87f86fb44)
This commit is contained in:
1 parent
a8d3a8dbc2
commit
2c1df0ebfa
3 files changed
+83
-74
No files matched your search
@@ -49,76 +49,56 @@ double Variance(const RefinementReflections &refl, size_t i, double beta, bool w
|
||||
return refl.epsilon[i] * beta + (refl.centric[i] ? s2 : 2.0 * s2);
|
||||
}
|
||||
|
||||
// -ln L summed over rows `idx` at D and beta.
|
||||
double BinNegLogLikelihood(const RefinementReflections &refl, const std::vector<double> &fc,
|
||||
const std::vector<size_t> &idx, double d, double beta, bool with_sigma) {
|
||||
double sum = 0, dfc = 0;
|
||||
for (size_t i : idx)
|
||||
sum += refine_math::AmplitudeNegLogLikelihood<double>(refl.fo[i], fc[i], d, Variance(refl, i, beta, with_sigma),
|
||||
refl.centric[i], dfc);
|
||||
return sum;
|
||||
}
|
||||
|
||||
// Rows `idx` at a fixed beta, gathered for searches over D alone: the variance and its logarithm taken once rather
|
||||
// than at every evaluation. FixedVarianceNegLogLikelihood() at D is BinNegLogLikelihood() at D and that beta, to
|
||||
// the bit: the same expressions in the same order, only log(var) hoisted.
|
||||
struct FixedVarianceRows {
|
||||
std::vector<double> fo, fc, var, log_var;
|
||||
std::vector<char> centric;
|
||||
FixedVarianceRows(const RefinementReflections &refl, const std::vector<double> &fc_all,
|
||||
const std::vector<size_t> &idx, double beta, bool with_sigma) {
|
||||
for (size_t i : idx) {
|
||||
fo.push_back(refl.fo[i]);
|
||||
fc.push_back(fc_all[i]);
|
||||
var.push_back(Variance(refl, i, beta, with_sigma));
|
||||
log_var.push_back(std::log(var.back()));
|
||||
centric.push_back(refl.centric[i]);
|
||||
}
|
||||
}
|
||||
// The first and second derivatives of one row's -ln L (refine_math::AmplitudeNegLogLikelihood) in D and in the
|
||||
// variance, at D and var.
|
||||
struct RowDerivatives {
|
||||
double d, dd, v, vv;
|
||||
};
|
||||
|
||||
// refine_math::AmplitudeNegLogLikelihood() summed over the rows at D, with log(var) given.
|
||||
double FixedVarianceNegLogLikelihood(const FixedVarianceRows &rows, double d) {
|
||||
double sum = 0;
|
||||
for (size_t k = 0; k < rows.fo.size(); k++) {
|
||||
const double fo = rows.fo[k], fc = rows.fc[k], var = rows.var[k];
|
||||
if (!rows.centric[k]) {
|
||||
const double x = 2.0 * d * fo * fc / var;
|
||||
double log_i0, ratio;
|
||||
refine_math::BesselI0I1(x, log_i0, ratio);
|
||||
sum += rows.log_var[k] + (fo * fo + d * d * fc * fc) / var - log_i0;
|
||||
} else {
|
||||
const double x = d * fo * fc / var;
|
||||
const double log_cosh = x + std::log(1.0 + std::exp(-2.0 * x)) - 0.6931471805599453;
|
||||
sum += 0.5 * rows.log_var[k] + (fo * fo + d * d * fc * fc) / (2.0 * var) - log_cosh;
|
||||
}
|
||||
RowDerivatives LikelihoodDerivatives(double fo, double fc, double d, double var, bool centric) {
|
||||
const double q = fo * fo + d * d * fc * fc;
|
||||
if (!centric) {
|
||||
// x = 2 D Fo Fc / var, m = I1(x) / I0(x) = d ln I0 / dx, m' = 1 - m / x - m^2
|
||||
const double a = 2.0 * fo * fc / var, x = a * d;
|
||||
double log_i0, m;
|
||||
refine_math::BesselI0I1(x, log_i0, m);
|
||||
const double m1 = x > 0 ? 1.0 - m / x - m * m : 0.5;
|
||||
return {2.0 * d * fc * fc / var - a * m,
|
||||
2.0 * fc * fc / var - a * a * m1,
|
||||
1.0 / var - q / (var * var) + m * x / var,
|
||||
(-1.0 + 2.0 * q / var - x * (m1 * x + 2.0 * m)) / (var * var)};
|
||||
}
|
||||
return sum;
|
||||
// x = D Fo Fc / var, t = tanh x = d ln cosh / dx
|
||||
const double a = fo * fc / var, x = a * d;
|
||||
const double t = std::tanh(x), t1 = 1.0 - t * t;
|
||||
return {d * fc * fc / var - a * t,
|
||||
fc * fc / var - a * a * t1,
|
||||
0.5 / var - q / (2.0 * var * var) + t * x / var,
|
||||
(-0.5 + q / var - x * (t1 * x + 2.0 * t)) / (var * var)};
|
||||
}
|
||||
|
||||
// The minimum of f over [lo, hi] by golden-section search.
|
||||
// The minimum of a function over [lo, hi] from x, given its first and second derivative (derivatives(x, g, h)):
|
||||
// Newton's method on the first derivative, kept inside the interval where that changes sign and bisecting it
|
||||
// wherever a step would leave it or the curvature is not positive. A minimum on a bound is the bound.
|
||||
template <typename F>
|
||||
double GoldenMinimum(F f, double lo, double hi, int iterations) {
|
||||
const double g = 0.5 * (std::sqrt(5.0) - 1.0);
|
||||
double a = lo, b = hi;
|
||||
double c = b - g * (b - a), d = a + g * (b - a);
|
||||
double fc = f(c), fd = f(d);
|
||||
for (int i = 0; i < iterations; i++) {
|
||||
if (fc < fd) {
|
||||
b = d;
|
||||
d = c;
|
||||
fd = fc;
|
||||
c = b - g * (b - a);
|
||||
fc = f(c);
|
||||
} else {
|
||||
a = c;
|
||||
c = d;
|
||||
fc = fd;
|
||||
d = a + g * (b - a);
|
||||
fd = f(d);
|
||||
}
|
||||
double NewtonMinimum(F derivatives, double x, double lo, double hi) {
|
||||
x = std::clamp(x, lo, hi);
|
||||
for (int iteration = 0; iteration < 200; iteration++) {
|
||||
double g = 0, h = 0;
|
||||
derivatives(x, g, h);
|
||||
if (g < 0)
|
||||
lo = x;
|
||||
else
|
||||
hi = x;
|
||||
double next = h > 0 ? x - g / h : lo - 1.0;
|
||||
if (!(next > lo && next < hi))
|
||||
next = 0.5 * (lo + hi);
|
||||
const bool converged = std::fabs(next - x) < 1e-10 || hi - lo < 1e-10;
|
||||
x = next;
|
||||
if (converged)
|
||||
break;
|
||||
}
|
||||
return 0.5 * (a + b);
|
||||
return x;
|
||||
}
|
||||
|
||||
class RefinementEngineCPU : public RefinementEngine {
|
||||
@@ -333,6 +313,7 @@ void EstimateLikelihoodParameters(const RefinementReflections &refl, const std::
|
||||
}
|
||||
|
||||
std::vector<double> centre(bins), bin_d(bins), bin_beta(bins);
|
||||
const bool warm = rows.bin_d.size() == bins;
|
||||
// each bin is fitted on its own
|
||||
ParallelFor(static_cast<int>(bins), nthreads, [&](int b) {
|
||||
const std::vector<size_t> idx(free.begin() + b * free.size() / bins, free.begin() + (b + 1) * free.size() / bins);
|
||||
@@ -349,25 +330,47 @@ void EstimateLikelihoodParameters(const RefinementReflections &refl, const std::
|
||||
// beta: the spread of the free amplitudes about D Fc, measurement error included, by maximum likelihood
|
||||
// (alternating one-dimensional minimisations over ln D and ln beta)
|
||||
for (int round = 0; round < 4; round++) {
|
||||
const FixedVarianceRows at_beta(refl, fc, idx, beta, false);
|
||||
const double ld = GoldenMinimum([&](double x) { return FixedVarianceNegLogLikelihood(at_beta, std::exp(x)); },
|
||||
std::log(d) - 2.0, std::log(std::min(d * 7.4, 3.0)), 40);
|
||||
const double ld = NewtonMinimum([&](double x, double &g, double &h) {
|
||||
const double dx = std::exp(x);
|
||||
for (size_t i : idx) {
|
||||
const RowDerivatives r = LikelihoodDerivatives(refl.fo[i], fc[i], dx, refl.epsilon[i] * beta,
|
||||
refl.centric[i]);
|
||||
g += dx * r.d;
|
||||
h += dx * dx * r.dd + dx * r.d;
|
||||
}
|
||||
}, std::log(d), std::log(d) - 2.0, std::log(std::min(d * 7.4, 3.0)));
|
||||
d = std::exp(ld);
|
||||
const double lb = GoldenMinimum([&](double x) { return BinNegLogLikelihood(refl, fc, idx, d, std::exp(x), false); },
|
||||
std::log(beta) - 3.0, std::log(beta) + 3.0, 40);
|
||||
const double lb = NewtonMinimum([&](double x, double &g, double &h) {
|
||||
for (size_t i : idx) {
|
||||
const double var = refl.epsilon[i] * std::exp(x);
|
||||
const RowDerivatives r = LikelihoodDerivatives(refl.fo[i], fc[i], d, var, refl.centric[i]);
|
||||
g += var * r.v;
|
||||
h += var * var * r.vv + var * r.v;
|
||||
}
|
||||
}, std::log(beta), std::log(beta) - 3.0, std::log(beta) + 3.0);
|
||||
beta = std::exp(lb);
|
||||
}
|
||||
// D: by maximum likelihood on the working reflections, with the measurement error added to that beta.
|
||||
// Where the error is a large part of the spread, the variance counts it twice and D falls towards 0 -
|
||||
// the high-resolution D and figure of merit REFMAC 5.8 reports.
|
||||
// D: by maximum likelihood on the working reflections, with the measurement error added to that beta, from
|
||||
// where the last fit found it. Where the error is a large part of the spread, the variance counts it twice
|
||||
// and D falls towards 0 - the high-resolution D and figure of merit REFMAC 5.8 reports.
|
||||
// Following Murshudov et al. (2011) Acta Cryst. D67, 355-367 (D from the working set, the variance from the free)
|
||||
if (!work_in_bin[b].empty()) {
|
||||
const FixedVarianceRows working(refl, fc, work_in_bin[b], beta, true);
|
||||
d = GoldenMinimum([&](double x) { return FixedVarianceNegLogLikelihood(working, x); }, 0.0, 3.0, 50);
|
||||
std::vector<double> var;
|
||||
for (size_t i : work_in_bin[b])
|
||||
var.push_back(Variance(refl, i, beta, true));
|
||||
d = NewtonMinimum([&](double x, double &g, double &h) {
|
||||
for (size_t k = 0; k < var.size(); k++) {
|
||||
const size_t i = work_in_bin[b][k];
|
||||
const RowDerivatives r = LikelihoodDerivatives(refl.fo[i], fc[i], x, var[k], refl.centric[i]);
|
||||
g += r.d;
|
||||
h += r.dd;
|
||||
}
|
||||
}, warm ? rows.bin_d[b] : d, 0.0, 3.0);
|
||||
}
|
||||
bin_d[b] = d;
|
||||
bin_beta[b] = beta;
|
||||
});
|
||||
rows.bin_d = bin_d;
|
||||
rows.d.resize(n);
|
||||
rows.var.resize(n);
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
|
||||
Reference in new issue
Block a user