Commit 9aa8a223 by Maarten L. Hekkelman

symmetry work

parent fb59adcf
...@@ -718,7 +718,6 @@ template <int N> ...@@ -718,7 +718,6 @@ template <int N>
class spherical_dots class spherical_dots
{ {
public: public:
constexpr static int P = 2 * N * 1; constexpr static int P = 2 * N * 1;
using array_type = typename std::array<point, P>; using array_type = typename std::array<point, P>;
......
...@@ -27,6 +27,8 @@ ...@@ -27,6 +27,8 @@
#pragma once #pragma once
#include "cif++/exports.hpp" #include "cif++/exports.hpp"
#include "cif++/matrix.hpp"
#include "cif++/point.hpp"
#include <array> #include <array>
#include <cstdint> #include <cstdint>
...@@ -156,8 +158,189 @@ extern CIFPP_EXPORT const symop_datablock kSymopNrTable[]; ...@@ -156,8 +158,189 @@ extern CIFPP_EXPORT const symop_datablock kSymopNrTable[];
extern CIFPP_EXPORT const std::size_t kSymopNrTableSize; extern CIFPP_EXPORT const std::size_t kSymopNrTableSize;
// -------------------------------------------------------------------- // --------------------------------------------------------------------
// Some more symmetry related stuff here.
class datablock;
class cell;
class spacegroup;
class rtop;
class sym_op;
// --------------------------------------------------------------------
// class cell
class cell
{
public:
cell(float a, float b, float c, float alpha = 90.f, float beta = 90.f, float gamma = 90.f);
cell(const datablock &db);
float get_a() const { return m_a; }
float get_b() const { return m_b; }
float get_c() const { return m_c; }
float get_alpha() const { return m_alpha; }
float get_beta() const { return m_beta; }
float get_gamma() const { return m_gamma; }
matrix3x3<float> get_orthogonal_matrix() const { return m_orthogonal; }
matrix3x3<float> get_fractional_matrix() const { return m_fractional; }
private:
void init();
float m_a, m_b, m_c, m_alpha, m_beta, m_gamma;
matrix3x3<float> m_orthogonal, m_fractional;
};
/// @brief A class that encapsulates the symmetry operations as used in PDB files, i.e. a rotational number and a translation vector
class sym_op
{
public:
sym_op(uint8_t nr = 1, uint8_t ta = 5, uint8_t tb = 5, uint8_t tc = 5)
: m_nr(nr)
, m_ta(ta)
, m_tb(tb)
, m_tc(tc)
{
}
explicit sym_op(std::string_view s);
sym_op(const sym_op &) = default;
sym_op(sym_op &&) = default;
sym_op &operator=(const sym_op &) = default;
sym_op &operator=(sym_op &&) = default;
constexpr bool is_identity() const
{
return m_nr == 1 and m_ta == 5 and m_tb == 5 and m_tc == 5;
}
explicit constexpr operator bool() const
{
return not is_identity();
}
std::string string() const;
friend class spacegroup;
private:
uint8_t m_nr;
uint8_t m_ta, m_tb, m_tc;
};
namespace literals
{
inline sym_op operator""_symop(const char *text, size_t length)
{
return sym_op({ text, length });
}
} // namespace literals
class transformation
{
public:
transformation(const symop_data &data);
transformation(const matrix3x3<float> &r, const cif::point &t)
: m_rotation(r)
, m_translation(t)
{
}
transformation(const transformation &) = default;
transformation(transformation &&) = default;
transformation &operator=(const transformation &) = default;
transformation &operator=(transformation &&) = default;
point operator()(const cell &c, const point &pt) const;
private:
matrix3x3<float> m_rotation;
point m_translation;
};
// --------------------------------------------------------------------
int get_space_group_number(std::string_view spacegroup); // alternative for clipper's parsing code, using space_group_name::full
int get_space_group_number(std::string_view spacegroup, space_group_name type); // alternative for clipper's parsing code
class spacegroup : public std::vector<transformation>
{
public:
spacegroup(std::string_view name)
: spacegroup(get_space_group_number(name))
{
}
spacegroup(std::string_view name, space_group_name type)
: spacegroup(get_space_group_number(name, type))
{
}
spacegroup(int nr);
int get_nr() const { return m_nr; }
std::string get_name() const;
point operator()(const point &pt, const cell &c, sym_op symop);
private:
int m_nr;
size_t m_index;
};
// --------------------------------------------------------------------
int get_space_group_number(std::string spacegroup); // alternative for clipper's parsing code, using space_group_name::full int get_space_group_number(std::string spacegroup); // alternative for clipper's parsing code, using space_group_name::full
int get_space_group_number(std::string spacegroup, space_group_name type); // alternative for clipper's parsing code int get_space_group_number(std::string spacegroup, space_group_name type); // alternative for clipper's parsing code
// class rtop
// {
// public:
// rtop(const spacegroup &sg, const cell &c, int nr);
// friend rtop operator+(rtop rt, cif::point t);
// friend cif::point operator*(cif::point p, rtop rt);
// private:
// cell m_c;
// cif::quaternion m_q;
// cif::point m_t;
// };
// class spacegroup
// {
// public:
// spacegroup(const cif::datablock &db);
// private:
// std::vector<
// };
static_assert(sizeof(sym_op) == 4, "Sym_op should be four bytes");
// --------------------------------------------------------------------
// Symmetry operations on points
template <typename T>
inline point_type<T> operator*(const matrix3x3<T> &m, const point_type<T> &pt)
{
return point_type(m(0, 0) * pt.m_x + m(0, 1) * pt.m_y + m(0, 2) * pt.m_z,
m(1, 0) * pt.m_x + m(1, 1) * pt.m_y + m(1, 2) * pt.m_z,
m(2, 0) * pt.m_x + m(2, 1) * pt.m_y + m(2, 2) * pt.m_z);
}
inline point orthogonal(const point &pt, const cell &c)
{
return c.get_orthogonal_matrix() * pt;
}
inline point fractional(const point &pt, const cell &c)
{
return c.get_fractional_matrix() * pt;
}
} // namespace cif } // namespace cif
...@@ -25,6 +25,7 @@ ...@@ -25,6 +25,7 @@
*/ */
#include "cif++/point.hpp" #include "cif++/point.hpp"
#include "cif++/matrix.hpp"
#include <cassert> #include <cassert>
#include <random> #include <random>
...@@ -33,245 +34,6 @@ namespace cif ...@@ -33,245 +34,6 @@ namespace cif
{ {
// -------------------------------------------------------------------- // --------------------------------------------------------------------
// We're using expression templates here
template <typename M>
class MatrixExpression
{
public:
uint32_t dim_m() const { return static_cast<const M &>(*this).dim_m(); }
uint32_t dim_n() const { return static_cast<const M &>(*this).dim_n(); }
double &operator()(uint32_t i, uint32_t j)
{
return static_cast<M &>(*this).operator()(i, j);
}
double operator()(uint32_t i, uint32_t j) const
{
return static_cast<const M &>(*this).operator()(i, j);
}
};
// --------------------------------------------------------------------
// matrix is m x n, addressing i,j is 0 <= i < m and 0 <= j < n
// element m i,j is mapped to [i * n + j] and thus storage is row major
class Matrix : public MatrixExpression<Matrix>
{
public:
template <typename M2>
Matrix(const MatrixExpression<M2> &m)
: m_m(m.dim_m())
, m_n(m.dim_n())
, m_data(m_m * m_n)
{
for (uint32_t i = 0; i < m_m; ++i)
{
for (uint32_t j = 0; j < m_n; ++j)
operator()(i, j) = m(i, j);
}
}
Matrix(size_t m, size_t n, double v = 0)
: m_m(m)
, m_n(n)
, m_data(m_m * m_n)
{
std::fill(m_data.begin(), m_data.end(), v);
}
Matrix() = default;
Matrix(Matrix &&m) = default;
Matrix(const Matrix &m) = default;
Matrix &operator=(Matrix &&m) = default;
Matrix &operator=(const Matrix &m) = default;
size_t dim_m() const { return m_m; }
size_t dim_n() const { return m_n; }
double operator()(size_t i, size_t j) const
{
assert(i < m_m);
assert(j < m_n);
return m_data[i * m_n + j];
}
double &operator()(size_t i, size_t j)
{
assert(i < m_m);
assert(j < m_n);
return m_data[i * m_n + j];
}
private:
size_t m_m = 0, m_n = 0;
std::vector<double> m_data;
};
// --------------------------------------------------------------------
class SymmetricMatrix : public MatrixExpression<SymmetricMatrix>
{
public:
SymmetricMatrix(uint32_t n, double v = 0)
: m_n(n)
, m_data((m_n * (m_n + 1)) / 2)
{
std::fill(m_data.begin(), m_data.end(), v);
}
SymmetricMatrix() = default;
SymmetricMatrix(SymmetricMatrix &&m) = default;
SymmetricMatrix(const SymmetricMatrix &m) = default;
SymmetricMatrix &operator=(SymmetricMatrix &&m) = default;
SymmetricMatrix &operator=(const SymmetricMatrix &m) = default;
uint32_t dim_m() const { return m_n; }
uint32_t dim_n() const { return m_n; }
double operator()(uint32_t i, uint32_t j) const
{
return i < j
? m_data[(j * (j + 1)) / 2 + i]
: m_data[(i * (i + 1)) / 2 + j];
}
double &operator()(uint32_t i, uint32_t j)
{
if (i > j)
std::swap(i, j);
assert(j < m_n);
return m_data[(j * (j + 1)) / 2 + i];
}
private:
uint32_t m_n;
std::vector<double> m_data;
};
class IdentityMatrix : public MatrixExpression<IdentityMatrix>
{
public:
IdentityMatrix(uint32_t n)
: m_n(n)
{
}
uint32_t dim_m() const { return m_n; }
uint32_t dim_n() const { return m_n; }
double operator()(uint32_t i, uint32_t j) const
{
return i == j ? 1 : 0;
}
private:
uint32_t m_n;
};
// --------------------------------------------------------------------
// matrix functions, implemented as expression templates
template <typename M1, typename M2>
class MatrixSubtraction : public MatrixExpression<MatrixSubtraction<M1, M2>>
{
public:
MatrixSubtraction(const M1 &m1, const M2 &m2)
: m_m1(m1)
, m_m2(m2)
{
assert(m_m1.dim_m() == m_m2.dim_m());
assert(m_m1.dim_n() == m_m2.dim_n());
}
uint32_t dim_m() const { return m_m1.dim_m(); }
uint32_t dim_n() const { return m_m1.dim_n(); }
double operator()(uint32_t i, uint32_t j) const
{
return m_m1(i, j) - m_m2(i, j);
}
private:
const M1 &m_m1;
const M2 &m_m2;
};
template <typename M1, typename M2>
MatrixSubtraction<M1, M2> operator-(const MatrixExpression<M1> &m1, const MatrixExpression<M2> &m2)
{
return MatrixSubtraction(*static_cast<const M1 *>(&m1), *static_cast<const M2 *>(&m2));
}
template <typename M>
class MatrixMultiplication : public MatrixExpression<MatrixMultiplication<M>>
{
public:
MatrixMultiplication(const M &m, double v)
: m_m(m)
, m_v(v)
{
}
uint32_t dim_m() const { return m_m.dim_m(); }
uint32_t dim_n() const { return m_m.dim_n(); }
double operator()(uint32_t i, uint32_t j) const
{
return m_m(i, j) * m_v;
}
private:
const M &m_m;
double m_v;
};
template <typename M>
MatrixMultiplication<M> operator*(const MatrixExpression<M> &m, double v)
{
return MatrixMultiplication(*static_cast<const M *>(&m), v);
}
// --------------------------------------------------------------------
template <class M1>
Matrix Cofactors(const M1 &m)
{
Matrix cf(m.dim_m(), m.dim_m());
const size_t ixs[4][3] = {
{ 1, 2, 3 },
{ 0, 2, 3 },
{ 0, 1, 3 },
{ 0, 1, 2 }
};
for (size_t x = 0; x < 4; ++x)
{
const size_t *ix = ixs[x];
for (size_t y = 0; y < 4; ++y)
{
const size_t *iy = ixs[y];
cf(x, y) =
m(ix[0], iy[0]) * m(ix[1], iy[1]) * m(ix[2], iy[2]) +
m(ix[0], iy[1]) * m(ix[1], iy[2]) * m(ix[2], iy[0]) +
m(ix[0], iy[2]) * m(ix[1], iy[0]) * m(ix[2], iy[1]) -
m(ix[0], iy[2]) * m(ix[1], iy[1]) * m(ix[2], iy[0]) -
m(ix[0], iy[1]) * m(ix[1], iy[0]) * m(ix[2], iy[2]) -
m(ix[0], iy[0]) * m(ix[1], iy[2]) * m(ix[2], iy[1]);
if ((x + y) % 2 == 1)
cf(x, y) *= -1;
}
}
return cf;
}
// --------------------------------------------------------------------
template<typename T> template<typename T>
quaternion_type<T> normalize(quaternion_type<T> q) quaternion_type<T> normalize(quaternion_type<T> q)
...@@ -443,8 +205,8 @@ double LargestDepressedQuarticSolution(double a, double b, double c) ...@@ -443,8 +205,8 @@ double LargestDepressedQuarticSolution(double a, double b, double c)
quaternion align_points(const std::vector<point> &pa, const std::vector<point> &pb) quaternion align_points(const std::vector<point> &pa, const std::vector<point> &pb)
{ {
// First calculate M, a 3x3 Matrix containing the sums of products of the coordinates of A and B // First calculate M, a 3x3 matrix containing the sums of products of the coordinates of A and B
Matrix M(3, 3, 0); matrix3x3<double> M;
for (uint32_t i = 0; i < pa.size(); ++i) for (uint32_t i = 0; i < pa.size(); ++i)
{ {
...@@ -462,8 +224,8 @@ quaternion align_points(const std::vector<point> &pa, const std::vector<point> & ...@@ -462,8 +224,8 @@ quaternion align_points(const std::vector<point> &pa, const std::vector<point> &
M(2, 2) += a.m_z * b.m_z; M(2, 2) += a.m_z * b.m_z;
} }
// Now calculate N, a symmetric 4x4 Matrix // Now calculate N, a symmetric 4x4 matrix
SymmetricMatrix N(4); symmetric_matrix4x4<double> N(4);
N(0, 0) = M(0, 0) + M(1, 1) + M(2, 2); N(0, 0) = M(0, 0) + M(1, 1) + M(2, 2);
N(0, 1) = M(1, 2) - M(2, 1); N(0, 1) = M(1, 2) - M(2, 1);
...@@ -512,10 +274,10 @@ quaternion align_points(const std::vector<point> &pa, const std::vector<point> & ...@@ -512,10 +274,10 @@ quaternion align_points(const std::vector<point> &pa, const std::vector<point> &
double lambda = LargestDepressedQuarticSolution(C, D, E); double lambda = LargestDepressedQuarticSolution(C, D, E);
// calculate t = (N - λI) // calculate t = (N - λI)
Matrix t = N - IdentityMatrix(4) * lambda; matrix<double> t(N - identity_matrix(4) * lambda);
// calculate a Matrix of cofactors for t // calculate a matrix of cofactors for t
Matrix cf = Cofactors(t); auto cf = matrix_cofactors(t);
int maxR = 0; int maxR = 0;
for (int r = 1; r < 4; ++r) for (int r = 1; r < 4; ++r)
......
...@@ -25,6 +25,8 @@ ...@@ -25,6 +25,8 @@
*/ */
#include "cif++/symmetry.hpp" #include "cif++/symmetry.hpp"
#include "cif++/datablock.hpp"
#include "cif++/point.hpp"
#include <stdexcept> #include <stdexcept>
...@@ -34,13 +36,140 @@ namespace cif ...@@ -34,13 +36,140 @@ namespace cif
{ {
// -------------------------------------------------------------------- // --------------------------------------------------------------------
// Unfortunately, clipper has a different numbering scheme than CCP4
cell::cell(float a, float b, float c, float alpha, float beta, float gamma)
: m_a(a)
, m_b(b)
, m_c(c)
, m_alpha(alpha)
, m_beta(beta)
, m_gamma(gamma)
{
init();
}
cell::cell(const datablock &db)
{
auto &_cell = db["cell"];
cif::tie(m_a, m_b, m_c, m_alpha, m_beta, m_gamma) =
_cell.front().get("length_a", "length_b", "length_c", "angle_alpha", "angle_beta", "angle_gamma");
init();
}
void cell::init()
{
auto alpha = (m_alpha * kPI) / 180;
auto beta = (m_beta * kPI) / 180;
auto gamma = (m_gamma * kPI) / 180;
auto alpha_star = std::acos((std::cos(gamma) * std::cos(beta) - std::cos(alpha)) / (std::sin(beta) * std::sin(gamma)));
m_orthogonal = identity_matrix(3);
m_orthogonal(0, 0) = m_a;
m_orthogonal(0, 1) = m_b * std::cos(gamma);
m_orthogonal(0, 2) = m_c * std::cos(beta);
m_orthogonal(1, 1) = m_b * std::sin(gamma);
m_orthogonal(1, 2) = -m_c * std::sin(beta) * std::cos(alpha_star);
m_orthogonal(2, 2) = m_c * std::sin(beta) * std::sin(alpha_star);
m_fractional = inverse(m_orthogonal);
}
// --------------------------------------------------------------------
sym_op::sym_op(std::string_view s)
{
auto b = s.data();
auto e = b + s.length();
int rnri;
auto r = std::from_chars(b, e, rnri);
m_nr = rnri;
m_ta = r.ptr[1] - '0';
m_tb = r.ptr[2] - '0';
m_tc = r.ptr[3] - '0';
if (r.ec != std::errc() or rnri > 192 or r.ptr[0] != '_' or m_ta > 9 or m_tb > 9 or m_tc > 9)
throw std::invalid_argument("Could not convert string into sym_op");
}
// --------------------------------------------------------------------
transformation::transformation(const symop_data &data)
{
const auto &d = data.data();
m_rotation(0, 0) = d[0];
m_rotation(0, 1) = d[1];
m_rotation(0, 2) = d[2];
m_rotation(1, 0) = d[3];
m_rotation(1, 1) = d[4];
m_rotation(1, 2) = d[5];
m_rotation(2, 0) = d[6];
m_rotation(2, 1) = d[7];
m_rotation(2, 2) = d[8];
m_translation.m_x = d[9] == 0 ? 0 : 1.0 * d[9] / d[10];
m_translation.m_y = d[11] == 0 ? 0 : 1.0 * d[11] / d[12];
m_translation.m_y = d[13] == 0 ? 0 : 1.0 * d[13] / d[14];
}
// --------------------------------------------------------------------
spacegroup::spacegroup(int nr)
: m_nr(nr)
{
const size_t N = cif::kSymopNrTableSize;
int32_t L = 0, R = static_cast<int32_t>(N - 1);
while (L <= R)
{
int32_t i = (L + R) / 2;
if (cif::kSymopNrTable[i].spacegroup() < m_nr)
L = i + 1;
else
R = i - 1;
}
m_index = L;
for (size_t i = L; i < N and cif::kSymopNrTable[i].spacegroup() == m_nr; ++i)
emplace_back(cif::kSymopNrTable[i].symop().data());
}
std::string spacegroup::get_name() const
{
for (auto &s : kSpaceGroups)
{
if (s.nr == m_nr)
return s.name;
}
throw std::runtime_error("Spacegroup has an invalid number: " + std::to_string(m_nr));
}
point spacegroup::operator()(const point &pt, const cell &c, sym_op symop)
{
if (symop.m_nr < 1 or symop.m_nr > size())
throw std::out_of_range("symmetry operator number out of range");
transformation t = at(symop.m_nr - 1);
return pt;
}
// --------------------------------------------------------------------
// Unfortunately, clipper has a different numbering scheme than PDB
// for rotation numbers. So we created a table to map those. // for rotation numbers. So we created a table to map those.
// Perhaps a bit over the top, but hey.... // Perhaps a bit over the top, but hey....
// -------------------------------------------------------------------- // --------------------------------------------------------------------
int get_space_group_number(std::string spacegroup) int get_space_group_number(std::string_view spacegroup)
{ {
if (spacegroup == "P 21 21 2 A") if (spacegroup == "P 21 21 2 A")
spacegroup = "P 21 21 2 (a)"; spacegroup = "P 21 21 2 (a)";
...@@ -73,7 +202,7 @@ int get_space_group_number(std::string spacegroup) ...@@ -73,7 +202,7 @@ int get_space_group_number(std::string spacegroup)
{ {
for (size_t i = 0; i < kNrOfSpaceGroups; ++i) for (size_t i = 0; i < kNrOfSpaceGroups; ++i)
{ {
auto& sp = kSpaceGroups[i]; auto &sp = kSpaceGroups[i];
if (sp.xHM == spacegroup) if (sp.xHM == spacegroup)
{ {
result = sp.nr; result = sp.nr;
...@@ -83,14 +212,14 @@ int get_space_group_number(std::string spacegroup) ...@@ -83,14 +212,14 @@ int get_space_group_number(std::string spacegroup)
} }
if (result == 0) if (result == 0)
throw std::runtime_error("Spacegroup name " + spacegroup + " was not found in table"); throw std::runtime_error("Spacegroup name " + std::string(spacegroup) + " was not found in table");
return result; return result;
} }
// -------------------------------------------------------------------- // --------------------------------------------------------------------
int get_space_group_number(std::string spacegroup, space_group_name type) int get_space_group_number(std::string_view spacegroup, space_group_name type)
{ {
if (spacegroup == "P 21 21 2 A") if (spacegroup == "P 21 21 2 A")
spacegroup = "P 21 21 2 (a)"; spacegroup = "P 21 21 2 (a)";
...@@ -145,9 +274,9 @@ int get_space_group_number(std::string spacegroup, space_group_name type) ...@@ -145,9 +274,9 @@ int get_space_group_number(std::string spacegroup, space_group_name type)
// not found, see if we can find a match based on xHM name // not found, see if we can find a match based on xHM name
if (result == 0) if (result == 0)
throw std::runtime_error("Spacegroup name " + spacegroup + " was not found in table"); throw std::runtime_error("Spacegroup name " + std::string(spacegroup) + " was not found in table");
return result; return result;
} }
} } // namespace cif
...@@ -315,7 +315,10 @@ int main(int argc, char* const argv[]) ...@@ -315,7 +315,10 @@ int main(int argc, char* const argv[])
{ {
case State::skip: case State::skip:
if (line == "begin_spacegroup") if (line == "begin_spacegroup")
{
state = State::spacegroup; state = State::spacegroup;
cur = {};
}
break; break;
case State::spacegroup: case State::spacegroup:
......
...@@ -247,3 +247,67 @@ BOOST_AUTO_TEST_CASE(dh_q_1) ...@@ -247,3 +247,67 @@ BOOST_AUTO_TEST_CASE(dh_q_1)
BOOST_TEST(dh == angle, tt::tolerance(0.1f)); BOOST_TEST(dh == angle, tt::tolerance(0.1f));
} }
} }
// --------------------------------------------------------------------
BOOST_AUTO_TEST_CASE(symm_1)
{
cif::cell c(10, 10, 10);
cif::point p{ 1, 1, 1 };
cif::point f = fractional(p, c);
BOOST_TEST(f.m_x == 0.1f, tt::tolerance(0.01));
BOOST_TEST(f.m_y == 0.1f, tt::tolerance(0.01));
BOOST_TEST(f.m_z == 0.1f, tt::tolerance(0.01));
cif::point o = orthogonal(f, c);
BOOST_TEST(o.m_x == 1.f, tt::tolerance(0.01));
BOOST_TEST(o.m_y == 1.f, tt::tolerance(0.01));
BOOST_TEST(o.m_z == 1.f, tt::tolerance(0.01));
}
BOOST_AUTO_TEST_CASE(symm_2)
{
using namespace cif::literals;
auto symop = "1_555"_symop;
BOOST_TEST(symop.is_identity() == true);
}
BOOST_AUTO_TEST_CASE(symm_3)
{
using namespace cif::literals;
cif::spacegroup sg(18);
BOOST_TEST(sg.size() == 4);
BOOST_TEST(sg.get_name() == "P 21 21 2");
}
BOOST_AUTO_TEST_CASE(symm_4)
{
using namespace cif::literals;
// based on 2b8h
auto sg = cif::spacegroup(154); // p 32 2 1
auto c = cif::cell(107.516, 107.516, 338.487, 90.00, 90.00, 120.00);
cif::point a{ -8.688, 79.351, 10.439 }; // O6 NAG A 500
cif::point b{ -35.356, 33.693, -3.236 }; // CG2 THR D 400
cif::point sb( -6.916, 79.34, 3.236); // 4_565 copy of b
BOOST_TEST(distance(a, sg(a, c, "1_455"_symop)) == static_cast<float>(c.get_a()));
BOOST_TEST(distance(a, sg(a, c, "1_545"_symop)) == static_cast<float>(c.get_b()));
BOOST_TEST(distance(a, sg(a, c, "1_554"_symop)) == static_cast<float>(c.get_c()));
auto sb2 = sg(b, c, "4_565"_symop);
BOOST_TEST(sb.m_x == sb2.m_x);
BOOST_TEST(sb.m_y == sb2.m_y);
BOOST_TEST(sb.m_z == sb2.m_z);
BOOST_TEST(distance(a, sb2) == 7.42f);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment