Add XDS-order scaling of the rot3d fulls: --scale-fulls

The rot3d combine emits fulls with partiality == 1 and image_scale_corr == 1,
so the fulls are only ever scaled as per-frame partials upstream - their
per-frame scale G is fit through the rocking-curve/partiality model
(G*partiality*B*lp*Itrue - Iobs) and so absorbs any model error. XDS/DIALS
instead scale the 3D-integrated fulls directly.

--scale-fulls inserts a second scaling pass on the combined fulls with the
Unity model (G*Itrue - I_full, no partiality term), between Combine3D and
MergeOnTheFly, reusing ScaleOnTheFly on a Unity-configured experiment copy. It
is a pure post-correction (updates the fulls' image_scale_corr 1 -> 1/G, no
re-combine).

HEWL crystal 2, anomalous S-peak height vs XDS: 0.53x -> 0.57x and ISa
9.4 -> 10.5 - improving precision and accuracy together (not the CC1/2-up /
anomalous-down trade-off of outlier rejection).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-25 20:43:04 +02:00
co-authored by Claude Opus 4.8
parent 50ebf1694e
commit 8d0cd19e48
3 changed files with 38 additions and 1 deletions
+10 -1
View File
@@ -50,6 +50,7 @@ void print_usage() {
std::cout << " Scaling and merging" << std::endl;
std::cout << " -M, --scale-merge Scale and merge (refine mosaicity) and write scaled.hkl + image.dat" << std::endl;
std::cout << " --global-scale Joint global scaling (all images + Itrue in one fit) instead of the alternating loop; implies -M" << std::endl;
std::cout << " --scale-fulls After -P rot3d combine, refit a per-frame scale on the fulls (XDS order, Unity model); implies -M" << std::endl;
std::cout << " -P, --partiality <txt> Partiality model fixed|rot|rot3d|unity (default: fixed). rot3d = rot + 3D combine of per-frame partials" << std::endl;
std::cout << " -A, --anomalous Anomalous mode (don't merge Friedel pairs)" << std::endl;
std::cout << " -B, --refine-bfactor Refine per image B-factor" << std::endl;
@@ -93,7 +94,8 @@ enum {
OPT_REFERENCE_COLUMN,
OPT_DUMP_OBSERVATIONS,
OPT_INTEGRATOR,
OPT_GLOBAL_SCALE
OPT_GLOBAL_SCALE,
OPT_SCALE_FULLS
};
static option long_options[] = {
@@ -115,6 +117,7 @@ static option long_options[] = {
{"wedge", optional_argument, nullptr, 'w'},
{"scale-merge", no_argument, nullptr, 'M'},
{"global-scale", no_argument, nullptr, OPT_GLOBAL_SCALE},
{"scale-fulls", no_argument, nullptr, OPT_SCALE_FULLS},
{"refine", required_argument, nullptr, 'r'},
{"two-pass-rotation", optional_argument, nullptr, 'R'},
@@ -275,6 +278,7 @@ int main(int argc, char **argv) {
std::optional<float> rotation_indexing_range;
bool run_scaling = false;
bool global_scaling = false;
bool scale_fulls = false;
bool anomalous_mode = false;
std::optional<int64_t> space_group_number;
std::optional<UnitCell> fixed_reference_unit_cell;
@@ -508,6 +512,10 @@ int main(int argc, char **argv) {
run_scaling = true;
global_scaling = true;
break;
case OPT_SCALE_FULLS:
run_scaling = true;
scale_fulls = true;
break;
case OPT_MIN_PARTIALITY:
min_partiality = std::stod(optarg);
break;
@@ -791,6 +799,7 @@ int main(int argc, char **argv) {
config.forced_rotation_lattice = forced_rotation_lattice;
config.run_scaling = run_scaling;
config.global_scaling = global_scaling;
config.scale_fulls = scale_fulls;
config.scaling_iter = scaling_iter;
config.reference_data = reference_data;
config.observation_dump_path = dump_observations;