// Chemfiles, a modern library for chemistry file reading and writing // Copyright (C) Guillaume Fraux and contributors -- BSD license #include #include #include #include "chemfiles/UnitCell.hpp" #include "chemfiles/types.hpp" #include "chemfiles/error_fmt.hpp" #include "chemfiles/unreachable.hpp" #include "chemfiles/warnings.hpp" using namespace chemfiles; // Sinus and Cosine for degree values constexpr double pi = 3.141592653589793238463; static double deg2rad(double x) { return x * pi / 180.0; } static double rad2deg(double x) { return x * 180.0 / pi; } static double cosd(double theta) { return cos(deg2rad(theta)); } static double sind(double theta) { return sin(deg2rad(theta)); } namespace chemfiles { namespace private_details { bool is_roughly_zero(double value) { // We think that 0.00001 is close enough to 0 return fabs(value) < 1e-5; } bool is_roughly_90(double value) { // We think that 89.999° is close enough to 90° return fabs(value - 90.0) < 1e-3; } bool is_diagonal(const Matrix3D& matrix) { return is_roughly_zero(matrix[1][0]) && is_roughly_zero(matrix[2][0]) && is_roughly_zero(matrix[0][1]) && is_roughly_zero(matrix[2][1]) && is_roughly_zero(matrix[0][2]) && is_roughly_zero(matrix[1][2]); } bool is_upper_triangular(const Matrix3D& matrix) { return is_roughly_zero(matrix[1][0]) && is_roughly_zero(matrix[2][0]) && is_roughly_zero(matrix[2][1]); } }} using namespace chemfiles::private_details; static void check_lengths(const Vector3D& lengths) { if (lengths[0] < 0 || lengths[1] < 0 || lengths[2] < 0) { throw error("a unit cell can not have negative lengths"); } if (lengths != Vector3D(0, 0, 0) && (is_roughly_zero(lengths[0]) || is_roughly_zero(lengths[1]) || is_roughly_zero(lengths[2]))) { warning("", "trying to set one or two unit cell lengths to zero, something might be wrong"); } } static void check_angles(const Vector3D& angles) { if (angles[0] < 0 || angles[1] < 0 || angles[2] < 0) { throw error("a unit cell can not have negative angles"); } if (is_roughly_zero(angles[0]) || is_roughly_zero(angles[1]) || is_roughly_zero(angles[2])) { throw error("a unit cell can not have 0° angles"); } if (angles[0] >= 180 || angles[1] >= 180 || angles[2] >= 180) { throw error("a unit cell can not have angles larger than or equal to 180°"); } } static Matrix3D cell_matrix_from_lenths_angles(Vector3D lengths, Vector3D angles) { check_lengths(lengths); check_angles(angles); if (is_roughly_90(angles[0]) && is_roughly_90(angles[1]) && is_roughly_90(angles[2])) { angles = {90, 90, 90}; } auto matrix = Matrix3D::zero(); matrix[0][0] = lengths[0]; matrix[1][0] = 0; matrix[2][0] = 0; matrix[0][1] = cosd(angles[2]) * lengths[1]; matrix[1][1] = sind(angles[2]) * lengths[1]; matrix[2][1] = 0; matrix[0][2] = cosd(angles[1]); matrix[1][2] = (cosd(angles[0]) - cosd(angles[1]) * cosd(angles[2])) / sind(angles[2]); matrix[2][2] = sqrt(1 - matrix[0][2] * matrix[0][2] - matrix[1][2] * matrix[1][2]); matrix[0][2] *= lengths[2]; matrix[1][2] *= lengths[2]; matrix[2][2] *= lengths[2]; assert(is_upper_triangular(matrix)); return matrix; } static Vector3D calc_lengths_from_cell_matrix(const Matrix3D& matrix) { Vector3D v1 = {matrix[0][0], matrix[1][0], matrix[2][0]}; Vector3D v2 = {matrix[0][1], matrix[1][1], matrix[2][1]}; Vector3D v3 = {matrix[0][2], matrix[1][2], matrix[2][2]}; return {v1.norm(), v2.norm(), v3.norm()}; } static Vector3D calc_angles_from_cell_matrix(const Matrix3D& matrix) { Vector3D v1 = {matrix[0][0], matrix[1][0], matrix[2][0]}; Vector3D v2 = {matrix[0][1], matrix[1][1], matrix[2][1]}; Vector3D v3 = {matrix[0][2], matrix[1][2], matrix[2][2]}; return { rad2deg(acos(dot(v2, v3) / (v2.norm() * v3.norm()))), rad2deg(acos(dot(v1, v3) / (v1.norm() * v3.norm()))), rad2deg(acos(dot(v1, v2) / (v1.norm() * v2.norm()))) }; } static bool is_infinite(const Vector3D& lengths) { return is_roughly_zero(lengths[0]) && is_roughly_zero(lengths[1]) && is_roughly_zero(lengths[2]); } static bool is_orthorhombic(const Vector3D& lengths, const Vector3D& angles) { if (is_infinite(lengths)) { return false; } // we support cells with one or two lengths of 0 which results in NaN angles return (is_roughly_90(angles[0]) || std::isnan(angles[0])) && (is_roughly_90(angles[1]) || std::isnan(angles[1])) && (is_roughly_90(angles[2]) || std::isnan(angles[2])); } UnitCell::UnitCell(): UnitCell({0, 0, 0}) {} UnitCell::UnitCell(Vector3D lengths): UnitCell(std::move(lengths), {90, 90, 90}) {} UnitCell::UnitCell(Vector3D lengths, Vector3D angles): UnitCell(cell_matrix_from_lenths_angles(std::move(lengths), std::move(angles))) {} UnitCell::UnitCell(Matrix3D matrix): matrix_(std::move(matrix)), matrix_inv_(Matrix3D::unit()) { auto determinant = matrix_.determinant(); if (determinant < 0.0) { throw error("invalid unit cell matrix with negative determinant"); } auto lengths = calc_lengths_from_cell_matrix(matrix_); auto angles = calc_angles_from_cell_matrix(matrix_); if (!is_diagonal(matrix_) && is_orthorhombic(lengths, angles)) { throw error("orthorhombic cell must have their a vector along x axis, b vector along y axis and c vector along z axis"); } if (is_diagonal(matrix_)) { if (is_roughly_zero(matrix_[0][0]) && is_roughly_zero(matrix_[1][1]) && is_roughly_zero(matrix_[2][2])) { shape_ = INFINITE; matrix_ = Matrix3D::zero(); } else { shape_ = ORTHORHOMBIC; } } else { shape_ = TRICLINIC; } if (!is_roughly_zero(this->volume())) { // Do not try to invert a cell with a 0 volume matrix_inv_ = matrix_.invert(); } } double UnitCell::volume() const { switch (shape_) { case INFINITE: return 0; case ORTHORHOMBIC: case TRICLINIC: return matrix_.determinant(); } unreachable(); } void UnitCell::set_shape(CellShape shape) { if (shape == ORTHORHOMBIC) { if (!is_diagonal(matrix_)) { throw error( "can not set cell shape to ORTHORHOMBIC: some angles are not 90°" ); } } else if (shape == INFINITE) { if (!is_diagonal(matrix_)) { throw error( "can not set cell shape to INFINITE: some angles are not 90°" ); } auto lengths = this->lengths(); if (!(is_roughly_zero(lengths[0]) && is_roughly_zero(lengths[1]) && is_roughly_zero(lengths[2]))) { throw error( "can not set cell shape to INFINITE: some lengths are not 0" ); } } shape_ = shape; } Vector3D UnitCell::lengths() const { switch (shape_) { case INFINITE: return {0, 0, 0}; case ORTHORHOMBIC: return {matrix_[0][0], matrix_[1][1], matrix_[2][2]}; case TRICLINIC: return calc_lengths_from_cell_matrix(matrix_); } unreachable(); } Vector3D UnitCell::angles() const { switch (shape_) { case INFINITE: case ORTHORHOMBIC: return {90, 90, 90}; case TRICLINIC: return calc_angles_from_cell_matrix(matrix_); } unreachable(); } void UnitCell::set_lengths(Vector3D lengths) { if (shape_ == INFINITE) { throw error("can not set lengths for an infinite cell"); } check_lengths(lengths); if (!is_upper_triangular(matrix_)) { warning("UnitCell", "resetting unit cell orientation in set_lengths"); } // Reset the unit cell, and remove any existing rotation. *this = UnitCell(std::move(lengths), this->angles()); } void UnitCell::set_angles(Vector3D angles) { if (shape_ != TRICLINIC) { throw error("can not set angles for a non-triclinic cell"); } check_angles(angles); if (!is_upper_triangular(matrix_)) { warning("UnitCell", "resetting unit cell orientation in set_angles"); } // Reset the unit cell, and remove any existing rotation. *this = UnitCell(this->lengths(), std::move(angles)); } Vector3D UnitCell::wrap_orthorhombic(const Vector3D& vector) const { auto lengths = this->lengths(); return { vector[0] - round(vector[0] / lengths[0]) * lengths[0], vector[1] - round(vector[1] / lengths[1]) * lengths[1], vector[2] - round(vector[2] / lengths[2]) * lengths[2] }; } Vector3D UnitCell::wrap_triclinic(const Vector3D& vector) const { auto fractional = matrix_inv_ * vector; fractional[0] -= round(fractional[0]); fractional[1] -= round(fractional[1]); fractional[2] -= round(fractional[2]); return matrix_ * fractional; } Vector3D UnitCell::wrap(const Vector3D& vector) const { switch (shape_) { case INFINITE: return vector; case ORTHORHOMBIC: return wrap_orthorhombic(vector); case TRICLINIC: return wrap_triclinic(vector); } unreachable(); } namespace chemfiles { bool operator==(const UnitCell& rhs, const UnitCell& lhs) { if (lhs.shape() != rhs.shape()) { return false; } return rhs.matrix() == lhs.matrix(); } bool operator!=(const UnitCell& rhs, const UnitCell& lhs) { return !(rhs == lhs); } }