Program Listing for File cc.cpp

Return to documentation for file (SeQuant/domain/mbpt/models/cc.cpp)

#include <SeQuant/core/expr.hpp>
#include <SeQuant/core/rational.hpp>
#include <SeQuant/core/runtime.hpp>
#include <SeQuant/core/utility/macros.hpp>
#include <SeQuant/domain/mbpt/context.hpp>
#include <SeQuant/domain/mbpt/convention.hpp>
#include <SeQuant/domain/mbpt/models/cc.hpp>
#include <SeQuant/domain/mbpt/op.hpp>
#include <SeQuant/domain/mbpt/spin.hpp>
#include <SeQuant/domain/mbpt/utils.hpp>

#include <cstdint>
#include <memory>
#include <new>
#include <stdexcept>
#include <utility>

namespace sequant::mbpt {

CC::CC(size_t n, Ansatz a, bool screen, bool use_topology)
    : N(n), ansatz_(a), screen_(screen), use_topology_(use_topology) {}

CC::Ansatz CC::ansatz() const { return ansatz_; }

bool CC::unitary() const {
  return ansatz_ == Ansatz::U || ansatz_ == Ansatz::oU;
}

bool CC::screen() const { return screen_; }

bool CC::use_topology() const { return use_topology_; }

std::vector<ExprPtr> CC::t(size_t commutator_rank, size_t pmax, size_t pmin) {
  pmax = (pmax == std::numeric_limits<size_t>::max() ? N : pmax);
  const bool skip_singles = ansatz_ == Ansatz::oT || ansatz_ == Ansatz::oU;

  SEQUANT_ASSERT(pmax >= pmin && "pmax should be >= pmin");

  // 1. construct hbar(op) in canonical form
  auto hbar = mbpt::lst(H(), T(N, skip_singles), commutator_rank,
                        {.unitary = unitary()});

  // 2. project onto each manifold, screen, lower to tensor form and wick it
  std::vector<ExprPtr> result(pmax + 1);
  for (std::int64_t p = pmax; p >= static_cast<std::int64_t>(pmin); --p) {
    // 2.a. screen out terms that cannot give nonzero after projection onto
    // <p|
    std::shared_ptr<Sum>
        hbar_for_vev;  // keeps products that can produce non-zero VEV
    std::shared_ptr<Sum>
        hbar_le_p;  // keeps products that can produce excitations rank <=p

    if (screen_) {  // if operator level screening is on
      for (auto& term : *hbar) {
        SEQUANT_ASSERT(term->is<Product>() || term->is<op_t>());
        if (raises_vacuum_up_to_rank(term, p)) {
          if (!hbar_le_p)
            hbar_le_p = std::make_shared<Sum>(ExprPtrList{term});
          else
            hbar_le_p->append(term);
          if (raises_vacuum_to_rank(term, p)) {
            if (!hbar_for_vev)
              hbar_for_vev = std::make_shared<Sum>(ExprPtrList{term});
            else
              hbar_for_vev->append(term);
          }
        }
      }
      hbar = hbar_le_p;
    } else {  // no screening, use full hbar
      hbar_for_vev = hbar.is<Sum>() ? hbar.as_shared_ptr<Sum>()
                                    : std::make_shared<Sum>(hbar);
    }

    // 2.b project onto <p| (i.e., multiply by P(p) if p>0) and compute VEV
    result.at(p) =
        this->ref_av(p != 0 ? P(nₚ(p)) * hbar_for_vev : hbar_for_vev);
  }

  return result;
}

std::vector<ExprPtr> CC::λ(size_t commutator_rank) {
  SEQUANT_ASSERT(commutator_rank >= 1 && "commutator rank should be >= 1");
  SEQUANT_ASSERT(!unitary() && "there is no need for CC::λ for unitary ansatz");
  const bool skip_singles = ansatz_ == Ansatz::oT || ansatz_ == Ansatz::oU;

  // construct hbar
  auto hbar = mbpt::lst(H(), T(N, skip_singles), commutator_rank - 1,
                        {.unitary = unitary()});

  const auto One = ex<Constant>(1);
  auto lhbar = simplify((One + Λ(N)) * hbar);

  const auto op_connect = concat(default_op_connections(),
                                 OpConnections<OpType>{{OpType::h, OpType::A},
                                                       {OpType::f, OpType::A},
                                                       {OpType::g, OpType::A},
                                                       {OpType::h, OpType::S},
                                                       {OpType::f, OpType::S},
                                                       {OpType::g, OpType::S}});

  // 2. project onto each manifold, screen, lower to tensor form and wick it
  std::vector<ExprPtr> result(N + 1);
  for (auto p = N; p >= 1; --p) {
    // 2.a. screen out terms that cannot give nonzero after projection onto
    // <P|
    std::shared_ptr<Sum>
        lhbar_for_vev;  // keeps products that can produce non-zero VEV
    std::shared_ptr<Sum>
        lhbar_le_p;  // keeps products that can produce excitations rank <=p
    if (screen_) {   // if operator level screening is enabled
      for (auto& term : *lhbar) {  // pick terms from lhbar
        SEQUANT_ASSERT(term->is<Product>() || term->is<op_t>());

        if (lowers_rank_or_lower_to_vacuum(term, p)) {
          if (!lhbar_le_p)
            lhbar_le_p = std::make_shared<Sum>(ExprPtrList{term});
          else
            lhbar_le_p->append(term);
          if (lowers_rank_to_vacuum(term, p)) {
            if (!lhbar_for_vev)
              lhbar_for_vev = std::make_shared<Sum>(ExprPtrList{term});
            else
              lhbar_for_vev->append(term);
          }
        }
      }
      lhbar = lhbar_le_p;
    } else {  // no screening
      lhbar_for_vev = lhbar.is<Sum>() ? lhbar.as_shared_ptr<Sum>()
                                      : std::make_shared<Sum>(lhbar);
    }

    // 2.b multiply by adjoint of P(p) (i.e., P(-p)) on the right side and
    // compute VEV
    result.at(p) = this->ref_av(lhbar_for_vev * P(nₚ(-p)), op_connect);
  }
  return result;
}

std::vector<ExprPtr> CC::t_pt(size_t rank, size_t order,
                              std::optional<size_t> nbatch) {
  SEQUANT_ASSERT(order == 1 &&
                 "sequant::mbpt::CC::t_pt(): only first-order perturbation is "
                 "supported now");
  SEQUANT_ASSERT(rank == 1 &&
                 "sequant::mbpt::CC::t_pt(): only one-body perturbation "
                 "operator is supported now");
  SEQUANT_ASSERT(ansatz_ == Ansatz::T && "unitary ansatz is not yet supported");

  // construct h1_bar
  // truncate h1_bar at rank 2 for one-body perturbation
  // operator and at rank 4 for two-body perturbation operator
  const auto h1_truncate_at = rank == 1 ? 2 : 4;
  const auto h1_bar = mbpt::lst(H_pt(rank, {.order = order, .nbatch = nbatch}),
                                T(N), h1_truncate_at);

  // construct [hbar, T(1)]
  const auto hbar_pert =
      mbpt::lst(H(), T(N), 3) * T_pt(N, {.order = order, .nbatch = nbatch});

  // [Eq. 34, WIREs Comput Mol Sci. 2019; 9:e1406]
  const auto expr = simplify(h1_bar + hbar_pert);

  // connectivity:
  // connect t and t1 with {h,f,g}
  // connect h1 with t
  const auto op_connect =
      concat(default_op_connections(),
             OpConnections<OpType>{{OpType::h, OpType::t_1},
                                   {OpType::f, OpType::t_1},
                                   {OpType::g, OpType::t_1},
                                   {OpType::h_1, OpType::t}});

  std::vector<ExprPtr> result(N + 1);
  for (auto p = N; p >= 1; --p) {
    const auto freq_term = ex<Variable>(L"ω") * P(nₚ(p)) *
                           T_pt_(p, {.order = order, .nbatch = nbatch});
    result.at(p) =
        this->ref_av(P(nₚ(p)) * expr, op_connect) - this->ref_av(freq_term);
  }
  return result;
}

std::vector<ExprPtr> CC::λ_pt(size_t rank, size_t order,
                              std::optional<size_t> nbatch) {
  SEQUANT_ASSERT(order == 1 &&
                 "sequant::mbpt::CC::λ_pt(): only first-order perturbation is "
                 "supported now");
  SEQUANT_ASSERT(rank == 1 &&
                 "sequant::mbpt::CC::λ_pt(): only one-body perturbation "
                 "operator is supported now");
  SEQUANT_ASSERT(ansatz_ == Ansatz::T && "unitary ansatz is not yet supported");

  // construct hbar
  const auto hbar = mbpt::lst(H(), T(N), 4);

  // construct h1_bar
  // truncate h1_bar at rank 2 for one-body perturbation
  // operator and at rank 4 for two-body perturbation operator
  const auto h1_truncate_at = rank == 1 ? 2 : 4;
  const auto h1_bar = mbpt::lst(H_pt(rank, {.order = order, .nbatch = nbatch}),
                                T(N), h1_truncate_at);

  // construct [hbar, T(1)]
  const auto hbar_pert =
      mbpt::lst(H(), T(N), 3) * T_pt(N, {.order = order, .nbatch = nbatch});

  // [Eq. 35, WIREs Comput Mol Sci. 2019; 9:e1406]
  const auto One = ex<Constant>(1);
  const auto expr =
      simplify((One + Λ(N)) * (h1_bar + hbar_pert) +
               Λ_pt(N, {.order = order, .nbatch = nbatch}) * hbar);

  // connectivity:
  // t and t1 with {h,f,g}
  // projectors with {h,f,g}
  // h1 with t
  // h1 with projectors
  const auto op_connect =
      concat(default_op_connections(),
             OpConnections<OpType>{{OpType::h, OpType::t_1},
                                   {OpType::f, OpType::t_1},
                                   {OpType::g, OpType::t_1},
                                   {OpType::h_1, OpType::t},
                                   {OpType::h, OpType::A},
                                   {OpType::f, OpType::A},
                                   {OpType::g, OpType::A},
                                   {OpType::h, OpType::S},
                                   {OpType::f, OpType::S},
                                   {OpType::g, OpType::S},
                                   {OpType::h_1, OpType::A},
                                   {OpType::h_1, OpType::S}});

  std::vector<ExprPtr> result(N + 1);
  for (auto p = N; p >= 1; --p) {
    const auto freq_term = ex<Variable>(L"ω") *
                           Λ_pt_(p, {.order = order, .nbatch = nbatch}) *
                           P(nₚ(-p));
    result.at(p) =
        this->ref_av(expr * P(nₚ(-p)), op_connect) + this->ref_av(freq_term);
  }
  return result;
}

std::vector<ExprPtr> CC::eom_r(nₚ np, nₕ nh) {
  SEQUANT_ASSERT(!unitary() && "Unitary ansatz is not yet supported");
  SEQUANT_ASSERT((np > 0 || nh > 0) && "Unsupported excitation order");

  if (np != nh)
    SEQUANT_ASSERT(
        get_default_context().spbasis() != SPBasis::Spinfree &&
        "spin-free basis does not yet support non particle-conserving cases");
  const bool skip_singles = ansatz_ == Ansatz::oT;

  // construct hbar
  const auto hbar = mbpt::lst(H(), T(N, skip_singles), 4);

  // hbar * R
  const auto hbar_R = hbar * R(np, nh);

  // connectivity:
  // default connections + connect R with {h,f,g}
  const auto op_connect = concat(default_op_connections(),
                                 OpConnections<OpType>{{OpType::h, OpType::R},
                                                       {OpType::f, OpType::R},
                                                       {OpType::g, OpType::R}});

  // initialize result vector
  std::vector<ExprPtr> result;
  using std::min;
  result.resize(min(np, nh) + 1);  // for EE first element will be empty

  std::int64_t rp = np, rh = nh;
  while (rp >= 0 && rh >= 0) {
    if (rp == 0 && rh == 0) break;
    // project with <rp, rh| (i.e., multiply P(rp, rh)) and compute VEV
    result.at(min(rp, rh)) =
        this->ref_av(P(nₚ(rp), nₕ(rh)) * hbar_R, op_connect);
    if (rp == 0 || rh == 0) break;
    rp--;
    rh--;
  }

  return result;
}

std::vector<ExprPtr> CC::eom_l(nₚ np, nₕ nh) {
  SEQUANT_ASSERT(!unitary() && "Unitary ansatz is not yet supported");
  SEQUANT_ASSERT((np > 0 || nh > 0) && "Unsupported excitation order");

  if (np != nh)
    SEQUANT_ASSERT(
        get_default_context().spbasis() != SPBasis::Spinfree &&
        "spin-free basis does not support non particle-conserving cases");
  const bool skip_singles = ansatz_ == Ansatz::oT;

  // construct hbar
  const auto hbar = mbpt::lst(H(), T(N, skip_singles), 4);

  // L * hbar
  const auto L_hbar = L(np, nh) * hbar;

  // connectivity:
  // default connections + connect H with projectors
  const auto op_connect = concat(default_op_connections(),
                                 OpConnections<OpType>{{OpType::h, OpType::A},
                                                       {OpType::f, OpType::A},
                                                       {OpType::g, OpType::A},
                                                       {OpType::h, OpType::S},
                                                       {OpType::f, OpType::S},
                                                       {OpType::g, OpType::S}});

  // initialize result vector
  std::vector<ExprPtr> result;
  using std::min;
  result.resize(min(nh, np) + 1);  // for EE first element will be empty

  std::int64_t rp = np, rh = nh;
  while (rp >= 0 && rh >= 0) {
    if (rp == 0 && rh == 0) break;
    // right project with |rp,rh> (i.e., multiply P(-rp, -rh)) and compute VEV
    result.at(min(rp, rh)) =
        this->ref_av(L_hbar * P(nₚ(-rp), nₕ(-rh)), op_connect);
    if (rp == 0 || rh == 0) break;
    rp--;
    rh--;
  }

  return result;
}
}  // namespace sequant::mbpt