XtalOptimizer: Fix how monoclinic is generated
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 10m3s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 11m32s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 12m37s
Build Packages / Generate python client (push) Successful in 30s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 13m7s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 13m35s
Build Packages / Create release (push) Has been skipped
Build Packages / Build documentation (push) Successful in 1m0s
Build Packages / build:rpm (rocky8) (push) Successful in 14m27s
Build Packages / build:rpm (rocky9) (push) Successful in 14m50s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 15m15s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 9m25s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 8m1s
Build Packages / Unit tests (push) Has been cancelled

This commit is contained in:
2026-02-18 20:44:58 +01:00
parent f05c1977f9
commit 7634cd819a
@@ -307,11 +307,11 @@ inline void LatticeToRodriguesLengthsBeta_Mono(const CrystalLattice &latt,
const Coord c = latt.Vec2();
Eigen::Vector3d A(a[0], a[1], a[2]);
Eigen::Vector3d B(b[0], b[1], b[2]);
Eigen::Vector3d Bv(b[0], b[1], b[2]);
Eigen::Vector3d C(c[0], c[1], c[2]);
const double a_len = A.norm();
const double b_len = B.norm();
const double b_len = Bv.norm();
const double c_len = C.norm();
lengths[0] = a_len;
@@ -323,35 +323,34 @@ inline void LatticeToRodriguesLengthsBeta_Mono(const CrystalLattice &latt,
if (a_len > 0.0 && c_len > 0.0)
cos_beta = std::max(-1.0, std::min(1.0, A.dot(C) / (a_len * c_len)));
beta_rad = std::acos(cos_beta);
Eigen::Vector3d e2, ax;
// e2 along b (unique axis)
if (b_len > 0.0)
e2 = B / b_len;
else
e2 = Eigen::Vector3d::UnitY();
// Recover R from the same forward model used in refinement:
// L ≈ R * D(a,b,c) * B(beta) => R ≈ L * (D*B)^-1
Eigen::Matrix3d L;
L.col(0) = A;
L.col(1) = Bv;
L.col(2) = C;
if (a_len > 0.0)
ax = A / a_len;
else
ax = Eigen::Vector3d::UnitX();
Eigen::Matrix3d Bmono = Eigen::Matrix3d::Identity();
Bmono(0, 2) = std::cos(beta_rad);
Bmono(2, 2) = std::sin(beta_rad);
Eigen::Vector3d e1 = ax - (ax.dot(e2)) * e2;
double n1 = e1.norm();
if (n1 < 1e-15) {
// Fallback: use any perpendicular to e2
e1 = (std::abs(e2.x()) < 0.9
? Eigen::Vector3d::UnitX().cross(e2)
: Eigen::Vector3d::UnitY().cross(e2));
Eigen::DiagonalMatrix<double, 3> D(lengths[0], lengths[1], lengths[2]);
Eigen::Matrix3d M = D * Bmono;
Eigen::Matrix3d R_est = Eigen::Matrix3d::Identity();
if (std::abs(M.determinant()) > 1e-15) {
R_est = L * M.inverse();
}
e1.normalize();
// e3 completes the right-handed frame
Eigen::Vector3d e3 = e1.cross(e2).normalized();
Eigen::Matrix3d R;
R.col(0) = e1;
R.col(1) = e2;
R.col(2) = e3;
// Project to nearest proper rotation (polar decomposition via SVD)
Eigen::JacobiSVD<Eigen::Matrix3d> svd(R_est, Eigen::ComputeFullU | Eigen::ComputeFullV);
Eigen::Matrix3d R = svd.matrixU() * svd.matrixV().transpose();
if (R.determinant() < 0.0) {
Eigen::Matrix3d U = svd.matrixU();
U.col(2) *= -1.0;
R = U * svd.matrixV().transpose();
}
Eigen::AngleAxisd aa(R);
Eigen::Vector3d r = aa.angle() * aa.axis();
@@ -520,7 +519,7 @@ bool XtalOptimizerInternal(XtalOptimizerData &data,
else {
const double dist_range = 0.1;
problem.SetParameterLowerBound(&distance_mm, 0, distance_mm * (1.0 - dist_range));
problem.SetParameterLowerBound(&distance_mm, 0, distance_mm * (1.0 + dist_range));
problem.SetParameterUpperBound(&distance_mm, 0, distance_mm * (1.0 + dist_range));
}
if (!data.refine_beam_center) {