Finitedimensional Hilbert spaces
Class hilbert_space
is an abstraction of a finitedimensional state
space of a quantum system. It contains information needed to construct a
linear operator object out of a
polynomial expression.
A hilbert_space
is defined as an (ordered) direct product of
elementary spaces \(\mathcal{H}_i\),
An elementary space associated with an algebra generator \(g\) is a vector space of dimension \(2^b\), where \(b\) is the smallest integer sufficient to construct a matrix representation of \(g\). For instance, an elementary space associated with a fermionic creation/annihilation operator is twodimensional (\(b = 1\)); It is spanned by the occupation number states \(0\rangle\) and \(1\rangle\). For a spin1 operator, we have a 4dimensional elementary space. There are three basis vectors \(m=0\rangle\), \(m=\pm 1\rangle\) of the irreducible representation, but one has to round the dimension up to the nearest power of 2 (\(b = 2\)). Finally, in the case of a bosonic generator one has to truncate the infinitedimensional state space and manually set \(b\geq 1\) (\(b = 1\) would correspond to a hardcore boson with two allowed states \(0\rangle\) and \(1\rangle\)).
The ordering of the elementary spaces in the product is established by the algebra IDs of the generators \(g\) these spaces are associated with (the smaller algebra ID comes first). Two elementary spaces sharing the same ID are ordered according to the rules specific to the corresponding algebra.
The requirement of dimensions of the elementary spaces being powers of two is not arbitrary. It is justified by the way basis states of the full space \(\mathcal{H}\) are enumerated. Each basis state of \(\mathcal{H}\) is a direct product of basis vectors of the elementary spaces,
In the code, the basis vectors are represented by 64bit unsigned integers
(sv_index_type
).
The binary form of \(n\rangle_\mathcal{H}\) is
then a concatenation of binary forms of \(n_i\rangle_{\mathcal{H}_i}\).
For example, the following picture shows memory representation of basis
state \(90\rangle_\mathcal{H} = 0\rangle_{\mathcal{H}_1} \otimes
5\rangle_{\mathcal{H}_2} \otimes 1\rangle_{\mathcal{H}_3} \otimes
1\rangle_{\mathcal{H}_4}\). Cells (bits) of the same color belong to the same
elementary space and the higher blank cells 763 are unused – set to zero.
The simplest way to construct a hilbert_space
object is by calling
make_hilbert_space()
on an expression.
using namespace libcommute;
using namespace static_indices; // For n()
auto H = 2.0 * n("up", 0) * n("dn", 0);
// Construct a 4dimensional Hilbert space, which is a product of two
// fermionic elementary spaces (for {"up", 0} and {"dn", 0}).
auto hs = make_hilbert_space(H);
make_hilbert_space()
is a convenience function that forwards its
arguments to one of hilbert_space
’s constructors. That constructor
iterates over all generators found in the expression and adds their associated
elementary spaces into the product \(\mathcal{H}\). Sometimes, it may
require extra bits of information to translate a generator into an elementary
space. Perhaps the most prominent example here is a bosonic elementary space,
whose truncated dimension must be set by the user. It is possible to
customize the Hilbert space construction procedure
by passing an extra argument to make_hilbert_space()
. The following
code snippet shows how to set the truncated space dimension for all bosonic
generators at once.
using namespace libcommute;
using namespace static_indices; // For a_dag() and a()
auto H = 2.0 * (a_dag(0) * a(0)  0.5) + 3.0 * (a_dag(1) * a(1)  0.5);
// hs is a direct product of two bosonic elementary spaces, each with
// dimension 2^4 = 16.
auto hs = make_hilbert_space(H, boson_es_constructor(4));
Other, more refined ways to create a Hilbert space are (a) to explicitly provide
a list of elementary spaces or (b) to start from an empty product and add
elementary spaces one by one. One might have to resort to these approaches when
some elementary spaces must be added into the product, but their corresponding
generators are not necessarily found in H
.
using namespace libcommute;
using namespace static_indices; // For make_space_*()
// A product of three elementary spaces
// std::string and int are index types of generators
hilbert_space<std::string, int> hs1(
make_space_fermion("dn", 0), // Fermion
make_space_boson(4, "x", 0), // Boson truncated to dim = 2^4
make_space_spin(0.5, "i", 0) // Spin1/2
);
// Empty space, to be filled later
hilbert_space<int> hs2;
// Fill the space
hs2.add(make_space_fermion(0)); // Add a fermion
hs2.add(make_space_boson(4, 0)); // Add a boson
The order in which the elementary spaces are passed to the constructor or added does not matter – they will be reordered automatically.

