Program Listing for File pspin_spherical.hpp#

↰ Return to documentation for file (source/pele/pspin_spherical.hpp)

#ifndef _P_SPIN_SPHERICAL_H_
#define _P_SPIN_SPHERICAL_H_

#include <cassert>
#include <cmath>
#include <iomanip>
#include <iostream>

#include "array.hpp"
#include "base_potential.hpp"
#include "pele/combination.hpp"
#include "pele/meta_pow.hpp"

namespace pele {

template <size_t N>
struct Factorial {
  enum { value = N * Factorial<N - 1>::value };
};

template <>
struct Factorial<0> {
  enum { value = 1 };
};

template <size_t p>
class MeanFieldPSpinSpherical : public BasePotential {
 protected:
  pele::Array<double> m_interactions, m_spins;
  pele::Array<size_t> m_indexes;
  size_t m_N;
  double m_tol, m_sqrt_N, m_N_prf;
  static const size_t m_p = p;
  static const size_t m_pf = Factorial<m_p>::value;
  pele::combination_generator<size_t *> m_combination_generator;
  pele::combination_generator<size_t *> m_combination_generator_grad;
  pele::combination_generator<size_t *> m_combination_generator_hess;
  inline size_t m_get_index(size_t *comb) {
    size_t idx = comb[0];
    size_t d = m_N;
    for (size_t i = 1; i < m_p; ++i) {
      idx += comb[i] * d;
      d *= m_N;
    }
    return idx;
  }

  // this function normalizes the spin vector and orthogonalises the gradient to
  // it
  inline void m_orthogonalize(Array<double> const &spins_,
                              Array<double> &grad) {
    if (spins_.size() != grad.size()) {
      throw std::invalid_argument("grad.size() be the same as spin_.size()");
    }
    Array<double> norm_spins = spins_.copy();
    norm_spins /= norm(spins_);
    double dot_prod = dot(grad, norm_spins);
    for (size_t i = 0; i < grad.size(); ++i) {
      grad[i] -= dot_prod * norm_spins[i];
    }
    dot_prod = dot(grad, norm_spins);
    bool success = std::abs(dot_prod) < m_tol;

    while (success == false) {
      for (size_t i = 0; i < grad.size(); ++i) {
        grad[i] -= dot_prod * norm_spins[i];
      }
      dot_prod = dot(grad, norm_spins);
      success = std::abs(dot_prod) < m_tol;
    }
  }

  inline void m_normalize_spins(Array<double> &spins_) {
    spins_ /= (norm(spins_) / m_sqrt_N);
  }

