Finding invariant subspaces of a linear operator
This page explains how to use space_partition
, an efficient tool to
performs the following two tasks.
Partition of a Hilbert space into a set of disjoint subspaces invariant under action of a given Hermitian operator \(\hat H\) (Hamiltonian).
Merge some of the invariant subspaces together to ensure that a given operator \(\hat O\) and its Hermitian conjugate \(\hat O^\dagger\) generate only one-to-one connections between the subspaces.
Partition of the Hilbert space is a preparatory step that can immensely reduce complexity of an exact diagonalization problem by reducing it to a series of smaller problem.
Merging some of the invariant subspaces increases computational costs of diagonalization but simplifies computation of many-body correlation functions of operators \(\hat O\), \(\hat O^\dagger\).
For a detailed description of the algorithm see [SKFP16].
-
template<typename ScalarType>
using matrix_elements_map = std::map<std::pair<sv_index_type, sv_index_type>, ScalarType>; Sparse storage type for matrix elements of a linear operator \(\hat O\). Pairs of indices \((i,j)\) are mapped to \(\langle i| \hat O |j\rangle\).
-
class space_partition
Partition of a Hilbert space into disjoint subspaces.
-
template<typename HSType, typename LOpScalarType, int... LOpAlgebraIDs>
space_partition(loperator<LOpScalarType, LOpAlgebraIDs...> const &h, HSType const &hs) Partition Hilbert space hs into invariant subspaces of Hermitian linear operator h (Hamiltonian).
-
template<typename HSType, typename LOpScalarType, int... LOpAlgebraIDs>
space_partition(loperator<LOpScalarType, LOpAlgebraIDs...> const &h, HSType const &hs, loperator_melem_t<LOpScalarType, LOpAlgebraIDs...> &me) Partition Hilbert space hs into invariant subspaces of Hermitian linear operator h (Hamiltonian). This constructor reveals all matrix elements of h and writes them into me.
-
template<typename HSType, typename LOpScalarType, int... LOpAlgebraIDs>
auto merge_subspaces(loperator<LOpScalarType, LOpAlgebraIDs...> const &Od, loperator<LOpScalarType, LOpAlgebraIDs...> const &O, HSType const &hs, bool store_matrix_elements = true) -> std::pair<loperator_melem_t<LOpScalarType, LOpAlgebraIDs...>, loperator_melem_t<LOpScalarType, LOpAlgebraIDs...>> Merge some of the found invariant subspaces to ensure that linear operators Od and O generate only one-to-one connections between the subspaces.
Matrix elements of Od and O will be returned as a pair of
matrix_elements_map
objects if store_matrix_elements == true. Otherwise, the returned maps will be empty.This method can be called multiple types to make the partition simultaneously fulfil the one-to-one conditions for many \(\hat O_i\), \(\hat O^\dagger_i\) pairs.
-
sv_index_type dim() const
Dimension of the Hilbert space used to construct this partition.
-
sv_index_type n_subspaces() const
Number of disjoint subspaces in this partition.
Calls to
merge_subspaces()
can decrease this number.
-
sv_index_type operator[](sv_index_type index) const
Return serial number of the disjoint subspace the basis state with index belongs to.
The disjoint subspaces can be re-enumerated after a call to
merge_subspaces()
.
-
template<typename HSType, typename LOpScalarType, int... LOpAlgebraIDs>
auto find_connections(loperator<LOpScalarType, LOpAlgebraIDs...> const &op, HSType const &hs) -> std::set<std::pair<sv_index_type, sv_index_type>> Analyze connections between subspaces generated by operator op. The connections are returned as a set of pairs of subspace serial numbers, (source subspace, destination subspace).
-
std::vector<sv_index_type> subspace_basis(sv_index_type index) const
Build and return a list of indices of all basis states spanning a given subspace index.
-
std::vector<std::vector<sv_index_type>> subspace_bases() const
Build and return lists of indices of all basis states spanning all subspaces in the partition. The returned lists are disjoint and their union spans the entire Hilbert space.
-
template<typename F>
friend void foreach(space_partition const &sp, F &&f) Apply functor f to all basis states in a given space partition. The functor must take two arguments, index of the basis state, and serial number of the subspace this basis state belongs to.
-
template<typename HSType, typename LOpScalarType, int... LOpAlgebraIDs>
#include <libcommute/expression/factories.hpp>
#include <libcommute/loperator/mapped_basis_view.hpp>
#include <libcommute/loperator/space_partition.hpp>
#include <iostream>
#include <vector>
using namespace libcommute;
int main() {
using namespace static_indices; // For c(), c_dag() and n()
//
// Build Hamiltonian of the 3-orbital Hubbard-Kanamori atom
//
int const n_orbs = 3;
double const mu = 0.7; // Chemical
double const U = 3.0; // Coulomb repulsion
double const J = 0.3; // Hund interaction
// Hamiltonian
expression<double, std::string, int> H;
// Chemical potential terms
for(int o = 0; o < n_orbs; ++o) {
H += -mu * (n("up", o) + n("dn", o));
}
// Intraorbital interactions
for(int o = 0; o < n_orbs; ++o) {
H += U * n("up", o) * n("dn", o);
}
// Interorbital interactions, different spins
for(int o1 = 0; o1 < n_orbs; ++o1) {
for(int o2 = 0; o2 < n_orbs; ++o2) {
if(o1 == o2) continue;
H += (U - 2 * J) * n("up", o1) * n("dn", o2);
}
}
// Interorbital interactions, equal spins
for(int o1 = 0; o1 < n_orbs; ++o1) {
for(int o2 = 0; o2 < n_orbs; ++o2) {
if(o2 >= o1) continue;
H += (U - 3 * J) * n("up", o1) * n("up", o2);
H += (U - 3 * J) * n("dn", o1) * n("dn", o2);
}
}
// Spin flip and pair hopping terms
for(int o1 = 0; o1 < n_orbs; ++o1) {
for(int o2 = 0; o2 < n_orbs; ++o2) {
if(o1 == o2) continue;
H += -J * c_dag("up", o1) * c_dag("dn", o1) * c("up", o2) * c("dn", o2);
H += -J * c_dag("up", o1) * c_dag("dn", o2) * c("up", o2) * c("dn", o1);
}
}
//
// Hilbert space of the problem
//
auto hs = make_hilbert_space(H);
//
// Linear operator form of the Hamiltonian
//
auto Hop = make_loperator(H, hs);
//
// Reveal invariant subspaces of H
//
auto sp1 = space_partition(Hop, hs);
std::cout << "Total dimension of the Hilbert space is " << sp1.dim() << '\n';
std::cout << "H has " << sp1.n_subspaces() << " invariant subspaces\n";
//
// Once again, this time saving the matrix elements of H
//
matrix_elements_map<double> H_elements;
auto sp2 = space_partition(Hop, hs, H_elements);
std::cout << "H has " << H_elements.size() << " non-vanishing matrix elements"
<< '\n';
//
// Now merge some invariant subspaces to make sure that all electron
// creation and annihilation operators connect one subspace to one subspace.
//
for(std::string spin : {"up", "dn"}) {
for(int o = 0; o < n_orbs; ++o) {
auto Cdagop = make_loperator(c_dag(spin, o), hs);
auto Cop = make_loperator(c(spin, o), hs);
auto matrix_elements = sp2.merge_subspaces(Cdagop, Cop, hs);
std::cout << c_dag(spin, o) << " has " << matrix_elements.first.size()
<< " non-vanishing matrix elements\n";
std::cout << c(spin, o) << " has " << matrix_elements.first.size()
<< " non-vanishing matrix elements\n";
}
}
std::cout << "Number of disjoint subspaces after merging is "
<< sp2.n_subspaces() << '\n';
//
// foreach()
//
std::cout << "Distribution of basis states over the subspaces:\n";
foreach(sp2, [](sv_index_type index, sv_index_type subspace) {
std::cout << index << " => " << subspace << '\n';
});
//
// Bonus: Using mapped_basis_view to act on a vector in one of the invariant
// subspaces.
//
// Collect all basis state indices from one subspace
std::vector<sv_index_type> basis_states_in_subspace_24 =
sp2.subspace_basis(24);
auto sp24_dim = basis_states_in_subspace_24.size();
std::cout << "Subspace 24 is " << sp24_dim << "-dimensional\n";
// Make a mapper object
basis_mapper mapper(basis_states_in_subspace_24);
// 'psi' and 'phi' are small vectors entirely lying in the 24-th subspace
std::vector<double> psi(sp24_dim), phi(sp24_dim);
//
// These views have dimension of the full Hilbert space!
//
auto psi_view = mapper.make_const_view(psi);
auto phi_view = mapper.make_view(phi);
//
// Act on 'psi_view' as if it was a vector in the full space
// No exception here because 'Hop' connects subspace 24 only to itself.
//
Hop(psi_view, phi_view);
return 0;
}
“TRIQS/CTHYB: A continuous-time quantum Monte Carlo hybridisation expansion solver for quantum impurity problems”, P. Seth, I. Krivenko, M. Ferrero and O. Parcollet, Comp. Phys. Comm. 200, March 2016, 274-284, http://dx.doi.org/10.1016/j.cpc.2015.10.023 (section 4.2)