Merge branch 'dq-reg4' into rc174-all3

# Conflicts:
#	docs/CHANGELOG.md
This commit is contained in:
2026-10-05 10:14:47 +02:00
2 changed files with 49 additions and 2 deletions
+1
View File
@@ -15,6 +15,7 @@
* Rugnux drops a rotation reflection whose spot held a saturated (overloaded) pixel, as XDS does, and reports the count as `OBSERVATIONS_REJECTED_OVERLOAD=`.
* Rugnux fits a rotation sweep's per-frame scale on the partial reflections, even with few reflections per frame, when scaling from the fulls alone leaves no measurable error model.
* Rugnux no longer refuses a genuine high-symmetry point group because one of its operators agrees with the data unusually well; it refuses a group whose operators split into an agreeing subgroup and a disagreeing rest.
* Rugnux no longer adopts an intermediate space group (e.g. C222_1 for a twinned P2_1 on a hexagonal metric) when the higher group was refused and the twin-immune reflections show the added two-folds to be a twin law.
* Rugnux names glide planes in groups without a centre of symmetry (e.g. I-42d, Fdd2), reports a group with the same absences (C2/c and Cc) as an alternative, and states how the centre was decided (`SPACE_GROUP_CENTRE`, `CENTRE_STATISTICS_*`).
### 1.0.0-rc.173
+48 -2
View File
@@ -7828,8 +7828,53 @@ ProcessResult Rugnux::RunPipeline(RugnuxObserver *observer, bool write_output, b
if (alt.centring_type() == cand.centering) { chosen = alt; break; }
const bool point_group_higher = s2.best_space_group.has_value()
&& s2.point_group_order > sg_search.point_group_order;
const bool higher = point_group_higher
&& chosen->centring_type() == cand.centering;
bool higher = point_group_higher
&& chosen->centring_type() == cand.centering;
// A promotion over a refusal is arbitrated by the twin-immune zone of the added
// operators (TwinZoneVerdict), here as where the two arms disagree: where a twin
// gate of the search in hand refused a higher group, this ask reopens the question
// that gate answered, for whichever of the refused group's operators the new
// lattice holds, and its own search cannot answer it - the gates there are relative
// too, and a twin law or a pseudo-symmetry the refused group's other operators
// diluted passes them alone. Measured on a monoclinic crystal on a hexagonal metric,
// a pseudo-merohedral twin: 622 refused on the added-operator R, the C-centred
// orthorhombic sub-lattice then asked, its two in-plane two-folds at R 0.061 against
// 0.045 for the genuine screw, and C 2 2 21 adopted with the zone of those two-folds
// reading acentric. The group in hand is carried into the candidate's setting for
// it: h -> Q h with Q the reindex from the basis it was found in (read where GEMMI's
// table names the result, as every setting the search enumerates is).
std::string zone_refusal;
if (higher && sg_search.refused_by_twin_gate && sg_search.point_group_representative) {
gemmi::Mat33 q = reindex;
if (commit_reindex)
q = reindex.multiply(commit_reindex->inverse());
const gemmi::Mat33 q_inv = q.inverse();
gemmi::GroupOps lower_ops;
lower_ops.cen_ops = gemmi::centring_vectors(cand.centering);
for (const auto &op : sg_search.point_group_representative->operations().sym_ops) {
// apply_to_hkl uses rot^T, so the Miller-index matrix of op is rot^T / DEN.
gemmi::Mat33 m;
for (int i = 0; i < 3; ++i)
for (int j = 0; j < 3; ++j)
m.a[i][j] = static_cast<double>(op.rot[j][i]) / gemmi::Op::DEN;
const gemmi::Mat33 m_new = q.multiply(m).multiply(q_inv);
gemmi::Op op_new = gemmi::Op::identity();
for (int i = 0; i < 3; ++i)
for (int j = 0; j < 3; ++j)
op_new.rot[i][j] = static_cast<int>(std::lround(m_new[j][i])) * gemmi::Op::DEN;
lower_ops.sym_ops.push_back(op_new);
}
if (const gemmi::SpaceGroup *lower_sg = gemmi::find_spacegroup_by_ops(lower_ops)) {
SearchSpaceGroupResult lower = sg_search;
lower.point_group_representative = *lower_sg;
if (TwinZoneVerdict(merged_all, *o2.cell, s2, lower, result) < 0) {
zone_refusal = sg_search.refused_point_group_hm + " was refused (" +
sg_search.refused_reason + "), and the twin-immune zone of the "
"operators this group adds reads acentric";
higher = false;
}
}
}
const auto &uc = cand.conventional.GetUnitCell();
// The two asks are told apart by what they were asked against: the class a search
// has already been given, or the rotations the intensities have actually confirmed.
@@ -7853,6 +7898,7 @@ ProcessResult Rugnux::RunPipeline(RugnuxObserver *observer, bool write_output, b
s2.best_space_group.has_value() ? s2.best_space_group->xhm() : "nothing",
s2.point_group_order, sg_search.point_group_order,
higher ? " - adopted"
: !zone_refusal.empty() ? " - keeping the group already found; " + zone_refusal
: (point_group_higher
? " - keeping the group already found; the point group IS "
"higher, but no candidate of it carries the centring the "