Model validation expands strict NCS; the written cell follows the model's indexing
- ModelValidation reads the model and, where it carries non-given NCS operators (_struct_ncs_oper 'generate' / MTRIX without the given flag), expands them (gemmi expand_ncs, vendored assembly.cpp from GEMMI 0.7.5, MPL-2.0 as the rest of gemmi_gph). Same in the battery's model_check.py and depdata_check.py. An icosahedral-virus entry deposited as 3 of the 45 chains of its ASU was scored against a fifteenth of its atoms: R-free 0.545 -> 0.297. - The indexing the model chooses (AdoptModelFrame) now moves the cell with the indices (reseat_integrated in the main path; CellInBasis in the forecast, twin-file and stills/scale paths). A near-symmetry law (b within 2% of c, beta a degree off 90, a~b in P1) relabelled the reflections only, so the files were written with two axes swapped or beta flipped relative to the indices. Where the law moved the cell, the validation is remade on the moved cell. Measured on 18 open/in-house sets with models: p.hkl and MTZ reflection data bit-identical on all; cell and model R unchanged where the law is exact; the cell now matches the deposited one on the 6 pseudo-merohedral cases (orthorhombic a/c swap, three monoclinic beta flips, a P1 alpha/beta swap), R-free within +-0.001 there. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EizBKhTqrAYago9KJG3dA3
This commit is contained in:
@@ -134,6 +134,46 @@ TEST_CASE("ModelValidation_ReadsPdbAndMmcif", "[ModelValidation]") {
|
||||
std::filesystem::remove(misnamed);
|
||||
}
|
||||
|
||||
// A deposition can hold part of the asymmetric unit and the strict-NCS operators that generate the rest
|
||||
// (MTRIX without the given flag). The model it describes is the expanded one: here the two atoms and
|
||||
// their copy half a cell along a, which the explicit four-atom model spells out.
|
||||
TEST_CASE("ModelValidation_ExpandsStrictNcs", "[ModelValidation]") {
|
||||
Logger logger("ModelValidation_ExpandsStrictNcs");
|
||||
|
||||
const char *with_ncs =
|
||||
"CRYST1 40.000 50.000 60.000 90.00 90.00 90.00 P 1 1\n"
|
||||
"MTRIX1 1 1.000000 0.000000 0.000000 0.00000 1\n"
|
||||
"MTRIX2 1 0.000000 1.000000 0.000000 0.00000 1\n"
|
||||
"MTRIX3 1 0.000000 0.000000 1.000000 0.00000 1\n"
|
||||
"MTRIX1 2 1.000000 0.000000 0.000000 20.00000\n"
|
||||
"MTRIX2 2 0.000000 1.000000 0.000000 0.00000\n"
|
||||
"MTRIX3 2 0.000000 0.000000 1.000000 0.00000\n"
|
||||
"ATOM 1 CA GLY A 1 10.000 12.000 14.000 1.00 20.00 C\n"
|
||||
"ATOM 2 CB GLY A 1 12.000 14.000 16.000 1.00 20.00 C\n"
|
||||
"END\n";
|
||||
const char *expanded =
|
||||
"CRYST1 40.000 50.000 60.000 90.00 90.00 90.00 P 1 1\n"
|
||||
"ATOM 1 CA GLY A 1 10.000 12.000 14.000 1.00 20.00 C\n"
|
||||
"ATOM 2 CB GLY A 1 12.000 14.000 16.000 1.00 20.00 C\n"
|
||||
"ATOM 3 CA GLY B 1 30.000 12.000 14.000 1.00 20.00 C\n"
|
||||
"ATOM 4 CB GLY B 1 32.000 14.000 16.000 1.00 20.00 C\n"
|
||||
"END\n";
|
||||
const auto ncs_path = WriteTemp("model_validation_test_ncs.pdb", with_ncs);
|
||||
const auto full_path = WriteTemp("model_validation_test_ncs_full.pdb", expanded);
|
||||
|
||||
const auto from_ncs = ModelReferenceIntensities(ncs_path, {}, {}, 4.0, logger);
|
||||
const auto from_full = ModelReferenceIntensities(full_path, {}, {}, 4.0, logger);
|
||||
REQUIRE(from_ncs.size() == from_full.size());
|
||||
REQUIRE_FALSE(from_ncs.empty());
|
||||
for (size_t i = 0; i < from_ncs.size(); i++) {
|
||||
CHECK(from_ncs[i].h == from_full[i].h);
|
||||
CHECK(from_ncs[i].I == Catch::Approx(from_full[i].I).margin(1e-3 * from_full[i].I + 1e-6));
|
||||
}
|
||||
|
||||
std::filesystem::remove(ncs_path);
|
||||
std::filesystem::remove(full_path);
|
||||
}
|
||||
|
||||
// A model that cannot be used must say why. Returning an empty result and logging was enough to lose
|
||||
// the fact entirely: the run finished successfully with no R-free and no maps, which is exactly what
|
||||
// a run without --model looks like.
|
||||
|
||||
Reference in New Issue
Block a user