Program Listing for File utils.hpp

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

//
// Created by Zack Williams on 01/12/2020.
//

#ifndef UW12_UTILS_HPP
#define UW12_UTILS_HPP

#include <vector>

#include "linalg.hpp"

namespace uw12::utils {
inline auto square(const linalg::Vec &vec, const double factor = 1) {
  const auto n_1 = linalg::n_elem(vec);
  const auto n_2 = static_cast<size_t>(std::sqrt(8 * n_1 - 1) / 2);

  if (n_2 * (n_2 + 1) / 2 != n_1) {
    throw std::logic_error("vector must be of length n(n+1)/2");
  }

  auto matrix = linalg::mat(n_2, n_2);
  size_t idx = 0;
  for (size_t i = 0; i < n_2; ++i) {
    for (size_t j = 0; j <= i; ++j) {
      if (i != j) {
        matrix(i, j) = factor * vec(idx);
        matrix(j, i) = factor * vec(idx);
      } else {
        matrix(i, i) = vec(idx);
      }
      idx++;
    }
  }

  assert(idx == n_1);

  return matrix;
}

inline auto lower(const linalg::Mat &mat, const double factor = 1) {
  const auto n_row = linalg::n_rows(mat);
  if (!linalg::is_square(mat)) {
    throw std::logic_error("matrix is not square");
  }
  if (!linalg::is_symmetric(mat)) {
    throw std::logic_error("matrix is not symmetric");
  }

  linalg::Vec vec(n_row * (n_row + 1) / 2);
  size_t idx = 0;
  for (size_t i = 0; i < n_row; ++i) {
    for (size_t j = 0; j < i; ++j) {
      vec(idx++) = factor * mat(i, j);
    }
    vec(idx++) = mat(i, i);
  }

  assert(idx == n_row * (n_row + 1) / 2);

  return vec;
}

using MatVec = std::vector<linalg::Mat>;

inline auto operator*(const MatVec &object, const double factor) {
  MatVec result(object.size());
  for (size_t i = 0; i < object.size(); ++i) {
    result[i] = factor * object[i];
  }
  return result;
}

inline auto operator*(const double factor, const MatVec &object) {
  return object * factor;
}


inline auto &operator+=(MatVec &lhs, const MatVec &rhs) {
  const auto length = lhs.size();
  if (rhs.size() != length) {
    throw std::logic_error("containers are of different sizes");
  }

  for (size_t i = 0; i < length; ++i) {
    if (linalg::n_rows(lhs[i]) != linalg::n_rows(rhs[i]) ||
        linalg::n_cols(lhs[i]) != linalg::n_cols(rhs[i])) {
      throw std::logic_error("matrices are of different sizes");
    }

    lhs[i] = lhs[i] + rhs[i];
  }
  return lhs;
}

inline auto operator+(MatVec left, const MatVec &right) {
  left += right;
  return left;
}


using OccupationVector = linalg::Vec;

using Orbitals = MatVec;

using Occupations = std::vector<OccupationVector>;

using DensityMatrix = MatVec;

using FockMatrix = MatVec;

struct FockMatrixAndEnergy {
  FockMatrix fock;
  double energy;
};

inline auto operator*(const FockMatrixAndEnergy &fock, const double factor)
    -> FockMatrixAndEnergy {
  return {factor * fock.fock, factor * fock.energy};
}

inline auto operator*(const double factor, const FockMatrixAndEnergy &fock) {
  return fock * factor;
}


inline auto operator+=(
    FockMatrixAndEnergy &lhs, const FockMatrixAndEnergy &rhs
) {
  lhs.fock += rhs.fock;
  lhs.energy += rhs.energy;

  return lhs;
}

inline auto operator+(FockMatrixAndEnergy lhs, const FockMatrixAndEnergy &rhs) {
  lhs += rhs;
  return lhs;
}


template <typename T>
auto nearly_zero(T value, int epsilon = 4) {
  return std::abs(value) <= epsilon * std::numeric_limits<T>::epsilon();
}

template <typename T>
auto spin_channels(const T &object) {
  const size_t n_spin = object.size();
  if (n_spin < 1 || n_spin > 2) {
    throw std::logic_error("invalid number of spin channels");
  }

  return n_spin;
}

inline auto symmetrise_fock(FockMatrixAndEnergy fock) {
  const auto n_spin = spin_channels(fock.fock);

  for (size_t sigma = 0; sigma < n_spin; sigma++) {
    fock.fock[sigma] =
        0.5 * (fock.fock[sigma] + linalg::transpose(fock.fock[sigma]));
  }

  return fock;
}

inline auto freeze_core(
    const Orbitals &orbitals, const std::vector<size_t> &n_active
) {
  const auto n_spin = spin_channels(orbitals);
  if (spin_channels(n_active) != n_spin) {
    throw std::runtime_error(
        "Different number of spin channels in orbitals and n_active"
    );
  }

  Orbitals frozen_orbitals;
  for (size_t sigma = 0; sigma < n_spin; ++sigma) {
    frozen_orbitals.push_back(
        linalg::tail_cols(orbitals[sigma], n_active[sigma], true)
    );
  }

  return frozen_orbitals;
}

inline auto construct_density(const Orbitals &orbitals) {
  const auto n_spin = spin_channels(orbitals);

  const auto nao = linalg::n_rows(orbitals[0]);

  DensityMatrix result;
  for (size_t sigma = 0; sigma < n_spin; ++sigma) {
    const auto &C_sigma = orbitals[sigma];

    if (const auto n_occ = linalg::n_cols(C_sigma); n_occ == 0) {
      result.push_back(linalg::zeros(nao, nao));
    } else {
      const auto n_electron_per_orbital = (n_spin == 1) ? 2 : 1;

      result.emplace_back(
          n_electron_per_orbital * C_sigma * linalg::transpose(C_sigma)
      );
    }
  }

  return result;
}

inline auto occupation_weighted_orbitals(
    const Orbitals &orb, const Occupations &occ
) {
  const auto n_spin = orb.size();
  if (occ.size() != n_spin) {
    throw std::runtime_error("orbitals and occupations must be of equal size");
  }

  Orbitals result(n_spin);
  const auto n_electron_per_orbital = (n_spin == 1) ? 2 : 1;
  for (size_t i = 0; i < n_spin; ++i) {
    if (!linalg::all_positive(occ[i])) {
      throw std::runtime_error("occupations must be positive");
    }
    const auto n_occ = occ[i].size();
    const auto C_occ = linalg::head_cols(orb[i], n_occ);
    const linalg::Vec weights = linalg::sqrt(occ[i] / n_electron_per_orbital);
    result[i] = C_occ * linalg::diagmat(weights);
  }

  return result;
}
}  // namespace uw12::utils

#endif  // UW12_UTILS_HPP