All checks were successful
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 8m53s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 9m40s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 8m25s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 8m17s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 9m5s
Build Packages / Generate python client (push) Successful in 34s
Build Packages / Build documentation (push) Successful in 42s
Build Packages / Create release (push) Has been skipped
Build Packages / build:rpm (rocky8) (push) Successful in 8m35s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 8m2s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 8m40s
Build Packages / build:rpm (rocky9) (push) Successful in 9m14s
Build Packages / Unit tests (push) Successful in 1h15m9s
This is an UNSTABLE release and not recommended for production use (please use rc.11 instead). * jfjoch_broker: Experimental rotation (3D) indexing * jfjoch_broker: Minor fix to error in optimizer potentially returning NaN values Reviewed-on: #18 Co-authored-by: Filip Leonarski <filip.leonarski@psi.ch> Co-committed-by: Filip Leonarski <filip.leonarski@psi.ch>
231 lines
6.2 KiB
C++
231 lines
6.2 KiB
C++
// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
||
// SPDX-License-Identifier: GPL-3.0-only
|
||
|
||
#include <cmath>
|
||
#include "CrystalLattice.h"
|
||
#include "JFJochException.h"
|
||
|
||
#include "gemmi/symmetry.hpp"
|
||
#include "gemmi/unitcell.hpp"
|
||
|
||
#define DEG_TO_RAD static_cast<float>(M_PI/180.0)
|
||
|
||
CrystalLattice::CrystalLattice(const UnitCell &cell) {
|
||
vec[0] = {cell.a, 0, 0};
|
||
vec[1] = {cell.b * cosf(cell.gamma * DEG_TO_RAD), cell.b * sinf(cell.gamma * DEG_TO_RAD), 0};
|
||
float cx = cell.c * cosf(cell.beta * DEG_TO_RAD);
|
||
float cy = cell.c
|
||
* (cosf(cell.alpha * DEG_TO_RAD) - cosf(cell.beta * DEG_TO_RAD) * cosf(cell.gamma * DEG_TO_RAD))
|
||
/ sinf(cell.gamma * DEG_TO_RAD);
|
||
vec[2] = {cx, cy, sqrtf(cell.c*cell.c-cx*cx-cy*cy)};
|
||
|
||
FixHandeness();
|
||
}
|
||
|
||
CrystalLattice::CrystalLattice(const Coord &a, const Coord &b, const Coord &c) {
|
||
vec[0] = a;
|
||
vec[1] = b;
|
||
vec[2] = c;
|
||
|
||
FixHandeness();
|
||
}
|
||
|
||
const Coord &CrystalLattice::Vec0() const {
|
||
return vec[0];
|
||
}
|
||
|
||
const Coord &CrystalLattice::Vec1() const {
|
||
return vec[1];
|
||
}
|
||
|
||
const Coord &CrystalLattice::Vec2() const {
|
||
return vec[2];
|
||
}
|
||
|
||
UnitCell CrystalLattice::GetUnitCell() const {
|
||
UnitCell cell{};
|
||
cell.a = vec[0].Length();
|
||
cell.b = vec[1].Length();
|
||
cell.c = vec[2].Length();
|
||
cell.alpha = angle_deg(vec[1], vec[2]);
|
||
cell.beta = angle_deg(vec[0], vec[2]);
|
||
cell.gamma = angle_deg(vec[0], vec[1]);
|
||
return cell;
|
||
}
|
||
|
||
std::vector<float> CrystalLattice::GetVector() const {
|
||
std::vector<float> output(9);
|
||
for (int i = 0; i < 3; i++) {
|
||
output[3 * i + 0] = vec[i].x;
|
||
output[3 * i + 1] = vec[i].y;
|
||
output[3 * i + 2] = vec[i].z;
|
||
}
|
||
return output;
|
||
}
|
||
|
||
float CrystalLattice::CalcVolume() const {
|
||
// Calculate the cell volume
|
||
// V = a · (b × c)
|
||
Coord cross_product = vec[1] % vec[2];
|
||
return vec[0] * cross_product;
|
||
}
|
||
|
||
void CrystalLattice::Sort() {
|
||
if (vec[0].Length() > vec[1].Length())
|
||
std::swap(vec[0], vec[1]);
|
||
if (vec[1].Length() > vec[2].Length())
|
||
std::swap(vec[1], vec[2]);
|
||
if (vec[0].Length() > vec[1].Length())
|
||
std::swap(vec[0], vec[1]);
|
||
}
|
||
|
||
void CrystalLattice::FixHandeness() {
|
||
if (CalcVolume() < 0)
|
||
vec[2] *= -1;
|
||
}
|
||
|
||
|
||
Coord CrystalLattice::Astar() const {
|
||
return (vec[1] % vec[2]) * (1.0f / CalcVolume());
|
||
}
|
||
|
||
Coord CrystalLattice::Bstar() const {
|
||
return (vec[2] % vec[0]) * (1.0f / CalcVolume());
|
||
}
|
||
|
||
Coord CrystalLattice::Cstar() const {
|
||
return (vec[0] % vec[1]) * (1.0f / CalcVolume());
|
||
}
|
||
|
||
CrystalLattice::CrystalLattice(float a, float b, float c, float alpha, float beta, float gamma)
|
||
: CrystalLattice(UnitCell{.a = a, .b = b, .c = c, .alpha = alpha, .beta = beta, .gamma = gamma}) {}
|
||
|
||
CrystalLattice::CrystalLattice(const std::vector<float> &input) {
|
||
if (input.size() != 9)
|
||
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,"Wrong size of crystal lattice vector");
|
||
for (int i = 0; i < 3; i++) {
|
||
vec[i].x = input[3 * i + 0];
|
||
vec[i].y = input[3 * i + 1];
|
||
vec[i].z = input[3 * i + 2];
|
||
}
|
||
}
|
||
|
||
void CrystalLattice::ReorderABEqual() {
|
||
double la = vec[0].Length();
|
||
double lb = vec[1].Length();
|
||
double lc = vec[2].Length();
|
||
|
||
double dab = std::abs(la - lb);
|
||
double dbc = std::abs(lb - lc);
|
||
double dac = std::abs(la - lc);
|
||
|
||
Coord a = vec[0];
|
||
Coord b = vec[1];
|
||
Coord c = vec[2];
|
||
|
||
if (dbc < dab && dbc < dac) {
|
||
// b≈c → [b, c, a]
|
||
vec[0] = b;
|
||
vec[1] = c;
|
||
vec[2] = a;
|
||
} else if (dac < dab && dac < dbc) {
|
||
// a≈c → [a, c, b]
|
||
vec[0] = a;
|
||
vec[1] = c;
|
||
vec[2] = b;
|
||
} // else a≈b → keep [a, b, c]
|
||
|
||
FixHandeness();
|
||
}
|
||
|
||
void CrystalLattice::ReorderMonoclinic() {
|
||
float alpha = angle_deg(vec[1], vec[2]);
|
||
float beta = angle_deg(vec[0], vec[2]);
|
||
float gamma = angle_deg(vec[0], vec[1]);
|
||
|
||
float da = std::abs(alpha - 90.0f);
|
||
float db = std::abs(beta - 90.0f);
|
||
float dg = std::abs(gamma - 90.0f);
|
||
|
||
Coord a = vec[0];
|
||
Coord b = vec[1];
|
||
Coord c = vec[2];
|
||
|
||
if (da > db && da > dg) {
|
||
// alpha most different -> [b, c, a]
|
||
vec[0] = b;
|
||
vec[1] = a;
|
||
vec[2] = c;
|
||
} else if (dg > db && dg > da) {
|
||
// gamma most different -> [c, a, b]
|
||
vec[0] = a;
|
||
vec[1] = c;
|
||
vec[2] = b;
|
||
} // else beta most different -> keep [a, b, c]
|
||
|
||
if (vec[0].Length() > vec[2].Length())
|
||
std::swap(vec[0], vec[2]);
|
||
|
||
FixHandeness();
|
||
|
||
// Enforce obtuse beta (>= 90°). Beta is the angle between a and c.
|
||
// Flip signs of a and b simultaneously to keep handedness and lengths unchanged,
|
||
// which maps beta -> 180° - beta.
|
||
float beta_now = angle_deg(vec[0], vec[2]);
|
||
if (beta_now < 90.0f) {
|
||
vec[0] *= -1.0f; // a -> -a
|
||
vec[1] *= -1.0f; // b -> -b (preserves cell volume sign)
|
||
// beta becomes 180 - beta_now (> 90°)
|
||
}
|
||
}
|
||
|
||
CrystalLattice CrystalLattice::Multiply(const RotMatrix &input) const {
|
||
CrystalLattice l;
|
||
l.vec[0] = input * vec[0];
|
||
l.vec[1] = input * vec[1];
|
||
l.vec[2] = input * vec[2];
|
||
l.FixHandeness();
|
||
return l;
|
||
}
|
||
|
||
CrystalLattice CrystalLattice::Multiply(const gemmi::Mat33 &c2p) const {
|
||
CrystalLattice l;
|
||
for (int i = 0; i < 3; i++) {
|
||
for (int j = 0; j < 3; j++) {
|
||
l.vec[i][j] = c2p[i][0] * vec[0][j] + c2p[i][1] * vec[1][j] + c2p[i][2] * vec[2][j];
|
||
}
|
||
}
|
||
l.FixHandeness();
|
||
return l;
|
||
}
|
||
|
||
CrystalLattice CrystalLattice::FromPrimitive(char centering) const {
|
||
if (centering == 'P')
|
||
return *this;
|
||
|
||
return Multiply(gemmi::rot_as_mat33(gemmi::centred_to_primitive(centering)).inverse());
|
||
}
|
||
|
||
CrystalLattice CrystalLattice::ToPrimitive(char centering) const {
|
||
if (centering == 'P')
|
||
return *this;
|
||
|
||
return Multiply(gemmi::rot_as_mat33(gemmi::centred_to_primitive(centering)));
|
||
}
|
||
|
||
void CrystalLattice::Regularize(const gemmi::CrystalSystem &input) {
|
||
switch (input) {
|
||
case gemmi::CrystalSystem::Monoclinic:
|
||
ReorderMonoclinic();
|
||
break;
|
||
case gemmi::CrystalSystem::Tetragonal:
|
||
case gemmi::CrystalSystem::Hexagonal:
|
||
ReorderABEqual();
|
||
break;
|
||
default:
|
||
Sort();
|
||
FixHandeness();
|
||
break;
|
||
}
|
||
}
|