make_symmetry.cc
//
// Copyright (C) 2019 by the adcc authors
//
// This file is part of adcc.
//
// adcc is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// adcc is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with adcc. If not, see <http://www.gnu.org/licenses/>.
//
#include "make_symmetry.hh"
#include "exceptions.hh"
namespace libadcc {
namespace {
/** Return the irreps of the passed cartesian transformation in the passed point group */
std::vector<std::string> irreps_of_cartesian_transformation(
const std::string& point_group, const std::string& cartesian_transformation,
const std::string& irrep_totsym) {
// Handle the special cases first
if (cartesian_transformation == "1") {
return {irrep_totsym}; // Totally symmetric is always totally symmetric ^^
}
if (point_group == "C1") {
return {irrep_totsym}; // C1 only has one irrep
}
using pg = std::string;
using trafo = std::string;
using irrep = std::string;
std::map<pg, std::map<irrep, std::vector<trafo>>> map{
{
"C2v",
{
{"A1", {"z", "xx", "yy", "zz"}}, //
{"A2", {"xy", "Rz"}}, //
{"B1", {"x", "Ry", "xz"}}, //
{"B2", {"y", "Rx", "yz"}} //
},
},
{
"D2",
{
{"A", {"xx", "yy", "zz"}}, //
{"B1", {"z", "Rz", "xy"}}, //
{"B2", {"x", "Rx", "yz"}}, //
{"B3", {"y", "Ry", "xz"}} //
},
}};
const auto itpg = map.find(point_group);
if (itpg == map.end()) {
throw invalid_argument("Invalid point group or point group not implemented: " +
point_group);
}
// Go over all irreps in the point group and check if the cartesian_transformation
// is part of it. If yes then add it to the returned list.
std::vector<std::string> ret;
for (const auto& irrep_trafos : itpg->second) {
const std::string& irrep = irrep_trafos.first;
for (const std::string& trafos : irrep_trafos.second) {
if (trafos == cartesian_transformation) {
ret.push_back(irrep);
}
}
}
if (ret.empty()) {
throw invalid_argument("Could not find cartesian_transformation " +
cartesian_transformation + "in point group " + point_group +
".");
}
return ret;
}
} // namespace
std::shared_ptr<Symmetry> make_symmetry_orbital_energies(
std::shared_ptr<const MoSpaces> mospaces_ptr, const std::string& space) {
auto sym = std::make_shared<Symmetry>(mospaces_ptr, space);
if (sym->ndim() != 1) {
throw invalid_argument("Expect exactly a one-dimensional space string, not " + space +
".");
}
// Setup spin symmetry
if (mospaces_ptr->restricted) {
sym->set_spin_block_maps({{"a", "b", 1.0}});
}
return sym;
}
std::shared_ptr<Symmetry> make_symmetry_orbital_coefficients(
std::shared_ptr<const MoSpaces> mospaces_ptr, const std::string& space,
size_t n_bas, const std::string& blocks) {
if (space.back() != 'b') {
throw invalid_argument(
"Expect exactly a two-dimensional space string like 'o1b', not " + space + ".");
}
if (blocks != "ab" && blocks != "a" && blocks != "b" && blocks != "abstack") {
throw invalid_argument(
"Invalid argument to 'blocks' parameter. Only valid values are 'ab' (both "
"alpha and beta coefficients in a block-diagonal fashion), "
"'a' (only alpha coefficients), 'b' (only beta coefficients) and 'abstack'"
"(alpha and beta coefficients stacked on top of another)");
}
// Setup extra "b" axis in a way that it twice n_bas in length
// (alpha and beta coefficients)
std::map<std::string, std::pair<size_t, size_t>> extra_axis{{"b", {n_bas, n_bas}}};
if (blocks == "a" || blocks == "b" || blocks == "abstack") {
// Cut the second spin block in the axis (i.e. only have either alpha
// or beta spin along the "b" axis)
extra_axis = {{"b", {n_bas, 0}}};
}
auto sym = std::make_shared<Symmetry>(mospaces_ptr, space, extra_axis);
if (sym->ndim() != 2) {
throw invalid_argument("Expect exactly a two-dimensional space string, not " + space +
".");
}
// Set point-group symmetry:
// I (mfh) am not entirely sure why this always has to be the totally symmetric irrep
// I would imagine this to be the irrep of the groundstate
// ... but this is how it's done elsewhere
sym->set_irreps_allowed({mospaces_ptr->irrep_totsym()});
// Setup spin symmetry (spin block mapping)
if (mospaces_ptr->restricted) {
// Note: Libtensor assumes for the spin block mappings that there are
// an even number of blocks along the spin axis. We explicitly check
// for this in in as_lt_symmetry and ignore the spin symmetry if this
// is not the case, but it has to be properly tested if this does
// the trick. Notice that e.g. adcman does it like it is coded here
// and ignores all symmetry between the spin blocks in the case of
// an unrestricted reference. Technically speaking this is not
// completely necessary and thus the if(restricted) should be dropped.
if (blocks == "ab") {
// ab and ba are forbidden, eventually "aa" and "bb" mapped on top.
sym->set_spin_blocks_forbidden({"ab", "ba"});
if (mospaces_ptr->restricted) {
sym->set_spin_block_maps({{"aa", "bb", 1.0}});
}
} else if (blocks == "a") {
// Only alpha spin is allowed
sym->set_spin_blocks_forbidden({"bx"});
} else if (blocks == "b") {
// Only beta spin is allowed
sym->set_spin_blocks_forbidden({"ax"});
} else if (blocks == "abstack") {
if (mospaces_ptr->restricted) {
sym->set_spin_block_maps({{"ax", "bx", 1.0}});
}
}
} // spin symmetry
return sym;
}
std::shared_ptr<Symmetry> make_symmetry_eri(std::shared_ptr<const MoSpaces> mospaces_ptr,
const std::string& space) {
auto sym = std::make_shared<Symmetry>(mospaces_ptr, space);
const std::vector<std::string>& ss = sym->subspaces();
const MoSpaces& mo = *mospaces_ptr;
if (sym->ndim() != 4) {
throw invalid_argument("Expect exactly a four-dimensional space string, not " +
space + ".");
}
// adcc uses anti-symmetrised repulsion integrals in the physicist's
// convention, i.e. the integral <ij || kl> = < ij | kl > - < ij | lk>
// is anti-symmetric in the first two indices and the last two
// indices but symmetric if the first two are swapped with the last two.
//
// These symmetries of course only apply if we deal with the same MO subspace
// blocks in the respective indices.
std::vector<std::string> permutations{"ijkl"};
if (ss[0] == ss[1]) permutations.push_back("-jikl");
if (ss[2] == ss[3]) permutations.push_back("-ijlk");
if (ss[0] == ss[2] && ss[1] == ss[3]) permutations.push_back("klij");
if (permutations.size() > 1) {
sym->set_permutations(permutations);
}
// Set point-group symmetry: Repulsion integrals are totally symmetric
sym->set_irreps_allowed({mo.irrep_totsym()});
// Set spin symmetry:
if (mospaces_ptr->restricted) {
// Note: Libtensor assumes for the spin block mappings that there are
// an even number of blocks along the spin axis. We explicitly check
// for this in in as_lt_symmetry and ignore the spin symmetry if this
// is not the case, but it has to be properly tested if this does
// the trick. Notice that e.g. adcman does it like it is coded here
// and ignores all symmetry between the spin blocks in the case of
// an unrestricted reference. Technically speaking this is not
// completely necessary and as for example the next statement of
// forbidden blocks should actually be shifted one up out of the if.
sym->set_spin_blocks_forbidden({"aaab", "aaba", "aabb", "abaa", "abbb", //
"bbba", "bbab", "bbaa", "babb", "baaa"});
sym->set_spin_block_maps({
{"aaaa", "bbbb", 1.},
{"abab", "baba", 1.},
{"abba", "baab", 1.},
});
}
return sym;
}
std::shared_ptr<Symmetry> make_symmetry_eri_symm(
std::shared_ptr<const MoSpaces> mospaces_ptr, const std::string& space) {
auto sym = std::make_shared<Symmetry>(mospaces_ptr, space);
const std::vector<std::string>& ss = sym->subspaces();
const MoSpaces& mo = *mospaces_ptr;
if (sym->ndim() != 4) {
throw invalid_argument("Expect exactly a four-dimensional space string, not " +
space + ".");
}
// We setup the symmetry of the ERI <ij | kl> in Physicists' indexing convention.
// This one is symmetric in the 1st and 3rd, under exchange of shell pairs
// and if first two and last two are swapped.
// These symmetries only apply if they connect identical MO subspaces.
std::vector<std::string> permutations{"ijkl"};
if (ss[0] == ss[2]) permutations.push_back("kjil");
if (ss[0] == ss[1] && ss[2] == ss[3]) permutations.push_back("jilk");
if (ss[0] == ss[2] && ss[1] == ss[3]) permutations.push_back("klij");
if (permutations.size() > 1) {
sym->set_permutations(permutations);
}
// Set point-group symmetry: Repulsion integrals are totally symmetric
sym->set_irreps_allowed({mo.irrep_totsym()});
// Set spin symmetry:
if (mospaces_ptr->restricted) {
// Note: Libtensor assumes for the spin block mappings that there are
// an even number of blocks along the spin axis. We explicitly check
// for this in in as_lt_symmetry and ignore the spin symmetry if this
// is not the case, but it has to be properly tested if this does
// the trick. Notice that e.g. adcman does it like it is coded here
// and ignores all symmetry between the spin blocks in the case of
// an unrestricted reference. Technically speaking this is not
// completely necessary and as for example the next statement of
// forbidden blocks should actually be shifted one up out of the if.
sym->set_spin_blocks_forbidden({"aaab", "aaba", "aabb", "abba", "abaa", "abbb", //
"bbba", "bbab", "bbaa", "baab", "babb", "baaa"});
sym->set_spin_block_maps({
{"aaaa", "bbbb", 1.},
{"abab", "baba", 1.},
{"abab", "aaaa", 1.}, // That's only true for restricted!
});
}
return sym;
}
std::shared_ptr<Symmetry> make_symmetry_operator(
std::shared_ptr<const MoSpaces> mospaces_ptr, const std::string& space,
bool symmetric, const std::string& cartesian_transformation) {
auto sym = std::make_shared<Symmetry>(mospaces_ptr, space);
const std::vector<std::string>& ss = sym->subspaces();
if (sym->ndim() != 2) {
throw invalid_argument("Expect exactly a two-dimensional space string, not " + space +
".");
}
// Operator is a symmetric matrix iff symmetric and both spaces equal
if (symmetric && ss[0] == ss[1]) {
sym->set_permutations({"ij", "ji"});
}
// Point-group symmetry
const std::vector<std::string> irreps = irreps_of_cartesian_transformation(
mospaces_ptr->point_group, cartesian_transformation,
mospaces_ptr->irrep_totsym());
sym->set_irreps_allowed(irreps);
// A one-electron (spin-free) Operator is aa / bb block-diagonal only.
if (mospaces_ptr->restricted) {
// Note: Libtensor assumes for the spin block mappings that there are
// an even number of blocks along the spin axis. We explicitly check
// for this in in as_lt_symmetry and ignore the spin symmetry if this
// is not the case, but it has to be properly tested if this does
// the trick. Notice that e.g. adcman does it like it is coded here
// and ignores all symmetry between the spin blocks in the case of
// an unrestricted reference. Technically speaking this is not
// completely necessary and as for example the next statement of
// forbidden blocks should actually be shifted one up out of the if.
sym->set_spin_blocks_forbidden({"ab", "ba"});
sym->set_spin_block_maps({{"aa", "bb", 1.}});
}
return sym;
}
std::shared_ptr<Symmetry> make_symmetry_operator_basis(
std::shared_ptr<const MoSpaces> mospaces_ptr, size_t n_bas, bool symmetric) {
// Setup symmetry in a way that the "b" axis has "b" alphas and "b" betas
using map_type = std::map<std::string, std::pair<size_t, size_t>>;
auto sym =
std::make_shared<Symmetry>(mospaces_ptr, "bb", map_type{{"b", {n_bas, n_bas}}});
if (symmetric) sym->set_permutations({"ij", "ji"});
// A one-electron (spin-free) Operator is aa / bb block-diagonal only
// and the basis functions are spin-free, thus aa == bb.
sym->set_spin_blocks_forbidden({"ab", "ba"});
sym->set_spin_block_maps({{"aa", "bb", 1.}});
return sym;
}
} // namespace libadcc