.. _program_listing_file_SeQuant_domain_mbpt_rdm.hpp: Program Listing for File rdm.hpp ================================ |exhale_lsh| :ref:`Return to documentation for file ` (``SeQuant/domain/mbpt/rdm.hpp``) .. |exhale_lsh| unicode:: U+021B0 .. UPWARDS ARROW WITH TIP LEFTWARDS .. code-block:: cpp // // Created by Conner Masteran on 7/1/21. // #ifndef SEQUANT_DOMAIN_MBPT_RDM_HPP #define SEQUANT_DOMAIN_MBPT_RDM_HPP #include #include namespace sequant { namespace mbpt { namespace decompositions { ExprPtr cumu_to_density(ExprPtr ex_) { assert(ex_->is()); assert(ex_->as().rank() == 1); assert(ex_->as().label() == optype2label.at(OpType::RDMCumulant)); auto down_0 = ex_->as().ket()[0]; auto up_0 = ex_->as().bra()[0]; auto density = ex(optype2label.at(OpType::RDM), bra{up_0}, ket{down_0}); return density; } ExprPtr cumu2_to_density(ExprPtr ex_) { assert(ex_->is()); assert(ex_->as().rank() == 2); assert(ex_->as().label() == optype2label.at(OpType::RDMCumulant)); auto down_0 = ex_->as().ket()[0]; auto up_0 = ex_->as().bra()[0]; auto down_1 = ex_->as().ket()[1]; auto up_1 = ex_->as().bra()[1]; const auto rdm_label = optype2label.at(OpType::RDM); auto density2 = ex(rdm_label, bra{up_0, up_1}, ket{down_0, down_1}); auto density_1 = ex(rdm_label, bra{up_0}, ket{down_0}); auto density_2 = ex(rdm_label, bra{up_1}, ket{down_1}); auto d1_d2 = antisymmetrize(density_1 * density_2); return density2 + ex(-1) * d1_d2.result; } ExprPtr cumu3_to_density(ExprPtr ex_) { assert(ex_->is()); assert(ex_->as().rank() == 3); assert(ex_->as().label() == optype2label.at(OpType::RDMCumulant)); auto down_0 = ex_->as().ket()[0]; auto up_0 = ex_->as().bra()[0]; auto down_1 = ex_->as().ket()[1]; auto up_1 = ex_->as().bra()[1]; auto down_2 = ex_->as().ket()[2]; auto up_2 = ex_->as().bra()[2]; const auto rdm_label = optype2label.at(OpType::RDM); auto cumulant2 = ex(optype2label.at(OpType::RDMCumulant), bra{up_1, up_2}, ket{down_1, down_2}); auto density_1 = ex(rdm_label, bra{up_0}, ket{down_0}); auto density_2 = ex(rdm_label, bra{up_1}, ket{down_1}); auto density_3 = ex(rdm_label, bra{up_2}, ket{down_2}); auto density3 = ex(rdm_label, bra{up_0, up_1, up_2}, ket{down_0, down_1, down_2}); auto d1_d2 = antisymmetrize(density_1 * density_2 * density_3 + density_1 * cumulant2); auto temp_result = density3 * ex(-1) * d1_d2.result; for (auto&& product : temp_result->as().summands()) { for (auto&& factor : product->as().factors()) { if (factor->is() && (factor->as().label() == optype2label.at(OpType::RDMCumulant)) && (factor->as().rank() == 2)) { factor = cumu2_to_density(factor); } } } for (auto&& product : temp_result->as().summands()) { for (auto&& factor : product->as().factors()) { if (factor->is() && factor->as().label() == optype2label.at(OpType::RDMCumulant) && factor->as().rank() == 1) { factor = cumu_to_density(factor); } } } return temp_result; } ExprPtr one_body_sub( ExprPtr ex_) { // J. Chem. Phys. 132, 234107 (2010); // https://doi.org/10.1063/1.3439395 eqn 15 for assert(ex_->is()); assert(ex_->as().rank() == 1); auto down_0 = ex_->as().annihilators()[0].index(); auto up_0 = ex_->as().creators()[0].index(); const auto a = ex(cre({up_0}), ann({down_0})); const auto cumu1 = ex(optype2label.at(OpType::RDMCumulant), bra{down_0}, ket{up_0}); auto result = a + (ex(-1) * cumu1); return (result); } ExprPtr two_body_decomp( ExprPtr ex_, bool approx = false) { // J. Chem. Phys. 132, 234107 (2010); // https://doi.org/10.1063/1.3439395 // eqn 16 for \tilde{a}^{pr}_{qs} assert(ex_->is()); assert(ex_->as().rank() == 2); auto down_0 = ex_->as().annihilators()[0].index(); auto down_1 = ex_->as().annihilators()[1].index(); auto up_0 = ex_->as().creators()[0].index(); auto up_1 = ex_->as().creators()[1].index(); const auto cumu1 = ex(optype2label.at(OpType::RDMCumulant), bra{down_0}, ket{up_0}); const auto cumu2 = ex(optype2label.at(OpType::RDMCumulant), bra{down_1}, ket{up_1}); const auto a = ex(cre{up_1}, ann{down_1}); const auto a2 = ex(cre{up_0, up_1}, ann{down_0, down_1}); const auto double_cumu = ex(optype2label.at(OpType::RDMCumulant), bra{down_0, down_1}, ket{up_0, up_1}); auto term1 = cumu1 * a; auto term2 = cumu1 * cumu2; auto term3 = double_cumu; auto sum_of_terms = antisymmetrize(term1 + term2 + term3); sum_of_terms.result = ex(-1) * sum_of_terms.result; auto result = a2 + sum_of_terms.result; return (result); } // express 3-body term as sums of 1 and 2-body term. as described in J. Chem. // Phys. 132, 234107 (2010); https://doi.org/10.1063/1.3439395 eqn 17. std::pair, std::vector>> three_body_decomp(ExprPtr ex_, bool approx = true) { assert(ex_->is()); assert(ex_->as().rank() == 3); auto down_0 = ex_->as().annihilators()[0].index(); auto down_1 = ex_->as().annihilators()[1].index(); auto down_2 = ex_->as().annihilators()[2].index(); std::vector initial_lower{down_0, down_1, down_2}; auto up_0 = ex_->as().creators()[0].index(); auto up_1 = ex_->as().creators()[1].index(); auto up_2 = ex_->as().creators()[2].index(); std::vector initial_upper{up_0, up_1, up_2}; const auto cumulant = ex(optype2label.at(OpType::RDMCumulant), bra{down_0}, ket{up_0}); const auto a = ex(cre{up_1, up_2}, ann{down_1, down_2}); auto a_cumulant = cumulant * a; auto cumulant2 = ex(optype2label.at(OpType::RDMCumulant), bra{down_1}, ket{up_1}); auto cumulant3 = ex(optype2label.at(OpType::RDMCumulant), bra{down_2}, ket{up_2}); auto cumulant_3x = cumulant * cumulant2 * cumulant3; auto a1 = ex(cre{up_0}, ann{down_0}); auto a1_cumu1_cumu2 = a1 * cumulant2 * cumulant3; auto two_body_cumu = ex(optype2label.at(OpType::RDMCumulant), bra{down_1, down_2}, ket{up_1, up_2}); auto a1_cumu2 = a1 * two_body_cumu; auto cumu1_cumu2 = cumulant * two_body_cumu; auto sum_of_terms = antisymmetrize(a_cumulant + cumulant_3x + a1_cumu1_cumu2 + a1_cumu2 + cumu1_cumu2); if (!approx) { auto cumu3 = ex(optype2label.at(OpType::RDMCumulant), bra{down_0, down_1, down_2}, ket{up_0, up_1, up_2}); sum_of_terms.result = cumu3 + sum_of_terms.result; } auto temp_result = sum_of_terms.result; simplify(temp_result); // std::wcout << "result before substitiutions: " << // to_latex_align(temp_result) << std::endl; for (auto&& product : temp_result->as().summands()) { // replace all the two body terms // with one body terms. if (product->is()) { for (auto&& factor : product->as().factors()) { if (factor->is() && factor->as().rank() == 2) { factor = two_body_decomp(factor); } } } else { } } simplify(temp_result); for (auto&& product : temp_result->as().summands()) { // replace the one body terms with // the substituted expression if (product->is()) { for (auto&& factor : product->as().factors()) { if (factor->is() && factor->as().rank() == 1) { factor = one_body_sub(factor); } } } } std::pair, std::vector> initial_pairing( initial_lower, initial_upper); std::pair, std::vector>> result( temp_result, initial_pairing); // simplify(temp_result); // std::wcout << "result before substitiutions: " << // to_latex_align(temp_result,20,7) << std::endl; return result; } std::pair, std::vector>> three_body_decomposition(ExprPtr ex_, int rank, bool fast = false) { std::pair, std::vector> initial_pairing; if (rank == 3) { auto ex_pair = three_body_decomp(ex_); ex_ = ex_pair.first; initial_pairing = ex_pair.second; simplify(ex_); for (auto&& product : ex_->as().summands()) { if (product->is()) { for (auto&& factor : product->as().factors()) { if (factor->is()) { if (factor->as().label() == optype2label.at(OpType::RDMCumulant) && factor->as().rank() == 3) { factor = cumu3_to_density(factor); } else if (factor->as().label() == optype2label.at(OpType::RDMCumulant) && factor->as().rank() == 2) { factor = cumu2_to_density(factor); } else if (factor->as().label() == optype2label.at(OpType::RDMCumulant) && factor->as().rank() == 1) { factor = cumu_to_density(factor); } else { assert(factor->as().label() != optype2label.at(OpType::RDMCumulant)); } } } } } simplify(ex_); } else if (rank == 2) { if (fast) { assert(ex_->is()); // FNOp does not store a list of indices so I have to do this auto down_0 = ex_->as().annihilators()[0].index(); auto down_1 = ex_->as().annihilators()[1].index(); auto down_2 = ex_->as().annihilators()[2].index(); std::vector initial_lower{down_0, down_1, down_2}; auto up_0 = ex_->as().creators()[0].index(); auto up_1 = ex_->as().creators()[1].index(); auto up_2 = ex_->as().creators()[2].index(); std::vector initial_upper{up_0, up_1, up_2}; initial_pairing.first = initial_lower; initial_pairing.second = initial_upper; // make tensors which can be decomposed into the constituent pieces later // in the procedure. auto DE2 = ex(L"DE2", bra{down_0, down_1, down_2}, ket{up_0, up_1, up_2}); auto DDE = ex(L"DDE", bra{down_0, down_1, down_2}, ket{up_0, up_1, up_2}); auto D2E = ex(L"D2E", bra{down_0, down_1, down_2}, ket{up_0, up_1, up_2}); auto result = DE2 + D2E - ex(2) * DDE; return {result, initial_pairing}; } auto ex_pair = three_body_decomp(ex_, true); ex_ = ex_pair.first; initial_pairing = ex_pair.second; simplify(ex_); for (auto&& product : ex_->as().summands()) { if (product->is()) { for (auto&& factor : product->as().factors()) { if (factor->is()) { if (factor->as().label() == optype2label.at(OpType::RDMCumulant) && factor->as().rank() > 2) { factor = ex(0); } else if (factor->as().label() == optype2label.at(OpType::RDMCumulant) && factor->as().rank() == 2) { factor = cumu2_to_density(factor); } else if (factor->as().label() == optype2label.at(OpType::RDMCumulant)) { factor = cumu_to_density(factor); } else { assert(factor->as().label() != optype2label.at(OpType::RDMCumulant)); } } } } } simplify(ex_); // std::wcout << " cumulant replacment: " << to_latex_align(_ex,20, 7) << // std::endl; } else if (rank == 1) { auto ex_pair = three_body_decomp(ex_, true); ex_ = ex_pair.first; initial_pairing = ex_pair.second; simplify(ex_); for (auto&& product : ex_->as().summands()) { if (product->is()) { for (auto&& factor : product->as().factors()) { if (factor->is()) { if (factor->as().label() == optype2label.at(OpType::RDMCumulant) && factor->as().rank() > 1) { factor = ex(0); } else if (factor->as().label() == optype2label.at(OpType::RDMCumulant)) { factor = cumu_to_density(factor); } else { assert(factor->as().label() != optype2label.at(OpType::RDMCumulant)); } } } } } simplify(ex_); } else { throw "rank not supported!"; } return {ex_, initial_pairing}; } // in general a three body substitution can be approximated with 1, 2, or 3 body // terms(3 body has no approximation). this is achieved by replacing densities // with with particle number > rank by the each successive cumulant // approximation followed by neglect of the particle rank sized term. // TODO this implementation is ambitious and currently we only support rank 2 // decompositions. // // fast implementation represent non-constant solution interms of like terms and // permutation operators. ExprPtr three_body_substitution(ExprPtr& input, int rank, bool fast = false) { // just return back if the input is zero. if (input == ex(0)) { return input; } if (fast) { assert(rank == 2); if (input->is()) { for (auto&& product : input->as().summands()) { if (product->is()) { for (auto&& factor : product->as().factors()) { if (factor->is() && (factor->as().rank() == 3)) { // find the 3-body terms auto fac_pair = decompositions::three_body_decomposition( factor, rank, fast); // decompose that term and replace the existing term. factor = fac_pair.first; } } } } simplify(input); return input; } else if (input->is()) { for (auto&& factor : input->as().factors()) { if (factor->is() && (factor->as().rank() == 3)) { // find the 3-body terms auto fac_pair = decompositions::three_body_decomposition( factor, rank, fast); // decompose that term and replace the existing term. factor = fac_pair.first; } } simplify(input); return input; } else if (input->is()) { auto fac_pair = decompositions::three_body_decomposition( input, rank, fast); // decompose that term and replace the existing term. input = fac_pair.first; simplify(input); return input; } } std::pair, std::vector> initial_pairing; if (input->is()) { for (auto&& product : input->as().summands()) { if (product->is()) { for (auto&& factor : product->as().factors()) { if (factor->is() && (factor->as().rank() == 3)) { // find the 3-body terms auto fac_pair = decompositions::three_body_decomposition( factor, rank); // decompose that term and replace the existing term. factor = fac_pair.first; initial_pairing = fac_pair.second; if (get_default_context().spbasis() == SPBasis::spinfree) { factor = antisymm::spin_sum(initial_pairing.second, initial_pairing.first, factor); non_canon_simplify(factor); } else { throw " wrong spin basis"; } } } } } } else if (input->is()) { for (auto&& factor : input->as().factors()) { if (factor->is() && (factor->as().rank() == 3)) { // find the 3-body terms auto fac_pair = decompositions::three_body_decomposition( factor, rank); // decompose that term and replace the existing term. factor = fac_pair.first; initial_pairing = fac_pair .second; // decompose that term and replace the existing term. if (get_default_context().spbasis() == SPBasis::spinfree) { factor = antisymm::spin_sum(initial_pairing.second, initial_pairing.first, factor); non_canon_simplify(factor); } } } } else if (input->is()) { auto fac_pair = decompositions::three_body_decomposition( input, rank); // decompose that term and replace the existing term. input = fac_pair.first; initial_pairing = fac_pair.second; if (get_default_context().spbasis() == SPBasis::spinfree) { // std::wcout << to_latex_align(input,20) << std::endl; input = antisymm::spin_sum(initial_pairing.second, initial_pairing.first, input); non_canon_simplify(input); } } else { throw "cannot handle this type"; } return input; }; } // namespace decompositions } // namespace mbpt } // namespace sequant #endif // SEQUANT_DOMAIN_MBPT_RDM_HPP