template<typename ...IndexTypes>
class hilbert_space Defined in <libcommute/loperator/hilbert_space.hpp>
State space of a quantum system as a direct product of
elementary spaces
.Parameter pack
IndexTypes
must agree with that ofelementary_space
and/orlibcommute::expression
objects, which are used to construct this Hilbert space.Constructors

expression() = default
Construct an empty space.

template<typename ...Args>
explicit hilbert_space(Args&&... args) Construct from a list of elementary spaces. The elementary spaces need not be given in any particular order. Throws
hilbert_space_too_big
if all elementary spaces together would overflow the 64bit integer type of the basis state index.

template<typename ScalarType, typename ESConstructor = default_es_constructor>
hilbert_space(libcommute::expression<ScalarType, IndexTypes...> const &expr, ESConstructor &&es_constr = {}) Inspect an expression expr and collect all elementary spaces associated with algebra generators found in expr. Construction of the elementary spaces is performed by the functor es_constr. Throws
hilbert_space_too_big
if all collected elementary spaces together would overflow the 64bit integer type of the basis state index.
Copy/moveconstructors and assignments

hilbert_space(hilbert_space const&)

hilbert_space(hilbert_space&&) noexcept = default

hilbert_space &operator=(hilbert_space const&)

hilbert_space &operator=(hilbert_space&&) noexcept = default
Other methods and friend functions

friend bool operator==(hilbert_space const &hs1, hilbert_space const &hs2)

friend bool operator!=(hilbert_space const &hs1, hilbert_space const &hs2)
Check that two Hilbert spaces have an identical/different structure.

void add(elementary_space<IndexTypes...> const &es)
Insert a new elementary space into the product. Throws
elementary_space_exists
if an elementary space equivalent to es is already part of the product. Throwshilbert_space_too_big
if adding es into the product would overflow the 64bit integer type of the basis state index.

bool has(elementary_space<IndexTypes...> const &es) const
Is elementary space es part of the product?

int index(elementary_space<IndexTypes...> const &es) const
Position of a given elementary space in the product.

std::pair<int, int> bit_range(elementary_space<IndexTypes...> const &es) const
Returns the range of bits in the binary representation of a basis state index that is occupied by the elementary space es. The range is returned as a pair (first_bit, last_bit). Throws
elementary_space_not_found
if es is not part of the product.

bool has_algebra(int algebra_id) const
Is an elementary space with a given algebra ID found in this Hilbert space?

std::pair<int, int> const &algebra_bit_range(int algebra_id) const
Return the range of bits in the binary representation of a basis state index that is occupied by all elementary spaces with a given algebra ID. The range is always contiguous because elementary spaces with the same algebra ID are grouped together in the product. Throws
std::runtime_error
if there is no elementary spaces with the given ID in the product.

std::size_t size() const
The number of elementary spaces in the product.

int total_n_bits() const
The total number of used bits in the binary representation of a basis state index.

std::size_t dim() const

friend std::size_t get_dim(hilbert_space const &hs)
The dimension of this Hilbert space computed as a product of dimensions of the elementary spaces.

template<typename Functor>
friend void foreach(hilbert_space const &hs, Functor &&f) Apply functor f to all basis state indices in hs. f must accept one argument of type
sv_index_type
.

sv_index_type basis_state_index(elementary_space<IndexTypes...> const &es, sv_index_type n)
Given an elementary space es and an index n of a basis state within it, return the corresponding basis state index within the full Hilbert space.
Exception types

struct elementary_space_exists : public std::runtime_error
Thrown when one tries to add an elementary space that is already part of the product.

struct elementary_space_not_found : public std::runtime_error
Given elementary space is not part of the product.

struct hilbert_space_too_big : public std::runtime_error
The total basis state index size exceeds 64 bits.

expression() = default

template<typename ScalarType, typename ...IndexTypes, typename ESConstructor = default_es_constructor>
hilbert_space<IndexTypes...> make_hilbert_space(expression<ScalarType, IndexTypes...> const &expr, ESConstructor &&es_constr = {}) Defined in <libcommute/loperator/hilbert_space.hpp>
A helper factory function that constructs an
hilbert_space
instance from an expression expr using an elementary space constructor. This function is a more convenient equivalent of one ofhilbert_space
’s constructors.
Elementary spaces
An elementary space has an algebra ID assigned to it and carries a tuple of indices. Together, these two pieces of information link the elementary space to algebra generators acting in it.

