Program Listing for File antisymmetrizer.cpp

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

//
// Created by Eduard Valeyev on 9/22/24.
//

#include <SeQuant/domain/mbpt/antisymmetrizer.hpp>

namespace sequant {

antisymm_element::antisymm_element(ExprPtr ex_) {
  current_product = ex_;
  assert(ex_->is<Product>());
  Constant::scalar_type starting_constant = 1;
  unsigned long begining_index = 0;
  for (auto it = begin(*ex_); it != end(*ex_); it++) {
    if (it->get()->is<Tensor>()) {
      auto factor = it->get()->as<Tensor>();
      index_group.push_back(
          {begining_index, begining_index + factor.bra_rank()});
      begining_index += factor.bra_rank();
      assert(factor.bra_rank() == factor.ket_rank());
      for (int i = 0; i < factor.rank(); i++) {
        sorted_bra_indices.push_back(factor.bra()[i]);
        sorted_ket_indices.push_back(factor.ket()[i]);
      }
    } else if (it->get()->is<Constant>()) {
      starting_constant *= it->get()->as<Constant>().value();
    }

    else if (it->get()->is<FNOperator>()) {
      auto factor = it->get()->as<FNOperator>();
      index_group.push_back(
          {begining_index, begining_index + factor.nannihilators()});
      begining_index += factor.nannihilators();
      assert(factor.ncreators() == factor.nannihilators());
      for (int i = 0; i < factor.rank(); i++) {
        sorted_bra_indices.push_back(factor.annihilators()[i].index());
        sorted_ket_indices.push_back(factor.creators()[i].index());
      }
    } else {
      throw " unknown type of product, factor is not tensor, constant, or NormalOperator with FermiDirac statistics";
    }
  }

  unique_bras_list = gen_antisymm_unique(sorted_bra_indices);
  unique_kets_list = gen_antisymm_unique(sorted_ket_indices);

  auto new_sum = ex<Constant>(0);

  auto summand_exists =
      [&new_sum](ExprPtr ex) {  // check whether a summand has already been
        // generated to screen out same terms.
        bool value = false;
        for (auto summand = new_sum->begin_subexpr();
             summand != new_sum->end_subexpr(); summand++) {
          value =
              ex.get()->as<Product>() ==
              summand->get()->as<Product>();  // ensure that this equality is
          // mathematical and not hash based.
          if (value == true) {
            return value;
          }
        }
        return value;
      };

  for (int i = 0; i < unique_bras_list.size(); i++) {
    for (int j = 0; j < unique_kets_list.size(); j++) {  // product level

      auto new_product =
          ex<Constant>(unique_kets_list[i].first * unique_bras_list[j].first *
                       starting_constant);

      int index_label_pos = 0;
      for (auto it = begin(*ex_); it != end(*ex_); it++) {  // factor level
        if (it->get()->is<Constant>()) {
        }  // constant already captured in the first loop.
        else if (it->get()->is<Tensor>()) {
          auto old_tensor = it->get()->as<Tensor>();
          auto label = old_tensor.label();
          std::vector<Index> new_bras;
          std::vector<Index> new_kets;
          for (auto k = 0; k < old_tensor.rank(); k++) {  // index level
            new_bras.push_back(unique_bras_list[i].second[index_label_pos]);
            new_kets.push_back(unique_kets_list[j].second[index_label_pos]);
            index_label_pos++;
          }
          auto new_tensor = ex<Tensor>(label, bra(std::move(new_bras)),
                                       ket(std::move(new_kets)));
          new_product = new_tensor * new_product;
          new_product->canonicalize();
        }

        else if (it->get()->is<FNOperator>()) {
          auto old_Nop = it->get()->as<FNOperator>();
          std::vector<Index> new_anni;
          std::vector<Index> new_crea;
          for (auto k = 0; k < old_Nop.rank(); k++) {
            new_anni.push_back(unique_bras_list[i].second[index_label_pos]);
            new_crea.push_back(unique_kets_list[j].second[index_label_pos]);
            index_label_pos++;
          }
          auto new_Nop = ex<FNOperator>(cre(new_crea), ann(new_anni));
          new_product = new_product * new_Nop;
          // std::wcout << "product:  " << to_latex(new_product) << std::endl;
          new_product->canonicalize();
        }

        else {
          throw " unknown type of product, factor is not tensor, constant, or NormalOperator with FermiDirac statistics";
        }
      }
      if (!summand_exists(
              new_product)) {  // since products are canonicalized, repeats
        // can be found.
        new_sum = new_sum + new_product;
      }
    }
  }

  result = new_sum;
}

template <typename T>
std::vector<std::pair<int, std::vector<T>>>
antisymm_element::gen_antisymm_unique(std::vector<T> ordered_indices) {
  std::vector<std::pair<int, std::vector<T>>> result;
  // next_permutation needs an ordered list. works for integers quite well.
  // Easiest to map T to an integer corresponding to its position in a vector
  // and then go back.
  std::vector<int> ordered_numbers;
  for (int i = 0; i < ordered_indices.size(); i++) {
    ordered_numbers.push_back(i);
  }

  // return {next_exists, nswaps for this permutation} N.B.
  // nswaps_relative_to_input != nswaps_relative_to_оriginal
  auto swapcounting_tracking_next_permutation =
      [](auto first, auto last) -> std::pair<bool, int> {
    int nswaps = 0;
    using BidirIt = decltype(first);
    if (first == last) return std::make_pair(false, 0);
    BidirIt i = last;
    if (first == --i) return std::make_pair(false, 0);
    while (true) {
      BidirIt i1, i2;
      i1 = i;
      if (*--i < *i1) {
        i2 = last;
        while (!(*i < *--i2))
          ;
        std::iter_swap(i, i2);
        ++nswaps;
        std::reverse(i1, last);
        nswaps += (std::distance(i1, last)) / 2;  // logic from
        // https://en.cppreference.com/w/cpp/algorithm/reverse
        return std::make_pair(true, nswaps);
      }
      if (i == first) {
        std::reverse(first, last);
        nswaps += (std::distance(first, last)) / 2;
        return std::make_pair(false, nswaps);
      }
    }
  };

  std::vector<int> numbers = ordered_numbers;
  result.push_back({1, ordered_indices});

  int total_swaps = 0;  // even # swaps produces positive and odd # of swaps
  // produce negative
  [[maybe_unused]] int counter = 0;

  bool do_next_perm = true;

  while (do_next_perm) {
    auto do_swaps =
        swapcounting_tracking_next_permutation(begin(numbers), end(numbers));
    do_next_perm = do_swaps.first;
    total_swaps += do_swaps.second;
    if (!do_next_perm) {
      break;
    }
    auto is_canonical_sign =
        [this, &total_swaps](const auto& indices) -> std::pair<bool, bool> {
      for (auto& group :
           this->index_group) {  // There is only one sorted possibility in a
        // set (tensor) considering that no index
        // label should be the same.
        if (!is_sorted(indices.begin() + group.first,
                       indices.begin() + group.second))
          return {false,
                  total_swaps % 2 ==
                      0};  // total swaps divisible by 2 means true = positive.
      }
      return {true, total_swaps % 2 == 0};
    };
    // sieve out non-canonical terms
    auto [is_canonical, sign] = is_canonical_sign(numbers);
    if (is_canonical) {
      std::vector<T> return_vec(ordered_indices.size());
      for (int i = 0; i < numbers.size(); i++) {
        return_vec[i] = ordered_indices[numbers[i]];
      }
      if (sign) {
        result.push_back({1, return_vec});
      } else {
        result.push_back({-1, return_vec});
      }
    }
    counter += 1;
  }
  return result;
}

antisymmetrize::antisymmetrize(ExprPtr s) {
  if (s->is<Sum>()) {
    for (auto&& product :
         s->as<Sum>().summands()) {  // for each element in the sum
      if (product->is<Product>()) {
        antisymm_element ele(
            product);  // calculate the sum of all the valid permutations for
        // each term. each object here should only live until
        // this loop ends.
        result = result + ele.result;  // append that to the final list;
      } else {
        result = result + product;
      }
    }
  } else if (s->is<Product>()) {
    antisymm_element answer(ex<Product>(s->as<Product>()));
    result = answer.result;
  } else {
    result = s;
  }
}

namespace antisymm {

int num_closed_loops(std::vector<Index> init_upper,
                     std::vector<Index> init_lower,
                     std::vector<Index> new_upper,
                     std::vector<Index> new_lower) {
  int result = 0;
  auto equal_indices = [](Index i1, Index i2) {
    return i1.label() == i2.label();
  };
  auto where_same_ele = [&](Index i1, std::vector<Index> ref_list) {
    int hit_counter = 0;
    int where;
    bool in_list = false;
    for (int i = 0; i < ref_list.size(); i++) {
      if (equal_indices(i1, ref_list[i])) {
        hit_counter += 1;
        where = i;
        in_list = true;
      }
    }
    assert(hit_counter < 2);
    std::pair<int, bool> result(where, in_list);
    return result;
  };

  std::vector<Index> in_loop;  // lower indices already in a loop.
  for (int i = 0; i < new_upper.size(); i++) {
    assert(new_upper.size() == new_lower.size());
    if (!where_same_ele(new_lower[i], in_loop).second) {
      auto starting_point = where_same_ele(new_upper[i], init_upper).first;
      auto chain_end = init_lower[starting_point];

      bool closed = equal_indices(chain_end, new_lower[i]);
      auto lower_index = new_lower[i];
      while (!closed) {
        auto where_exist = where_same_ele(
            init_upper[where_same_ele(lower_index, init_lower).first],
            new_upper);  // if the  initial upper index is the same particle as
        // the new lower index in question, find it in the new
        // upper list.
        if (!where_exist
                 .second) {  // if the upper particle index originally connected
          // to the lower index in question is not part of a
          // contraction, there is no loop with these.
          break;
        } else {
          lower_index = new_lower[where_exist.first];  // the lower index below
          // the found upper index
          in_loop.push_back(
              lower_index);  // this lower index is part of one loop so it
          // cannot be part of another.
          closed = equal_indices(chain_end,
                                 lower_index);  // is the lower index the same
          // as the end of the chain?
        }
      }
      if (closed) {
        result += 1;
      }
      in_loop.push_back(
          new_lower[i]);  // put the starting lower index in the list
    }
  }
  assert(in_loop.size() <= init_lower.size());
  assert(result <= init_lower.size());
  return result;
}

ExprPtr max_similarity(const std::vector<Index>& original_upper,
                       const std::vector<Index>& original_lower,
                       ExprPtr expression) {
  // index pairing is originally understood as a position in the original
  // vectors, but for this case, a map may do better.
  std::map<std::wstring, std::wstring> original_map;
  for (int i = 0; i < original_upper.size(); i++) {
    original_map.emplace(original_upper[i].to_latex(),
                         original_lower[i].to_latex());
  }
  for (auto&& product : expression->as<Sum>().summands()) {
    for (auto&& factor : product->as<Product>().factors()) {
      int og_pairs = 0;
      int new_pairs = 0;
      if (factor->is<Tensor>()) {
        std::vector<Index> current_upper;
        std::vector<Index> current_lower;
        if (factor->as<Tensor>().rank() == 2) {
          for (int i = 0; i < 2; i++) {
            assert(
                original_map.find(factor->as<Tensor>().ket()[i].to_latex()) !=
                original_map.end());
            if (original_map.find(factor->as<Tensor>().ket()[i].to_latex())
                    ->second == factor->as<Tensor>().bra()[i].to_latex()) {
              og_pairs += 1;
            }
            current_upper.push_back(factor->as<Tensor>().ket()[i]);
            current_lower.push_back(factor->as<Tensor>().bra()[i]);
          }
          std::iter_swap(current_lower.begin(), current_lower.begin() + 1);
          for (int i = 0; i < 2; i++) {
            assert(original_map.find(current_upper[i].to_latex()) !=
                   original_map.end());
            if (original_map.find(current_upper[i].to_latex())->second ==
                current_lower[i].to_latex()) {
              new_pairs += 1;
            }
          }
        }
        if (new_pairs > og_pairs) {
          factor = ex<Constant>(-1) * ex<Tensor>(factor->as<Tensor>().label(),
                                                 bra(std::move(current_lower)),
                                                 ket(std::move(current_upper)));
        }
      } else if (factor->is<FNOperator>()) {
        std::vector<Index> current_upper;
        std::vector<Index> current_lower;
        if (factor->as<FNOperator>().rank() == 2) {
          for (int i = 0; i < 2; i++) {
            assert(original_map.find(factor->as<FNOperator>()
                                         .creators()[i]
                                         .index()
                                         .to_latex()) != original_map.end());
            if (original_map
                    .find(factor->as<FNOperator>()
                              .creators()[i]
                              .index()
                              .to_latex())
                    ->second ==
                factor->as<FNOperator>().annihilators()[i].index().to_latex()) {
              og_pairs += 1;
            }
            current_upper.push_back(
                factor->as<FNOperator>().creators()[i].index());
            current_lower.push_back(
                factor->as<FNOperator>().annihilators()[i].index());
          }
          std::iter_swap(current_lower.begin(), current_lower.begin() + 1);
          for (int i = 0; i < 2; i++) {
            assert(original_map.find(current_upper[i].to_latex()) !=
                   original_map.end());
            if (original_map.find(current_upper[i].to_latex())->second ==
                current_lower[i].to_latex()) {
              new_pairs += 1;
            }
          }
        }
        if (new_pairs > og_pairs) {
          factor =
              ex<Constant>(-1) * ex<FNOperator>(cre(std::move(current_upper)),
                                                ann(std::move(current_lower)));
        }
      }
    }
  }
  simplify(expression);
  return expression;
}

ExprPtr spin_sum(std::vector<Index> original_upper,
                 std::vector<Index> original_lower, ExprPtr expression,
                 bool singlet_state) {
  if (singlet_state) {
    // std::wcout << "before spin sum! :" << std::endl <<
    // to_latex_align(expression) << std::endl;
    auto init_upper = original_upper;
    auto init_lower = original_lower;
    max_similarity(original_upper, original_lower, expression);
    // may need to add separate loop if the result is a single product or
    // Operator/Tensor
    auto return_val = ex<Constant>(0);
    for (auto&& product : expression->as<Sum>().summands()) {
      auto prefactor = ex<Constant>(
          rational{1, 8});  // each term in the 3 body decomp, should have
      // (1 /2^n) prefactor where n is rank of product
      // so product rank always 3 for 3 body decomp
      std::vector<Index> new_upper;
      std::vector<Index> new_lower;
      for (auto&& factor : product->as<Product>().factors()) {
        if (factor->is<Tensor>() && factor->as<Tensor>().label() == L"γ") {
          // prefactor = ex<Constant>(-0.5) *
          // ex<Constant>(factor->as<Tensor>().rank()) * prefactor;
          for (int i = 0; i < factor->as<Tensor>().rank(); i++) {
            new_upper.push_back(factor->as<Tensor>().ket()[i]);
            new_lower.push_back(factor->as<Tensor>().bra()[i]);
          }
          factor = ex<Tensor>(L"Γ", factor->as<Tensor>().bra(),
                              factor->as<Tensor>().ket());
        } else if (factor->is<FNOperator>()) {
          // prefactor = ex<Constant>(-0.5) *
          // ex<Constant>(factor->as<Tensor>().rank()) * prefactor;
          for (int i = 0; i < factor->as<FNOperator>().rank(); i++) {
            new_upper.push_back(factor->as<FNOperator>().creators()[i].index());
            new_lower.push_back(
                factor->as<FNOperator>().annihilators()[i].index());
          }
        }
      }
      // std::wcout << to_latex_align(product) << std::endl;
      int nloops =
          num_closed_loops(init_upper, init_lower, new_upper, new_lower);
      // std::wcout  << "nloops: " << nloops << std::endl;
      if (nloops == 0) {
      } else {
        prefactor = ex<Constant>(pow2(nloops)) * prefactor;
      }
      return_val = (product * prefactor) + return_val;
    }
    return_val->canonicalize();
    return return_val;
  } else {
    throw " non-singlet states not yet supported";
  }
}

}  // namespace antisymm

}  // namespace sequant