 public:
  virtual ~MeanFieldPSpinSpherical() {}
  // right now interactions_ is of size N**p which is not obtimal has it
  // contains all the permutations of the interactions this is fast but costs a
  // lot in terms of memory
  MeanFieldPSpinSpherical(pele::Array<double> interactions_, size_t nspins_,
                          double tol_)
      : m_interactions(interactions_.copy()),
        m_spins(nspins_),
        m_indexes(nspins_),
        m_N(nspins_),
        m_tol(tol_),
        m_sqrt_N(sqrt((double)m_N)),
        m_N_prf(m_p > 2 ? std::pow(m_sqrt_N, m_p - 1.) : 1),
        m_combination_generator(m_indexes.begin(), m_indexes.end(), m_p),
        m_combination_generator_grad(m_indexes.begin(), m_indexes.end(),
                                     m_p - 1),
        m_combination_generator_hess(m_indexes.begin(), m_indexes.end(),
                                     m_p - 2) {
    static_assert(m_p >= 2, "MeanFieldPSpinSpherical: p cannot be less than 2");
    if ((size_t)std::pow(m_N, m_p) != m_interactions.size()) {
      // note: in an ideal implementation interactions should actually be of
      // size (N r)
      throw std::invalid_argument(
          "MeanFieldPSpinSpherical: interactions is of the wrong size");
    }
    for (size_t i = 0; i < m_N; ++i) {
      m_indexes[i] = i;
    }
  }
  inline double add_energy(pele::Array<double> const &x);
  inline double add_energy_gradient(pele::Array<double> const &x,
                                    pele::Array<double> &grad);
  inline double add_energy_gradient_hessian(pele::Array<double> const &x,
                                            pele::Array<double> &grad,
                                            pele::Array<double> &hess);
  virtual inline double get_energy(pele::Array<double> const &x);
  virtual inline double get_energy_gradient(pele::Array<double> const &x,
                                            pele::Array<double> &grad);
  virtual void numerical_gradient(Array<double> const &x, Array<double> &grad,
                                  double eps = 1e-6);
  virtual void numerical_hessian(Array<double> const &x, Array<double> &hess,
                                 double eps = 1e-6);
};

template <size_t p>
inline double MeanFieldPSpinSpherical<p>::add_energy(
    pele::Array<double> const &x) {
  if (x.size() != m_N) {
    throw std::invalid_argument("x.size() be the same as m_N");
  }
  double e = 0;
  size_t comb[m_p];
  bool go_on = true;
  while (go_on) {
    go_on = m_combination_generator(comb);
    double sigmaprod = 1;
    for (size_t i = 0; i < m_p; ++i) {
      sigmaprod *= x[comb[i]];
    }
    e -= m_interactions[this->m_get_index(comb)] * sigmaprod;
  }
  /*std::copy(x.data(),x.data()+m_N, m_spins.data()); //lagrange
  e += x[m_N] * (dot(m_spins, m_spins) - m_N);*/ //lagrange
  return e / m_N_prf;
}

template <size_t p>
inline double MeanFieldPSpinSpherical<p>::add_energy_gradient(
    pele::Array<double> const &x, pele::Array<double> &grad) {
  size_t comb_full[m_p];
  size_t combg[m_p - 1];
  for (size_t i = 0; i < m_N; ++i) {
    double g = 0;
    bool go_on = true;
    while (go_on) {
      go_on = m_combination_generator_grad(combg);
      bool repeated_idx =
          std::find(combg, combg + m_p - 1, i) != combg + m_p - 1;
      if (!repeated_idx) {
        std::copy(combg, combg + m_p - 1, comb_full);
        comb_full[m_p - 1] = i;
        double sigmaprod = 1;
        for (size_t j = 0; j < m_p - 1; ++j) {
          sigmaprod *= x[combg[j]];
        }
        g -= m_interactions[this->m_get_index(comb_full)] * sigmaprod;
      }
    }
    // now set the gradient element i
    /*grad[i] = g + 2 * x[m_N] * x[i];*/  // lagrange
    grad[i] = g / m_N_prf;  // rr here we might want to divide by m_p because in
                            // the gradient we have only fact(m_p-1) terms
  }
  // now normalize the spin vector and orthogonalise the gradient to it
  double e = this->add_energy(x);
  /*grad[m_N] = dot(m_spins, m_spins) - m_N;*/  // lagrange
  this->m_orthogonalize(x, grad);               // rr
  return e;
}

template <size_t p>
inline double MeanFieldPSpinSpherical<p>::get_energy(
    pele::Array<double> const &x) {
  Array<double> xnew = x.copy();
  this->m_normalize_spins(xnew);  // normalize spins vector
  return this->add_energy(xnew);
}

template <size_t p>
inline double MeanFieldPSpinSpherical<p>::get_energy_gradient(
    pele::Array<double> const &x, pele::Array<double> &grad) {
  Array<double> xnew = x.copy();
  this->m_normalize_spins(xnew);  // normalize spins vector
  return this->add_energy_gradient(xnew, grad);
}

template <size_t p>
void MeanFieldPSpinSpherical<p>::numerical_gradient(Array<double> const &x,
                                                    Array<double> &grad,
                                                    double eps) {
  Array<double> xnew = x.copy();
  this->m_normalize_spins(xnew);  // normalize spins vector
  BasePotential::numerical_gradient(xnew, grad, eps);
  this->m_orthogonalize(xnew, grad);
}

template <size_t p>
void MeanFieldPSpinSpherical<p>::numerical_hessian(Array<double> const &x,
                                                   Array<double> &hess,
                                                   double eps) {
  if (hess.size() != x.size() * x.size()) {
    throw std::invalid_argument("hess.size() be the same as x.size()*x.size()");
  }
  Array<double> xnew = x.copy();
  this->m_normalize_spins(xnew);  // normalize spins vector
  size_t const N = xnew.size();

  Array<double> gplus(xnew.size());
  Array<double> gminus(xnew.size());

  for (size_t i = 0; i < xnew.size(); ++i) {
    double xbackup = xnew[i];
    xnew[i] -= eps;
    add_energy_gradient(xnew, gminus);
    xnew[i] += 2. * eps;
    add_energy_gradient(xnew, gplus);
    xnew[i] = xbackup;

    for (size_t j = 0; j < xnew.size(); ++j) {
      hess[N * i + j] = (gplus[j] - gminus[j]) / (2. * eps);
    }
  }
}

template <size_t p>
inline double MeanFieldPSpinSpherical<p>::add_energy_gradient_hessian(
    Array<double> const &x, Array<double> &grad, Array<double> &hess) {
  size_t comb_full[m_p];
  size_t combh[m_p - 2];

  if (m_p == 2) {
    // this handles m_p = 2 correctly
    hess.assign(0);
  } else {
    for (size_t i = 0; i < m_N; ++i) {
      for (size_t j = i; j < m_N; ++j) {
        double h = 0;
        if (i != j) {
          bool go_on = true;
          while (go_on) {
            go_on = m_combination_generator_hess(combh);
            bool repeated_idx =
                (std::find(combh, combh + m_p - 2, i) != combh + m_p - 2 ||
                 std::find(combh, combh + m_p - 2, j) != combh + m_p - 2);
            if (!repeated_idx) {
              std::copy(combh, combh + m_p - 2, comb_full);
              comb_full[m_p - 2] = i;
              comb_full[m_p - 1] = j;
              double sigmaprod = 1;
              for (size_t k = 0; k < m_p - 2; ++k) {
                sigmaprod *= x[combh[k]];
              }
              h -= m_interactions[this->m_get_index(comb_full)] * sigmaprod;
            }
          }
        }
        // now set the hessian element ij
        // rr
        hess[i + (m_N + 1) * j] = h;
        hess[j + (m_N + 1) * i] = h;
        // lagrange
        /*hess[i+(m_N+1)*j] = h + 2 * x[m_N] * (double) (i==j);
        hess[j+(m_N+1)*i] = hess[i+(m_N+1)*j];*/
      }
      // rr
      hess[(m_N + 1) * i + m_N] = 0.;
      hess[(m_N + 1) * m_N + i] = 0.;
      // lagrange
      /*hess[(m_N+1)*i+m_N] = 2 * x[i];
      hess[(m_N+1)*m_N+i] = 2 * x[i];*/
    }
    hess[(m_N + 1) * (m_N + 1)] = 0.;
  }
  // now I need to change the basis of the matrix
  // I can do so by performing svd, orthogonalizing each vector wrt the radial
  // vector and rebuilding it
  return this->add_energy_gradient(x, grad);
}

}  // namespace pele
#endif