template<typename ...IndexTypes>
class elementary_space Defined in <libcommute/loperator/elementary_space.hpp>
Abstract base class for elementary spaces.
IndexTypes
are index types of the associated algebra generators.
using index_types = std::tuple<IndexTypes...>
Index tuple type.
Constructors

template<typename ...Args>
elementary_space(Args&&... indices) 
elementary_space(index_types const &indices)

elementary_space(index_types &&indices)
Construct from a list/tuple of indices.
Copy/moveconstructors, assignments and destructor

elementary_space(elementary_space const&) = default

elementary_space(elementary_space&&) noexcept = default

elementary_space &operator=(elementary_space const&) = default

elementary_space &operator=(elementary_space&&) noexcept = default

virtual ~elementary_space()

virtual std::unique_ptr<elementary_space> clone() const = 0
Virtual copyconstructor. Makes a copy of this elementary space managed by a unique pointer.
Algebra ID

virtual int algebra_id() const = 0
Algebra ID of the generators associated with this elementary space.
Index sequence

index_types const &indices() const
Readonly access to the index tuple carried by this elementary space.
Ordering within a direct product

protected virtual bool equal(elementary_space const &es) const

protected virtual bool less(elementary_space const &es) const

protected virtual bool greater(elementary_space const &es) const
These methods can be overridden by the derived classes to establish the order of es w.r.t. *this assuming both elementary spaces are associated with the same algebra. The default implementation compares index tuples of *this and es.

friend bool operator==(generator const &es1, generator const &es2)

friend bool operator!=(generator const &es1, generator const &es2)

friend bool operator<(generator const &es1, generator const &es2)

friend bool operator>(generator const &es1, generator const &es2)
Comparison operators for a pair of elementary spaces. First, they compare algebra IDs of es1 and es2. If those are equal, es1.equal(es2), es1.less(es2) or es1.greater(es2) is called.
Binary representation of the basis state index

virtual int n_bits() const = 0
The number \(b\) of bits occupied by this elementary space (dimension of the space is \(2^b\)).

using index_types = std::tuple<IndexTypes...>
Predefined concrete elementary space types

template<typename ...IndexTypes>
class elementary_space_fermion : public elementary_space<IndexTypes...> Defined in <libcommute/loperator/elementary_space_fermion.hpp>
An elementary space associated with fermionic algebra generators. This elementary space is twodimensional (\(b = 1\)).

template<typename ...IndexTypes>
elementary_space_fermion<IndexTypes...> libcommute::static_indices::make_space_fermion(IndexTypes&&... indices) Defined in <libcommute/loperator/elementary_space_fermion.hpp>
Make an elementary space associated with fermionic algebra generators with given indices.

template<typename ...IndexTypes>
elementary_space_fermion<dyn_indices> libcommute::dynamic_indices::make_space_fermion(IndexTypes&&... indices) Defined in <libcommute/loperator/elementary_space_fermion.hpp>
Make an elementary space associated with fermionic algebra generators with a given dynamic index sequence.

template<typename ...IndexTypes>
class elementary_space_boson : public elementary_space<IndexTypes...> Defined in <libcommute/loperator/elementary_space_boson.hpp>
An elementary space associated with bosonic algebra generators. This elementary space is truncated and can have an arbitrary dimension of form \(2^b\).
Part of the interface not inherited from / identical to
elementary_space
.Construct a bosonic elementary space of dimension \(2^\text{n_bits}\).

template<typename ...IndexTypes>
elementary_space_boson<IndexTypes...> libcommute::static_indices::make_space_boson(int n_bits, IndexTypes&&... indices) Defined in <libcommute/loperator/elementary_space_boson.hpp>
Make an elementary space of dimension \(2^\text{n_bits}\) associated with bosonic algebra generators with given indices.

template<typename ...IndexTypes>
elementary_space_boson<dyn_indices> libcommute::dynamic_indices::make_space_boson(int n_bits, IndexTypes&&... indices) Defined in <libcommute/loperator/elementary_space_boson.hpp>
Make an elementary space of dimension \(2^\text{n_bits}\) associated with bosonic algebra generators with a given dynamic index sequence.

