Program Listing for File distance.hpp#
↰ Return to documentation for file (source/pele/distance.hpp)
#ifndef _PELE_DISTANCE_H
#define _PELE_DISTANCE_H
#include <cmath>
#include <stdexcept>
#include <type_traits>
#include "array.hpp"
/*
* References on round etc:
* http://www.cplusplus.com/reference/cmath/floor/
* http://www.cplusplus.com/reference/cmath/ceil/
* http://www.cplusplus.com/reference/cmath/round/
*/
// round is missing in visual studio
#ifdef _MSC_VER
inline double round(double r) {
return (r > 0.0) ? floor(r + 0.5) : ceil(r - 0.5);
}
#endif
/*
* Inspired by Lua - see:
* http://stackoverflow.com/questions/17035464/a-fast-method-to-round-a-double-to-a-32-bit-int-explained
* This method should be around three times as fast as normal rounding.
* Instead of half away from zero, this rounds half to even. But this shouldn't
* be a problem, since having a dx/box=0.5 means the particles are as far
* apart as possible.
* The temporary variable r_round is necessary for reinterpret_cast and should
* be optimized out.
*/
inline double round_fast(double r) {
double r_round = r + 6755399441055744.0;
return double(reinterpret_cast<int &>(r_round));
}
/*
* These classes and structs are used by the potentials to compute distances.
* They must have a member function get_rij() with signature
*
* void get_rij(double * r_ij, double const * const r1,
* double const * const r2)
*
* Where r1 and r2 are the position of the two atoms and r_ij is an array of
* size 3 which will be used to return the distance vector from r1 to r2.
*
* References:
* Used here: general reference on template meta-programming and recursive
* template functions:
* http://www.itp.phys.ethz.ch/education/hs12/programming_techniques
*/
namespace pele {
template <size_t IDX>
struct meta_dist {
static void f(double *const r_ij, double const *const r1,
double const *const r2) {
const static size_t k = IDX - 1;
r_ij[k] = r1[k] - r2[k];
meta_dist<k>::f(r_ij, r1, r2);
}
};
template <>
struct meta_dist<1> {
static void f(double *const r_ij, double const *const r1,
double const *const r2) {
r_ij[0] = r1[0] - r2[0];
}
};
template <size_t ndim>
struct cartesian_distance {
static const size_t _ndim = ndim;
inline void get_rij(double *const r_ij, double const *const r1,
double const *const r2) const {
static_assert(ndim > 0, "illegal box dimension");
meta_dist<ndim>::f(r_ij, r1, r2);
}
inline void put_atom_in_box(double *const xnew, const double *x) const {
// There is no box, so do nothing
for (size_t i = 0; i < ndim; ++i) {
xnew[i] = x[i];
}
}
inline void put_atom_in_box(double *const x) const {}
};
template <size_t IDX>
struct meta_periodic_distance {
static void f(double *const r_ij, double const *const r1,
double const *const r2, const double *box, const double *ibox) {
const static size_t k = IDX - 1;
r_ij[k] = r1[k] - r2[k];
r_ij[k] -= round_fast(r_ij[k] * ibox[k]) * box[k];
meta_periodic_distance<k>::f(r_ij, r1, r2, box, ibox);
}
};
template <>
struct meta_periodic_distance<1> {
static void f(double *const r_ij, double const *const r1,
double const *const r2, const double *box, const double *ibox) {
r_ij[0] = r1[0] - r2[0];
r_ij[0] -= round_fast(r_ij[0] * ibox[0]) * box[0];
}
};
template <size_t IDX>
struct meta_image {
static void f(double *const xnew, const double *x, const double *ibox,
const double *box) {
const static size_t k = IDX - 1;
xnew[k] = x[k] - round_fast(x[k] * ibox[k]) * box[k];
// Correction for values close to the box boundary
double half_box = 0.5 * box[k];
int outside = (xnew[k] < -half_box) - (xnew[k] > half_box);
xnew[k] += box[k] * outside;
meta_image<k>::f(xnew, x, ibox, box);
}
static void f(double *const x, const double *ibox, const double *box) {
const static size_t k = IDX - 1;
x[k] -= round_fast(x[k] * ibox[k]) * box[k];
// Correction for values close to the box boundary
double half_box = 0.5 * box[k];
int outside = (x[k] < -half_box) - (x[k] > half_box);
x[k] += box[k] * outside;
meta_image<k>::f(x, ibox, box);
}
};
template <>
struct meta_image<1> {
static void f(double *const xnew, const double *x, const double *ibox,
const double *box) {
xnew[0] = x[0] - round_fast(x[0] * ibox[0]) * box[0];
// Correction for values close to the box boundary
double half_box = 0.5 * box[0];
int outside = (xnew[0] < -half_box) - (xnew[0] > half_box);
xnew[0] += box[0] * outside;
}
static void f(double *const x, const double *ibox, const double *box) {
x[0] -= round_fast(x[0] * ibox[0]) * box[0];
// Correction for values close to the box boundary
double half_box = 0.5 * box[0];
int outside = (x[0] < -half_box) - (x[0] > half_box);
x[0] += box[0] * outside;
}
};
template <size_t ndim>
class periodic_distance {
public:
static const size_t _ndim = ndim;
double m_box[ndim];
double m_ibox[ndim];
periodic_distance(Array<double> const box) {
static_assert(ndim > 0, "illegal box dimension");
if (box.size() != ndim) {
throw std::invalid_argument("box.size() must be equal to ndim");
}
for (size_t i = 0; i < ndim; ++i) {
m_box[i] = box[i];
m_ibox[i] = 1 / box[i];
}
}
periodic_distance() {
static_assert(ndim > 0, "illegal box dimension");
throw std::runtime_error(
"The empty constructor is not available for periodic boundaries.");
}
inline void get_rij(double *const r_ij, double const *const r1,
double const *const r2) const {
meta_periodic_distance<ndim>::f(r_ij, r1, r2, m_box, m_ibox);
}
inline double *get_box() const { return m_box; }
inline double get_ndim() const { return ndim; }
inline void put_atom_in_box(double *const xnew, const double *x) const {
meta_image<ndim>::f(xnew, x, m_ibox, m_box);
}
inline void put_atom_in_box(double *const x) const {
meta_image<ndim>::f(x, m_ibox, m_box);
}
inline void put_in_box(Array<double> &coords) const {
const size_t N = coords.size();
for (size_t i = 0; i < N; i += ndim) {
put_atom_in_box(&coords[i]);
}
}
};
template <size_t IDX>
struct meta_leesedwards_distance {
static void f(double *const r_ij, double const *const r1,
double const *const r2, const double *box, const double *ibox,
const double &dx) {
const static size_t k = IDX - 1;
r_ij[k] = r1[k] - r2[k];
r_ij[k] -= round_fast(r_ij[k] * ibox[k]) * box[k];
meta_leesedwards_distance<k>::f(r_ij, r1, r2, box, ibox, dx);
}
};
template <>
struct meta_leesedwards_distance<2> {
static void f(double *const r_ij, double const *const r1,
double const *const r2, const double *box, const double *ibox,
const double &dx) {
// Calculate difference
r_ij[0] = r1[0] - r2[0];
r_ij[1] = r1[1] - r2[1];
// Apply Lees-Edwards boundary conditions in y-direction
double round_y = round_fast(r_ij[1] * ibox[1]);
r_ij[0] = r_ij[0] - round_y * dx;
r_ij[1] = r_ij[1] - round_y * box[1];
// Apply periodic boundary conditions in x-direction
r_ij[0] -= round_fast(r_ij[0] * ibox[0]) * box[0];
}
};
template <size_t IDX>
struct meta_leesedwards_image {
static void f(double *const xnew, const double *x, const double *ibox,
const double *box, const double dx) {
const static size_t k = IDX - 1;
xnew[k] = x[k] - round_fast(x[k] * ibox[k]) * box[k];
// Correction for values close to the box boundary
double half_box = 0.5 * box[k];
int outside = (xnew[k] < -half_box) - (xnew[k] > half_box);
xnew[k] += box[k] * outside;
meta_leesedwards_image<k>::f(xnew, x, ibox, box, dx);
}
static void f(double *const x, const double *ibox, const double *box,
const double dx) {
const static size_t k = IDX - 1;
x[k] -= round_fast(x[k] * ibox[k]) * box[k];
// Correction for values close to the box boundary
double half_box = 0.5 * box[k];
int outside = (x[k] < -half_box) - (x[k] > half_box);
x[k] += box[k] * outside;
meta_leesedwards_image<k>::f(x, ibox, box, dx);
}
};
template <>
struct meta_leesedwards_image<2> {
static void f(double *const xnew, const double *x, const double *ibox,
const double *box, const double dx) {
// Apply Lees-Edwards boundary conditions in y-direction
double round_y = round_fast(x[1] * ibox[1]);
xnew[0] = x[0] - round_y * dx;
xnew[1] = x[1] - round_y * box[1];
// Correction for values close to the box boundary
double half_box = 0.5 * box[1];
int outside = (xnew[1] < -half_box) - (xnew[1] > half_box);
xnew[1] += box[1] * outside;
xnew[0] += dx * outside;
// Apply periodic boundary conditions in x-direction
xnew[0] -= round_fast(xnew[0] * ibox[0]) * box[0];
// Correction for values close to the box boundary
half_box = 0.5 * box[0];
outside = (xnew[0] < -half_box) - (xnew[0] > half_box);
xnew[0] += box[0] * outside;
}
static void f(double *const x, const double *ibox, const double *box,
const double dx) {
// Apply Lees-Edwards boundary conditions in y-direction
double round_y = round_fast(x[1] * ibox[1]);
x[0] -= round_y * dx;
x[1] -= round_y * box[1];
// Correction for values close to the box boundary
double half_box = 0.5 * box[1];
int outside = (x[1] < -half_box) - (x[1] > half_box);
x[1] += box[1] * outside;
x[0] += dx * outside;
// Apply periodic boundary conditions in x-direction
x[0] -= round_fast(x[0] * ibox[0]) * box[0];
// Correction for values close to the box boundary
half_box = 0.5 * box[0];
outside = (x[0] < -half_box) - (x[0] > half_box);
x[0] += box[0] * outside;
}
};
template <size_t ndim>
class leesedwards_distance {
private:
double m_box[ndim];
double m_ibox[ndim];
double
m_dx;
public:
static const size_t _ndim = ndim;
leesedwards_distance(Array<double> const box, const double shear)
: m_dx(fmod(shear, 1.0) * box[1]) {
static_assert(ndim >= 2,
"box dimension must be at least 2 for "
"lees-edwards boundary conditions");
if (box.size() != ndim) {
throw std::invalid_argument("box.size() must be equal to ndim");
}
for (size_t i = 0; i < ndim; ++i) {
m_box[i] = box[i];
m_ibox[i] = 1.0 / box[i];
}
}
leesedwards_distance() {
static_assert(ndim >= 2,
"box dimension must be at least 2 for "
"lees-edwards boundary conditions");
throw std::runtime_error(
"The empty constructor is not available for Lees-Edwards boundaries.");
}
inline void get_rij(double *const r_ij, double const *const r1,
double const *const r2) const {
meta_leesedwards_distance<ndim>::f(r_ij, r1, r2, m_box, m_ibox, m_dx);
}
inline void put_atom_in_box(double *const xnew, const double *x) const {
meta_leesedwards_image<ndim>::f(xnew, x, m_ibox, m_box, m_dx);
}
inline void put_atom_in_box(double *const x) const {
meta_leesedwards_image<ndim>::f(x, m_ibox, m_box, m_dx);
}
inline void put_in_box(Array<double> &coords) const {
const size_t N = coords.size();
for (size_t i = 0; i < N; i += ndim) {
put_atom_in_box(&coords[i]);
}
}
};
/*
* Interface classes to pass a generic non template pointer to DistanceInterface
* (this should reduce the need for templating where best performance is not
* essential)
*/
class DistanceInterface {
protected:
public:
virtual void get_rij(double *const r_ij, double const *const r1,
double const *const r2) const = 0;
virtual ~DistanceInterface() {}
};
template <size_t ndim>
class CartesianDistanceWrapper : public DistanceInterface {
protected:
cartesian_distance<ndim> _dist;
public:
static const size_t _ndim = ndim;
inline void get_rij(double *const r_ij, double const *const r1,
double const *const r2) const {
_dist.get_rij(r_ij, r1, r2);
}
};
template <size_t ndim>
class PeriodicDistanceWrapper : public DistanceInterface {
protected:
periodic_distance<ndim> _dist;
public:
static const size_t _ndim = ndim;
PeriodicDistanceWrapper(Array<double> const box) : _dist(box){};
inline void get_rij(double *const r_ij, double const *const r1,
double const *const r2) const {
_dist.get_rij(r_ij, r1, r2);
}
};
template <size_t ndim>
class LeesEdwardsDistanceWrapper : public DistanceInterface {
protected:
leesedwards_distance<ndim> _dist;
public:
static const size_t _ndim = ndim;
LeesEdwardsDistanceWrapper(Array<double> const box, const double shear)
: _dist(box, shear){};
inline void get_rij(double *const r_ij, double const *const r1,
double const *const r2) const {
_dist.get_rij(r_ij, r1, r2);
}
};
} // namespace pele
#endif // #ifndef _PELE_DISTANCE_H