Model scale fit: no log ratio at |Fcalc| = 0, and no LM step once the model has underflowed
Two ways the scale fit's normal matrix went singular and gemmi's jordan_solve threw "Trying to reverse singular matrix. Column 0 is zeroed.", ending the run: 1. The isotropic start (fit_isotropic_b_approximately) takes log(Fobs/|Fcalc|) over the strong reflections. At a phase-restricted reflection whose Fcalc and Fmask share a phase, some solvent pair of the k_sol/b_sol grid can cancel the model exactly in float, |Fcalc + k_sol exp(-b_sol s^2) Fmask| = 0. log(Fobs/0) = inf made that grid point's k_overall and B NaN, every sum of the Levenberg-Marquardt NaN, and the solve failed. This is what stopped three open-arm sets in the output pass after MR on the GPU (the scale fit after model validation's rigid body): one strong reflection, one grid point. The CPU path has the same exposure; which grid point hits an exact zero depends on the last bits of Fcalc, so the --no-gpu run of the same set went through. Such a reflection has no log ratio and is now left out of the start - in the GPU kernel, in FixedSolventFit::Start and, as a marked local change, in gemmi's own fit_isotropic_b_approximately, so the three stay equal and the held-solvent fits are covered too. 2. In FitModelScale's CPU Levenberg-Marquardt (ModelValidation_CCModelUnrelatedModelConfirmsNothing in a CUDA=OFF build): on 66 reflections of an unrelated model the fit walked to k_overall -424 and a B where k exp(-B s^2) |F| underflows at every reflection; the squares in J^T J were all 0 while J^T r kept a residue near 1e-210, which jordan_solve rejects. The fit now ends there, at its best so far, as it would at the lambda limit; that grid point then loses on R. The GPU fit takes the derivatives in float and their squares in double, so it cannot reach this state. Both changes only touch fits that threw before. Byte identity checked against an rc176 build on the standard and inhouse timing benchmarks and on 4nwv, 8tyy, 6oel (p.hkl, p_model.pdb, MTZ data, MR_* report keys). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PXc1zD1yxHqaR1DGdCTHL4
This commit is contained in:
1 parent
234774ff09
commit
210664f454
5 files changed
+67
-1
No files matched your search
@@ -382,4 +382,24 @@ TEST_CASE("ModelScaleGPUPool_MatchesFitModelScale", "[ModelValidation][gpu]") {
|
||||
}
|
||||
}
|
||||
|
||||
// ModelScaling fits past a reflection the solvent cancels exactly (ModelScalingTest.cpp), on the card.
|
||||
TEST_CASE("ModelScaleGPUPool_FitsPastAReflectionTheSolventCancelsExactly", "[ModelValidation][gpu]") {
|
||||
if (get_gpu_count() == 0)
|
||||
SKIP("No GPU");
|
||||
gemmi::UnitCell cell(72, 44, 51, 90, 112, 90);
|
||||
gemmi::Scaling<float> scaling = MakeScaling(cell, *gemmi::find_spacegroup_by_name("C 1 2 1"), 2.5);
|
||||
// exp(-b_sol s^2) = 1 at s = 0, so at the grid's k_sol = 0.10 the sum is -0.1f + 0.1f = 0 for every b_sol
|
||||
auto &p = *std::find_if(scaling.points.begin(), scaling.points.end(),
|
||||
[](const auto &q) { return q.fobs >= 1 && q.fobs >= q.sigma; });
|
||||
p.stol2 = 0;
|
||||
p.fcmol = {-0.1f, 0.f};
|
||||
p.fmask = {1.f, 0.f};
|
||||
|
||||
ModelScaleGPUPool pool(0, scaling.points.size(), 1);
|
||||
ModelScaleReport report;
|
||||
REQUIRE_NOTHROW(report = pool.Fit(scaling, 80.0, 2));
|
||||
CHECK(std::isfinite(report.r_work_fit));
|
||||
CHECK(std::isfinite(scaling.k_overall));
|
||||
}
|
||||
|
||||
#endif
|
||||
Reference in new issue
Block a user