template<typename ...IndexTypes>
class elementary_space_spin : public elementary_space<IndexTypes...> Defined in <libcommute/loperator/elementary_space_spin.hpp>
An elementary space associated with spin algebra generators. Dimension of this elementary space depends on spin \(S\), and is computed as \(2S+1\) rounded up to the nearest power of 2.
Part of the interface not inherited from / identical to
elementary_space
.

template<typename ...IndexTypes>
elementary_space_spin<IndexTypes...> libcommute::static_indices::make_space_spin(double spin, IndexTypes&&... indices) Defined in <libcommute/loperator/elementary_space_spin.hpp>
Make a spin elementary space with \(S\) = spin and given indices.

template<typename ...IndexTypes>
elementary_space_spin<dyn_indices> libcommute::dynamic_indices::make_space_spin(double spin, IndexTypes&&... indices) Defined in <libcommute/loperator/elementary_space_spin.hpp>
Make a spin elementary space with \(S\) = spin and a given dynamic index sequence.
Advanced: Customization of automatic Hilbert space construction
make_hilbert_space()
delegates the task of translating algebra
generators into elementary spaces to the functor passed as its second (optional)
argument. It is possible to customize the translation process by giving
make_hilbert_space()
a callable object similar to the following one
// A custom elementary space constructor object
struct my_es_constructor {
template<typename... IndexTypes>
std::unique_ptr<elementary_space<IndexTypes...>>
operator()(generator<IndexTypes...> const& g) const {
//
// Create an elementary space associated with 'g' and return it
// wrapped in a unique pointer.
//
}
// Other members if needed ...
};
This approach gives total control over elementary space creation. It works best when expressions to be translated do not mix too many algebras and the body of my_es_constructor::operator() can be kept relatively simple.
Now imagine a different, more common situation, when expressions mix generators
of various predefined algebras as well as generators of a new userdefined
algebra my_algebra
. It would be desirable to instruct
make_hilbert_space()
how to translate instances of
generator_my_algebra
into elementary_space_my_algebra
without
rewriting all the code needed to processed the predefined generators. This goal
can be achieved in a few steps by means of a special utility class
es_constructor
.

template<int... AlgebraIDs>
class es_constructor Defined in <libcommute/loperator/es_constructor.hpp>
Define a new algebra ID, e.g. my_algebra_id.
// A unique integer >=min_user_defined_algebra_id static constexpr int my_algebra_id = 7;
Specialize class
libcommute::es_constructor
as followstemplate<> class es_constructor<my_algebra_id> { public: es_constructor() = default; template<typename... IndexTypes> std::unique_ptr<elementary_space<IndexTypes...>> operator()(generator<IndexTypes...> const& g) const { // // Create an elementary space associated with 'g' and return it // wrapped in a unique pointer. This method will be called only for // the generators of the new algebra, i.e. only when // g.algebra_id() == my_algebra_id // } };
es_constructor
is obviously a valid elementary space constructor formy_algebra
Instantiate
es_constructor
with multiple template parameters (algebra IDs).auto es_constr = es_constructor<fermion, spin, my_algebra_id>();
Now, es_constr knows how to process
fermionic
,spin
andmy_algebra
generators.Warning
The algebra IDs must come in the ascending order when used as template parameters of
es_constructor
.Finally, call
make_hilbert_space()
with two arguments.auto hs = make_hilbert_space(expr, es_constr);
It is worth noting that by default make_hilbert_space()
uses the
following constructor type as its second argument.

using default_es_constructor = es_constructor<fermion, spin>
In other words, it recognizes only fermionic and spin generators, and throws
es_construction_failure
for all other algebra IDs. If there are
bosonic creation/annihilation operators found in the expression, one may
use another elementary space constructor,

using boson_es_constructor = es_constructor<fermion, boson, spin>
Note
Calling
es_constructor<ID1, ID2, ..., IDN>(arg1, arg2, ..., argK)
will internally construct a series of objects
es_constructor<ID1>, es_constructor<ID2>, …,
es_constructor<IDN>. The arguments will be ‘fed’ into
constructors of the singleID objects in order, at most one
argument per constructor. For example,
es_constructor<fermion, boson, spin>(4) will call
es_constructor<fermion>(),
es_constructor<boson>(4) and
es_constructor<spin>() because the bosonic constructor is the
first in the sequence accepting one argument.