// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute // SPDX-License-Identifier: GPL-3.0-only #include "LeftoverLattices.h" #include #include #include #include "../common/JFJochMath.h" // PI const char *LeftoverLatticeKindName(LeftoverLatticeKind kind) { switch (kind) { case LeftoverLatticeKind::Self: return "SELF"; case LeftoverLatticeKind::Domain: return "DOMAIN"; case LeftoverLatticeKind::TwinDomain: return "TWIN_DOMAIN"; case LeftoverLatticeKind::Segmented: return "SEGMENTED"; case LeftoverLatticeKind::RelatedCell: return "RELATED_CELL"; case LeftoverLatticeKind::Foreign: return "FOREIGN"; } return "FOREIGN"; } double LatticeMisorientationDeg(const CrystalLattice &a, const CrystalLattice &b) { auto mat = [](const CrystalLattice &l) { Eigen::Matrix3d M; const Coord v[3] = {l.Vec0(), l.Vec1(), l.Vec2()}; for (int c = 0; c < 3; c++) M.col(c) = Eigen::Vector3d(v[c].x, v[c].y, v[c].z); return M; }; const Eigen::Matrix3d A = mat(a), B = mat(b); const Eigen::Matrix3d GA = A.transpose() * A; const double scale = GA.diagonal().mean(); // Every unimodular change of basis with entries -1..1: enough for two reduced cells. double best = NAN; for (int code = 0; code < 19683; code++) { Eigen::Matrix3d M; int c = code; for (int e = 0; e < 9; e++) { M(e / 3, e % 3) = c % 3 - 1; c /= 3; } if (std::abs(M.determinant() - 1.0) > 1e-6) continue; const Eigen::Matrix3d BM = B * M; if ((BM.transpose() * BM - GA).cwiseAbs().maxCoeff() > 0.06 * scale) continue; // The rotation nearest to the (slightly non-orthogonal) map from A to BM. Eigen::JacobiSVD svd(BM * A.inverse(), Eigen::ComputeFullU | Eigen::ComputeFullV); const Eigen::Matrix3d R = svd.matrixU() * svd.matrixV().transpose(); if (R.determinant() < 0) continue; const double ang = std::acos(std::clamp((R.trace() - 1.0) / 2.0, -1.0, 1.0)) * 180.0 / PI; if (std::isnan(best) || ang < best) best = ang; } return best; } LeftoverLatticeKind ClassifyLeftoverLattice(double misorientation_deg, double volume_ratio, double sweep_concentration) { if (std::isfinite(misorientation_deg)) { if (misorientation_deg <= 1.0) return LeftoverLatticeKind::Self; if (sweep_concentration >= 0.6) return LeftoverLatticeKind::Segmented; if (misorientation_deg >= 177.0) return LeftoverLatticeKind::TwinDomain; return LeftoverLatticeKind::Domain; } for (int n = 1; n <= 8; n++) if (std::abs(volume_ratio / n - 1.0) < 0.06 || std::abs(volume_ratio * n - 1.0) < 0.06) return LeftoverLatticeKind::RelatedCell; return LeftoverLatticeKind::Foreign; }