Program Listing for File lattice.hpp

Return to documentation for file (src/lattice.hpp)

/* This file is part of brille.

Copyright © 2019,2020 Greg Tucker <greg.tucker@stfc.ac.uk>

brille is free software: you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free
Software Foundation, either version 3 of the License, or (at your option)
any later version.

brille is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with brille. If not, see <https://www.gnu.org/licenses/>.            */


#ifndef BRILLE_LATTICE_CLASS_H_
#define BRILLE_LATTICE_CLASS_H_
#include <assert.h>
#include <vector>
#include "primitive.hpp"
#include "basis.hpp"
#include "array2.hpp"
namespace brille {

// forward declare the two types of lattices so that they can be mutually-referential
class Lattice;
class Direct;
class Reciprocal;

enum class AngleUnit { not_provided, radian, degree, pi };

template<class T, class I> void latmat_to_lenang(const T* latmat, const I c, const I r, T* len, T* ang){
  T n[9];
  // compute the dot product of each row with itself
  for (int i=0; i<3; ++i)  for (int j=0; j<3; ++j)  len[i] += latmat[i*c+j*r]*latmat[i*c+j*r];
  // the lattice vector lengths are the square root of this
  for (int i=0; i<3; ++i) len[i] = std::sqrt(len[i]);
  // normalize the row vectors, leaving only angle information (n is contiguous but latmat may not be!)
  for (int i=0; i<3; ++i) for (int j=0; j<3; ++j) n[i*3+j] = latmat[i*c+j*r]/len[i];
  // take the dot product between cyclically permuted rows: 0=1⋅2, 1=2⋅0, 2=0⋅1
  for (int i=0; i<3; ++i) for (int j=0; j<3; ++j)  ang[i] += n[3*((i+1)%3)+j]*n[3*((i+2)%3)+j];
  // the lattice angles are the arccosines of these dot products of normalized lattice vectors
  for (int i=0; i<3; ++i) ang[i] = std::acos(ang[i]);
}
template<class T> void latmat_to_lenang(const brille::Array2<T>& lm, T* len, T* ang){
  auto st = lm.stride();
  latmat_to_lenang(lm.ptr(0), st[0], st[1], len, ang);
}

class Lattice{
protected:
  std::array<double,3> len;
  std::array<double,3> ang;
  double volume;
  Bravais bravais;
  Symmetry spgsym;
  PointSymmetry ptgsym;
  Basis basis;
protected:
  double unitvolume() const;
  Lattice inner_star() const;
  template<class I>
  void set_len_pointer(const double *lvec, const I span){
    for (int i=0;i<3;i++) this->len[i] = lvec[i*span];
  }
  template<class I>
  void set_ang_pointer(const double *avec, const I span, const AngleUnit angle_unit){
    AngleUnit au = angle_unit;
    if (au == AngleUnit::not_provided){
      double minang = (std::numeric_limits<double>::max)();
      double maxang = (std::numeric_limits<double>::lowest)();
      for (int i=0; i<3; ++i){
        if (avec[i*span] < minang) minang = avec[i*span];
        if (avec[i*span] > maxang) maxang = avec[i*span];
      }
      if (minang < 0.) throw std::runtime_error("Unexpected negative inter-facial cell angle");
      // 1 is not a good separator between π-radian and radian, since 1 radian ≈ 57.3°
      // au = (maxang < 1.0) ? AngleUnit::pi : (maxang < brille::pi) ? AngleUnit::radian : AngleUnit::degree;
      au = (maxang < brille::pi) ? AngleUnit::radian : AngleUnit::degree;
    }
    double conversion = (AngleUnit::radian == au) ? 1.0 : brille::pi/((AngleUnit::degree == au) ? 180.0 : 1.0);
    for (int i=0;i<3;i++) this->ang[i] = avec[i*span]*conversion;
  }
  // void set_len_scalars(const double, const double, const double);
  // void set_ang_scalars(const double, const double, const double, const AngleUnit);
  void check_ang(const AngleUnit);
  void check_hall_number(const int h);
  void check_IT_name(const std::string& itname, const std::string& choice="");
public:
  Lattice(const std::array<double,3>& l, const std::array<double,3>& a,
          const Bravais b, const Symmetry sgs, const PointSymmetry pgs,
          const Basis base):
    len(l), ang(a), bravais(b), spgsym(sgs), ptgsym(pgs), basis(base) {
      this->volume = this->calculatevolume();
    }
  Lattice(const double *, const int h=1);
  Lattice(const brille::Array2<double>& latmat, const std::vector<std::array<double,3>>& pos, const std::vector<unsigned long>& typ, const Symmetry& sym){
    double l[3]={0,0,0}, a[3]={0,0,0};
    latmat_to_lenang(latmat,l,a);
    this->set_len_pointer(l,1);
    this->set_ang_pointer(a,1, AngleUnit::radian);
    this->volume=this->calculatevolume();
    this->set_basis(pos,typ);
    this->set_spacegroup_symmetry(sym);
  }
  template<class I>//, typename=typename std::enable_if<std::is_integral<I>::value>::type>
  Lattice(const double * latmat, std::vector<I>& strides, const int h){
    double l[3]={0,0,0}, a[3]={0,0,0};
    latmat_to_lenang(latmat,strides[0]/sizeof(double),strides[1]/sizeof(double),l,a);
    this->set_len_pointer(l,1);
    this->set_ang_pointer(a,1, AngleUnit::radian);
    this->volume=this->calculatevolume();
    this->check_hall_number(h);
  }
  template<class I>//, typename=typename std::enable_if<std::is_integral<I>::value>::type>
  Lattice(const double * lengths, std::vector<I>& lenstrides, const double * angles, std::vector<I>& angstrides, const int h, const AngleUnit au=AngleUnit::not_provided){
    this->set_len_pointer(lengths,lenstrides[0]/sizeof(double));
    this->set_ang_pointer(angles,angstrides[0]/sizeof(double), au);
    this->volume=this->calculatevolume();
    this->check_hall_number(h);
  }
  Lattice(const double *, const double *, const int h=1, const AngleUnit au=AngleUnit::not_provided);
  Lattice(const double la=1.0, const double lb=1.0, const double lc=1.0, const double al=brille::halfpi, const double bl=brille::halfpi, const double cl=brille::halfpi, const int h=1);
  Lattice(const double *, const std::string&, const std::string& choice="");
  Lattice(const double *, const double *, const std::string&, const std::string& choice="", const AngleUnit au=AngleUnit::not_provided);
  template<class I>//, typename=typename std::enable_if<std::is_integral<I>::value>::type>
  Lattice(const double * latmat, std::vector<I>& strides, const std::string& itname, const std::string& choice=""){
    double l[3]={0,0,0}, a[3]={0,0,0};
    latmat_to_lenang(latmat,strides[0]/sizeof(double),strides[1]/sizeof(double),l,a);
    this->set_len_pointer(l,1);
    this->set_ang_pointer(a,1, AngleUnit::radian);
    this->volume=this->calculatevolume();
    this->check_IT_name(itname, choice);
  }
  template<class I>//, typename=typename std::enable_if<std::is_integral<I>::value>::type>
  Lattice(const double * lengths, std::vector<I>& lenstrides, const double * angles, std::vector<I>& angstrides, const std::string& itname, const std::string& choice="", const AngleUnit au=AngleUnit::not_provided){
    this->set_len_pointer(lengths,lenstrides[0]/sizeof(double));
    this->set_ang_pointer(angles,angstrides[0]/sizeof(double), au);
    this->volume=this->calculatevolume();
    this->check_IT_name(itname, choice);
  }
  Lattice(const double, const double, const double, const double, const double, const double, const std::string&, const std::string& choice="");
  virtual ~Lattice() = default;
  double get_a     () const {return len[0];}
  double get_b     () const {return len[1];}
  double get_c     () const {return len[2];}
  double get_alpha () const {return ang[0];}
  double get_beta  () const {return ang[1];}
  double get_gamma () const {return ang[2];}
  double get_volume() const {return volume;}
  double calculatevolume();
  void get_metric_tensor(double * mt) const ;
  void get_covariant_metric_tensor(double *mt) const ;
  void get_contravariant_metric_tensor(double *mt) const ;
  std::vector<double> get_metric_tensor() const ;
  std::vector<double> get_covariant_metric_tensor() const ;
  std::vector<double> get_contravariant_metric_tensor() const ;
  // some functions don't logically make sense for this base class, but
  // do for the derived classes. define them here for funsies
  bool issame(const Lattice&) const; // this should really have a tolerance
  bool isapprox(const Lattice&) const;
  int ispermutation(const Lattice&) const;
  virtual void print();
  virtual std::string string_repr();
  Bravais get_bravais_type() const { return bravais; }
  Bravais set_bravais_type(const Bravais b) {
    this->bravais = b;
    return this->get_bravais_type();
  }
  Symmetry get_spacegroup_symmetry(const int time_reversal=0) const {
    if (time_reversal && !spgsym.has_space_inversion()){
      Symmetry gens = spgsym.generators();
      Motion<int,double> space_inversion({{-1,0,0, 0,-1,0, 0,0,-1}},{{0.,0.,0.}});
      gens.add(space_inversion);
      return gens.generate();
    }
    return spgsym;
  }
  Symmetry set_spacegroup_symmetry(const Symmetry& gens){
    spgsym = gens.generate();
    bravais = spgsym.getcentring();
    ptgsym = PointSymmetry(get_unique_rotations(spgsym.getallr(),0));
    return spgsym;
  }
  PointSymmetry get_pointgroup_symmetry(const int time_reversal=0) const {
    if (time_reversal && !ptgsym.has_space_inversion()){
      // time_reversal == space_inversion. requested but not present
      // get the generators of the pointgroup
      PointSymmetry gens = ptgsym.generators();
      // add time-reversal/space-inversion
      std::array<int,9> trsi{{-1,0,0, 0,-1,0, 0,0,-1}};
      gens.add(trsi);
      // generate the new pointgroup
      return gens.generate();
    } else {
      return ptgsym;
    }
  }
  bool has_space_inversion() const { return ptgsym.has_space_inversion(); }
  Basis get_basis() const {return basis; }
  //template <class R, class II>
  Basis set_basis(const std::vector<std::array<double,3>>& pos, const std::vector<unsigned long>& typ) {
    this->basis = Basis(pos, typ);
    return this->get_basis();
  }
  Basis set_basis(const Basis& b) {
    this->basis = b;
    return this->get_basis();
  }
};

class Direct: public Lattice{
public:
  template<class ...Types> Direct(Types ... args): Lattice(args...){}
  Direct(const Lattice& lat): Lattice(lat){}
  Reciprocal star() const;
  void get_xyz_transform(double*) const;
  void get_xyz_transform(double*, const size_t, const size_t) const;
  template<class I> void get_xyz_transform(double*, std::vector<I>&) const;
  std::vector<double> get_xyz_transform() const;
  void get_inverse_xyz_transform(double*) const;
  void get_inverse_xyz_transform(double*, const size_t, const size_t) const;
  template<class I> void get_inverse_xyz_transform(double*, std::vector<I>&) const;
  std::vector<double> get_inverse_xyz_transform() const;
  void get_lattice_matrix(double*) const;
  void get_lattice_matrix(double*, const size_t, const size_t) const;
  template<class I> void get_lattice_matrix(double*, std::vector<I>&) const;
  bool isstar(const Direct&) const;
  bool isstar(const Reciprocal&) const;
  void print() override;
  std::string string_repr() override;
  Direct primitive(void) const;
};
class Reciprocal: public Lattice{
public:
  template<class ...Types> Reciprocal(Types ... args): Lattice(args...){}
  Reciprocal(const Lattice& lat): Lattice(lat){}
  Direct star() const;
  void get_B_matrix(double*) const;
  void get_B_matrix(double*, const size_t, const size_t) const;
  template<class I> void get_B_matrix(double*, std::vector<I>&) const;
  void get_xyz_transform(double*) const;
  void get_xyz_transform(double*, const size_t, const size_t) const;
  template<class I> void get_xyz_transform(double*, std::vector<I>&) const;
  std::vector<double> get_xyz_transform() const;
  void get_inverse_xyz_transform(double*) const;
  void get_inverse_xyz_transform(double*, const size_t, const size_t) const;
  template<class I> void get_inverse_xyz_transform(double*, std::vector<I>&) const;
  std::vector<double> get_inverse_xyz_transform() const;
  void get_lattice_matrix(double*) const;
  void get_lattice_matrix(double*, const size_t, const size_t) const;
  template<class I> void get_lattice_matrix(double*, std::vector<I>&) const;
  bool isstar(const Reciprocal&) const;
  bool isstar(const Direct&) const;
  void print() override;
  std::string string_repr() override;
  Reciprocal primitive(void) const;
};

template <typename T> struct LatticeTraits{
  using type = void;
  using star = void;
};
#ifndef DOXYGEN_SHOULD_SKIP_THIS
template<> struct LatticeTraits<Direct>{
  using type = Direct;
  using star = Reciprocal;
};
template<> struct LatticeTraits<Reciprocal>{
  using type = Reciprocal;
  using star = Direct;
};
#endif

}
#endif